在日常工作中,开发者最怕的应该便是线上的溃散了。线上的溃散不像咱们开发中遇到的溃散,能够在 Xcode 的 log 中直观的看到溃散信息。

不过,线上的溃散也并不是线索全无,让咱们卖虾的不拿秤 — 抓瞎。

每逢 App 产生溃散时,体系会自动生成一个后缀 ips 的溃散陈述。咱们能够经过溃散陈述来进行问题定位。但溃散陈述的内容繁多,新手看很容易一脸懵。所以本文先讲解一下陈述中各字段的意义,后边再说陈述符号化。

废话不多说,让咱们开始吧!

前期准备

首要,陈述解读咱们需求先生成一个 crash 陈述。

1、新建一个项目,在 ViewController 中写下面的代码:

NSString *value;
NSDictionary *dict = @{@"key": value}; // 字典的 value 不可为 nil,所以会溃散

2、在真机上运转项目,然后去设置 – 隐私与安全性 – 剖析与改善 – 剖析数据,拿去生成的 crash 陈述(陈述的姓名与项目姓名共同,比如我的项目名为:CrashDemo,溃散陈述的名则为:CrashDemo-2023-05-30-093930.ips)。

留意:连着 Xcode 运转时不会产生溃散陈述,需求真机拔掉数据线再次运转 app 才会生成溃散陈述。

拿到陈述,接下来便是解读了。

陈述内容解读

官网的示例图:

iOS crash 报告分析系列 - 看懂 crash 报告的内容

Header

首要来看 Header:

Incident Identifier: 9928A955-FE71-464F-A2AF-A4593A42A26B
CrashReporter Key:   7f163d1c67c5ed3a6be5c879936a44f10b50f0a0
Hardware Model:      iPhone14,5
Process:             CrashDemo [45100]
Path:                /private/var/containers/Bundle/Application/6C9D4CF7-4C16-4B50-A4A5-389BED62C699/CrashDemo.app/CrashDemo
Identifier:          cn.com.fengzhihao.CrashDemo
Version:             1.0 (1)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           cn.com.fengzhihao.CrashDemo [3547]
Date/Time:           2023-05-30 09:39:29.6418 +0800
Launch Time:         2023-05-30 09:39:28.5579 +0800
OS Version:          iPhone OS 16.3.1 (20D67)
Release Type:        User
Baseband Version:    2.40.01
Report Version:      104

Header 首要描绘了方针设备的软硬件环境。比如上图能够看出:是 iphone 14 的设备,体系版本是16.3,产生溃散的事件是 2023-05-30 09:39:29 等等。

需求留意的是 Incident Identifier 相当于当时陈述的 id,陈述和 Incident Identifier 是一一对应的关系,绝对不会存在两份不同的陈述 Incident Identifier 相同的状况。

Exception information

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000

这一部分首要是告知咱们 app 是因为什么错误而导致的溃散,但不会包括完整的信息。

能够看到当时的 Type 为:EXC_CRASH (SIGABRT),这代表当时进程因收到了 SIGABRT 信号而导致溃散,这是一个很常见的类型,字典 value 为nil或许归于越界等都会是此类型。更多的 Exception Type 解释请参见此处。

Diagnostic messages

Application Specific Information:
abort() called

操作体系有时包括额定的诊断信息。此信息运用多种格局,具体取决于溃散的原因,并且不会出现在每个溃散陈述中。

本次的溃散原因是因为调用了 abort() 函数。

接下来,便是陈述的重点了。

Backtraces

这部分记载了当时进程的线程的函数调用栈,咱们能够经过调用栈来定位出问题的代码。

溃散进程的每一条线程都会被捕获成回溯。回溯会展示当时线程被中止时的线程的函数调用栈。如果溃散是由于言语异常形成的,会额定有一个Last Exception Backtrace,位于榜首个线程之前。

比如咱们示例中的溃散便是由于言语异常形成的,所以溃散陈述中会有 Last Exception Backtrace。

Last Exception Backtrace:
0   CoreFoundation                	       0x191560e38 __exceptionPreprocess + 164
1   libobjc.A.dylib               	       0x18a6f78d8 objc_exception_throw + 60
2   CoreFoundation                	       0x191706078 -[__NSCFString characterAtIndex:].cold.1 + 0
3   CoreFoundation                	       0x1917113ac -[__NSPlaceholderDictionary initWithCapacity:].cold.1 + 0
4   CoreFoundation                	       0x19157c2b8 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:] + 320
5   CoreFoundation                	       0x19157c158 +[NSDictionary dictionaryWithObjects:forKeys:count:] + 52
6   CrashDemo                     	       0x104a69e0c -[ViewController touchesBegan:withEvent:] + 152
.... 中间内容省掉
25  CrashDemo                     	       0x104a6a0c4 main + 120
26  dyld                          	       0x1afed0960 start + 2528

以下是上述每一列元素的意义:

  • 榜首列:栈帧号。仓库帧按调用顺序排列,其间帧 0 是在履行暂停时正在履行的函数。第 1 帧是调用第 0 帧函数的函数,依此类推
  • 第二列:包括正在履行函数的二进制包名
  • 第三列:正在履行的机器指令的地址
  • 第四列:在彻底符号化的溃散陈述中,正在履行的函数的称号。出于隐私原因,函数称号有时限制为前 100 个字符
  • 第五列(+ 号后边的数字):函数入口点到函数中当时指令的字节偏移量

经过第 6 行咱们能够推断出问题是由 NSDictionary 引起的。

但大部分时分咱们得到的陈述都是未符号化的,咱们需求对陈述进行符号化来获得更多的信息。关于符号化的相关内容能够看这里。

Thread state

Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000   x1: 0x0000000000000000   x2: 0x0000000000000000   x3: 0x0000000000000000
...中间内容省掉
far: 0x00000001e4d30560  esr: 0x56000080  Address size fault

溃散陈述的线程状态部分列出了应用程序停止时溃散线程的 CPU 寄存器及其值。

Binary images

0x1cf074000 -        0x1cf0abfeb libsystem_kernel.dylib arm64e  <c76e6bed463530c68f19fb829bbe1ae1> /usr/lib/system/libsystem_kernel.dylib
       ...中间内容省掉
       0x18b8ca000 -        0x18c213fff Foundation arm64e  <e5f615c7cc5e3656860041c767812a35> /System/Library/Frameworks/Foundation.framework/Foundation

以下是上述每一列元素的意义:

  • 榜首列:二进制镜像在进程中的地址规模
  • 第二列:二进制镜像的称号
  • 第三列:操作体系加载到进程中的二进制映像中的 CPU 架构
  • 第四列:唯一标识二进制映像的构建 UUID。符号化溃散陈述时运用此值定位相应的 dSYM 文件
  • 第五列:二进制文件在磁盘上的途径

至此,陈述上的一切 section 都已经解读完。期望大家看完这篇文章后,再剖析溃散日志的时分能更加得心应手。