前段时间通过搭建 Crash 平台的机会,知道了如何进行 Crash 的解析和聚类。那么如何去理解一个 Crash 呢?这篇文章是通过 WWDC 中的资料进行整理学习的。
- Understanding Crashes and Crash Logs - WWDC 2018 及相关的视频和资料。
- 参考文章:Understanding Crashes and Crash Logs
崩溃的基本原理
什么是崩溃
崩溃当你的 app 试图做一些不被允许的事情导致被突然终止。如:
- impossible for CPU to execute code : CPU 无法执行某些代码 (除以0)
- Operating system is enforcing a policy: 操作系统正在执行某些策略 (为了保证系统流畅性,操作系统kill了 启动时间过长、使用了太多内存的app)
- Programming language is preventing failure: 编程语言自身阻止失败并触发崩溃 (数组越界)
- Developer is preventing failure: 开发者自己触发(assert)
查看崩溃的方式
- Crashes Organizer window
- Devices window
- Automated testing (Xcode、 Xcode Server、 Xcode-build)
- Console app
- Sharing from device (用户隐私共享数据分享)
如何阅读崩溃日志
log文件中包含crash的的基本信息,crash原因,崩溃栈,寄存器、loader images。
如何分析 Crash 原因
有了这些信息后,怎么分析crash原因呢?首先看一下Exception Type,通过这个可以知道crash的原因,
EXC_BAD_INSTRUCTION (SIGILL)
下面这个例子中 EXC_BAD_INSTRUCTION 的意思是 CPU 可能尝试执行不合法的指令。也可以看一下Crash Thread的调用栈.
引出断言和前提条件 (发生错误时故意中止该过程)
- 强制拆开存储 nil 的 Optional (针对 Swift)
- Array越界访问
- 算术溢出
- 未捕获的异常
- 代码中的自定义断言
EXC_CRASH (SIGKILL)
另一个具体的例子 EXC_CRASH ,下面这个crash信息可以看出,具体原因是看门狗定时器超时,一般是因为APP启动的时间过长或者响应系统事件事件超时导致;比如在主线程进行网络请求,主线程会一直卡住直到网络回调回来。后来占用资源也可能会被系统 kill (0xdead10cc)。
被操作系统kill的几种情况
- Watchdog events :Watchdog 定时器超时
- Device overheated :设备过热
- Memory exhaustion : 内存超出
- Invalid code signature :无效签名
Avoiding Launch Timeouts (如何避免启动超时)
- Frequent crash reason in app review (进行app审查)
- Disabled in Simulator and in the debugger (在模拟器和调试器中被禁用)
- Test your app without the debugger :
1:without the debugger : 非 debug 模式测试你的应用
2:on a real device : 在真实的设备上
3:on older hardware : 在较旧的硬件上
EXC_BAD_ACCESS (SIGSEGV)
这个例子是出现了 Memory Errors 导致。
导致内存出错的原因:
- 写入只读存储器
- 从根本不存在的内存中读取。(读取释放后的对象)
- over released
- buffer overflow
通过crash地址可以得到更多信息,7fdd5e70700这个地址在MALLOC_TINY的地址空间范围内,
当free函数删除一个对象时,它会将其插入到其他 dead 对象的空闲列表中。
如何找到具体的对象 ?有没有办法知道具体是哪个object被多次release导致的crash呢?日志里面虽然有调用栈信息,但是都是编译器生成的函数,没有跟crash相关的具体信息。下面通过一个具体的例子说明如何找到LoginViewController中被多次release的对象。
- 在命令行或者xcode打开lldb
- command script import lldb.macosx.crashlog
- 加载crash log文件
Exception Codes 异常出错的代码(常见代码有以下几种)
0x8badf00d错误码:Watchdog超时,意为“ate bad food”。
0xdeadfa11错误码:用户强制退出,意为“dead fall”。
0xbaaaaaad错误码:用户按住Home键和音量键,获取当前内存状态,不代表崩溃。
0xbad22222错误码:VoIP应用(因为太频繁?)被iOS干掉。
0xc00010ff错误码:因为太烫了被干掉,意为“cool off”。
0xdead10cc错误码:因为在后台时仍然占据系统资源(比如通讯录)被干掉,意为“dead lock”。
Crash Log Analysis Summary
- Understand the crash reason : 明白 Crash 原因
- Examine the crashed thread’s stack trace : 检查崩溃线程的堆栈跟踪
- Look for more clues in bad address and disassembly : 在错误地址和反汇编中查找更多线索
Crash Analysis Tips
- Look at code other than the line that crashed : 看看除了崩溃的行之外的代码
- Look at thread stack traces other than the crashed thread : 查看崩溃线程以外的线程堆栈跟踪
- Look at more than one crash log : 查看多个 crash log
- Use Address Sanitizer and Zombies to reproduce memory errors : 使用 Address Sanitizer 和僵 Zombies 来重现内存错误
-> 参考资料:Understanding and Analyzing Application Crash Reports
Multithreading Issues (多线程问题)
Symptoms of Multithreading Bugs in Crash Logs
- One of the hardest bug types to reproduce and diagnose: 最难复制和诊断的错误类型之一
- Multithreading bugs often cause memory corruptions: 多线程错误通常会导致内存损坏
- Multiple threads currently executing similar code: 当前正在执行类似代码的多个线程
- One bug can appear as different crash points : 一个bug可以显示为不同的崩溃点
Edit Scheme → Dignostics → Thread Sanitizer → finding buffer overflows
Tips
- Test your app on real devices
- Try to reproduce crashes
- Use bug-finding tools on hard-to-reproduce crashes
- Address Sanitizer for memory corruption bugs : 使用Address Sanitizer 调试内存问题
- Thread Sanitizer for multithreading problems : 使用Thread Sanitizer调试多线程问题
Summary
- User Organizer to access crash logs: 关注 Organizer 中的crash
- Analyze reproducible crahses : 分析重复的crahses
- Look for signs of memory corruption and threading issues : 查找内存损坏和线程问题的迹象
- Use bug-finding tools to help reproduce: 利用工具帮助复现问题
- 给每个线程加个名字,发生崩溃容易定位问题
参考文档 & 视频:
如何判定发生了 OOM (Out Of Memory)
收到低内存警告不一定会 crash,OOM 时也不一定能收到低内存警告
facebook的做法是在app启动时使用排除法:
- App没有升级
- App没有调用exit()或abort()退出
- 用户没有强退App
- 系统没有升级/重启
- App当时没有后台运行
- App出现FOOM
如果 app 收到了低内存警告,又在几秒钟之内 crash 了,基本上就可以 100% 确定发生了 OOM。