iOS崩溃统计原理 & 日志分析整理
简介
当应用崩溃时,会产生崩溃日志并且保存在设施上。崩溃日志形容了应用结束时所处的环境信息,通常包含完整的线程堆栈追溯信息,这些数据对于调试应用错误非常有帮助。
包含追溯信息的崩溃日志在分析前需要进行符号化。符号化将内存地址替换为更直观的函数名以及行数。
崩溃起因
崩溃是指应用产生了系统不允许的行为时,系统终止其运行导致的现象。崩溃发生的起因有:
- 存在CPU无法运行的代码
不存在或者者无法执行 - 操作系统执行某项策略,终止程序
启动时间过长或者者消耗过多内存时,操作系统会终止程序运行 - 编程语言为了避免错误终止程序:抛出异常
- 开发者为了避免失败终止程序:Assert
产生崩溃日志
在程序出现以上问题时,系统会抛出异常,结束程序:
出现异常情况,终止程序:
捕获异常
分析崩溃日志
在发生崩溃时,会产生崩溃日志并且保存在设施上,用于后期对问题定位,崩溃日志的内容包括以下部分:程序信息、异常信息、崩溃堆栈、二进制镜像。下面对每部分进行说明。
崩溃日志程序信息:
Incident Identifier: B6FD1E8E-B39F-430B-ADDE-FC3A45ED368CCrashReporter Key: f04e68ec62d3c66057628c9ba9839e30d55937dcHardware Model: iPad6,8Process: TheElements [303]Path: /private/var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElementsIdentifier: com.example.apple-samplecode.TheElementsVersion: 1.12Code Type: ARM-64 (Native)Role: ForegroundParent Process: launchd [1]Coalition: com.example.apple-samplecode.TheElements [402] Date/Time: 2016-08-22 10:43:07.5806 -0700Launch Time: 2016-08-22 10:43:01.0293 -0700OS Version: iPhone OS 10.0 (14A5345a)Report Version: 104
汇总部分包含崩溃发生环境的基本信息:
Incident Identifier:日志ID。
CrashReport Key:设施匿名ID,同一设施的崩溃日志该值相同。
Beta Identifier:设施和崩溃应用组合ID。
Process:执行程序名,等同CFBundleExecutable。
Version:程序版本号,等同CFBundleVersion/CFBundleVersionString。
Code type:程序构造:ARM-64、ARM、x86
异常信息:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000Termination Signal: Segmentation fault: 11Termination Reason: Namespace SIGNAL, Code 0xbTerminating Process: exc handler [0]Triggered by Thread: 0
异常信息:
Exception Codes:使用十六进制表示的程序特定信息,一般不展现。
Exception Subtype:易读(相比十六进制地址)的异常信息。
Exception Message:异常的额外信息。
Exception Note:不特指某种异常类型的额外信息。
Termination Reason:程序终止的异常信息。
Triggered Thread:异常发生时的线程。
崩溃堆栈:
Thread 0 name: Dispatch queue: com.apple.main-threadThread 0 Crashed:0 TheElements 0x000000010006bc20 -[AtomicElementViewController myTransitionDidStop:finished:context:] (AtomicElementViewController.m:203)1 UIKit 0x0000000194cef0f0 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 3122 UIKit 0x0000000194ceef30 -[UIViewAnimationState animationDidStop:finished:] + 1603 QuartzCore 0x0000000192178404 CA::Layer::run_animation_callbacks(void*) + 2604 libdispatch.dylib 0x000000018dd6d1c0 _dispatch_client_callout + 165 libdispatch.dylib 0x000000018dd71d6c _dispatch_main_queue_callback_4CF + 10006 CoreFoundation 0x000000018ee91f2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 127 CoreFoundation 0x000000018ee8fb18 __CFRunLoopRun + 16608 CoreFoundation 0x000000018edbe048 CFRunLoopRunSpecific + 4449 GraphicsServices 0x000000019083f198 GSEventRunModal + 18010 UIKit 0x0000000194d21bd0 -[UIApplication _run] + 68411 UIKit 0x0000000194d1c908 UIApplicationMain + 20812 TheElements 0x00000001000653c0 main (main.m:55)13 libdyld.dylib 0x000000018dda05b8 start + 4 Thread 1:0 libsystem_kernel.dylib 0x000000018deb2a88 __workq_kernreturn + 81 libsystem_pthread.dylib 0x000000018df75188 _pthread_wqthread + 9682 libsystem_pthread.dylib 0x000000018df74db4 start_wqthread + 4 ...
第一行列出了线程信息以及所在队列,之后是追溯链中独立栈帧的详细信息:
- 栈帧号。栈帧号为0的代表当前执行停顿的函数,1则是调用当前停顿函数的主调函数,即0为1的被调函数,1为0的主调函数,以此类推。
- 执行函数所在的二进制包
- 地址信息:对于0栈帧来说,代表当前执行停顿的地址。其余栈帧则是获取控制权后接下来执行的地址。
- 函数名
二进制镜像:
Binary Images:0x100060000 - 0x100073fff TheElements arm64 <2defdbea0c873a52afa458cf14cd169e> /var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements...
日之内包含多个二进制镜像,每个二进制镜像内包含以下信息:
- 二进制镜像在程序内的地址空间
- 二进制的名称或者者bundleID
- 二进制镜像的架构信息 arm64等
- 二进制镜像的UUID,每次构建都会改变,该值用于在符号化日志时定位对应的dSYM文件。
- 磁盘上的二进制路径
符号化
app.xcarchive文件,包内容包含dSYM和应用的二进制文件。
更准确的符号化,可以结合崩溃日志、项目二进制文件、dSYM文件,对其进行反汇编,从而取得更详细的信息。
符号化就是将追溯的地址信息转换成函数名及行数等信息,便于研发人员定位问题。
当程序结束运行时,会产生崩溃日志,日志内包含每个线程的堆栈信息。当我们使用Xcode进行调试时,崩溃或者者断点信息都会展现出实例和方法名等信息(符号信息)。相反,当应用被发布后,符号信息并不会包含在应用的二进制文件中,所以服务端收到的是未符号化的包含十六进制地址信息的日志文件。
查看本机崩溃日志步骤如下:
- 将手机连接到Mac
- 启动Xcode->Window->Devices and simulators
- 选择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对追溯链中的每个地址进行符号化,转换为函数信息,产生的结果就是符号化后的崩溃日志。
函数调用堆栈
我们知道,崩溃日志内包含函数调用的追溯信息,明白堆栈是怎样产生的有利于我们了解和分析崩溃日志。
函数调用堆栈
函数调用是在栈进行的,函数从调用和被调用方分为:主调函数和被调函数,这次我们只探讨每个函数在栈中的几个核心部分:
- 上一个函数(主调函数)的堆栈信息。
- 入参。
- 局部变量。
入参和局部变量容易了解,下面探讨为什么要保存主调函数的堆栈信息。
说到这点就需要聊到寄存器。
寄存器
寄存器
寄存器的类型和基本功能:
- eax:累加寄存器,用于运算。
- ebx:基址寄存器,用于地址索引。
- ecx:计数寄存器,用于计数。
- edx:数据寄存器,用于数据传递。
- esi:源变址寄存器,存放相对于DS段之源变址指针。
- edi:目的变址寄存器,存放相对于ES段之目的的变址指针。
- esp:堆栈指针,指向当前堆栈位置。
- ebp:基址指针寄存器,相对基址位置。
寄存器商定
背景:
- 所有函数都可以访问和操作寄存器,寄存器对于单个CPU来说数量是固定的
- 单个CPU来说,某一时刻只有一个函数在执行
- 需要保证函数调用其余函数时,被调函数不会修改或者覆盖主调函数稍后使用的寄存器值
被调函数在执行时,需要使用寄存器来保存数据和执行计算,但是在被调函数完成时,需要把寄存器复原,用于主调函数的执行,所以出现了寄存器商定。
商定内容:
- 主调函数的保存寄存器,在唤起被调函数前,需要显示的将其保存在栈中。
主调寄存器:%eax、%edx、%ecx - 被调函数的保存寄存器,使用前压栈,并在函数返回前从栈中恢还原值。
被调寄存器:%ebx、%esi、%edi - 被调函数必需保存%ebp和%esp,并在函数返回后恢复调用前的值。
遵守寄存器商定的函数堆栈调用
理解了寄存器功能和寄存器商定后,我们再看函数调用堆栈:
函数调用堆栈
- 栈帧逻辑:栈帧的边界由栈帧基地址指针EBP和堆栈指针ESP界定(指针存放在相应寄存器中)。EBP指向当前栈帧底部(高地址),在当前栈帧内位置固定;ESP指向当前栈帧顶部(低地址),当程序执行时ESP会随着数据的入栈和出栈而移动。因而函数中对大部分数据的访问都基于EBP进行。
- 保存栈帧:被调函数必需保持寄存器%ebp和%esp,并在函数返回后将其恢复到调用前的值,亦即必需恢复主调函数的栈帧。
- 回溯:所以获取到崩溃时线程的ebp和esp 就能回溯到上一个调用,依次类推,回溯出所有的调用堆栈
总结
通过以上内容,我们理解了崩溃日志产生原理、崩溃日志内容和崩溃日志分析,下面分享几个分析崩溃日志的小提醒作为结束:
- 不止关注崩溃本行,结合上下文进行分析。
- 不止关注崩溃线程,要结合其余线程的堆栈信息。
- 通过多个崩溃日志,组合分析。
- 使用地址定位和野指针工具重现内存问题。
参考资料
- Apple Understanding and Analyzing Application Crash Reports
Overview Of iOS Crash Reporting Tools - Understanding Crashes and Crash Logs Video
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS崩溃统计原理 & 日志分析整理