什么是崩溃日志
iOS的App在崩溃时,系统会记录下当前的每个线程的调用栈信息等等,并保存到设备中。这些信息汇总起来就是我们所说的崩溃日志。
iOS崩溃日志收集的几种方式
- 通过Xcode的
Window > Devices
进入设备管理,使用View Device Logs查看 - 通过第三方SDK上传用户的崩溃日志,比如使用友盟,BugHD,Bugly等等
- 通过Apple后台搜集崩溃日志,可以在Organizer里直接查看线上的崩溃日志。但是如果用户选择不上传诊断信息,就看不到日志了。
为什么要符号化
从iOS设备采集的原始崩溃日志记录的都是调用栈地址
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x000000019959cb3c 0x19959c000 + 2876
1 libsystem_platform.dylib 0x0000000199679534 0x199674000 + 21812
2 libobjc.A.dylib 0x0000000198c5b200 0x198c44000 + 94720
3 libobjc.A.dylib 0x0000000198c6443c 0x198c44000 + 132156
4 CoreFoundation 0x0000000184190cbc 0x184064000 + 1232060
......
这样的崩溃日志是无法使用的,通过符号化可以将它转换成可读的日志
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x000000019959cb3c syscall_thread_switch + 8
1 libsystem_platform.dylib 0x0000000199679534 _os_lock_handoff_lock_slow + 120
2 libobjc.A.dylib 0x0000000198c5b200 spinlock_t::lockTwo(spinlock_t*, spinlock_t*) + 52
3 libobjc.A.dylib 0x0000000198c6443c objc_storeWeakOrNil + 120
4 CoreFoundation 0x0000000184190cbc _NSObjectStoreWeak + 80
5 CoreFoundation 0x00000001841167f8 -[_CFXNotificationObjcObserverRegistration initWithObserver:parent:] + 92
......
怎样符号化
大多数iOS程序员应该都知道使用View Device Logs查看设备日志时会自动符号化崩溃日志,但有时候可以符号化,但有时候却不行。
为什么呢?除了通过View Device Logs自动符号化文件,还有没有其他方式可以手动符号化崩溃日志呢?下面会一一解答这些问题。
符号化的原理
dSYM文件
dSYM文件(符号文件)是存储了类名和方法名等调试信息的文件。在release模式下,build后会有.app.dSYM文件。
debug模式下调试信息在可执行文件中,所以不会生成dSYM文件。符号化就是通过在dSYM文件查找地址对应的方法名来实现的。
Xcode的View Device Logs符号化时如何寻找符号文件
每个Crash日志都记录了它所对应符号文件的uuid,打开一个crash文件,可以看到类似于下面这行的信息
Binary Images:
0x10006c000 - 0x100073fff Demo1 arm64 <2cf1790547ff3a1cac055152319617ba> /var/mobile/Containers/Bundle/Application/7E6DE925-0B33-4699-89F7-05381876AD81/Demo1.app/Demo1
例子中的App名字是Demo1,符号文件所对应的uuid就是2cf1790547ff3a1cac055152319617ba
。当符号化这个崩溃日志的时候,符号化工具会通过这个uuid去寻找对应的dSYM文件,如果找到了就可以正确的符号化了。
由此我们就可以知道,使用View Device Logs查看崩溃日志时,有时不能符号化的原因就是没有查到对应的dSYM文件。因为每次build都会导致uuid发生变化,所以大部分的崩溃日志所对应的dSYM文件其实都被覆盖掉了。
通过符号化命令行工具深入了解符号化的过程
在Xcode7.0中可以在一下路径找到符号化命令行工具
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
可以为这个工具设置alias:
alias symbolicatecrash="/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash"
这时候直接调用的话会报
Error: "DEVELOPER_DIR" is not defined at ...
这样的错误,只要设置环境变量DEVELOPER_DIR
就行了 。
export 'DEVELOPER_DIR'="/Applications/Xcode.app/Contents/Developer"
后面我都会直接使用symbolicatecrash
进行命令行下的符号化
Xcode就是通过这个工具符号化崩溃日志的
在符号化之前我们先准备好几样东西:
- 原始的崩溃日志
- dSYM文件
- symbolicatecrash可执行文件
我们先查看crash日志是否和符号文件是匹配的,下面的命令将提取dSYM文件的uuid
xcrun dwarfdump --uuid Demo1.app.dSYM/Contents/Resources/DWARF/Demo1
我提取出来的uuid是:
UUID: 2CF17905-47FF-3A1C-AC05-5152319617BA (arm64) Demo1.app.dSYM/Contents/Resources/DWARF/Demo1
崩溃日志的uuid是:
0x10006c000 - 0x100073fff Demo1 arm64 <2cf1790547ff3a1cac055152319617ba> /var/mobile/Containers/Bundle/Application/7E6DE925-0B33-4699-89F7-05381876AD81/Demo1.app/Demo1
都是 2cf1790547ff3a1cac055152319617ba
,所以使用这个dSYM文件既可以符号化上面的崩溃日志。
用symbolicatecrash来符号化崩溃日志
symbolicatecrash demo1.crash Demo1.app.dSYM -o processed.crash
符号化后的crash文件将会被写入processed.crash
中,
然后我们来看看如果把Demo1.app.dSYM
去掉会怎样,为了看到符号化的过程,我们可以加上 -v
symbolicatecrash demo1.crash -o processed.crash -v
我们主要看Demo1的符号化过程,其他系统调用其实也会在这个过程中被符号化。#后面是我添加的注释
-- [2cf1790547ff3a1cac055152319617ba] fetching symbol file for Demo1 #开始寻找Demo1的符号文件
Running mdfind "com_apple_xcode_dsym_uuids == 2CF17905-47FF-3A1C-AC05-5152319617BA" #使用Spotlight 搜索uuid为2CF17905-47FF-3A1C-AC05-5152319617BA的dsym文件
#接下来是使用file,lipo,otool等来分析dSYM的相关信息
.
.
-- [2cf1790547ff3a1cac055152319617ba] MATCH (spotlight): ... #确认找到对应的dSYM文件
.
.
atos -arch arm64 -l 0x10006c000 -o '..../Demo1.app.dSYM/Contents/Resources/DWARF/Demo1' 0x00000001000706e8 0x0000000100070a80
我们看到最后会发现实际上symbolicatecrash
是使用atos
来寻找调用栈地址对应的调试符号的。
我们来看一下atos
所使用的参数:
- -arch 所运行设备的架构,有arm64,armv7等等
- -l 二进制镜像运行时加载的地址
- -o 后面是符号文件或者含有调试符号的可执行文件(debug编译所产生的可执行文件默认是包含调试符号的)。
- 再后面就是需要符号化的调用栈地址,
5 Demo1 0x00000001000706e8 0x10006c000 + 18152
,0x00000001000706e8
就是其中的一个地址。
二进制镜像运行时加载的地址通过如下方式获得
Binary Images:
0x10006c000 - 0x100073fff Demo1 arm64 <2cf1790547ff3a1cac055152319617ba> /var/mobile/Containers/Bundle/Application/7E6DE925-0B33-4699-89F7-05381876AD81/Demo1.app/Demo1
中的0x10006c000
就是这个崩溃日志所对应的二进制镜像加载地址。
综上所述,symbolicatecrash
主要帮我们做了两件事情,匹配到对应的dSYM文件,使用atos
符号化每个调用栈地址。