本文内容基于WWDC 2018中 Understanding Crashes and Crash Logs 整理的。这个Session主要介绍iOS Crash相关的知识:如何分析crash logs,怎么调试和修复crash问题,比如难以重现的内存问题和多线程问题。
Sudden app crashes are a source of bad user experience and app review rejections. Learn how crash logs can be analyzed, what information they contain and how to diagnose the causes of crashes, including hard-to-reproduce memory corruptions and multithreading issues.
Fundamentals
什么是Crash,为什么会产生crash?
- impossible for CPU to execute code
- Operating system is enforcing a policy,为了保证系统流畅性,操作系统会kill 掉内存占用过多的App
- Programming language is preventing failure, 比如assert
- Developer is preventing failure
Crash长啥样
当App发生crash时, attached的debugger会暂停App的运行,并定位的crash位置。
详细看一下crash栈,这里是App启动入口
这里是crash的具体位置
发生crash时,debugger会收到signal,然后暂停App的运行,显示crash的调用栈
如果当前没有attached的debugger,系统会吧crash堆栈信息dump到一个log文件中
release版本的app发生crash时,log文件的调用栈只有地址信息
xcode符号化后的crash log
Accessing crash logs
线上APP发生崩溃时,系统会把log文件上传到云端,然后可以通过xcode的organizer获取并分析crash,整体流程如图:
crash organizer中有TestFlight and App Store上App的crash数据。包括了最近crash的统计数据,受影响最大的devices信息,crash具体位置信息等
使用Crash Organizer
- App Store users opt in
- 使用apple id登录Xcode
- 上传带符号的App
- 打开Crash Organizer
crash堆栈符号化最佳实践
- Upload app symbols for server-side symbolication
- Keep your app archive for local symbolication
- Download debug symbols for bitcode apps
Analyzing crash logs
log文件中包含crash的的基本信息,crash原因,崩溃栈,寄存器等。
有了这些信息后,怎么分析crash原因呢?首先看一下Exception Type,通过这个可以知道crash的原因,比如这个例子中EXC_BAD_INSTRUCTION的意思是CPU可能尝试执行不合法的指令。也可以看一下Crash Thread的调用栈。
Assertions and Preconditions
Deliberately halt the process when an error has occurred Examples:
- Forced unwrap of an Optional that stores nil
- Out-of-bounds Swift.Array access
- Swift arithmetic overflow
- Uncaught exception
- Custom assertion in your code
操作系统 Kill
- Watchdog 定时器超时
- 设备过热
- 内存耗尽
- 无效签名
一个具体的例子,下面这个crash信息可以看出,具体原因是看门狗定时器超时,一般是因为APP启动的时间过长或者响应系统事件事件超时导致;比如在主线程进行网络请求,主线程会一直卡住直到网络回调回来。
Avoiding Launch Timeouts
- Frequent crash reason in app review
- Disabled in Simulator and in the debugger
- Test your app without the debugger
- on a real device
- on older hardware
Memory issues
内存问题分析
- over released
- buffer overflow
- used after free
通过crash地址可以得到更多信息,7fdd5e70700这个地址在MALLOC_TINY的地址空间范围内,下图描述了objc_release的正常过程。
但如果该对象已经被free了,objc_release会导致崩溃。
找到具体的对象
有没有办法知道具体是哪个object被多次release导致的crash呢?日志里面虽然有调用栈信息,但是都是编译器生成的函数,没有跟crash相关的具体信息。下面通过一个具体的例子说明如何找到LoginViewController中被多次release的对象。
// LoginViewController.swift
class LoginViewController: UIViewController {
var userName: String
var database: DatabaseProxy
var views: [UIView]
...
}
- 在命令行或者xcode打开lldb
- command script import lldb.macosx.crashlog
- 加载crash log文件
反汇编(disassemble),打印出crash位置的汇编代码,通过分析汇编代码可以知道哪里出了问题。
常见内存问题
- Crash in objc_msgSend or retain/release
- Unrecognized selector exception
- abort() inside malloc/free
小结
- 找到crash的原因
- 看一下crash线程的调用堆栈
- 通过crash地址和反汇编的方式找到更多的线索
Multithread issues
- 多线程问题比较难重现和排查
- 多线程问题经常会导致内存问题
- 同一个问题崩溃位置通常不一致
使用Thread Sanitizer调试多线程问题
- Reliably reproduces multithreaded
- bugs Works in Simulator
给每个线程加个名字,发生崩溃是容易定位问题
小结
- Use Organizer to access crash logs
- Analyze reproducible crashes
- Look for signs of memory corruption and threading issues
- Use bug-finding tools to help reproduce
- Address Sanitizer for memory corruption bugs
- Thread Sanitizer for multithreading problems