[译]理解 iOS 异常类型

原文链接:Understanding iOS Exception Types (PS.由于未知原因已失效,经检查,文章中引用的链接都还有效 :)

翻译:CoderWangx

当你的iOS应用崩溃的时候,我们需要去分析异常日志以定位根本原因。崩溃可能是 “低内存崩溃 Low Memory Crash” 或者 “普通异常崩溃”。当碰到“异常”时,更好的理解“不同类型的异常”能够真正帮助我们快速定位问题所在。

在这篇文章中,我们将研究 iOS 应用可能碰到的不同类型的“异常”,例如EXC_CRASHEXC_BAD_ACCESSEXC_RESOURCE00000020 等。

崩溃日志中的“异常”

“异常”这个词在“崩溃日志”语境下更多与“Mach 异常”(以“EXC_为前缀”)和 “UNIX 信号”(如: SIGSEGVSIGBUS等)相关。在某些情况下(应该是有对应的dSYM符号文件时)系统会通过映射将底层的 Mach 异常 翻译为 UNIX 信号。这就是为什么你能log中看到有用 “EXC_CRASH(SIGABRT)” 及 “EXC_BAC_ACCESS(SIGSEGV)” 作为 异常类型(Exception Type)

对于某些异常,还会附带一个关联的 处理器定制异常码(processor-specific Exception Code) 或者 异常子类型(Exception Subtype),用以包含更多问题相关信息。举例来说, “EXC_BAC_ACCESS” 类型异常可能有一行如“KERN_INVALID_ADDRESS at 0x80000010”作为“异常码”; “EXC_RESOURCE” 可能有一行"WAKEUPS"作为"异常子类别"。

UNIX 信号

iOS开发者常见的 UNIX 信号 如下:

UNIX 信号注释
SIGSEGV访问无效的内存地址。地址存在,但是应用程序无法访问。
SIGABRT程序崩溃。由 C函数 abort() 初始化。通常意味着系统检测到某些事务出错,例如 assert() 或者 NSAssert() 校验失败。
SIGBUS访问无效的内存地址。地址不存在,或对齐无效。(The address does not exist, or the alignment is invalid.)
SIGTRAP调试器相关
SIGILL尝试执行非法的、有缺陷、未知的或者需要权限的指令。

更多 UNIX 信号 可以参考这里:Unix_signal

Mach 异常

Mach 异常描述注释
EXC_BAD_ACCESS错误内存访问访问“错误”内存地址。“错误”可能指“地址不存在”或者“应用没有权限访问”。因此通常与 SIGBUS 及 SIGSEGV 相关联。
EXC_CRASH异常跳出通常与 SIGABRT 相关联,意思是由于检测到代码抛出的未捕获异常而使应用程序异常退出。
EXC_BREAKPOINT跟踪/断点捕获通用与 SIGTRAP 相关联。可以由你自己的代码或者 NSExceptions 抛出时触发。
EXC_GUARD违反了受保护资源的防护(Violated Guarded Resource Protection)由违背受保护资源防护触发,例如‘某些文件描述符’。
EXC_BAD_INSTRUCTION非法指令通常与特定非法或未定义指令/操作数相关。
EXC_RESOURCE资源限制应用由于达到资源消耗限制而退出。
00000020十六进制异常类型非 'OS Kernel' 异常。

查看完整 Mach 异常列表请参考 这里 (sys/osfmk/mach/exception_types.h)的源码文件。

异常

EXC_BAD_ACCESS(错误内存访问)

“EXC_BAD_ACCESS” 是APP崩溃时最常见的异常之一。不幸的是,调试起来却不容易。

一般有两种可能性:

  • 访问某些尚未初始化的对象。(SIGBUS)
  • 访问已经被 ARC 释放(导致地址变为不可访问)的对象。如果是这个情况,你通常可以在崩溃日志中的 “Backtrace” 顶部附近看到 objc_release

示例如:

 
  1. Exception Type: EXC_BAD_ACCESS (SIGSEGV)

  2. Exception Codes: KERN_INVALID_ADDRESS at 0x6d783f44

  3. ...

  4. Exception Type: EXC_BAD_ACCESS (SIGBUS)

  5. Exception Codes: KERN_PROTECTION_FAILURE at 0x00000011

  6. 复制代码

“EXC_BAD_ACCESS”也有关联的“异常码”以帮助提供额外信息。举例来说,KERN_PROTECTION_FAILURE 表示内存有效,但是不允许当前形式的访问,KERN_INVALID_ADDRESS 意思是地址当前无效。

查看这里的源码文件获取完整的可能值列表。

