1. EXC_BREAKPOINT (SIGTRAP)
和 EXC_BAD_INSTRUCTION (SIGILL)
断点异常类型表示跟踪陷阱
(trace trap
)中断了该进程。跟踪陷阱
使附加的调试器有机会在进程执行的特定点中断进程。
在 ARM
处理器上显示为 EXC_BREAKPOINT(SIGTRAP)
在 x86_64
处理器上显示为 EXC_BAD_INSTRUCTION(SIGILL)
- Swift 运行时错误 Swift 使用内存安全技术来及早捕获编程错误。如果 Swift 运行时遇到编程错误,运行时会捕获该错误并故意使程序崩溃,这些崩溃在崩溃报告中具有可识别的异常信息:
- 在 ARM 处理器:
Exception Type: EXC_BREAKPOINT (SIGTRAP)
...
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
- 在 Intel 处理器上:
Exception Type: EXC_BAD_INSTRUCTION (SIGILL)
...
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Illegal instruction: 4
Termination Reason: Namespace SIGNAL, Code 0x4
常见原因如:使用 !
强行解开可选值 nil
,或使用 as!
强制向下转换失败。
一些底层库(如 Dispatch
)在遇到不可恢复的错误时,会捕获此异常类型,并在 Additional Diagnostic Information
中记录有关该错误的附加信息。
若想在自己代码中使用相同技术来处理不可恢复的错误,请调用 __builtin_trap()
函数,这将允许系统生成带有线程回溯的崩溃报告,表明代码如何达到不可恢复的错误。
2. EXC_BAD_ACCESS
内存访问问题
2.1. 僵尸对象
当对象被释放后,再给其发送消息,此时是由运行时的僵尸对象接收。向已释放的对象发送消息可能会导致OC运行时的objc_msgSend
、objc_retain
、objc_release
函数崩溃。如:
Thread 0 Crashed:
0 libobjc.A.dylib 0x00000001a186d190 objc_msgSend 16
1 Foundation 0x00000001a1f31238 __NSThreadPerformPerform 232
2 CoreFoundation 0x00000001a1ac67e0 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 24
另一种可能由于僵尸对象导致的崩溃是 Last Exception Backtrace
包含 doesNotRecognizeSelector(_:)
:
Last Exception Backtrace:
0 CoreFoundation 0x1bf596a48 __exceptionPreprocess 220
1 libobjc.A.dylib 0x1bf2bdfa4 objc_exception_throw 55
2 CoreFoundation 0x1bf49a5a8 -[NSObject 193960 (NSObject) doesNotRecognizeSelector:] 139
如果能复现该崩溃,在控制台的日志如下:
代码语言:javascript复制Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[NSNumberFormatter playSound]:
unrecognized selector sent to instance 0x28360dac0'
该示例给对象发送一条消息未被实现的消息,所以崩溃了。
2.2. 内存访问问题
当程序以意外的方式使用内存时,会导致内存访问问题的崩溃报告。这些报告的异常类型为 EXC_BAD_ACCESS
或 EXC_BAD_ACCESS (SIGBUS)
。如:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
macOS 下内存访问问题有时只能通过信号来识别,如 SIGSEGV
或 SEGV_MAPERR
或 SEGV_NOOP
:
Exception Type: SIGSEGV
Exception Codes: SEGV_MAPERR at 0x41e0af0c5ab8
Xcode 调试内存访问的工具有:
- Address Sanitizer
- Undefined Behavior Sanitizer
- Thread Sanitizer
如果程序包含 OC 、C 或 C 代码,可使用静态分析器,可识别常见的编程错误。
2.2.1. 异常子类型
Exception Subtype
包含 kern_return_t
描述错误和被错误访问的内存地址,如:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
macOS 下 Exception Codes
包含 Exception Subtype
:
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_MEMORY_ERROR at 0x00000001098c1000
异常子类型:
KERN_INVALID_ADDRESS
:通过访问数据或取指令来访问未映射的内存KERN_PROTECTION_FAILURE
:尝试使用受保护的有效内存地址KERN_MEMORY_ERROR
:尝试访问但是无法返回数据的内存,如:不可用的内存映射文件EXC_ARM_DA_ALIGN
:尝试访问未正确对其的内存,此异常代码很少见,因为 64 位 ARM 的 CPU 会处理为对齐的数据。
arm64e 的 CPU 框架使用加密签名的指针身份验证代码来检测和防止内存中指针的意外更改。由于指针身份验证失败而导致的崩溃会有附加信息:
代码语言:javascript复制Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00006f126c1a9aa0 -> 0x000000126c1a9aa0 (possible pointer authentication failure)
有关指针身份验证的更多信息,可参阅 Preparing your app to work with pointer authentication
2.2.2. VM Region Info
VM Region Info
字段显示错误访问的特点内存相对于应用程序地址空间其他部分的位置,如:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
VM Region Info: 0 is not in any region. Bytes before following region: 4307009536
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 0000000100b7c000-0000000100b84000 [ 32K] r-x/r-x SM=COW ...pp/MyGreatApp
引用未映射的内存触发崩溃在 0x0000000000000000
,这是一个无效地址,即一个 NULL 指针。此无效地址位置是应用程序地址控件中有效内存区域的 4307009536
字节。
另外,例如:
代码语言:javascript复制Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000016c070a30
VM Region Info: 0x16c070a30 is in 0x16c070000-0x16c074000; bytes after start: 2608 bytes before end: 13775
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
Stack 000000016bfe8000-000000016c070000 [ 544K] rw-/rwx SM=COW thread 12
---> STACK GUARD 000000016c070000-000000016c074000 [ 16K] ---/rwx SM=NUL ...for thread 11
Stack 000000016c074000-000000016c0fc000 [ 544K] rw-/rwx SM=COW thread 11
内存地址为 0x000000016c070a30
,位于由箭头标识的堆栈保护的特殊内存区域中,该内存区域将一个线程的堆栈与另一个线程的堆栈缓冲。 PRT栏显示了内存区域当前的权限属性,r为可读,w为可写,x为可执行。由于没有权限,所以访问无效,且崩溃报告将此内存访问标识为违反内存保护属性。
堆栈保护只是受保护内存的一个示例,还有其他类型的受保护内存区域,具有不同的保护属性组合。
有关 VM Region Info
的更多信息可参阅:Interpreting vmmap’s Output
2.2.3. 内存访问类型
计数寄存器器包含导致内存访问异常指令的地址
- 无效内存读取:当代码取消引用无效指针时。计数寄存器与异常地址不同。如:
Exception Type: SIGSEGV
Exception Codes: SEGV_MAPERR at 0x21474feae2c8
...
Thread 12 crashed with X86-64 Thread State:
rip: 0x00007fff61f5739d rbp: 0x00007000026c72c0 rsp: 0x00007000026c7248 rax: 0xe85e2965c85400b4
rbx: 0x00006000023ee2b0 rcx: 0x00007f9273022990 rdx: 0x00007000026c6d88 rdi: 0x00006000023ee2b0
rsi: 0x00007fff358aae0f r8: 0x00000000000003ff r9: 0x00006000023edbc0 r10: 0x000021474feae2b0
r11: 0x00007fff358aae0f r12: 0x000060000237af10 r13: 0x00007fff61f57380 r14: 0x00006000023ee2b0
r15: 0x0000000000000006 rflags: 0x0000000000010202 cs: 0x000000000000002b fs: 0x0000000000000000
gs: 0x0000000000000000
计数寄存器是 0x00007fff61f5739d,与异常地址 0x21474feae2c8 不同。
- 无效指令读取:当函数通过错误的函数指针或通过对意外对象的函数调用跳转到另一个函数时。寄存器与异常地址相同。如:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000040
...
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 ??? 0x0000000000000040 0 64
...
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000002 x1: 0x0000000000000040 x2: 0x0000000000000001 x3: 0x000000016dcfe080
x4: 0x0000000000000010 x5: 0x000000016dcfdc8f x6: 0x000000016dcfdd80 x7: 0x0000000000000000
x8: 0x000000010210d3c8 x9: 0x0000000000000000 x10: 0x0000000000000014 x11: 0x0000000102835948
x12: 0x0000000000000014 x13: 0x0000000000000000 x14: 0x0000000000000001 x15: 0x0000000000000000
x16: 0x000000010210c0b8 x17: 0x00000001021063b0 x18: 0x0000000000000000 x19: 0x0000000102402b80
x20: 0x0000000102402b80 x21: 0x0000000204f6b000 x22: 0x00000001f6e6f984 x23: 0x0000000000000001
x24: 0x0000000000000001 x25: 0x00000001fc47b690 x26: 0x0000000102304040 x27: 0x0000000204eea000
x28: 0x00000001f6e78fae fp: 0x000000016dcfdec0 lr: 0x00000001021063c4
sp: 0x000000016dcfdec0 pc: 0x0000000000000040 cpsr: 0x40000000
esr: 0x82000006 (Instruction Abort) Translation fault
Binary Images:
0x102100000 - 0x102107fff MyCoolApp arm64 <87760ecf8573392ca5795f0db63a44e2> /var/containers/Bundle/Application/686CA3F1-6CC5-4F84-8126-EE22D03BC161/MyCoolApp.app/MyCoolApp
计数寄存器为 0x0000000000000040,与异常子类型中报告的地址一致。因为是一次错误的取指令,所以回溯中的0帧不包含正在运行的函数(是 ???,而不是符号名)。链接寄存器lr正常情况下包含调用后代码将返回的位置,可以跟踪到错误指令指针。 链接寄存器 0x00000001021063c4 是应用程序进程中加载二进制文件中的指令地址,二进制图像部分显示该地址位于二进制文件内。
x86_64 CPU 架构返回地址存储在堆栈上,而不是链接寄存器中,所以无法追踪函数指针来源。
3. EXC_CRASH(SIGABRT)
表示进程收到 SIGABRT
信号而终止,通常此信号是因为进程调用了 abort()
函数。
如应用程序遇到了未捕获的 OC 或 C 的语言异常。
3.1. 语言异常
Apple 的系统框架在运行时遇到某些类型的编程错误时会引发语言异常,如: 访问数组的索引越界 或 未实现协议所需的方法。这类异常包含以下信息:
代码语言:javascript复制Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
语言异常导致的崩溃包含 Last Exception Backtrace
:
Last Exception Backtrace:
0 CoreFoundation 0x19aae2a48 __exceptionPreprocess 220
1 libobjc.A.dylib 0x19a809fa4 objc_exception_throw 55
根据 Last Exception Backtrace
的堆栈信息,可以定位到引发异常的代码。
Note: C 异常引发的崩溃,Apple 不提供代码回溯。 Note: 如果抛出异常的 API 是
doesNotRecoganizeSelector(_:)
,则崩溃可能是由于僵尸对象造成的。
如果没有 Last Exception Backtrace
表明语言异常触发了崩溃,请查看崩溃线程的回溯以确定进程中的代码是否调用了 abort()
。
3.2. 配置错误 - 缺少框架
如果程序因缺少必要框架而崩溃,报告会包含 EXC_CRASH (SIGABRT)
的 Exception Codes
和 Termination Description
描述 dyld (识别动态链接器)找不到特定的框架。如:
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Description: DYLD, dependent dylib '@rpath/MyFramework.framework/MyFramework'
not found for '<path>/MyCoolApp.app/MyCoolApp', tried but didn't find:
'/usr/lib/swift/MyFramework.framework/MyFramework'
'<path>/MyCoolApp.app/Frameworks/MyFramework.framework/MyFramework'
'@rpath/MyFramework.framework/MyFramework'
'/System/Library/Frameworks/MyFramework.framework/MyFramework'
当 app 的 extension 花费太多时间在 initialize
时,系统会发送 SIGABRT
信号中断进程。报告的 Exception Subtype
字段会包含 LAUNCH_HANG
信息。因为 extensions 没有 main 函数,所以初始化所花费的时间都发生在 扩展 和 依赖库中的静态构造函数 和 load()
方法中,尽管异常信息不同于 watchdog 。
4. EXC_CRASH(SIGKILL)
操作系统终止了该进程,报告中的 Termination Reason
带有解释崩溃原因的代码。如:
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace RUNNINGBOARD, Code 0xdead10cc
0x8badf00d
watchdog
(ate bad food) 操作系统使用 watchdog 来监控应用程序的响应能力,watchdog 会终止长时间无法响应的应用程序。被 watchdog 终止的崩溃报告,Termination Reason
中的 Code 为 0x8badf00d
。 例如:
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Termination Description
包含应用程序如何花费时间的信息,如:
Termination Description: SPRINGBOARD,
scene-create watchdog transgression: application<com.example.MyCoolApp>:667
exhausted real (wall clock) time allowance of 19.97 seconds
| ProcessVisibility: Foreground
| ProcessState: Running
| WatchdogEvent: scene-create
| WatchdogVisibility: Foreground
| WatchdogCPUStatistics: (
| "Elapsed total CPU time (seconds): 15.290 (user 15.290, system 0.000), 28% CPU",
| "Elapsed application CPU time (seconds): 0.367, 1% CPU"
| )
Termination Description
中出现:
scene-create
:表示未在允许的事件内将UI的第一帧渲染到屏幕上
scene-update
:表示没有足够快地更新其UI,因为主线程太忙。
Elapsed total CPU time
:显示 CPU 在挂钟事件内为系统上的所有进程运行了多少时间。(此时间是跨 CPU 的总 CPU 利用率,可能超过100%。如:一个 CPU 利用率为 100%,第二个利用率为 20%,则总利用率为 120%)这个数字处于任意一个极端都是表明存在问题的,若过高,则应用程序正在其所有线程中执行大量工作(包括所有线程,而不仅是主线程);若过低,则应用程序大部分处于空闲状态,因为它正在等在系统资源,如:网络连接。
主线程的回溯并不一定包含问题根源。例如:有项任务需要4s,而允许的总挂钟时间为5s。当 watchdog 在 5s 后终止程序时,花费 4s 的代码不会出现在回溯中,因为它已经完成,但它几乎消耗掉了整个挂钟时间。崩溃报告记录了 watchdog 终止程序时正在执行的操作回溯,即使它并不是问题根源。
隐藏的同步网络代码:
代码语言:javascript复制Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x00000001c22f8670 semaphore_wait_trap 8
1 libdispatch.dylib 0x00000001c2195890 _dispatch_sema4_wait$VARIANT$mp 24
2 libdispatch.dylib 0x00000001c2195ed4 _dispatch_semaphore_wait_slow 140
3 CFNetwork 0x00000001c57d9d34 CFURLConnectionSendSynchronousRequest 388
4 CFNetwork 0x00000001c5753988 [NSURLConnection sendSynchronousRequest:returningResponse:error:] 116 14728
5 Foundation 0x00000001c287821c -[NSString initWithContentsOfURL:usedEncoding:error:] 256
6 libswiftFoundation.dylib 0x00000001f7127284 NSString.__allocating_init 680580 (contentsOf:usedEncoding:) 104
7 libswiftFoundation.dylib 0x00000001f712738c String.init 680844 (contentsOf:) 96
8 MyCoolApp 0x00000001009d31e0 ViewController.loadData() (in MyCoolApp) (ViewController.swift:21)
示例在第7帧中,调用init(contentsOf:)
方法,会在返回前隐式触发同步网络请求(网络好的情况下会立马返回,但在网络差的情况下会耗时很长)。其他通过 URL 初始化的类,如:XMLParser
和 NSData
也一样。
隐式同步网络其他常见示例:
SCNetworkReachability
:默认是同步的,如SCNetworkReachabilityGetFlags(_:_:)
方法。可以使用NWPathMonitor
代替。- BSD 提供的 DNS 方法:如:
gethostbyname(_:)
和gethostbyaddr(_:_:_:)
在主线程调用都是不安全的。getnameinfo(_:_:_:_:_:_:_:)
和getaddrinfo(_:_:_:_:)
只有当你只使用 IP 地址,而不是 DNS名称 时才是安全的(即:你分别指定AI_NUMERICHOST
和NI_NUMERICOST
)。可以使用CFHost
或<dns_sd.h>
里的 APIs 代替。
0xc00010ff
发热
(cool off) 系统由于发热事件终止了程序。可能是发生崩溃的特定设备或其运行环境的问题导致。有关使程序更高效运行,可参阅iOS Performance and Power Optimization with Instruments
0xdead10cc
死锁
(dead lock) 系统终止了程序,因为其在挂起期间保留了文件锁或 SQLite 数据库锁。使用 beginBackgroundTask(withName:expirationHandler:)
请求在主线程上额外的后台执行时间。在开始写入文件之前发出此请求,以便在应用程序挂起之前完成这些操作并放弃锁定。在程序扩展中使用 beginActivity(options:reason:)
来管理此工作。
0xbaadca11
错误访问
(bad call) 系统未能报告 CallKit
的调用,以响应 PushKit
的通知,而终止了程序
0xbad22222
频繁调用 系统终止了 VoIP 程序,因为它恢复太频繁
0xbaddd15c
空间不足 (bad disc) 系统终止程序以删除缓存来尝试回收磁盘空间。许多因素都会导致磁盘空间不足,建议最大限度地减少写入磁盘的内容并管理文件的整个生命周期。
0xc51bad01
占用CPU watchOS 终止了程序,因为它在执行后台任务时使用了太多CPU的时间。优化执行后台任务的代码以提供 CPU 效率,或减少程序在后台运行时执行的工作量以解决此崩溃问题。
0xc51bad02
超时执行 watchOS 终止了程序,因为它未能在分配的时间内完成后台任务。减少程序在后台运行时执行的工作量以解决此崩溃问题。
0xc51bad03
系统繁忙 watchOS 终止了程序,因为它未能在分配的时间内完成后台任务,但系统总体上足够繁忙,以至于程序可能没有获得太多的CPU时间来执行后台任务。尽管可以通过减少应用程序在后台任务中执行的工作量来避免该问题,但0xc51bad03
并不表明该应用程序做了任何错误。更有可能的是,由于整体系统负载,应用程序无法完成其工作。
5. EXC_CRASH(SIGQUIT)
应另一个进程的请求而终止
EXC_CRASH (信号退出)
表示进程应另一个有权管理其生命周期的进程的请求而终止。SIGQUIT
并不意味这进程崩溃了,但可能以可检测的方式出现了错误行为。
如果 iOS 和 iPadOS 键盘扩展加载时间过长,主应用程序会终止键盘扩展。尽管与 watchdog 的异常信息不同,可参考:2.4.1. 0x8badf00d watchdog
6. EXC_GUARD
受保护资源
进程入侵了受保护的资源,尽管受保护的系统资源有很多类型,但大多数受保护的资源崩溃都有来自受保护文件的描述,这些文件描述在字段中具有值。系统将文件描述标记为受保护,以使普通文件描述的 API 无法修改它们。
Exception Message
字段包含具体的违规行为:
CLOSE
:程序尝试对受保护的文件调用close()
DUP
:程序尝试通过F_DUPFD
或F_DUPFD_CLOEXEC
命令,对受保护的文件调用dup()
、dup2()
、fcntl()
NOCLOEXEC
:程序尝试删除受保护文件的FD_CLOEXEC
标志SOCKET_IPC
:程序尝试通过socket
发送受保护的文件FILEPORT
:程序尝试获取受保护文件的 Mach 发送权WRITE
:程序尝试对受保护的文件描进行写入
7. EXC_RESOURCE
超出了资源消耗限制
来自系统的EXC_RESOURCE
通知,表明该进程超出了资源消耗限制。
如果 Exception Note
字段包含 NON-FATAL CONDITION
,即使系统生成崩溃报告,进程也不会终止。
Exception Message
字段描述了特定时间间隔内消耗的资源量。
Exception Subtype
字段列出了特定资源:
CPU
和CPU_FATAL
:进程中的线程在短时间内占用过多的 CPUMEMORY
:进程超出了系统设置的内存限制IO
:进程短时间内对磁盘的写入量过多WAKEUPS
:进程中的线程每秒唤醒次数过多,这会消耗电池寿命。线程通信 API,如perform(_:on:with:waitUntilDone:)
、async(execute:)
或dispatch_async
,当无意识的调用导致频率远高于预期时会导致这种崩溃。因为触发此异常的通信频繁发生,通常多个后台线程具有非常相似的回溯,指示线程通信的起源。了解如何更有效地管理并发工作负载,可参阅Modernizing Grand Central Dispatch Usage
参考: Identifying the cause of common crashes Analyzing a crash report Addressing language exception crashes Understanding the exception types in a crash report Signal (IPC) Investigating memory access crashes Addressing watchdog terminations