分析Crash 日志
1、Header
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
Incident Identifier:crash日志的标示。
CrashReporter Key:设备标志符,同一个设备设备的两份日志,该标志相同。
Process:崩溃进程的可执行文件名。 这与应用程序的信息属性列表中的CFBundleExecutable键的值匹配。
Version:崩溃的进程版本。 此字段的值是崩溃的应用程序的CFBundleVersion和CFBundleVersionString的串联。
Code Type:崩溃的进程的目标体系结构。 这将是ARM-64,ARM,x86-64或x86之一。
Role:终止时分配给进程的task_role。
OS Version:发生崩溃的操作系统版本,包括内部版本号。
2、Exception Information
下面将列出了Mach异常类型和相关字段,这些字段提供的信息与崩溃性质有关。 但在崩溃报告中并非所有字段都会出现。
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0
上面是一个Objective-C异常引起的crash日志
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
上面是引用NULL指针引起的crash日志
Exception Codes:有关异常的处理器特定信息,编码为一个或多个64位十六进制数。通常,此字段将不存在,因为Crash Reporter会解析异常代码以将其作为人类可读的描述呈现在其他字段中。
- 0xbaaaaaad :此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发
- 0xbad22222 :当VOIP程序在后台太过频繁的激活时,系统可能会终止此类程序
- 0x8badf00d :程序启动或者恢复时间过长被watch dog终止
- 0xc00010ff :程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止
- 0xdead10cc :程序退到后台时还占用系统资源被系统终止,如通讯录
- 0xdeadfa11 :程序无响应用户强制关闭
Exception Subtype:异常代码的人类可读名称。
Exception Message:从异常代码中提取的其他人类可读信息。
Exception Note:非特定于一种异常类型的附加信息。如果此字段包含SIMULATED(这不是崩溃),则该进程不会崩溃,但是在系统请求(通常是监视程序)时被杀死。
Termination Reason:终止进程时指定的退出原因信息。进程内部和外部的关键系统组件将在遇到致命错误(例如,错误的代码签名,缺少的依赖库或在没有适当授权的情况下访问隐私敏感信息)时终止该进程。 macOS Sierra,iOS 10,watchOS 3和tvOS 10采用了新的基础设施来记录这些错误,这些操作系统生成的崩溃报告列出了终止原因字段中的错误消息。
Triggered by Thread:发生异常的线程。
3、常见的异常
-
Bad Memory Access【EXC_BAD_ACCESS // SIGSEGV // SIGBUS】
当进程去访问无效的内存或者去写入只读存储器,此时Exception SubType字段包含kern_return_t错误描述和访问的错误的内存地址。
- EXC_BAD_ACCESS:访问了不该访问的内存,一般该类型后面还会有提示,例如野指针造成的错误在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)
- SIGSEGV:小编没有遇到过,网上有人说时重复释放对象造成的、又有人说时试图访问未分配给对象的内存造成的~
- SIGBUS:非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于,后者是由于对合法存储地址的非法访问触发的
debug技巧
- 如果在堆栈的上部出现objc_msgSend或者objc_release,则说明该进程在试图给已释放的对象发消息,可使用Zombies Instrument帮助调试
- 如果在堆栈的上部出现gpus_ReturnNotPermittedKillClient,则说明进程在后台渲染OpenGL ES 或Metal。
- 使用Address Sanitizer检测app,address Sanitizer在编译咱们的代码时增加了额外的内存检测
-
ABnormal Exit【EXC_CRASH // SICABRT】
该进程异常退出。 最常见原因是**未被捕获的Objective-C / C ++异常以及对abort()**的调用。
如果App Extensions花费太多时间进行初始化(看门狗终止),则会以此异常类型终止。 如果由于启动时挂起而导致扩展名被终止,则生成的崩溃报告的Exception Subtype将为LAUNCH_HANG。 因为扩展没有main函数,所以花在初始化上的任何时间都发生在扩展和依赖库中的静态构造函数和+ load方法中。 你应该尽可能地推迟这项工作。
- SIGABRT: 收到Abort信号退出,终止指令
- 读写非法内存址(重复释放,释放野地址)
例如对象接收到未实现的消息、Foundation库的保护监测,例如插入nil到数组中等会遇到此类错误 - 堆越界
- assert
- SIGSEGV:-无效内存引用,实际是发生段错误
1、访问空指针发生段错误
volatile char *m = NULL;
*m;
2、执行没有执行权限的内存
((void (*)(void))NULL)();
将空指针转换为函数指针, 然后执行它所指向的内存. 显然空指针指向的内存是不可以被执行的, 所以触发了段错误.
3、向只读内存进行写操作
void foo()
{
// some codes
}
char *ptr = (char *)foo;
*ptr = 'a';
4、访问用户进程所不充许的地址都会触发段错误,
比如访问未分配的内存的虚地址, 内核内存地址, 系统保留地址等.
- Trace Trap [EXC_BREAKPOINT // SIGTRAP]
与异常退出类似,此异常是在使用debugger时在其执行的特定点中断进程。可以使用__builtin_trap()函数在代码中触发此异常。如果未附加调试器,则终止该过程并生成崩溃报告。
使用较低级别的库(例如libdispatch)时在遇到致命错误时捕获该进程。有关错误的其他信息可以在崩溃报告的“其他诊断信息”部分或设备的控制台中看到。还有就是Swift代码运行时出错以此异常类型终止,例如:具有nil值的非可选类型,强制类型转换失败
SIGTRAP 由断点指令或其它trap指令产生
-
Illegal Instruction 【EXC_BAD_INSTRUCTION (SIGILL)】
SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号
-
Quit 【SIGQUIT】
SIGQUIT 进程在因收到SIGQUIT退出时会产生coredump文件, 在这个意义上类似于一个程序错误信号。
- Killed 【SIGKILL】
此异常是进程在进行系统请求是产生的,一般是watchOS产生较多
SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
等等,以上的说明大部分来自于官网,自己遇到的比较少。
3、堆栈
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
第一行列出了线程号和当前正在执行的调度队列的标识符。
其余行列出了有关回溯中各个堆栈帧的详细信息。 从左到右:
- 第一竖列:堆栈帧号。 堆栈帧以调用顺序呈现,其中第0帧是进程暂停时执行的函数。 第1帧是第0帧函数调用的函数,依此类推。
- 第二竖列:堆栈帧的执行函数所在的二进制文件的名称。
- 第三竖列:对于第0帧,进程停止时执行的机器指令的地址。 对于剩余的堆栈帧,当控制返回到堆栈帧时将下一次执行的机器指令的地址。
- 第四竖列:在符号化崩溃报告中,堆栈框架中函数的方法名称。
4、异常
(1)Exception
数组的越界访问、改变不可改变的对象、没有实现要求实现的协议等等都会造成异常
注意:给已销毁的对象发送消息可能不是crash,而是NSInvalidArgumentException,这时可在堆栈中查看是否有
[NSObject(NSObject) doesNotRecognizeSelector:]
,可以在提交自己程序前使用Zombies检测一下。
(2)Last Exception Backtrace
如果未捕获到异常,它被一个名为uncaught exception handler的函数拦截。 对于未捕获的异常,处理程序将异常消息记录到设备的控制台,然后终止该进程。 在Last Exception Backtrace部分下,只将异常回溯写入生成的崩溃报告,如清单10所示。崩溃报告中省略了异常消息。 如果您收到带有Last Exception Backtrace的崩溃报告,您应该从原始设备获取控制台日志,以便更好地了解导致异常的条件。
相关文档
如果想查看如何使用Zombies模板工具来修复内存释放的crash,可以查看Eradicating Zombies with the Zombies Trace Template 。
如果想查看应用归档的信息,请参考 App Distribution Guide 。
如果想了解关于crash logs的解读,请参考Understanding Crash Reports on iPhone OS WWDC 2010 Session 。
如果对异常NSException不了解,可以点击查看NSException的介绍。
https://www.jianshu.com/p/8a46e78a450b
5、例子
工作中突然领导发给的crash日志,说其他组认为可能是你之前写的业务引起的carsh日志,让你看看,你能怎么办,当然要看了,如下图
先看到Termination Reason:Namespace ASSERTIOND, Code 0x8badf00d,关于0X8badf00d我们都熟悉是因为App启动时间过长或者主线程卡住时间过长,系统被看门狗杀死,但是前面还有Namespace Assertion,于是去stack overflow浏览了一番,了解了一下crash分析,又看了自己的业务代码不像是自己引起的啊,其他组的甩锅技巧很好啊,但是只有自己的技术强硬,都能招架
先看到Termination Reason:Namespace ASSERTIOND, Code 0x8badf00d,关于0X8badf00d我们都熟悉是因为App启动时间过长或者主线程卡住时间过长,系统被看门狗杀死,但是前面还有Namespace Assertion,于是去stack overflow浏览了一番,了解了一下crash分析,又看了自己的业务代码不像是自己引起的啊,其他组的甩锅技巧很好啊,但是只有自己的技术强硬,都能招架