关于Crash
当app发生crash时,系统会生成crash report 存储在设备上,其中会描述app在何种情况下被系统终止,一般会包括完整的线程调用堆栈。包括以下2种report:
- Crash report:包含堆栈信息,被符号化之前是运行内容中的实际函数地址,需要将report与 .dSYM或 .app共同分析出内存中的实际地址映射的函数方法,从而便于定位问题。
- Low Memory report:没有堆栈信息,由于低内存引发crash,下面会详细介绍
符号化
crash符号化常用的3个文件
- symbolicatecrash:是系统提供的符号化执行程序,依赖于.dSYM、.crash 和.app 3个文件
- .dSYM : 编译器在把你的源代码转换成机器码的同时,也会生成一份对应的Debug符号表,保存十六进制函数地址映射信息,通过他能得知log中的堆栈地址与具体方法的地址的映射关系
- .crash : 是系统保存的崩溃日志文件,包括所有线程状态,和方法调用堆栈
也可使用atos命令符号化, 只需要.crash和 .dSYM和app其中一个即可。
以下是符号化之前和符号化之后的堆栈信息,第3行符号化之后即可看到自己实现的函数名
为什么系统函数的堆栈能打印出来? 因为系统函数的符号表在xcode中已经内置,且不同系统版本不一样,所以每次我们在升级xcode后,就会包含xcode支持的最新iOS系统的符号表。
异常信息
carsh report 中的字段能提供异常信息,便于从宏观角度分析crash的原因。
下面是由于uncaught Objective-C exception
而导致的进程被停止的crash report的摘录
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0
下面是由于反向引用了一个NULL指针而造成进程被终止的crash report的摘录
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 0
其中
Exception Type
: 异常类型,下面会详细定义不同类型的含义。
Exception Codes
:和异常是有关的处理器指定信息,这些信息会被编码成一个或者多个64位二进制数字。一般来说,这个字段不应该存在,因为crash report生成时会把exception code转化成可读的信息并在其它字段进行体现。
Exception Subtype
:可读的exception code的名称。
Exception Message
:从exception code中解析出来的附加的可读信息。
Exception Note
:不特指某一种异常的额外信息。如果这个字段包含”SIMULATED”(不是Crash),则进程并没有发生crash,而是在系统层面被kill掉了,比如看门狗机制。
Termination Reason
:当进程被终止时的原因及信息。关键的信息模块,不论是进程内还是进程外,当遇到一个致命错误(fatal error,例如bad code signature,缺失依赖库,不恰当的访问私有敏感信息等)。MacOS Sierra,iOS 10, watch OS3和tvOS 10 已经采用新的架构去记录这些错误信息,所以这些系统之下的crash report会在Termination Reason这个字段里描述error message信息。
Triggered by Thread
:指出异常是在哪个线程发生的
看门狗
为了防止应用占用过多系统资源,看门狗机制能检测应用性能,若超出阈值,看门狗会强制终结这个应用的进程。
触发时机 | 看门狗出动的时间 |
---|---|
启动 | 20秒 |
恢复运行 | 10秒 |
挂起进程 | 10秒 |
退出应用 | 6秒 |
后台运行 | 10分钟 |
常见异常类型
Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
进程试图访问无效的内存空间,或尝试访问的方法不允许(例如去写只读的内存空间)
Abnormal Exit [EXC_CRASH // SIGABRT]
进程异常退出,常见原因是uncaught Objective-C/C++ exception
并且调用了abort()。
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
和Abnormal Exit
类似,这种异常是由于在特殊的节点加入debugger调试节点的原因。
Illegal Instruction [EXC_BAD_INSTRUCTION // SIGILL]
尝试执行一个非法或者未定义的指令时会触发该异常。
Quit [SIGQUIT]
这个异常是由于其他进程拥有高优先级且可以管理本进程所导致(被高优先级进程kill掉)。
Killed[SIGKILL]
进程收到系统指令被干掉,可以自行查看Termination Reason来定位线程被干掉的原因。
Guarded Resource Violation [EXC_GUARD]
进程访问了一个被保护的资源。
Resource Limit [EXC_RESOURCE]
进程的资源超过限定阈值,表示进程占用太多资源,Exception Subtype
会提示原因,例如
MEMORY
:暗示了进程占用已经超过系统限制。如果之后出现由于系统占用过多进程被Kill,可能和这有关。WAKEUP
:暗示线程每秒被进程唤醒太多次了,进而导致CPU被频繁唤醒并且造成电量损耗。 通常,这种事发生在线程间通信(通过peformSelector:onThread:
或者dispatch_async
),而且会远比预想的发生的更频繁。因为发生这种异常的通信被触发的如此频繁,所以很多后台线程会出现彼此高度雷同的堆栈信息——恰恰暗示了它们是从哪儿来的。
Other Exception Types
有些report可能出现无名的Exception Type
,取而代之出现的是16进制的地址(0x12387617823),下面列举一些
0xbaaaaaad
: 则说明此条logs是系统堆栈快照,并非crash report。可以通过同时按(手机)侧边按钮和音量键来记录堆栈快照。通常情况下,这些logs是用户无意中生成的,并非表示错误。0xbad22222
: 表示一个VoIP应用因为频繁暂停被iOS系统终止掉。0x8badf00d
:(读起来像badfood)则说明一个应用因为触发了看门狗机制被iOS系统终止掉,有可能是应用花了太长时间启动,终止,或者是响应系统事件。一种常见原因是在主线程上做网络同步逻辑。不论Thread0上(也就是主线程)想做什么(重要的事),都应该转移到后台线程,或者换一种方式触发,这样它才不会阻塞主线程。0xc00010ff
: 则说明app因为环境过热(的事件)被iOS系统干掉了。这个也许是和发生crash的特定设备有关,或者是和它所在的环境有关。0xdead10cc
: (读起来像deadlock)则说明一个应用被系统终止掉,原因是在应用挂起时拿到了文件锁或者sqlite数据库所长期不释放直到被冻结。如果你的app在挂起时拿到了文件锁或者sqlite数据库锁,它必须请求额外的后台执行时间(request additional background execution time )并在被挂起前完成解锁操作。0x2bad45ec
: 则说明app因为违规操作(安全违规)被iOS系统终止。终止描述会写:“进程被查到在安全模式进行非安全操作”,暗示app尝试在禁止屏幕绘制的时候绘制屏幕,例如当屏幕锁定时。用户可能会忽略这种异常,尤其当屏幕是关闭的或者当这种终止发生时正好锁屏。
低内存Low Memory Reports
当系统检测到内存不足时,虚拟内存系统会协同各应用来做内存释放,各个应用都会接受到内存警告,要求释放内存空间。如果内存依然不够,则你的应用会被终止,并生成report 存储在设备中,没有堆栈信息,可能的原因有一下几个:
[per-process-limit]
:进程占用超过了它的最大内存值。每一个进程在常驻内存上的限制是早已经由系统为每个应用分配好了的。超过这个限制会导致进程被系统干掉。[vm-pageshortage]/[vm-thrashing]/[vm]
:由于系统内存压力被干掉。[vnode-limit]
: 打开太多文件了。[highwater]
:一个系统守护进程超过过了它的内存占用高水位(就是已经很危险了)。[jettisoned]
:进程因为其它不可描述的原因被杀掉。