iOS开发中,经常遇到App在开发及测试时不会有问题,但是装在别人的设备中会出现各种不定时的莫名的 crash,因为iOS设备会保存应用的大部分的 crash Log,所以可以通过 crash Log 来定位 crash 原因。
一. 获取iOS设备上的 crash log
1. 将iOS设备连接到电脑上,打开 Xcode -> Organizer -> Devices,找到该台设备,在 Device logs 中找到 crash log(后缀为 .crash 的 log 文件),将其导出即可。
2. 如果你的应用已经上架App Store,那么开发者可以通过iTunes Connect(Manage Your Applications - View Details - Crash Reports)获取用户的crash log。不过这并不是100%有效的,而且大多数开发者并不依赖于此,因为这需要用户设备同意上传相关信息,详情可参见iOS: Providing Apple with diagnostics and usage information摘要。
二. 解析iOS crash log
获取到的 crash log 中的相关信息都是 16 进制的内存地址,并不能定位崩溃的代码,所以需要将 16 进制的内存地址解析为对应的类及方法。
解析 crash log 需要使用上传应用时所发送的 .app 及 .sYSM 两个文件(所以每次上传新版本时都要保存这两个文件,不然没法解析 crash log),可以将 .app .dYSM 及crash log文件拷贝到同一个文件夹下,使用 Symbolicatecrash 进行解析。
获取 .app 及 .dYSM 文件:在iOS开发中,需要使用Xcode打包生成 .xcarchiver 文件,可以在Xcode - > Organizer - > archive 中进行管理并导出相应的 .xcarchiver 文件,.xcarchiver 文件中就包含 .app 及 .dYSM 文件。
Symbolicatecrash 是一个隐藏文件,并且独立于 Xcode,位置在:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash
在利用 Symbolicatecrash 进行解析之前,要检查 .app .dYSM 及 crash log 三个文件的uuid是否一样,只有三者都一样才能进行解析。查看三个文件的 uuid 方法如下:
1. 查看xx.app文件的uuid的方法,在 terminal.app 中输入:
$ dwarfdump --uuid xxx.app/xxx (xxx工程名)
2. 查看xx.app.dSYM文件的uuid的方法,在命令行输入:
$ dwarfdump --uuid xxx.app.dSYM (xxx工程名)
3. 查看 crash log 文件的 uuid的方法:
在 crash log 文件中,找到 Binary Images: 项目名后面第一个尖括号中的一串码就是改 crash log 文件的 uuid。
利用 Symbolicatecrash 进行解析:
在 terminal.app 中输入如下命令:
$ ./symbolicatecrash xxx.crash xxx.app.dSYM > test.log
该命令会将 crash 文件解析成 test.log 文件,test.log 就是可读的函数文件。
输入上述命令可能会出现Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line 53.这个错误。
如果出现上述错误,输入命令:export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer,
然后继续执行./symbolicatecrash xxx.crash xxx.app.dSYM > test.log可以成功
三. 其他解析(根据地址解析):
解析某一个地址的内容
方法一:
$ xcrun atos -o xxx.app/xxx -arch armv7 0x38ad42f9 0x38ad42f9 0x38ad42f9(多个16进制地址,使用空格分开)
方法二:
$ dwarfdump -–lookup 0x000036d2 -–arch armv6 xxx.app.dSYM
如果根据以上两个方法操作,均提示找不到地址的话,可以使用方法三:
方法三:
查找以下内容所对应的地址:
(当前代码行地址 = 当前地址 + 地址偏移量,地址偏移量为十进制数值)
10 TestTransform 0x00057132 0x4f000 + 33074
1. 先说第一种比较简单的方法利用 "当前地址" 和 "当前代码行地址"
$ xcrun atos -o TestTransform.app/TestTransform -arch armv7 0x3d000 0x0004fc5c
//输出结果:
bddeMacBook-Pro:1 baidu$ xcrun atos -o /Users/baidu/Desktop/1/TestTransform.app/TestTransform -l 0x3d000 0x0004fc5c
got symbolicator for /Users/baidu/Desktop/1/TestTransform.app/TestTransform, base address 4000
main (in TestTransform) (main.m:16)
2. 第二种相对复杂一点,涉及到地址的偏移,首先查看起始地址,即使每次iOS app启动都会加载(main module)主模块在不同的内存地址,但是dSYM文件假设你的main module加载在地址0x1000(大多数情况是这个,也有0x4000的)。
获取此地址的方法:
$ otool -arch armv7 -l /Users/cnstar-tech/crash/xxx.app/xxx | grep -B 1 -A 10 "LC_SEGM" | grep -B 3 -A 8 "__TEXT"
调用以上方法返回结果如下:
Load command 1
cmd LC_SEGMENT
cmdsize 600
segname __TEXT
vmaddr 0x00004000
vmsize 0x00014000
fileoff 0
filesize 81920
maxprot 0x00000005
initprot 0x00000005
nsects 8
flags 0x0
看到vmaddr显示为0x00004000。
然后查看 "TestTransform 0x00057132 0x4f000 + 33074" 使用 起始地址+地址偏移量如:
0x4000 + 33074 = 0xc132 (注意:前面为前面为16进制后面为10进制相加,要都转换成10进制相加,把得出的结果转换成16进制)
就可以使用 "0xc132" 地址查看内容了:
//方法一:
$ dwarfdump --lookup 0xc132 --arch armv7 TestTransform.app.DSYM
//输出结果:
----------------------------------------------------------------------
File: TestTransform.app.DSYM/Contents/Resources/DWARF/TestTransform (armv7)
----------------------------------------------------------------------
Looking up address: 0x000000000000c132 in .debug_info... found!
0x00003947: Compile Unit: length = 0x00007b6e version = 0x0002 abbr_offset = 0x00000000 addr_size = 0x04 (next CU at 0x0000b4b9)
0x00003952: TAG_compile_unit [1] *
AT_producer( "Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)" )
AT_language( DW_LANG_ObjC )
AT_name( "/Users/baidu/Desktop/TestTransform/TestTransform/ViewController.m" )
AT_low_pc( 0x0000a950 )
AT_stmt_list( 0x0000089f )
AT_comp_dir( "/Users/baidu/Desktop/TestTransform" )
AT_APPLE_major_runtime_vers( 0x02 )
0x00003c22: TAG_subprogram [39] *
AT_name( "__29-[ViewController aaaaaaaaaa:]_block_invoke" )
AT_decl_file( "/Users/baidu/Desktop/TestTransform/TestTransform/ViewController.m" )
AT_decl_line( 191 )
AT_prototyped( 0x01 )
AT_APPLE_isa( 0x01 )
AT_accessibility( DW_ACCESS_public )
AT_low_pc( 0x0000c09c )
AT_high_pc( 0x0000c182 )
AT_frame_base( r7 )
0x00003c46: TAG_lexical_block [34] *
AT_low_pc( 0x0000c0d6 )
AT_high_pc( 0x0000c17e )
Line table dir : '/Users/baidu/Desktop/TestTransform/TestTransform'
Line table file: 'ViewController.m' line 193, column 0 with start address 0x000000000000c11e
Looking up address: 0x000000000000c132 in .debug_frame... found!
0x00000160: FDE
length: 0x0000000c
CIE_pointer: 0x00000000
start_addr: 0x0000c09c __29-[ViewController aaaaaaaaaa:]_block_invoke
range_size: 0x000000e6 (end_addr = 0x0000c182)
Instructions: 0x0000c09c: CFA=4294967295+4294967295
//方法二:
$ xcrun atos -o /Users/baidu/Desktop/1/TestTransform.app/TestTransform 0xc132
//输出结果:
__29-[ViewController aaaaaaaaaa:]_block_invoke (in TestTransform) (ViewController.m:193)
四. 如何判断两个 crash log 文件的 crash 是同一个原因:
1. 可以先比较 Triggered by Thread 看看是否为同一个线程,不同,则不是同一原因。
2. 如果有 Last Exception Backtrace,可以先比较 Last Exception Backtrace 中地址的行数,行数不同则原因不同,行数如果相同可以将两个 Last Exception Backtrace 中的地址逐个进行比较,(一般最开始几行和最后几行都是调用系统函数,所以地址都是一样的),直到出现第一个不一样的地址,可以用 dwarfdump 分别解析两个地址,如果解析得到的方法不一样,则 crash 原因可不一样。
五. Xcode 中 Organizer 自动解析:
如果项目是在自己机器上打包的,可以将iOS设备连接到电脑上,这样该设备中的 crash log 就会被 Organizer 自动进行解析,如果没有自动解析,可以 右击 crash log 选择 re-symbolicate 进行解析。(之所以 Organizer 可以进行自动解析,是因为在打包的时候会建立 .app 及 .dYSM 两个文件的索引,所以可以自动解析 crash log 文件)
也可以进行手动建立索引,即将该 App 的 .app 及 .dYSM 两个文件拷贝到同一个文件夹中,然后再用 midimport foldername 命令进行手动建立索引,然后,将 crash log 文件导入 Xcode Organizer 中,就会进行自动解析。(可以批量导入 crash log 文件,批量进行解析)
六. 使用专门的 crash log 解析工具进行解析:
比如QuincyKit, Crashlytics, Flurry等。