看懂 Crash 日志
Crash 头部信息
Incident Identifier:每个 Crash 生成的唯一的 uuid.
CrashReporter Key:CrashReporter 的 uuid, 如果自己捕获日志,这个可以忽略。
Hardware Model:机型
Process:进程名和进程ID
Path:进程的可执行文件路径
Identifier:Info.plist 中配置的 CFBundleIdentifier 值
Version: CFBundleVersion (CFBundleVersionShort) 即应用的Build号+版本号
Code Type:机型的 CPU 架构,但是不是详细的架构名。比如 arm64e 在这里也是 ARM-64
Parent Process:父进程和进程ID
Data/Time: Crash发生的具体时间。
Launch Time: 进程启动时间
OS Version: iOS 系统版本和 build号
Report Version: Crash日志格式的版本号,一般是 104。如果这个version偏高,用系统的symbolicatecrash命令不能符号化日志,一般如果看到是204, 改成104之后用symbolicatecrash就可以符号化了
Crash 异常码
在 Crash 头部信息之下, 会有个段记录了 Crash 异常码。类似下图:
这里我们应该关注:
Exception Type:异常码,一般格式是 Mach异常码 ( UNIX 信号类型 )
Exception Subtype:一般情况里面带的是 Mach异常的 subcode, 还有 Crash 相关地址信息。
Triggered by Thread:发生Crash的线程,大部分情况到这个线程的堆栈里面去看 Crash 堆栈。
Application Specific Information:如果是 Objc/c++ Exception 异常,这里是异常的信息,这个是定位异常的关键信息
Last Exception Backtrace:抛出异常的代码堆栈, 如果是 Objc/c++ Exception 异常造成的 Crash,就看这个堆栈,Crashed Thread: 里的堆栈是 abort(),没有意义。
附异常码的解释, 非所有异常码,只是我们 Crash 中可能会看到的 :
Mach 异常 | 简介 | 使用场景 |
---|---|---|
EXC_BAD_ACCESS (SIGBUS) | 总线错误 | 1、内存地址对齐出错 2、试图执行没有执行权限的代码地址 |
SIGSEGV | 段错误 | 1、访问未申请的虚拟内存地址 2、没有写权限的内存写入 |
EXC_BAD_INSTRUCTION (SIGILL) | 非法指令,即机器码指令不正确 | 1, iOS 上偶现的问题,遇到之后用户会连续闪退,直到应用二进制的缓存重新加载 或重启手机。此问题挺影响体验,但是报给苹果不认,因为苹果那边没有收集到,目前没有太好办法。因为 iOS 应用内无法对一篇内存同时获取 w+x 权限的,因此应用无法造成此类问题,所以判断是苹果的问题。 |
EXC_ARITHMETIC (SIGFPE) | 算术运算出错,比如除0错误 | iOS 默认是不启用的,所以我们一般不会遇到。 |
EXC_SOFTWARE (我们在 Crash 日志中一般不会看到这个类型,苹果的日志里会是 EXC_CRASH) (SIGSYS) | 系统调用异常 | 无 |
SIGPIPE | 管道破裂 | 1, Socket通信是可能遇到,如读进程以及终止时,写进程继续写入数据。2, 根据苹果的文档,我们可以忽略这个信号https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/CommonPitfalls/CommonPitfalls.html |
SIGABRT | abort() 发生的信号 | 典型的软件信号,通过 pthread_kill() 发送 |
SIGKILL | 进程内无法拦截 | 1, exit(), kill(9) 等函数调用 2, iOS系统杀进程用的,比如 watchDog 杀进程 |
EXC_BREAKPOINT | SIGTRAP 由断点指令或其它trap指令产生 | 部分系统框架里面会用 __builtin_trap() 来产生一个 SIGTRAP 类型的 Crash |
EXC_GUARD | 文件句柄错误 | 试图 close 一个内核的 fd. |
EXC_RESOURCE | 资源受限 | 线程调度太频繁,子线程每秒被唤醒次数超过150:https://stackoverflow.com/questions/25848441/app-shutdown-with-exc-resource-wakeups-exception-on-ios-8-gm |
Crash 堆栈
下面一张图介绍了 Crash 堆栈中每个段的含义(在网上找的说的挺细):
Signal信号的类型:
- SIGABRT–程序中止命令中止信号
- SIGALRM–程序超时信号
- SIGFPE–程序浮点异常信号
- SIGILL–程序非法指令信号
- SIGHUP–程序终端中止信号
- SIGINT–程序键盘中断信号
- SIGKILL–程序结束接收中止信号
- SIGTERM–程序kill中止信号
- SIGSTOP–程序键盘中止信号
- SIGSEGV–程序无效内存中止信号
- SIGBUS–程序内存字节未对齐中止信号
-
SIGPIPE–程序Socket发送失败中止信号
SIGABRT
就crash而言,SIGABRT是一个比较好解决的,因为他是一个可掌控的crash。App会在一个目的地终止,因为系统意识到app做了一些他不能支持的事情。
通常, SIGABRT 异常是由于某个对象接收到未实现的消息引起的。 或者,用简单的话说,在某个对象上调用了不存在的方法。
SIGSEGV
SIGSEGV程序无效内存中止信号,一般是表示内存不合法,
SIGBUS
SIGBUS程序内存字节未对齐中止信号,
补充常见的Exception Codes代码类型
Exception Codes: 常见代码有以下几种
0x8badf00d错误码:Watchdog超时,意为“ate bad food”。
0xdeadfa11错误码:用户强制退出,意为“dead fall”。
0xbaaaaaad错误码:用户按住Home键和音量键,获取当前内存状态,不代表崩溃。
0xbad22222错误码:VoIP应用(因为太频繁?)被iOS干掉。
0xc00010ff错误码:因为太烫了被干掉,意为“cool off”。
0xdead10cc错误码:因为在后台时仍然占据系统资源(比如通讯录)被干掉,意为“dead lock”
异常代码0x8badf00d指示应用程序已终止的iOS 因为看门狗超时发生,应用程序时间太长、终止,或对系统时间作出相应。一个常见的原因是做在主线程上的同步联网。无论操作是线程0上,需要搬到后台线程,或处理方式不同,所以它不会阻止在主线程。
补充常见的Exception Type异常类型的信息:
1、EXC_BAD_ACCESS:此类型是最常见的crash, 通常用于访问了不该访问的内存导致的,一般EXC_BAD_ACCESS后面的()还会带有补充信息
野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。因为你访问了一块已经不属于你的内存。
2、SIGSEGV:通常由于重复释放对象导致, 一般在ARC以后很少见到
3、SIGABRT: 收到Abort信号退出, 通常Foundtion库中的容器为了保护状态正常会做一些检测, 例如插入nil到数据中等会遇到此类错误.
4、SEGV(Segmentation Violation):代表无效内存地址, 比如空指针, 未初始化指针, 栈溢出等.
5、SIGBUS:总栈错误, 与SIGSEGV不同的是, SIGSEGV访问的是无效的地址, 而SIGBUS访问的是有效的地址, 但是总栈访问异常(如地址对齐问题)
6、SIGILL: 尝试执行非法的指令, 可能不被识别或者没有权限
7、SIGFPE: 数学计算相关问题, 比如除零操作
8、SIGIPIPE: 管道另一端没有进程接手数据
9、EXC_BAD_INSTRUCTION:此类异常通常由于线程执行非法指令导致
10、EXC_ARITHMETIC:除零错误会抛出此类异常