Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]
进程试图访问无效内存,或者试图访问超过内存保护等级的内存(比如访问只读内存)。异常子类型区域包含 kern_return_t,用于描述错误和被错误访问的内存地址。
调试 bad memory crash 的 tips:
- 如果在崩溃了的线程中,靠近堆栈顶部的地方有 objc_msgSend 或者 objc_release,那么这个进程可能在时区访问一个已经被销毁了的对象。你可以使用 Zombies instrument 工具来调试程序。
- 如果在崩溃线程的堆栈顶部有 gpus_ReturnNotPermittedKillClient ,那么说明由于在后台时,程序尝试使用 OpenGL ES 或者 Metal 做渲染工作,所以此进程被杀了。参考: QA1766: How to fix OpenGL ES application crashes when moving to the background
- 使用 Address Sanitizer 调试。
Abnormal Exit [EXC_CRASH // SIGABRT]
进程非正常退出。最常见的崩溃原因是由于发生了未被捕捉的 OC 或者 C++ 异常,调用了 abort()。
如果在初始化的时候耗时过长,应用扩展会被以这种异常类型终止(watchdog termination)。如果是一个扩展是由于启动时时间太长而被杀掉,那么其异常子类型应该为 LAUNCH_HANG。由于扩展没有 main 函数,所以其的初始化的时间都花费在你的扩展和依赖库的静态构造函数和 +load 函数中。你应该尽可能的推迟这项工作。
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
类似于非正常退出,这种异常的目的是为了给连接的调试器一个机会在特定的时间点来打断一个处在异常中的进程。你可以使用 __builtin_trap() 函数来在自己的代码中触发这个异常。如果没有连接调试器,进程会被终止,并生成一个 crash 报告。
低层级的库(e.g. libdispatch)在遇到致命错误时会捕获进程。关于错误的附加信息会出现在崩溃报告的 Additional Diagnostic Information 区域,或者设备的控制台里。
如果如下的未预期的状况发生在 runtime 时,Swift 代码会终止运行,同时报这种类型的异常:
- non-optional 类型的变量值为 nil
- 强制类型转换失败
当这些未预期的情况发生时,查看堆栈看看是哪里发生的问题。额外信息也会记录在设备的控制台里。你应该在崩溃的位置修改代码来处理这种运行时失败。比如,使用 Optional Binding 而不是强制解封一个 optional 值。
Illegal Instruction [EXC_BAD_INSTRUCTION // SIGILL]
进程试图执行一个非法或未定义的指令。进程可能企图通过一个错误配置的函数指针跳去一个无效地址。
在 intel 处理器里,ud2 操作码会造成一个 EXC_BAD_INSTRUCTION 异常,通常用来在调试中捕获进程。在 intel 处理器中的 Swift 代码遇到未预期的状况时会以这种异常类型终止程序。详见 Trace Trap。
Quit [SIGQUIT]
进程能够被另一个有权管理器生命周期的进程的要求下被终止。 SIGQUIT 不代表进程崩溃了,但是很可能确实表现不好。
在 iOS 里,如果键盘扩展程序载入时间太长,就会被主程序退掉。这种情况,堆栈记录里不太可能能够正确指向该负责的代码。更可能的情况,扩展花了很长时间完成了加载,但是在时间限制之前被停止了,此时还有其他代码在执行,当扩展被退出的时候,于是程序在调用栈之中展示的是正在执行的其他代码。你应该好好分析一下这个扩展应用,去理解启动的时候大多数工作在做什么。同时把这些工作移到一个后台线程去执行,或者推迟执行(比如等到扩展程序加载完成)。
Killed [SIGKILL]
进程被系统要求终止。查看 Termination Reason 来理解为何被终止。
Termination Reason 区域包含一个 namespace 和一个 code。以下 code 是专门针对 watchOS 的:
- 0xc51bad01 表明一个 watch app 由于执行后台任务的时候占用太多 CPU 时间而被终止。为了定位这个问题,优化后台执行代码使之更高效使用 CPU,或者减少后台执行的任务量。
- 0xc51bad02 表明一个 watch app 由于没有在分配的时间内完成一个后台任务而被终止。为了定位问题,减少后台执行的任务量。
- 0xc51bad03 表明一个 watch app 没有在分配的时间内完成一个后台任务,同时,系统在这段时间很忙,没有分配足够的 CPU 时间去完成这个后台任务。即使 app 可以通过减少后台任务量来避免这个问题,但是事实上,出现这个问题不代表这个 app 做错了什么,很有可能只是系统超负荷了。
Guarded Resource Violation [EXC_GUARD]
进程违反了被守护资源的保护政策。系统库可能标记特定的文件描述符为 guarded,在这些描述符上正常操作会触发 EXC_GUARD 异常(如果想使用这些文件操作符,系统会使用特定的 guarded 私有 APIs)。这样能够帮助你快速定位一些问题,比如企图关闭一个被系统库打开的文件描述符。例如,如果一个 app 关闭了一个用于返回到 Core Data 存储的 SQLite 文件的文件描述符,那么之后 Core Data 将神秘崩溃。这种 guard 异常让这些问题尽早被注意到,使之更易被调试。
更新版本的 iOS 的崩溃报告在 Exception Subtype 和 Exception Message 中包含 一些造成 EXC_GUARD 异常的人类可阅读详细信息。在 macOS 和老版本的 iOS 中,这些信息被编码到 Exception Code 的第一位中:
- [63:61] - Guard Type: 被守护的资源类型。0x2 代表资源是一个 file descriptor 文件描述符。
- [60:32] - Flavor: 以下情况的违规被触发:
- 如果首位(1 << 0)被设置,进程尝试对一个被守护的文件描述符调用 close()
- 如果第二位(1 << 1)被设置,进程尝试对一个被守护的文件描述符调用使用 F_DUPFD 或 F_DUPFD_CLOEXEC 命令调用 dup()/dup2()/fcnt1()
- 如果第三位(1 << 2)被设置,进程尝试通过 socket 发送一个被守护的文件描述符
- 如果第五位(1 << 4)被设置,进程尝试对一个被守护的文件描述符进行写入操作
- [31:0] - File Descriptor: 进程企图修改的文件描述符
Resource Limit [EXC_RESOURCE]
进程超出了资源使用限制。这是 OS 通知进程使用了太多资源。确切的资源被列在 Exception Subtype 区域。如果 Exception Note 区域包含 NON-FATAL CONDITION,那么这个进程并没有被杀死,即便生成了崩溃报告。
- 异常子类型 MEMORY 表明进程已经超过了系统设定的内存限制。这个可能是即将终止超过的内存使用的前兆。
- 异常子类型 WAKEUPS 表明进程中的线程每秒被唤醒太多次,使 CPU 唤醒太频繁,也会消耗电池生命。
一般来说,这是由于线程之间的交流产生的(一般是使用 performSelector:onThread: 或者 dispatch_async),这种在不知不觉中比想象的更频繁。由于触发这种异常的交流发生的如此频繁,这将产生好几个非常相似的堆栈记录,表明了交流的来源。
Other Exception Types
有一些崩溃报告可能包含未命名的异常类型,会使用十六进制的数值打印。如果你收到这种崩溃报告,直接查看 Exception Codes 区域获取更多信息。
- 0xbaaaaaad 表明这个日志是整个系统的堆栈记录,而不是一份崩溃报告。同时按下侧边栏按钮和两个音量键,可以得到一份 stackshot,通常这些log是用户偶然生成的,不是错误。
- 0xbad22222 表明一个 VoIP(网络语音电话) 程序被 iOS 终止了,因为重启太频繁。
- 0x8badf00d 表明程序被 iOS 终止了,由于 watchdog 超时发生了。这个应用花费了太长时间来启动、终止、或者响应系统事件。一个通常的原因是 在主线程进行了同步网络请求。无论此时是什么操作,都应该移到后台线程进行,或者用其他进程执行,这样就不会阻塞主线程。
- 0xc00010ff 表明这个应用由于过热事件被系统杀掉了。这个一般发生在特定的设备上,或者特定的操作环境中。使你的应用更高效,请参考 iOS Performance and Power Optimization with Instruments。
- 0xdead10cc 表明应用由于在暂停期间持有了一个文件锁或者数据库锁而被 OS 终止了。如果你的应用程序在暂停期间徐亚对一个已锁的文件或者 sqlite 数据库进行操作,那么它需要请求格外的后台运行时间来完成这些操作,并在暂停之前释放锁。
- 0x2bad45ec 表明由于安全违规问题呗 iOS 终止了。终止描述 “Process detected doing insecure drawing while in secure mode” 表明 app 在不被允许时尝试绘制到屏幕,比如屏幕被锁了。用户将注意不到这个终止直到屏幕被解锁,这个终止才发生。
注:使用 App 切换器终止一个暂停中的 app 不会产生崩溃日志。一旦一个 app 已经暂停了,那么它可以被 iOS 在任何时候被终止,且不产生任何崩溃日志。