官方的分析文档
苹果官方十年间(2009-01-29 ~ 2018-01-08)维护了一个关于Crash分析的文档十分有价值,主要讲解了以下内容
- 什么是符号,符号和App的关系
- 如何符号化(让人看得懂)
- 如何分析各种Crash的可能原因
- 内存和CPU的使用限制引起的Crash
本篇博客记录其中一些重点,推荐大家都去阅读原文
符号化某个地址的命令行工具
在 Symbolicating Crash Reports With atos 章节里提到了命令行 atos(addresses to symbols) 可以帮助我们单行符号化某个地址
1 |
|
如何区别OC的Crash和NULL指针的Crash
异常一般来说分为 OC 层的 NSException 和 底层的 NULL 错误,可以通过Crash报告的字段不同进行分析
OC层的长这个样子,包含Type、Codes、Note 三个字段
1 | Exception Type: EXC_CRASH (SIGABRT) |
NULL错误的长这个样子,包含 Type、SubType、Signal、Reason、Process等多个字段
1 | Exception Type: EXC_BAD_ACCESS (SIGSEGV) |
常见的 EXC SIG 崩溃原因分析
Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
访问了错误的内存,内存在使用时会有一定的保护等级,例如只可读,可读可写
官方文档列举了以下的常见原因:
- 如果堆栈顶部最后调用 objc_msgSend 或者 objc_release,大概率是访问了一个本来应该被释放的对象,可以通过僵尸对象( Zombies instrument)调试方法来查找
- gpus_ReturnNotPermittedKillClient 如果这个在堆栈顶部,一般都是OpenGL ES 或者 Metal 在后台渲染了什么东西
这种常见的内存访问错误的Crash,也可以在开发的Debug阶段开启 Address Sanitizer 他会帮你进行警告
Abnormal Exit [EXC_CRASH // SIGABRT]
异常退出,一般都是有没有Catch住的 OC/C++ 的异常导致的
官方文档列举了一种可能情况是
如果你的App每次启动就Crash,可能是你的启动App的时候写的逻辑太多了,被系统的看门狗干掉了,简化你的启动逻辑
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
这种类似于异常退出,主要是在代码里主动插入 __builtin_trap() 方法来给debugger提供调试的能力
如果没有debugger就会Crash,底层的一些库用他来处理一些重要的异常
根据官方文档描述,这种日志应该只会存在于Debug期间,常见的是Swift中的两种情况
- 给一个非optional的变量赋值了nil
- 或者错误的强制类型转换
Illegal Instruction [EXC_BAD_INSTRUCTION // SIGILL]
这个错误比较硬核,代表着执行了一个未定义的指令,或者是跳转到了一个非法的地址
其中前者 EXC_BAD_INSTRUCTION 多由Intel的 ud2 opcode 产生
目前文档中没有其他的官方提示
Quit [SIGQUIT]
这个是退出信号,一般来讲都是错误的操作或者被其他任务结束了
官方文档举出一个例子,比如键盘程序在某个App中被唤醒,如果长时间没有弹出来,就会被App杀掉,通过这个信号
从文档来看,如果出现了这个Crash,可以从输入框优先排查
Killed [SIGKILL]
这个信号和Quit的区别在于该App被系统强杀掉了,经常出现在Watch App中,有三个编码代表三个意思
代码 | 含义 |
---|---|
0xc51bad01 | Watch App在后台的任务占用了太多CPU,优化后台任务逻辑解决 |
0xc51bad02 | Watch App在后台的任务alloc失败,减少后台任务可以解决 |
0xc51bad03 | Watch App在后台的任务alloc失败,这个和 02 的区别在于不是App的原因,更可能是系统能力不足 |
Guarded Resource Violation [EXC_GUARD]
这个异常只会发生在MacOS或者早期的iOS版本,原因是系统会将某些文件设为保护,如果使用普通的文件操作方法去访问,就会报错
正确的方式是使用系统的私有API访问,但是这些错误都有比较详细的描述,可以通过描述确定是哪个方法产生Crash
Resource Limit [EXC_RESOURCE]
使用了过多的资源,这里的资源包括内存和CPU两种,可以通过Exception Subtype来区别
出现这个报告不代表App一定产生了Crash,当Exception Note 包含了 NON-FATAL CONDITION 字眼时,代表仅仅是警告,并没有真的Crash
官方文档详细的解析了SubType
SubType | 含义 |
---|---|
MEMORY | 使用了过多的内存,会产生被系统强制杀掉的风险 |
WAKEUPS | 代表某个Thread每秒钟唤醒次数太多了,这样会导致CPU唤醒次数过多从而消耗电量 |
不常见的Crash (Other Exception Types)
苹果还列举了一系列不常见的Crash的ID,例如 0xbaaaaaad、0xbad22222、0x8badf00d、0xc00010ff、0xdead10cc、0x2bad45ec
大多这些Crash没有什么共性,都是很特别的Case,如果实在找不到答案,建议直接联系苹果官方
Low Memory 的原因总结
在所有的Crash报告里,还需要关注的是一种和Crash报告头部很像,但是用于描述低内存情况的报告
iOS的内存机制中,如果触发了低内存阈值,系统级会有通知让所有App进行内存回收,如果进行完第一轮回收后,内存仍然不够
系统就会尝试杀掉当前的进程(process),具体机制不知道是不是随机的
如果你运气不好,你的App被干掉了,就会收获一个Low Memory Report,它和Crash Report的区别在于没有堆栈
而当前导致Low Memory的原因,就会被记录在最重要的 table of processes 的 reason 字段 ,常见的原因如下
Reason | 描述 |
---|---|
per-process-limit | 单一进程超过了系统规定的进程内存限制大小,常见于使用了MapView、SpriteKit的模块 |
vm-pageshortage/vm-thrashing/vm | 进程被强制杀掉了 |
vnode-limit | 打开了太多的文件 |
highwater | 系统的守护进程超过了最大值(水位线) |
jettisoned | 不知道什么原因,总之进程被废弃了 |
最重要的一点:如果你没有看到之上的原因,那么你这个报告就不是 Low Memory Report
关于其他内存的文章,可以参考Memory Usage Performance Guidelines,我还没看,但是看名字很厉害