iOS崩溃日志分析

什么是崩溃日志

iOS的App在崩溃时,系统会记录下当前的每个线程的调用栈信息等等,并保存到设备中。这些信息汇总起来就是我们所说的崩溃日志。

iOS崩溃日志收集的几种方式

  1. 通过Xcode的Window > Devices进入设备管理,使用View Device Logs查看
  2. 通过第三方SDK上传用户的崩溃日志,比如使用友盟,BugHD,Bugly等等
  3. 通过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就是通过这个工具符号化崩溃日志的

在符号化之前我们先准备好几样东西:
  1. 原始的崩溃日志
  2. dSYM文件
  3. 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所使用的参数:

  1. -arch 所运行设备的架构,有arm64,armv7等等
  2. -l 二进制镜像运行时加载的地址
  3. -o 后面是符号文件或者含有调试符号的可执行文件(debug编译所产生的可执行文件默认是包含调试符号的)。
  4. 再后面就是需要符号化的调用栈地址,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符号化每个调用栈地址。

你可能感兴趣的:(iOS崩溃日志分析)