跳转到内容

崩溃日志

进程一死,崩溃报告就写到设备上了。把它取出来、变成人能读的东西,就是这个模块干的事。

  1. 连上崩溃的那台设备 —— 报告只存在于产生它的设备上。
  2. 打开崩溃日志。
  3. 按应用名和时间戳找到对应条目。
  4. 导出,或者就地阅读。

报告会不断堆积。处理完的删掉,列表才用得下去,这个模块也能删。

原始报告是一堆地址。符号化把地址还原成函数名和行号。

要做到这一点,符号表必须与崩溃的那个二进制严格对应 —— 同版本、同构建号。请为每一个发出去的包保留 dSYM;没有它,无论用什么工具,你自己的栈帧都只会是十六进制。系统栈帧由对应 iOS 版本的系统符号还原。

按这个顺序。

1. 异常类型。 信息量最大的一行。

类型 通常意味着
EXC_BAD_ACCESS(SIGSEGV) 访问了不存在的东西 —— 已释放对象、野指针
EXC_CRASH (SIGABRT) 未捕获异常、断言失败,或主动 abort()
EXC_BREAKPOINT (SIGTRAP) Swift 运行时陷阱 —— 强解包 nil、数组越界、强制类型转换失败
EXC_RESOURCE 超出资源限制,常见的是内存或长时间高 CPU
0x8badf00d 看门狗。启动或响应太慢,被 iOS 杀掉
0xdead10cc 挂起状态下仍持有文件锁或共享资源

2. Terminating description。 有这一行的话,它会直接点名断言或异常。读它,再读栈。

3. 崩溃线程。 报告里有明确标注。这条栈才是重点。

4. 你自己的栈帧。 在崩溃线程里往下找属于你二进制的帧。上面的系统帧通常只是“怎么走进来的”;你代码里最靠上的那一帧才是起点。

列表里有两种条目看着像崩溃,其实不是:

  • Jetsam / 内存报告。 进程因为内存用太多被终止。栈顶没有 bug,bug 是此前那段一路上涨的内存曲线。用性能监控里的内存曲线交叉验证。
  • 看门狗终止。 主线程被阻塞。栈同样只显示阻塞在哪,不显示是什么把它堵住的 —— 去找同步 I/O 或者锁。
  1. 从产生它的那台设备取报告。差不多的设备不算。
  2. 读那个时间戳附近的实时日志 —— App 的临终遗言常常比调用栈更有用。
  3. 查使用记录,看当时还发生了什么。
  4. 查设备信息:系统版本、剩余存储、电池健康。“只在那台手机上”很多时候等于“只在磁盘快满的时候”。

实时日志 · 性能监控 · 设备信息