前提
应用崩溃影响用户体验,所以减少iOS应用奔溃是提高应用质量的重要途径。
一、获取iOS应用 crash信息
1,通过itunes同步
- 1,iOS设备上的应用闪退时, 操作系统会声称一个崩溃日志, 保存在设备上。
路径是: 设置 -> 隐私 ->诊断与用量 ->诊断与用量数据。在这里可以看到设备上所有的设备崩溃日志.
在“诊断与用量”界面,建议用户选择自动发送,这样可以每天自动发送诊断和用量数据到itunes,来帮助开发者分析崩溃.
- 2,设备与电脑上的ITunes Store同步后, 会将崩溃日志保存在电脑上,崩溃日志保存在以下位置:
Mac OSX :~/Library/Logs/CrashReporter/MobileDevice/
可以看到所有和该电脑同步过的设备的崩溃日志(.crash文件)
2,使用Xcode
-
1,code的Window菜单下的organizer,然后点击Devices tab,然后选中左边的Device Logs。选中某一个崩溃日志,点击Export Log可导出崩溃日志(.crash文件)
-
2,通过Xcode 获取appStore上架应用的crash信息。
3,其他方式获取部分堆栈信息
- 1,通过代码获取应用的crash堆栈信息,然后下次启动时上传服务器获得日志信息。
- 2,通过友盟等第三方SDK拿到应用crash部分堆栈信息。
这种方式获取的应用信息都只有部分堆栈信息
二,获取应用的符号表
1,日志文件
获取日志信息后,需要对堆栈调用信息分析
crash调用堆栈信息如图,有4列:第1列是栈顺序;第2列是二进制包名;第3列是运行地址;第四列是函数的动态加载地址,即加号前面的地址。
2,dSYM文件
在Xcode每次Archives的是都会生成dSYM文件,所有每次打包的时候的都需要保留包文件。点击.xcarchive文件,显示包含内容,在该目录下有生成的dSYM文件。dSYM文件也是Mach-o格式,Mach-O格式全称为Mach Object文件格式的缩写,是mac上可执行文件的格式,类似于windows上的PE格式 (Portable Executable ), Linux上的elf格式 (Executable and Linking Format)。
Mach-O可以分为3个部分
- (1)Header
- (2)Segment
-
(3)section
header后面是segment,然后再跟着section,而一个segment是可以包含多个section的。MachOview工具可以可视化查看Mach-O文件格式,我们把dSYM文件放入可视化工具:
该dSYM文件包含armv7和arm64两种架构的符号表。其中arm64的偏移地址时835520
3,UUID
每一个可执行程序都有一个build UUID来唯一标识。Crash日志包含发生crash的这个应用(app)的 build UUID以及crash发生的时候,应用加载的所有库文件的[build UUID]。UUID用来确定二进制包唯一标识
- 查看日志文件的UUID
localhost:Test guogh$ grep "crashTest arm64" crashTest2017.crash
0x1000cc000 - 0x1000d3fff crashTest arm64 <87f22ef6f56d3c7fac969d3416cd328a> /var/containers/Bundle/Application/13B84A6F-FB06-484B-8573-9390559A4A71/crashTest.app/crashTest
或者
localhost:Test guogh$ grep --after-context=2 "Binary Images:" crashTest2017.crash
Binary Images:
0x1000cc000 - 0x1000d3fff crashTest arm64 <87f22ef6f56d3c7fac969d3416cd328a> /var/containers/Bundle/Application/13B84A6F-FB06-484B-8573-9390559A4A71/crashTest.app/crashTest
0x100124000 - 0x100153fff dyld arm64 <f54ed85a94253887886a8028e20ed8ba> /usr/lib/dyld
其中 0x1000cc000 就是模块的加载地址。
- 查看二进制文件的UUID,dSYM文件
localhost:Test guogh$ xcrun dwarfdump --uuid dSYMs/crashTest.app.dSYM/Contents/Resources/DWARF/crashTest
UUID: 6D16D164-DA42-31C8-8890-94895DB43C47 (armv7) dSYMs/crashTest.app.dSYM/Contents/Resources/DWARF/crashTest
UUID: 87F22EF6-F56D-3C7F-AC96-9D3416CD328A (arm64) dSYMs/crashTest.app.dSYM/Contents/Resources/DWARF/crashTest
或者appName.app/appName文件
localhost:Test guogh$ xcrun dwarfdump --uuid Products/Applications/crashTest.app/crashTest
UUID: 6D16D164-DA42-31C8-8890-94895DB43C47 (armv7) Products/Applications/crashTest.app/crashTest
UUID: 87F22EF6-F56D-3C7F-AC96-9D3416CD328A (arm64) Products/Applications/crashTest.app/crashTest
需要三者的UUID的匹配才能正确的符号化。即都是:87F22EF6-F56D-3C7F-AC96-9D3416CD328A
三,符号化iOS Crash堆栈
我们拿到的crash信息是堆栈调用信息,所有信息都是16进制的内存地址,不能分析应用crash的原因,需要符号化才能分析。
1,使用Xcode
对于已经上架了appStore的应用的来说,crash文件通过用户上传到苹果服务器,Xcode可以直接下载下来,看到堆栈符号化的堆栈信息就和开发调试一样方便。
2, symbolicatecrash
symbolicatecrash是Xcode带的独立工具,可以拷贝出来使用。查找位置
localhost:~ guogh$ find /Applications/Xcode.app -name symbolicatecrash -type f
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
如果提示Error: "DEVELOPER_DIR" is not defined
,则执行
export DEVELOPER_DIR=/Applications/Xcode.app
把symbolicatecrash和奔溃日志,APP对应的.dSYM文件放在同一文件夹下,执行如下命令:
Symbolicatecrash + 崩溃日志.crash + APP对应的.dSYM文件 + > + 输出到的文件,
或者
Symbolicatecrash + 崩溃日志.crash + appName.app/appName + > + 输出到的文件,
如:
localhost:Test guogh$ ./symbolicatecrash crashTest2017.crash Products/Applications/crashTest.app/crashTest > aa.log
符号化前
Last Exception Backtrace:
0 CoreFoundation 0x1843391b8 __exceptionPreprocess + 124
1 libobjc.A.dylib 0x182d7055c objc_exception_throw + 56
2 CoreFoundation 0x1842147f4 -[__NSArrayI objectAtIndex:] + 184
3 crashTest 0x1000d0904 0x1000cc000 + 18692
4 crashTest 0x1000d0864 0x1000cc000 + 18532
5 UIKit 0x18a1ee924 -[UIViewController loadViewIfRequired] + 1056
6 UIKit 0x18a1ee4ec -[UIViewController view] + 28
7 UIKit 0x18a1f4c98 -[UIWindow addRootViewControllerViewIfPossible] + 76
8 UIKit 0x18a1f2138 -[UIWindow _setHidden:forced:] + 272
symbolicatecrash 符号化后
Last Exception Backtrace:
0 CoreFoundation 0x1843391b8 __exceptionPreprocess + 124
1 libobjc.A.dylib 0x182d7055c objc_exception_throw + 56
2 CoreFoundation 0x1842147f4 -[__NSArrayI objectAtIndex:] + 184
3 crashTest 0x1000d0904 -[ViewController crashTest] (ViewController.m:29)
4 crashTest 0x1000d0864 -[ViewController viewDidLoad] (ViewController.m:22)
5 UIKit 0x18a1ee924 -[UIViewController loadViewIfRequired] + 1056
6 UIKit 0x18a1ee4ec -[UIViewController view] + 28
7 UIKit 0x18a1f4c98 -[UIWindow addRootViewControllerViewIfPossible] + 76
8 UIKit 0x18a1f2138 -[UIWindow _setHidden:forced:] + 272
可以看到第3,4行已经符号化了,显示了哪个文件具体行数,对应代码
查看源码,找出代码crash的原因就容易的多了。但使用symbolicatecrash工具有很大的限制
- 只能分析官方格式的崩溃日志,需要从具体的设备中导出,获取和操作都不是很方便
- 符号化的结果也是没有具体的行号信息的,也经常会出现符号化失败的情况。
- 实际上, Xcode的Organizer内置了symbolicatecrash工具,所以开发者才可以直接看到符号化的崩溃堆栈日志。
3,使用命令行工具atos
命令格式:
atos -o executable -arch architecture -l loadAddress address
说明:
- executable dYSM文件或二进制执行文件appName.app/appName
- loadAddress 表示函数的动态加载地址,对应崩溃地址堆栈中 + 号前面的地址,即0x1000cc000
- address 表示运行时地址、对应崩溃地址堆栈中第一个地址,即0x1000d0904 ,实际上,崩溃地址堆栈中+号前后的地址相加即是运行时地址,即0x1000cc000 + 18692= 0x1000d0904
- 执行命令查询地址的符号,可以看到如下结果:
-[ViewController crashTest] (in crashTest) (ViewController.m:29)
四,总结
本文分析了如何获取用户的.crash文件,以及如何符号化的方法,对于分析应用crash很适用。