为了辅助调试 “EXC_BAD_ACCESS” 类型异常,你可以勾选 Xcode 中的 “Enable Zombie Objects” 后再尝试。

EXC_CRASH(异常跳出)

相较于 “EXC_BAD_ACCESS”,“EXC_CRASH" 更容易遇到。它通常发生在对象接收到未实现的消息时,如 Xcode 调试器中显示的 “unrecognized selector sent to instance 0x6a33840”。

一般情况里这个异常会与调试器一起发挥作用,因为调试器可以中断进程。如果没有附加调试器,会生成一个崩溃日志。

崩溃日志中展示的信息示例:

 
  1. Exception Type: EXC_CRASH (SIGABRT)

  2. Exception Codes: 0x0000000000000000, 0x0000000000000000

  3. ...

  4. ## Usually you will see a similar line in the "backtrace" part

  5. 2 CoreFoundation 0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166

  6.  
  7. 复制代码

可能存在某些与“unrecognized selector”无关的特殊情况。如果碰到了,请注意到处都有可能发生这种事情。

另一个常见的“EXC_CRASH”情况是关于“应用扩展(App Extensions)”。应用扩展如果“花了太长时间来初始化”则会被系统终止。在这种情况下,异常子类型(Exception Subtype)显示为 LAUNCH_HANG,附带一个得体的异常消息(Exception Message)

 
  1. Exception Type: EXC_CRASH (SIGABRT)

  2. Exception Subtype: LAUNCH_HANG

  3. Exception Message: The extension took too much time to initialize

  4.  
  5. 复制代码

EXC_BREAKPOINT(跟踪捕获)

与“EXC_CRASH”非常相似,EXC_BREAKPOINT 也往往与调试器一起发挥作用,在测试阶段被捕获。 当使用 Swift 时,在以下情况这个异常会在运行时抛出:

  • 一个非可选类型值为nil
  • 强制类型转换失败

示例信息如:

 
  1. Exception Type: EXC_BREAKPOINT (SIGTRAP)

  2. Exception Codes: 0x0000000000000002, 0x0000000000000000

  3.  
  4. 复制代码

你可以在代码中手动调用 __builtin_trap() 来触发这个异常。

EXC_GUARD(违反了受保护资源的防护)

与其他所有“EXC_”前缀的异常不同,这个异常不是一个“原生”的 Mach 异常。事实上,它是为 XNU - 一个苹果开发的衍生操作系统内核 而添加的。

"XNU" 代表 "X 不是 Unix"(X is Not Unix)。 “EXC_GUARD”的定义可以在这里-osfmk/mach/exception_types.h找到。

这个异常的一个较好例子是应用程序在 Core Data 访问 SQLite 文件时关闭了它的“文件描述符(file descriptor)”。

在 iOS7 之前,这个异常会附带一部分“异常码(Exception Codes)”以帮助理解情况。异常码包含“两个”位域代码(如:0x400000010000005e)及subcode(如:0x00007f8254a019c0)。

位域代码部分可分解为“3”个区:

  • Guard Type - 这个时候只有一种类型 - 受保护的文件描述符(guarded file descriptor(GUARD_TYPE_FD))。值为0x2。如果你看到时 0x4 作为代码的前缀,则这个崩溃与“文件描述符”相关。
  • Flavor - 当违反“受保护的文件描述符”时的不同条件: 如果设置了“第1”([32]: "1 << 0")位(kGUARD_EXC_CLOSE),则它曾试图在“受保护的文件描述”上调用 close()。 如果设置了“第2”([33]: "1 << 1")位(kGUARD_EXC_DUP),则它试图在“受保护的文件描述符”上使用 F_DUPFD 或 F_DUPFD_CLOEXEC 调用 dup(2),dup2(2),fcntl(2)。还包含了尝试使用 /dev/fd/打开“文件描述符”。 如果设置了“第3”([34]: "1 << 2")位(kGUARD_EXC_NOCLOEXEC),则它试图关闭“文件描述符”上的“close-on-exec”标志。 如果设置了“第4”([35]: "1 << 3")位(kGUARD_EXC_SOCKET_IPC),则它试图通过 套接字(socket)发送“受保护的文件描述符”。 如果设置了“第5”([36]: "1 << 4")位(GUARD_FILEPORT),则它曾试图通过 套接字(socket)从“受保护的文件描述符”创建一个文件端口。 如果设置了“第6”([37]: "1 << 5")位(kGUARD_EXC_MISMATCH),说明“受保护的文件描述符”与“守卫”不相符。 如果设置了“第7”([38]: << 6)位(kGUARD_EXC_WRITE),则它曾试图通过 套接字(socket)写入一个“受保护的文件描述符”。
  • File Descriptor - 应用尝试操作的受保护的文件描述符。- subcode部分包含“受保护的值”。

