程序员iOS开发之常用技术点iOS Developer

iOS崩溃统计原理 & 日志分析整理

2019-02-25  本文已影响114人  sea_biscute

简介

当应用崩溃时,会产生崩溃日志并且保存在设备上。崩溃日志描述了应用结束时所处的环境信息,通常包含完整的线程堆栈追溯信息,这些数据对于调试应用错误非常有帮助。
包含追溯信息的崩溃日志在分析前需要进行符号化。符号化将内存地址替换为更直观的函数名以及行数。

崩溃原因

崩溃是指应用产生了系统不允许的行为时,系统终止其运行导致的现象。崩溃发生的原因有:

产生崩溃日志

在程序出现以上问题时,系统会抛出异常,结束程序:
出现异常情况,终止程序:


捕获异常

分析崩溃日志

在发生崩溃时,会产生崩溃日志并且保存在设备上,用于后期对问题定位,崩溃日志的内容包括以下部分:程序信息异常信息崩溃堆栈二进制镜像。下面对每部分进行说明。

崩溃日志程序信息:

Incident Identifier: B6FD1E8E-B39F-430B-ADDE-FC3A45ED368C
CrashReporter Key: f04e68ec62d3c66057628c9ba9839e30d55937dc
Hardware Model: iPad6,8
Process: TheElements [303]
Path: /private/var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
Identifier: com.example.apple-samplecode.TheElements
Version: 1.12
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.example.apple-samplecode.TheElements [402]
 
Date/Time: 2016-08-22 10:43:07.5806 -0700
Launch Time: 2016-08-22 10:43:01.0293 -0700
OS Version: iPhone OS 10.0 (14A5345a)
Report Version: 104

汇总部分包含崩溃发生环境的基本信息:

异常信息:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 0

异常信息:

崩溃堆栈:

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   TheElements                     0x000000010006bc20 -[AtomicElementViewController myTransitionDidStop:finished:context:] (AtomicElementViewController.m:203)
1   UIKit                           0x0000000194cef0f0 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
2   UIKit                           0x0000000194ceef30 -[UIViewAnimationState animationDidStop:finished:] + 160
3   QuartzCore                      0x0000000192178404 CA::Layer::run_animation_callbacks(void*) + 260
4   libdispatch.dylib               0x000000018dd6d1c0 _dispatch_client_callout + 16
5   libdispatch.dylib               0x000000018dd71d6c _dispatch_main_queue_callback_4CF + 1000
6   CoreFoundation                  0x000000018ee91f2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
7   CoreFoundation                  0x000000018ee8fb18 __CFRunLoopRun + 1660
8   CoreFoundation                  0x000000018edbe048 CFRunLoopRunSpecific + 444
9   GraphicsServices                0x000000019083f198 GSEventRunModal + 180
10  UIKit                           0x0000000194d21bd0 -[UIApplication _run] + 684
11  UIKit                           0x0000000194d1c908 UIApplicationMain + 208
12  TheElements                     0x00000001000653c0 main (main.m:55)
13  libdyld.dylib                   0x000000018dda05b8 start + 4
 
Thread 1:
0   libsystem_kernel.dylib          0x000000018deb2a88 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x000000018df75188 _pthread_wqthread + 968
2   libsystem_pthread.dylib         0x000000018df74db4 start_wqthread + 4
 
...

第一行列出了线程信息以及所在队列,之后是追溯链中独立栈帧的详细信息:

二进制镜像:

Binary Images:
0x100060000 - 0x100073fff TheElements arm64 <2defdbea0c873a52afa458cf14cd169e> /var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
...

日之内包含多个二进制镜像,每个二进制镜像内包含以下信息:

符号化

app.xcarchive文件,包内容包含dSYM和应用的二进制文件。
更精确的符号化,可以结合崩溃日志、项目二进制文件、dSYM文件,对其进行反汇编,从而获得更详细的信息。

符号化就是将追溯的地址信息转换成函数名及行数等信息,便于研发人员定位问题。
当程序结束运行时,会产生崩溃日志,日志内包含每个线程的堆栈信息。当我们使用Xcode进行调试时,崩溃或者断点信息都会展示出实例和方法名等信息(符号信息)。相反,当应用被发布后,符号信息并不会包含在应用的二进制文件中,所以服务端收到的是未符号化的包含十六进制地址信息的日志文件。
查看本机崩溃日志步骤如下:

  1. 将手机连接到Mac
  2. 启动Xcode->Window->Devices and simulators
  3. 选择View Device Logs

选择左侧应用,之后就可以在右侧看到崩溃日志信息:


崩溃日志示例

日志内包含符号化内容-[__NSArrayI objectAtIndex:]和十六进制地址0x000db142 0xb1000 + 172354。这种日志类型成为部分符号化崩溃日志。
部分符号化的原因在于,Xcode只能符号化系统组件,例如UIKit、CoreFoundation等。但是对于非系统库产生的崩溃,在没有符号表的情况下就无法符号化。
分析第三行未符号化的代码:

0x000db142 0xb1000 + 172354

以上内容说明了崩溃发生在内存地址0x000db142,此地址和0xb1000 + 172354是相等的。0xb1000代表这部分许的起始地址,172354代表偏移位。

崩溃日志类型:
崩溃日志可能包含几种状态:未符号化、完全符号化、部分符号化。
未符号化的崩溃日志追溯链中没有函数的名字等信息,而是二进制镜像执行代码的十六进制地址。
完全符号化的崩溃日志中,所有的十六进制地址都被替换为对应的函数符号。

符号化流程

符号化需要两部分内容:崩溃的二进制代码和编译产生的对应dSYM。

符号表
当编译器将源码转换为机器码时,会生成一个调试符号表,表内是二进制结构到原始源码的映射关系。调试符号表保存在dSYM(debug symbol调试符号表)文件内。调试模式下符号表会保存在编译的二进制内,发布模式则将符号表保存在dSYM文件内用于减少包的体积。

当崩溃发生时,会在设备存储一份未符号化的崩溃日志
获取崩溃日志后,通过dSYM对追溯链中的每个地址进行符号化,转换为函数信息,产生的结果就是符号化后的崩溃日志。

函数调用堆栈

我们知道,崩溃日志内包含函数调用的追溯信息,明白堆栈是怎么产生的有利于我们理解和分析崩溃日志。

函数调用堆栈

函数调用是在栈进行的,函数从调用和被调用方分为:主调函数和被调函数,这次我们只讨论每个函数在栈中的几个核心部分:

入参和局部变量容易理解,下面讨论为什么要保存主调函数的堆栈信息。
说到这点就需要聊到寄存器。

寄存器

寄存器

寄存器的类型和基本功能:

寄存器约定
背景:

被调函数在执行时,需要使用寄存器来保存数据和执行计算,但是在被调函数完成时,需要把寄存器还原,用于主调函数的执行,所以出现了寄存器约定。

约定内容:

遵守寄存器约定的函数堆栈调用

了解了寄存器功能和寄存器约定后,我们再看函数调用堆栈:


函数调用堆栈

总结

通过以上内容,我们了解了崩溃日志产生原理、崩溃日志内容和崩溃日志分析,下面分享几个分析崩溃日志的小提示作为结束:

参考资料

上一篇下一篇

猜你喜欢

热点阅读