iOS开发中,对于线上版本或公测版本产生的crash,我们可以通过结合.app ,.dSYM 及 crash log 三个文件来进行解析定位。
最新更新: 最近对查找线上Crash做了整理,写成CrashScript.sh ,详情见下面查找Crash脚本
获取iOS设备上的 crash log
将iOS设备连接到电脑上,打开 Xcode -> Organizer -> Devices,找到该台设备,在 Device logs 中找到 crash log(后缀为 .crash 的 log 文件),将其导出即可。
如果你的应用已经上架App Store,那么开发者可以通过iTunes Connect(Manage Your Applications - View Details - Crash Reports)获取用户的crash log。不过这并不是100%有效的,而且大多数开发者并不依赖于此,因为这需要用户设备同意上传相关信息,详情可参见iOS: Providing Apple with diagnostics and usage information摘要。
第三方crash收集系统,甚至还带了符号化crash日志的功能。比较常用的有Crashlytics ,Flurry 等。
确保.app .dSYM和crash log的uuid相同 以上三者的uuid必须都一样才能进行解析,查看三个文件的 uuid :
查看xx.app文件的uuid的方法,在终端输入:
$ dwarfdump –uuid xxx.app/xxx (xxx工程名)
查看xx.app.dSYM文件的uuid的方法,在命令行输入:
$ dwarfdump –uuid xxx.app.dSYM (xxx工程名)
查看 crash log 文件的 uuid的方法: 在 crash log 文件中,找到 Binary Images: 项目名后面第一个尖括号中的一串码就是改 crash log 文件的 uuid。 如下,70464c7fc4df37f38f81eeaf88a0713d就是uuid。
1 2 Binary Images:0x10000c000 - 0 x100 cf7 fff xxx (xxx工程名) arm64 <70464 c7 fc4 df37 f38 f81 eeaf88 a0713 d>
显示.dYSM包内容 进入/Contents/Resources/DWARF路径下,执行命令:
atos -arch armv7 -o XXX(项目名称) 0x17D580(16进制crash奔溃地址)
显示结果
1 - [PCBabyMyGroupReplyTVCell setCellInfo:] (in BabyBook2) (PCBabyMyGroupReplyTVCell.m :66 )
可以看到是 PCBabyMyGroupReplyTVCell 类第66行出错。
如果定位不对,可能涉及到地址偏移的计算。首先查看起始地址,即使每次iOS app启动都会加载(main module)主模块在不同的内存地址,但是dSYM文件总是假设你的main module加载在地址 0x100000000(64位) ,或者 0x4000(32位) 。 查看偏移,显示.dYSM包内容,进入/Contents/Resources/DWARF路径下,执行命令:
1 otool -arch arm64 -l XXX(XXX为项目名) | grep -B 1 -A 10 "LC_SEGM" | grep -B 3 -A 8 "__TEXT"
显示结果
1 2 3 4 5 6 7 8 9 10 11 12 Load command 1 cmd LC_SEGMENT_64 cmdsize 1032 segname __TEXT vmaddr 0x0000000100000000 vmsize 0x0000000000620000 fileoff 0 filesize 6422528 maxprot 0x00000005 initprot 0x00000005 nsects 12 flags 0x0
看到vmaddr显示为0x0000000100000000,所以偏移地址计算为:0x17D580+0x0000000100000000=0x10017D580。 再次执行终端命令:
atos -arch armv7 -o XXX(项目名称) 0x10017D580
附:32位及64位设备的执行命令
32位: xcrun atos -arch armv7 -o xxx(应用名称) xxx(偏移地址) 64位: xcrun atos -arch arm64 -o xxx(应用名称) xxx(偏移地址)
查找Crash脚本 注意!!!Shell脚本默认无法处理带空格的文件路径,请确保.xcarchive
文件以及CrashScript.sh
脚本所在路径名不存在空格。(Xcode Archive生成的.xcarchive
文件名默认带空格,请去除)
获取打包生成的 .xcarchive
文件,将其和CrashScript.sh
脚本放到同一级目录下,运行脚本:
1 ./CrashScript.sh [-u] [-t <Device type >] -a <Code address>
参数说明:[]表示可选参数,<>表示必填参数
1 2 3 -u 是否查看UUID -t <Device type > 发生crash的设备类型,有两种值:32和64,默认64 -a <Code address> 10进制的出错地址
脚本完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 #!/bin/bash Release_path=$(pwd ) xcarchive_path="" cd ${Release_path} Valid_dic=false for i in `ls`;do extension=${i##*.} if [[ ${extension} == "xcarchive" ]]; then Valid_dic=true xcarchive_path="${Release_path} /${i} " fi done if [[ ${Valid_dic} == false ]]; then echo -e "\033[31mCrashScript.sh脚本所在路径不存在.xcarchive文件,请检查!!\033[0m" exit 2 fi Check_UUID="NO" Device_type="64" Have_code_address="NO" Code_address="" param_pattern=":ut:a:" OPTIND=1 while getopts $param_pattern optname do case "$optname " in "u" ) Check_UUID="YES" ;; "t" ) tmp_optind=$OPTIND tmp_optname=$optname tmp_optarg=$OPTARG OPTIND=$OPTIND -1 if getopts $param_pattern optname ;then echo -e "\033[31m选项参数错误 $tmp_optname \033[0m" exit 2 fi OPTIND=$tmp_optind Device_type=$tmp_optarg if [[ ${Device_type} != "32" && ${Device_type} != "64" ]]; then echo -e "\033[31m选项$tmp_optname 参数错误 $Device_type \033[0m" exit 2 fi ;; "a" ) tmp_optind=$OPTIND tmp_optname=$optname tmp_optarg=$OPTARG Have_code_address="YES" OPTIND=$OPTIND -1 if getopts $param_pattern optname ;then echo -e "\033[31m选项参数错误 $tmp_optname \033[0m" exit 2 fi OPTIND=$tmp_optind Code_address=$tmp_optarg ;; "?" ) echo -e "\033[31m选项错误: $OPTARG \033[0m" exit 2 ;; ":" ) echo -e "\033[31m选项 $OPTARG 必须带参数\033[0m" exit 2 ;; *) echo -e "\033[31m参数错误\033[0m" exit 2 ;; esac done dSYMs_path="${xcarchive_path} /dSYMs" dSYM_file="" cd ${dSYMs_path} if [ $? != 0 ]; then echo -e "\033[31m************* dSYMs路径不存在 **************\033[0m" echo -e "\033[31mdSYMs_path = ${dSYMs_path} \033[0m" exit 2 fi for file in `ls`;do extension=${file##*.} if [[ ${extension} == "dSYM" ]]; then dSYM_file=${file} fi done if [[ ${Check_UUID} == "YES" ]]; then echo -e "\033[32m*************** 获取 UUID ***************\033[0m" echo -e "\033[36mdSYM文件 : ${dSYM_file} \033[0m" ; echo '' dwarfdump --uuid ${dSYM_file} echo '' if [ $? != 0 ]; then echo -e "\033[31m************* 获取UUID出错 **************\033[0m" exit 2 fi fi if [[ ${Have_code_address} == "NO" ]]; then echo -e "\033[31m请输入出错的10进制代码地址!!\033[0m" exit 2 fi DWARF_path="${dSYMs_path} /${dSYM_file} /Contents/Resources/DWARF" echo -e "\033[32m*************** 获取 DWARF ***************\033[0m" cd ${DWARF_path} for file in `ls`;do echo -e "\033[36mDWARF文件 : ${file} \033[0m" echo '' echo -e "\033[32m*************** 获取出错代码 ***************\033[0m" if [[ ${Device_type} == "32" ]]; then searchAddress="0x" $(echo "ibase=10;obase=16;${Code_address} +16384" |bc); xcrun atos -arch armv7 -o ${file} ${searchAddress} elif [[ ${Device_type} == "64" ]]; then searchAddress="0x" $(echo "ibase=10;obase=16;${Code_address} +4294967296" |bc); xcrun atos -arch arm64 -o ${file} ${searchAddress} fi done