Understanding Crashes and Crash Logs

前段时间通过搭建 Crash 平台的机会,知道了如何进行 Crash 的解析和聚类。那么如何去理解一个 Crash 呢?这篇文章是通过 WWDC 中的资料进行整理学习的。

崩溃的基本原理

什么是崩溃

崩溃当你的 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。