# 理解 iOS 崩溃报告中的异常

By [Octree](https://paragraph.com/@octree) · 2022-03-01

---

iOS 中的异常类型有以下几种：

*   EXEC\_BREAKPOINT（SIGTRAP） 和 EXEC\_BAD\_INSTRUCTION（SIGILL）：进程被一个陷阱信号中
    
*   EXC\_BAD\_ACCESS：内存访问引起的崩溃
    
*   EXEC\_CRASH（SIGABRT）：进程因收到SIGABRT而终止
    
*   EXEC\_CRASH（SIGKILL）：操作系统终止了该进程
    
*   EXEC\_CRASH（SIGQUIT）：该进程在另一进程的请求下终止。
    
*   EXEC\_GUARD： 该过程违反了资源保护的防护措施。
    
*   EXC\_RESOURCE：该进程超过了资源消耗限制。
    
*   EXC\_ARITHMETIC：崩溃的线程执行了无效的算术操作，如零除法或浮点错误。
    

EXC\_BREAKPOINT (SIGTRAP) and EXC\_BAD\_INSTRUCTION (SIGILL)
------------------------------------------------------------

断点异常类型表示一个跟踪陷阱中断了进程。跟踪陷阱使附加的调试器有机会在进程执行中的特定点中断进程。在 `ARM` 处理器上，它显示为EXC\_BREAKPOINT（SIGTRAP）。在 `x86_64` 处理器上，它以 EXC\_BAD\_INSTRUCTION（SIGILL）的形式出现。

Swift 运行时使用跟踪陷阱来处理特定类型的不可恢复的错误--关于这些错误的信息，请参见[处理 Swift 运行时错误的崩溃](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes/addressing_crashes_from_swift_runtime_errors)。一些较低级的库（如 Dispatch）在遇到不可恢复的错误时，会用这个异常捕获进程，并在崩溃报告的附加诊断信息部分中记录有关该错误的附加信息。有关这些信息的信息，请参见[诊断信息](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/examining_the_fields_in_a_crash_report#3582416)。 如果你想在自己的代码中使用同样的技术来处理不可恢复的错误，可以调用`__builtin_trap()` 函数。这允许系统生成一个带有线程回溯的崩溃报告，显示你是如何到达不可恢复的错误的。

EXC\_CRASH (SIGABRT)
--------------------

EXC\_CRASH（SIGABRT） 表示进程因为收到`SIGABRT`信号而终止。通常情况下，这个信号的发送是因为进程中的一个函数调用了 abort()，比如当一个应用程序遇到一个未捕获的 Objective-C 或 C++ 语言异常时。[Addressing Language Exception Crashes](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes/addressing_language_exception_crashes) 详细解释了如何处理未捕获的语言异常。 如果没有 Last Exception Backtrace 表明语言异常触发了崩溃，请查看崩溃线程的 backtrace，以确定进程中的代码是否调用了abort()。 当 `App Extension` 需要花费过多的时间来初始化时，操作系统会向应用扩展进程发送一个 `SIGABRT`。这些崩溃包括一个值为 `LAUNCH_HANG` 的异常子类型字段。因为扩展没有主函数，任何用于初始化的时间都发生在你的扩展和依赖库中存在的静态构造函数和 load() 方法中。尽管在看门狗终止中的异常信息是不同的，但用在[解决看门狗终止](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes/addressing_watchdog_terminations)中讨论的相同技术来调查 LAUNCH\_HANG。

EXC\_CRASH (SIGKILL)
--------------------

EXC\_CRASH (SIGKILL) 表示操作系统终止了该进程。崩溃报告包含一个终止原因字段，其中有一个解释崩溃原因的代码。在下面的例子中，该代码是 0xdead10cc。

    Exception Type:  EXC_CRASH (SIGKILL)
    Exception Codes: 0x0000000000000000, 0x0000000000000000
    Exception Note:  EXC_CORPSE_NOTIFY
    Termination Reason: Namespace RUNNINGBOARD, Code 0xdead10cc
    

code 是以下数值之一：

*   **0x8badf00d** (发音为 "ate bad food")： 操作系统的看门狗终止了该应用程序。请参阅[处理看门狗终止](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes/addressing_watchdog_terminations)。
    
*   **0xc00010ff**（发音为 "cool off"）操作系统由于热事件而终止应用程序。这可能是发生此次崩溃的特定设备的问题，或者是其运行环境的问题。有关如何让你的应用更高效地运行的技巧，请观看 [iOS 性能和电源优化与 Instrucment WWDC Session](https://developer.apple.com/videos/play/wwdc2011/312/) 。
    
*   **0xdead10cc**（发音为："dead lock"）：操作系统因为应用在暂停期间持有文件锁或 SQLite 数据库锁而终止了它。用 beginBackgroundTask(withName:expressionHandler:) 在主线程上请求额外的后台执行时间。在开始向文件写入之前，要做好这个请求，以便在应用程序暂停之前完成这些操作并放弃锁。在应用扩展中，使用 beginActivity(options:reason:) 来管理这项工作。
    
*   **0xbaadca11**（发音为："bad call"）：操作系统因为未能响应 PushKit 通知的 CallKit 呼叫终止了应用程序。
    
*   **0xbad22222**：操作系统终止了一个 VoIP 应用，因为它唤醒过于频繁。
    
*   **0xc51bad01**：watchOS 终止应用程序，因为它在执行后台任务时使用了太多的 CPU 时间。优化执行后台任务的代码以提高 CPU 效率，或减少应用程序在后台运行时执行的工作量，以解决此崩溃。
    
*   **0xc51bad02**：watchOS 终止应用程序，因为它未能在分配的时间内完成后台任务。减少应用程序在后台运行时执行的工作量，以解决此崩溃问题。
    
*   **0xc51bad03**：watchOS 终止应用程序，因为它未能在分配的时间内完成后台任务，但系统总体上足够繁忙，应用程序可能没有获得太多的 CPU 时间来执行后台任务。虽然您可能可以通过减少应用程序在后台任务中执行的工作量来避免这个问题，但 0xc51bad03 并不表明应用程序做错了什么。更有可能的是，由于整体系统负载的原因，应用程序无法完成其工作。
    

EXC\_CRASH (SIGQUIT)
--------------------

EXC\_CRASH (SIGQUIT)表示该进程在另一个有权限管理其生命周期的进程的请求下终止。SIGQUIT并不意味着进程崩溃了，但它很可能以可检测的方式行为不当。 对于 iOS 和 iPadOS 的键盘扩展，如果键盘扩展加载时间过长，Host App 就会终止。虽然在看门狗终止中的异常信息是不同的，但用[解决看门狗终止](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes/addressing_watchdog_terminations)中讨论的相同技术来处理 EXC\_CRASH (SIGQUIT)。

EXC\_GUARD
----------

EXC\_GUARD 表示该进程违反了受保护的资源保护。虽然受保护的系统资源有多种类型，但大多数受保护的资源崩溃是来自受保护的文件描述符，这些文件描述符在异常子类型字段中具有 `GUARD_TYPE_FD` 值。操作系统将一个文件描述符标记为 受保护的，这样正常的文件描述符API就无法修改它们。例如，如果一个应用程序关闭了用于访问支持 Core Data 存储的 SQLite 文件的文件描述符，Core Data 可能会在很久以后神秘地崩溃。守护文件描述可以在这些问题发生时识别这些问题，使其更容易识别和解决。 异常消息字段包含具体的违规行为：

*   **CLOSE**： 进程试图在守护文件描述符上调用close()
    
*   **DUP**： 该进程试图在一个受保护的文件描述符上调用close()。进程试图在受保护的文件描述符上使用 **F\_DUPFD** 或 **F\_DUPFD\_CLOEXEC** 命令调用 dup()、dup2() 或 fcntl()。
    
*   **NOCLOEXEC**：进程试图从受保护的文件描述符上删除FD\_CLOEXEC标志。
    
*   **SOCKET\_IPC**：进程试图通过套接字发送一个受保护的文件描述符。
    
*   **FILEPORT**：进程试图为一个受保护的文件描述符获取马赫发送权。
    
*   **WRITE**：进程试图向受保护的文件描述符写入。
    

异常消息字段还标识了进程试图修改的特定的受保护文件描述符。要了解触发该异常的上下文，请查阅崩溃线程的回溯。

EXC\_RESOURCE
-------------

**EXC\_RESOURCE** 是操作系统发出的进程超过资源消耗限制的通知。如果 Exception Note 字段包含`NON-FATAL CONDITION`，那么即使操作系统生成了崩溃报告，该进程也没有被终止。异常消息字段描述了特定时间间隔内消耗的资源量。 崩溃报告在 `Exception Subtype` 字段中列出了具体的资源：

*   **CPU 和 CPU\_FATAL**：进程中的一个线程在短时间内使用了过多的 CPU。
    
*   **MEMORY**：进程越过了系统施加的内存限制。这可能是因过度使用内存而终止的前兆。
    
*   **IO**：进程在短时间内造成了过量的磁盘写入。
    
*   **WAKEUPS**：进程中的线程每秒唤醒次数过多，消耗电池寿命。线程与线程之间的通信API，如execute(_:on:with:waitUntilDone:)、async(execute:) 或 dispatch\_async(_:\_:)，在不知不觉中被调用的次数远远超过预期，就会造成这种情况。因为触发这种异常的通信发生得非常频繁，通常有多个后台线程的回溯非常相似，表明线程通信的起源。阅读 [Modernizing Grand Central Dispatch Usage](https://developer.apple.com/videos/play/wwdc2017/706/)。

---

*Originally published on [Octree](https://paragraph.com/@octree/ios)*