详细定义可以在这里/bsd/sys/guarded.h)找到。

从 iOS 7 开始,“Exception Codes”被提供更详细解释的“Exception Subtype”和“Exception Message”替代。

 
  1. # iOS 6

  2. Exception Type: EXC_GUARD

  3. Exception Codes: 0x400000010000005e, 0x00007f8254a019c0

  4. # The type is "GUARD_TYPE_FD" (0x4), with "kGUARD_EXC_CLOSE". The FD is "94".

  5. # -------

  6. # iOS 7 and above

  7. Exception Type: EXC_GUARD

  8. Exception Subtype: GUARD_TYPE_FD

  9. Exception Message: CLOSE on file descriptor 81 (guarded with 0x0000000017e6eed0)

  10. 复制代码

EXC_BAD_INSTRUCTION(非法指令)

“EXC_BAD_INSTRUCTION”,通常与“SIGILL”关联,是一个非常容易理解的异常 - 即你正在使用“错误”的指令或操作。然而,有时候也很难去调试。

以下是一些较常见的情况。 由于Xcode提供的调试信息,这个很容易识别 - 它是由于不安全的解包导致的。

 
  1. ## Usually show "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" in Xcode.

  2. “fatal error: unexpectedly found nil while unwrapping an Optional value”

  3. 复制代码

但是,像这样这样(均为StackOverflow上的问题)的就不容易了 - 第一个是有关于 GCD 的使用,另一个是苹果的bug。 以下是崩溃日志中的显示格式:

 
  1. Exception Type: EXC_BAD_INSTRUCTION (SIGILL)

  2. Exception Codes: 0x0000000000000001, 0x000000000000b6d2

  3. 复制代码

EXC_RESOURCE

“EXC_RESOURCE”意思是进程“达到资源消耗上限”。通常,当你的应用在一定时间内持续超出限制时会被触发。 这个异常包含“Exception Subtype”以帮助理解实际情况:

  • CPU - 限制为 50%,时间不超过 180秒
  • WAKEUPS - 表示线程每秒唤醒次数太多。限制为 150次/每秒, 时间不超过 300秒
  • MEMORY - 没有相关文档描述限制信息。

与“EXC_GUARD”类似,它曾使用“位域”来传递信息,现在也使用“Exception SubType”和“Exception Message”。

 
  1. Exception Type: EXC_RESOURCE

  2. Exception Subtype: CPU

  3. Exception Message: (Limit 50%) Observed 85% over 180 secs

  4. ---

  5. Exception Type: EXC_RESOURCE

  6. Exception Subtype: WAKEUPS

  7. Exception Message: (Limit 150/sec) Observed 206/sec over 300 secs

  8. ---

  9. Exception Type: EXC_RESOURCE

  10. Exception Subtype: MEMORY

  11. Exception Message: Crossed High Water Mark

  12. 复制代码

00000020

与“EXC_”异常不同,这个“异常类型”实际上不能告诉你任何信息。取而代之,你应该查看“异常代码”获取更多详情。

  • 0x8badf00d(读作 ate bad food)- 表示由于 watchdog 出现超时而导致应用被操作系统终止。通常意味着应用程序花了太长时间启动、关闭或者响应系统事件。一个非常典型的情况是“在主线程上做同步网络请求”。
  • 0xbaaaaaad(读作 “plooookhy”)- 表示日志是整个系统的堆栈,而不是崩溃报告。
  • 0xc00010ff(读作 cool off(冷静))- 表示应用程序被系统关闭以响应热事件。
  • 0xbad22222 - 表示操作系统终止了一个VoIP程序,因为它过于频繁的执行恢复。
  • 0xdead10cc(读作 dead lock(死锁))- 表示应用在后台运行时保持了系统资源。
  • 0xdeadfa11(读作 deadfall)- 表示应用被用户强制关闭了。强制关闭发生于用户先按下电源键直到“滑动来关机”出现然后按住主屏幕按钮。

这些“十六进制”代码实际上是六音词 - 由我们开发者创建作为不容易忘记的魔法数字。

扩展阅读

你可查看这篇文章 Demystifying iOS Application Crash Logs 以了解iOS异常日志结构。

转载于:https://juejin.im/post/5d04bdf4f265da1b83338b8b

转载博客:https://blog.csdn.net/weixin_34375251/article/details/93172045

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值