[译]《iOS Crash Dump Analysis》- 崩溃报告(iOS部分)

点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新
原书为 iOS Crash Dump Analysis Book,已得作者授权,欢迎 star

在本章中,我们将详细介绍崩溃报告中的内容。

我们将主要关注 iOS 奔溃报告。我们还将介绍 macOS 的奔溃报告,虽然报告的结构略有不同,但都足以让我们获取信息。

虽然,目前部分 App 可以会通过安装使用第三方的奔溃处理程序,以增加获取崩溃报告和诊断的能力,或者是基于 Web 服务来管理大量用户设备的奔溃信息。但是在本章中,我们假设 App 没有安装这种三方库,因此我们使用 Apple CrashReport 工具来处理奔溃报告。

当 App 发生奔溃时,ReportCrash从操作系统的崩溃过程中提取相关信息,并生成拓展名为.crash的文本文件。

当符号信息可用时,Xcode 将符号化奔溃报告然后显示符号化以后的名称而不是机器地址。这就提高了报告的可阅读性,更容易理解。

App 已经制作了一份详细的文档来解释 crash dump 的全部结构。

系统诊断

崩溃报告只是更大的系统诊断报告中的一部分。

通常,作为 App 的开发人员我们并不需要有进一步的更多的了解。但是,如果我们的问题可能是由一系列无解释的事件,或者是与硬件或与 Apple 提供的系统服务之间更复杂的交互而引起的,这时候我们不仅仅需要查看奔溃报告,而需要研究系统诊断报告。

提取系统诊断信息

当需要了解导致崩溃发生的环境时,我们可能需要安装手机设备管理配置文件(用于打开调试某些子系统),或创建虚拟网络接口(用于网络监测)。 Apple 提供了一个涵盖每个场景的 网页。

在 iOS 上,基本思路是我们先安装一个配置文件,这个配置文件会让设备记录更多的日志,然后复现崩溃操作(或者让客户这么操作)。 然后我们按下设备上的特殊按键组合(例如,同时按下音量按钮和侧按钮)。系统会短暂振动,表明它正在运行sysdiagnose 程序,这个程序会提取很多日志文件。然后我们用 iTunes 同步设备以检索生成的sysdiagnose_date_name.tar.gz 文件。打包文件中包含许多系统和子系统日志,我们可以看到崩溃发生的时间以及引起崩溃的上下文。

在 macOS 上我们也可以执行相同的操作。

iOS 崩溃报告导览

在这里,我们将浏览 iOS 崩溃报告中的每个部分并解释相应的字段。

出于目的,我们将 tvOS 和 watchOS 视作 iOS 的子集,并具有相似的崩溃报告。

请注意,此处我们所指的“ iOS崩溃报告 ” 是用来表示来自物理设备的崩溃报告。 当发生崩溃时,我们通常是在模拟器上调试应用程序。在这种情况下,异常代码可能会有所不同,因为模拟器使用不同的方法来导致应用在调试器下停止。

iOS 崩溃报告 Header 部分

崩溃报告通常以以下样式的开头:

Incident Identifier: E030D9E4-32B5-4C11-8B39-C12045CABE26
CrashReporter Key:   b544a32d592996e0efdd7f5eaafd1f4164a2e13c
Hardware Model:      iPad6,3
Process:             icdab_planets [2726]
Path:                /private/var/containers/Bundle/Application/
BEF249D9-1520-40F7-93F4-8B99D913A4AC/
icdab_planets.app/icdab_planets
Identifier:          www.perivalebluebell.icdab-planets
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           www.perivalebluebell.icdab-planets [1935]

这些类目由下表进行解释:

Entry Meaning
Incident Identifier 崩溃报告的唯一编号
CrashReporter Key 崩溃设备的唯一标识符
Hardware Model Apple 硬件模型(iPad,iPhone)
Process 崩溃的进程名称(或编号)
Path 设备文件系统上崩溃程序的完整路径名
Identifier 来自Info.plist 的 Bundle identifier
Version CFBundleVersion;括号中有 CFBundleVersionString
Code Type 崩溃进程的目标体系结构
Role 进程 task_role。如果我们在后台、前台或控制台应用程序中,都会显示一个指示器。主要影响进程的调度优先级。
Parent Process 崩溃进程的父级。launchd 是一个进程启动程序,通常是父进程。
Coalition 任务分组合并,然后他们就可以可以把资源消耗集中起来。

CFBundleVersion表示 bundle 构建迭代的版本号(发布与未发布) 而CFBundleVersionString 可能想说的是CFBundleShortVersionString 表示 bundle 发布版本号

首先要看的是版本。通常,如果我们是一个小团队或者是独立开发者,我们没有什么精力和资源去分析诊断旧版本的崩溃,所以首先我们要做的是让用户去更新最新版本。

如果我们有很多的崩溃,那么可能会出现一种现象。它可能来自于同一个用户(看到同一个 CrashReporter Key),也可能来自不同的用户(看到不同的 CrashReporter Key)。这可能影响我们对于崩溃的优先级判断。

Hardware Model 是一个值得关注的点。是只有 iPad设备,还是仅限iPhone设备,或者两者兼而有之? 对于特定的设备,我们很少测试或者代码做了特殊处理,亦或者指向一个我们并没有测试过的老旧设备。

APP 是在前台还是在后台中奔溃也是一个值得关注的点,大多数应用程序通常都不会测试其在后台运行时会发生什么,这里说的是 Role。例如,我们可能会接到一个电话,或者在应用之间进行切换。

现有的Code Type 通常是 64-bit ARM 的。但是我们可能看到原始的 32-bit ARM

iOS 崩溃报告 Date 和 Version 部分

接下来崩溃报告将提供日期和版本信息:

Date/Time:           2018-07-16 10:15:31.4746 +0100
Launch Time:         2018-07-16 10:15:31.3763 +0100
OS Version:          iPhone OS 11.3 (15E216)
Baseband Version:    n/a
Report Version:      104

这些类目由下表进行解释:

Entry Meaning
Date/Time 崩溃发生的时间
Launch Time 崩溃前最初启动该进程的时间
OS Version 操作系统版本(内部版本号)。
Baseband Version 蜂窝调制解调器固件版本号(用于电话呼叫)或 n/a(如果设备没有蜂窝调制解调器)(大多数 iPad,iPod Touch 等)
Report Version 生成报告的 ReportCrash 版本

首先要检查的是操作系统的版本。比我们测试的版本新还是旧?是 beta 版吗?

接下来要比较启动时间和崩溃发生时的时间差值。应用程序是立即崩溃还是经过很长时间后崩溃?启动崩溃有时可能是打包和部署问题。我们将利用一些技术来解决运行以后的崩溃问题。

日期是有有意义?有时,设备在某个时间会设置或转发,这可能会触发安全证书或许可证密钥的日期检查。确保日期看起来是真实的。

通常关注 Baseband Version 并没有什么用。基带的存在意味着应用会被通话打断(当然,无论如何也会有 VOIP 呼叫)。iPad 软件通常被认为是不打算接听电话的,但 iPad 也可以选择购买有蜂窝调制解调器的版本。

iOS崩溃报告异常部分

崩溃报告接下来将包含异常信息:

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  0

或者它可能具有更详细的异常信息:

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace <0xF>, Code 0xdead10cc
Triggered by Thread:  0

这通常发生在,MachOS 内核在有问题的进程上引发了操作系统异常,从而终止了该进程。 然后,ReportCrash 程序从操作系统中检索此类异常的详细信息。

这些类目由下表进行解释:

Entry Meaning
Exception Type Mach OS中的异常类型
Exception Codes 异常类型的编码,例如尝试访问无效的地址以及支持信息。
Exception Note 如果进程被看门狗计时器杀死,会显示SIMULATED(这不是崩溃),进程崩溃则显示 EXC_CORPSE_NOTIFY
Termination Reason 视情况而定,它给出一个命名空间(数字或子系统名称)和一个 magic number(通常是一个看起来像英语单词的十六进制数字)。 有关每个终止代码的详细信息,请参见下文。
Triggered by Thread 导致崩溃的进程中的线程

在本节中,最重要的项是异常类型。

Exception Type Meaning
EXC_CRASH (SIGABRT) 我们的程序触发了一个编程语言异常,例如失败的断言,这导致操作系统中止我们的应用程序
EXC_CRASH (SIGQUIT) 一个进程从另一个正在管理它的进程接收到退出信号。通常,这意味着某个拓展程序花费了太长的时间或者消耗了太多的内存。App 的拓展程序仅能获得有限的内存。
EXC_CRASH (SIGKILL) 系统终止了我们的 App(或 App 的拓展程序),通常是因为已经达到了某种资源的限制。我们需要研究终止原因,以确定违反的某个政策是终止原因。
EXC_BAD_ACCESS (SIGSEGV)EXC_BAD_ACCESS (SIGBUS) 我们的程序很可能试图访问错误的或者是我们没有权限访问的内存地址。或由于内存压力,该内存已被释放
EXC_BREAKPOINT (SIGTRAP) 这可能是由于触发了一个NSException(可能是我们自己的库触发的)或者是调用了_NSLockErrorobjc_exception_throw方法。例如,这可能是因为 Swift 检测到异常,例如强制展开 nil 可选
EXC_BAD_INSTRUCTION (SIGILL) 这是程序代码本身有问题,而不是因为错误的内存访问。 这在 iOS 设备上应该很少见; 可能是编译器或优化器错误,或者是错误的手写汇编代码。 但在模拟器上,是不一样的,因为使用未定义的操作码是 Swift 运行时用来停止访问僵尸对象(已分配对象)的一种技术。
EXC_GUARD 这个问题发生在程序去关闭一个受保护的文件。例如,关闭系统使用的 SQLite 库

当存在终止原因时,我们可以按如下方式查找代码:

Termination Code Meaning
0xdead10cc 我们在挂起之前持有文件锁或 sqlite 数据库锁。我们应该在挂起之前释放锁。
0xbaaaaaad 通过侧面和两个音量按钮对整个系统进行了 stackshot。请参阅前面的系统诊断部分
0xbad22222 可能是 VOIP 应用被频繁唤起导致的崩溃。也可以注意一下我们的后台调用网络的代码。 如果我们的TCP连接被唤醒太多次(例如 300 秒内唤醒 15 次),就会导致此崩溃。
0x8badf00d 我们的应用程序执行状态更改(启动、关闭、处理系统消息等)花费了太长时间。与看门口的时间策略发生冲突(超时)并导致终止。最常见的罪魁祸首是在主线程上进行同步的网络连接。
0xc00010ff 系统检测的设备发烫而终止了我们的 App。如果只在少量设备上(几个)发生,那就可能是由于硬件的问题,而不是我们 App 问题。但是如果发生在其他设备上,我们应该使用 Instruments 去检查我们 App 的耗电量问题。
0x2bad45ec 发生安全冲突。 如果 Termination Description 显示为 Process detected doing insecure drawing while in secure mode,则意味着我们的应用尝试在不允许的情况下进行绘制,例如在锁定屏幕的情况下。

Magic Numbers 和他们的黑话(意思)

出于某种怪异的幽默,在讨论终止代码时,下面的 Magic Number 是表达这些意思

Magic Number Spoken Phrase
0xdead10cc Deadlock
0xbaaaaaad Bad
0xbad22222 Bad too (two) many times
0x8badf00d Ate (eight) bad food
0xc00010ff Cool Off
0x2bad45ec Too bad for security

主动终止

Exception TypeSIGABRT 时,我们应该从崩溃堆栈中查找代码中存在哪些断言或异常。

内存问题

当我们存在内存问题时, Exception TypeEXC_BAD_ACCESS 括号里是SIGSEGV 或者 SIGBUS,我们根据括号中的异常代码来判断是什么内存问题。对于这类问题,我们可以打开 Xcode 中关于特定 target scheme 的相关诊断程序。应该打开 address sanitizer,以查看是否可以发现错误。

选择 Edit Scheme 选择 Run 选择 Address Sanitizer

如果 Xcode 显示 App 正在使用大量内存,那么可能是我们所依赖的内存已被系统释放。为此,请打开 Malloc Stack 日志记录选项,选择 All Allocation And Free History。然后在 App 运行的某个时刻,可以单击 MemGraph 按钮,然后探索对象的分配历史记录。

有关更多详细信息,请阅读 内存诊断 章节。

例外

当我们的异常类型为 EXC_BREAKPOINT 时,这可能会造成一定的困惑。该应用在没有 debugger 工具的情况下独自运行,那么断点从哪里来呢?通常,可能是因为我们运行了 NSException 代码。这使的系统在过程中跟踪陷阱信号,并使任何可用的调试器都这个过程中以帮助调试。所以即使我们在 debug 时断开断点,我们也会在这里停住,这样我们就可以找出存在运行时异常的原因。在正常的应用程序运行的情况下,没有 debugger 工具,系统只能崩溃应用程序。

非法指示

当我们的异常类型为EXC_BAD_INSTRUCTION时,异常代码(接下来的字符)将是有问题的汇编代码。这种情况应改是罕见的。这值得我们去调整 Build Settings 中代码的优先级别,因为更高级别的优化可能会导致在构建期间发出更多奇特的指令,从而增加了编译器错误的机会。或者说,问题可能出在代码中具有手动编译优化功能的底层库中,例如多媒体资源库。手写汇编指令可能是错误出现的原因。

保护例外

有些操作系统会使用某些文件,因此它们会受到特殊保护。当关闭(或以其他方式修改)此类文件时,我们会得到一个 EXC_GUARD 类型的异常。
例如:

Exception Type:  EXC_GUARD
Exception Codes: 0x0000000100000000, 0x08fd4dbfade2dead
Crashed Thread:  5

异常代码 0x08fd4dbfade2dead 表达了修改与数据库相关的文件(在我们的示例中它已被关闭)。这个十六进制字符串可以类似 黑话 读作 Ate (8) File Descriptor (fd) for (4) Database (db)

当出现这样的问题时,我们可以查看崩溃线程中的文件操作。 在我们的例子中:

Thread 5 name:  Dispatch queue: com.apple.root.default-priority
Thread 5 Crashed:
0   libsystem_kernel.dylib          0x3a287294 close + 8
1   ExternalAccessory               
0x32951be2 -[EASession dealloc] + 226

在这里执行了一个关闭操作。

当我们有与文件操作对应的描述代码时,我们应该特别检查一下我们的关闭操作代码。

我们可以从第一个异常代码推断文件操作。它是一个 64 位标志,指定如下:

Bit Range Meaning
63:61 Guard 类型,其中 0x2 表示文件描述符
60:32 Flavor
31:00 File descriptor number

从观察中,我们认为 Guard 类型没有被使用。

Flavor 是另一个向量:

Flavor Bit Meaning
0 尝试调用close()
1 dup() , dup2() 或者 fcntl()
2 通过套接字尝试调用sendmsg()
4 尝试调用write()

iOS 崩溃报告已过滤的 syslog 部分

奔溃报告接下来是 syslog 部分:

Filtered syslog:
None found

这是一个异常部分,因为他会去查看奔溃进程的进程 ID,然后查看该进程是否有任何 syslog (系统日志)部分。这个例子中我们并未在奔溃报告看到任何已过滤的信息,只看到 None found

iOS 崩溃报告中的异常回溯部分

当我们的应用程序检测到问题并要求操作系统终止该应用程序时,我们将获得报告的异常回溯部分。这涵盖了自己主动或通过 Swift,Objective-C 或 C 运行时支持库间接调用了abortNSException_NSLockErrorobjc_exception_throw的情况。

我们并没有办法得到实际发生断言的部分。但我们可以假定已经过滤的系统日志的前一个部分应该完成了这部分。虽然,通过 Window-> Devices and Simulators-> Open Console 会允许我们恢复该信息。

当我们在客户的崩溃报告中看到异常回溯时,我们应该要求崩溃设备的控制台日志。

例如,我们将看到:

default 13:36:58.000000 +0000   icdab_nsdata
     My data is <> - ok since we can handle a nil

default 13:36:58.000000 +0100   icdab_nsdata     
-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054

default 13:36:58.000000 +0100   icdab_nsdata
     *** Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSCFConstantString _isDispatchData]:
 unrecognized selector sent to
instance 0x3f054'
    *** First throw call stack:
    (0x25aa391b 0x2523ee17 0x25aa92b5 0x25aa6ee1 0x259d2238
     0x2627e9a5 0x3d997
    0x2a093785 0x2a2bb2d1 0x2a2bf285 0x2a2d383d 0x2a2bc7b3
     0x27146c07
    0x27146ab9 0x27146db9 0x25a65dff 0x25a659ed 0x25a63d5b
     0x259b3229
     0x259b3015 0x2a08cc3d 0x2a087189 0x3d80d 0x2565b873)

default 13:36:58.000000 +0100   SpringBoard  Application
'UIKitApplication:www.perivalebluebell.icdab-nsdata[0x51b9]'
 crashed.

default 13:36:58.000000 +0100   UserEventAgent
     2769630555571:
 id=www.perivalebluebell.icdab-nsdata pid=386, state=0

default 13:36:58.000000 +0000   ReportCrash  Formulating
report for corpse[386] icdab_nsdata

default 13:36:58.000000 +0000   ReportCrash  Saved type
 '109(109_icdab_nsdata)'
 report (2 of max 25) at
 /var/mobile/Library/Logs/CrashReporter/
 icdab_nsdata-2018-07-27-133658.ips

有趣的是这一行:

'-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054'

这意味着向 NSString 类发送了_isDispatchData 方法。不存在的方法。

在崩溃报告中看到匹配的异常回溯是:

Last Exception Backtrace:
0   CoreFoundation                  
0x25aa3916 __exceptionPreprocess + 122
1   libobjc.A.dylib                 
0x2523ee12 objc_exception_throw + 33
2   CoreFoundation                  0x25aa92b0
-[NSObject+ 1045168 (NSObject) doesNotRecognizeSelector:] + 183
3   CoreFoundation                  
0x25aa6edc ___forwarding___ + 695
4   CoreFoundation                  
0x259d2234 _CF_forwarding_prep_0 + 19
5   Foundation                      0x2627e9a0
-[_NSPlaceholderData initWithData:] + 123
6   icdab_nsdata                    0x000f89ba
-[AppDelegate application:didFinishLaunchingWithOptions:]
 + 27066 (AppDelegate.m:26)
7   UIKit                           0x2a093780
-[UIApplication
 _handleDelegateCallbacksWithOptions:isSuspended:restoreState:]
 + 387
8   UIKit                           0x2a2bb2cc
-[UIApplication
 _callInitializationDelegatesForMainScene:transitionContext:]
 + 3075
9   UIKit                           0x2a2bf280
-[UIApplication
 _runWithMainScene:transitionContext:completion:] + 1583
10  UIKit                           0x2a2d3838
__84-[UIApplication
 _handleApplicationActivationWithScene:transitionContext:
completion:]_block_invoke3286 + 31
11  UIKit                           0x2a2bc7ae
-[UIApplication workspaceDidEndTransaction:] + 129
12  FrontBoardServices              0x27146c02
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 13
13  FrontBoardServices              0x27146ab4
-[FBSSerialQueue _performNext] + 219
14  FrontBoardServices              0x27146db4
-[FBSSerialQueue _performNextFromRunLoopSource] + 43
15  CoreFoundation                  0x25a65dfa
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 9
16  CoreFoundation                  
0x25a659e8 __CFRunLoopDoSources0 + 447
17  CoreFoundation                  
0x25a63d56 __CFRunLoopRun + 789
18  CoreFoundation                  
0x259b3224 CFRunLoopRunSpecific + 515
19  CoreFoundation                  
0x259b3010 CFRunLoopRunInMode + 103
20  UIKit                           
0x2a08cc38 -[UIApplication _run] + 519
21  UIKit                           
0x2a087184 UIApplicationMain + 139
22  icdab_nsdata                    
0x000f8830 main + 26672 (main.m:14)
23  libdyld.dylib                   
0x2565b86e tlv_get_addr + 41

此回溯的格式与线程回溯的格式相同,稍后将进行介绍。

异常回溯部分的目的是提供比崩溃线程提供的更多的细节。

在上述情况下崩溃的线程有线程回溯:

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x2572ec5c __pthread_kill
 + 8
1   libsystem_pthread.dylib         0x257d8732 pthread_kill +
 62
2   libsystem_c.dylib               0x256c30ac abort + 108
3   libc++abi.dylib                 0x2521aae4 __cxa_bad_cast
 + 0
4   libc++abi.dylib                 0x2523369e
default_terminate_handler+ 104094 () + 266
5   libobjc.A.dylib                 0x2523f0b0
_objc_terminate+ 28848 () + 192
6   libc++abi.dylib                 0x25230e16
std::__terminate(void (*)+ 93718 ()) + 78
7   libc++abi.dylib                 0x252308f8
__cxa_increment_exception_refcount + 0
8   libobjc.A.dylib                 
0x2523ef5e objc_exception_rethrow + 42
9   CoreFoundation                  
0x259b32ae CFRunLoopRunSpecific + 654
10  CoreFoundation                  
0x259b3014 CFRunLoopRunInMode + 108
11  UIKit                           
0x2a08cc3c -[UIApplication _run] + 524
12  UIKit                           
0x2a087188 UIApplicationMain + 144
13  icdab_nsdata                    
0x000f8834 main + 26676 (main.m:14)
14  libdyld.dylib                   
0x2565b872 start + 2

如果只有线程回溯,我们将知道存在强制转换问题 __cxa_bad_cast ,但仅此而已。

从网上搜到 NSData具有一个私有的辅助类 _NSPlaceholderData

这个情况是在一个需要使用 NSData 的地方使用了 NSString 对象。

iOS 崩溃报告线程部分

崩溃报告接下来是线程回溯的部分(为便于演示而进行了格式化)

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib          0x0000000183a012ec
 __pthread_kill + 8
1   libsystem_pthread.dylib         0x0000000183ba2288
 pthread_kill$VARIANT$mp + 376
2   libsystem_c.dylib               0x000000018396fd0c
 abort + 140
3   libsystem_c.dylib               0x0000000183944000
 basename_r + 0
4   icdab_planets                   
0x0000000104e145bc
 -[PlanetViewController viewDidLoad] + 17852
  (PlanetViewController.mm:33)
5   UIKit                           0x000000018db56ee0
 -[UIViewController loadViewIfRequired] + 1020
6   UIKit                           0x000000018db56acc
 -[UIViewController view] + 28
7   UIKit                           0x000000018db47d60
 -[UIWindow addRootViewControllerViewIfPossible] + 136
8   UIKit                           0x000000018db46b94
 -[UIWindow _setHidden:forced:] + 272
9   UIKit                           0x000000018dbd46a8
-[UIWindow makeKeyAndVisible] + 48
10  UIKit                           0x000000018db4a2f0
 -[UIApplication
  _callInitializationDelegatesForMainScene:transitionContext:]
  + 3660
11  UIKit                           0x000000018db1765c
-[UIApplication
_runWithMainScene:transitionContext:completion:] + 1680
12  UIKit                           0x000000018e147a0c
__111-[__UICanvasLifecycleMonitor_Compatability
_scheduleFirstCommitForScene:transition:firstActivation:
completion:]_block_invoke + 784
13  UIKit                           0x000000018db16e4c
+[_UICanvas _enqueuePostSettingUpdateTransactionBlock:] + 160
14  UIKit                           0x000000018db16ce8
-[__UICanvasLifecycleMonitor_Compatability
_scheduleFirstCommitForScene:transition:
firstActivation:completion:] + 240
15  UIKit                           0x000000018db15b78
-[__UICanvasLifecycleMonitor_Compatability
activateEventsOnly:withContext:completion:] + 724
16  UIKit                           0x000000018e7ab72c
__82-[_UIApplicationCanvas
 _transitionLifecycleStateWithTransitionContext:
completion:]_block_invoke + 296
17  UIKit                           0x000000018db15268
-[_UIApplicationCanvas
 _transitionLifecycleStateWithTransitionContext:
completion:] + 432
18  UIKit                           0x000000018e5909b8
__125-[_UICanvasLifecycleSettingsDiffAction
performActionsForCanvas:
withUpdatedScene:settingsDiff:fromSettings:
transitionContext:]_block_invoke + 220
19  UIKit                           0x000000018e6deae8
_performActionsWithDelayForTransitionContext + 112
20  UIKit                           0x000000018db14c88
-[_UICanvasLifecycleSettingsDiffAction performActionsForCanvas:
withUpdatedScene:settingsDiff:fromSettings:
transitionContext:] + 248
21  UIKit                           0x000000018db14624
-[_UICanvas
scene:didUpdateWithDiff:transitionContext:completion:] + 368
22  UIKit                           0x000000018db1165c
-[UIApplication workspace:didCreateScene:withTransitionContext:
completion:] + 540
23  UIKit                           0x000000018db113ac
-[UIApplicationSceneClientAgent scene:didInitializeWithEvent:
completion:] + 364
24  FrontBoardServices              0x0000000186778470
-[FBSSceneImpl
_didCreateWithTransitionContext:completion:] + 364
25  FrontBoardServices              0x0000000186780d6c
__56-[FBSWorkspace client:handleCreateScene:withCompletion:]
_block_invoke_2 + 224
26  libdispatch.dylib               0x000000018386cae4
_dispatch_client_callout + 16
27  libdispatch.dylib               0x00000001838741f4
_dispatch_block_invoke_direct$VARIANT$mp + 224
28  FrontBoardServices              0x00000001867ac878
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
29  FrontBoardServices              0x00000001867ac51c
-[FBSSerialQueue _performNext] + 404
30  FrontBoardServices              0x00000001867acab8
-[FBSSerialQueue _performNextFromRunLoopSource] + 56
31  CoreFoundation                  0x0000000183f23404
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
32  CoreFoundation                  0x0000000183f22c2c
__CFRunLoopDoSources0 + 276
33  CoreFoundation                  0x0000000183f2079c
 __CFRunLoopRun + 1204
34  CoreFoundation                  0x0000000183e40da8
CFRunLoopRunSpecific + 552
35  GraphicsServices                0x0000000185e23020
 GSEventRunModal + 100
36  UIKit                           0x000000018de2178c
 UIApplicationMain + 236
37  icdab_planets                   0x0000000104e14c94
 main + 19604 (main.m:14)
38  libdyld.dylib                   0x00000001838d1fc0
 start + 4

Thread 1:
0   libsystem_pthread.dylib         0x0000000183b9fb04
 start_wqthread + 0

Thread 2:
0   libsystem_kernel.dylib          0x0000000183a01d84
 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x0000000183b9feb4
 _pthread_wqthread + 928
2   libsystem_pthread.dylib         0x0000000183b9fb08
 start_wqthread + 4

Thread 3:
0   libsystem_pthread.dylib         0x0000000183b9fb04
start_wqthread + 0

Thread 4:
0   libsystem_kernel.dylib          0x0000000183a01d84
 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x0000000183b9feb4
 _pthread_wqthread + 928
2   libsystem_pthread.dylib         0x0000000183b9fb08
start_wqthread + 4

Thread 5:
0   libsystem_kernel.dylib          0x0000000183a01d84
 __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x0000000183b9feb4
 _pthread_wqthread + 928
2   libsystem_pthread.dylib         0x0000000183b9fb08
 start_wqthread + 4

Thread 6 name:  com.apple.uikit.eventfetch-thread
Thread 6:
0   libsystem_kernel.dylib          0x00000001839dfe08
 mach_msg_trap + 8
1   libsystem_kernel.dylib          0x00000001839dfc80
 mach_msg + 72
2   CoreFoundation                  0x0000000183f22e40
__CFRunLoopServiceMachPort + 196
3   CoreFoundation                  0x0000000183f20908
 __CFRunLoopRun + 1568
4   CoreFoundation                  0x0000000183e40da8
CFRunLoopRunSpecific + 552
5   Foundation                      0x00000001848b5674
-[NSRunLoop+ 34420 (NSRunLoop) runMode:beforeDate:] + 304
6   Foundation                      0x00000001848b551c
-[NSRunLoop+ 34076 (NSRunLoop) runUntilDate:] + 148
7   UIKit                           0x000000018db067e4
-[UIEventFetcher threadMain] + 136
8   Foundation                      0x00000001849c5efc
__NSThread__start__ + 1040
9   libsystem_pthread.dylib         0x0000000183ba1220
 _pthread_body + 272
10  libsystem_pthread.dylib         0x0000000183ba1110
 _pthread_body + 0
11  libsystem_pthread.dylib         0x0000000183b9fb10
 thread_start + 4

Thread 7:
0   libsystem_pthread.dylib         0x0000000183b9fb04
 start_wqthread + 0

崩溃报告将明确告诉我们哪个线程崩溃了。

Thread 0 Crashed:

线程有编号,并且如果线程有名称的话,也会展示名称:

Thread 0 name:  Dispatch queue: com.apple.main-thread

我们应该将的大部分精力放在崩溃的线程上。 通常是线程 0。注意崩溃线程的名称。请注意,不能在主线程com.apple.main-thread 上执行诸如网络之类的长时间任务,因为该线程用于处理用户交互。

__workq_kernreturn 的引用仅表示正在等待工作的线程,因此除非有大量线程,否则可以将其忽略。

同样,对 mach_msg_trap 的引用仅指示线程正在等待消息进入。

当查看堆栈回溯时,首先出现堆栈帧 0,即堆栈的顶部,然后列出调用所有调用栈。 因此,最后一件事是在第 0 帧。

栈帧:每一次函数的调用,都会在调用栈(call stack)上维护一个独立的栈帧(stack frame)。

堆栈回溯项

现在让我们关注每个线程的回溯项。 例如:

20  UIKit                           0x000000018db14c88
-[_UICanvasLifecycleSettingsDiffAction
performActionsForCanvas:
withUpdatedScene:settingsDiff:fromSettings:
transitionContext:] + 248
Column Meaning
1 堆栈帧号,最近执行的是 0。
2 二进制文件执行。
3 执行位置(第 0 帧)或返回位置(第 1 帧以后)
4+ 符号化函数名称或函数中具有偏移量的地址

帧数越多,就使我们在程序执行顺序方面的时间倒退。 堆栈的顶部或最近运行的代码位于第 0 帧。用有意义的函数名编写代码的原因之一是调用堆栈从概念上描述了正在发生的事情。 使用小型单一用途功能的方法是一种好习惯。 它满足了诊断和可维护性的需求。

堆栈回溯项的第二列是二进制文件。我们主要关注自己的二进制代码,因为 Apple 的框架代码通常非常可靠。错误通常直接出现在我们的代码中,或者是由于错误使用 Apple API 引起的错误引起的。仅仅因为在 Apple 提供的代码中崩溃并不意味着该错误在 Apple 代码中。

第三栏,执行位置,有些棘手。 如果是第 0 帧,则它是代码中正在运行的实际位置。 如果用于任何后续帧,则它是代码中一旦子功能返回后将恢复的位置。

第四列是运行代码的站点(对于第 0 帧),或者正在进行函数调用的站点(对于以后的帧)。 对于符号化的崩溃,我们将看到地址的符号形式。 这将包括从函数开始到调用子函数的代码的位置偏移。 如果我们只有短函数,则此偏移量将是一个很小的值。 这意味着执行诊断时所需的单步执行代码要少得多,或者要读取的汇编代码要少得多。 这是保持我们的职能简短的另一个原因。 如果没有用符号表示崩溃,那么我们将只看到一个内存地址值。

因此,对于示例堆栈帧,我们有:

  • 堆栈帧 20
  • UIKit 二进制文件。
  • 0x000000018db14c88 返回 0-19 帧后的地址。
  • 调用位置是从方法 performActionsForCanvas 开始的第248字节
  • 类是 _UICanvasLifecycleSettingsDiffAction

iOS 崩溃报告线程状态部分

iOS 崩溃报告将来自 ARM-64 二进制文件(最常见)或传统 ARM 32 位二进制文件。

在两种情况下,我们都会获得类似的描述 ARM 寄存器状态的信息。

需要注意的一件事是特殊的十六进制代码,0xbaddc0dedeadbead 这意味着一个非初始化的指针。

32 位线程状态

Thread 0 crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x00000000      r2: 0x00000000
          r3: 0x00000000
    r4: 0x00000006    r5: 0x3c42f000      r6: 0x3b66d304
          r7: 0x002054c8
    r8: 0x14d5f480    r9: 0x252348fd     r10: 0x90eecad7
         r11: 0x14d5f4a4
    ip: 0x00000148    sp: 0x002054bc      lr: 0x257d8733
          pc: 0x2572ec5c
  cpsr: 0x00000010

64 位线程状态

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000028   x1: 0x0000000000000029
       x2: 0x0000000000000008
       x3: 0x0000000183a4906c
    x4: 0x0000000104440260   x5: 0x0000000000000047
       x6: 0x000000000000000a
       x7: 0x0000000138819df0
    x8: 0x0000000000000000   x9: 0x0000000000000000
      x10: 0x0000000000000003
      x11: 0xbaddc0dedeadbead
   x12: 0x0000000000000012  x13: 0x0000000000000002
     x14: 0x0000000000000000
     x15: 0x0000010000000100
   x16: 0x0000000183b9b8cc  x17: 0x0000000000000100
     x18: 0x0000000000000000
     x19: 0x00000001b5c241c8
   x20: 0x00000001c0071b00  x21: 0x0000000000000018
     x22: 0x000000018e89b27a
     x23: 0x0000000000000000
   x24: 0x00000001c4033d60  x25: 0x0000000000000001
     x26: 0x0000000000000288
     x27: 0x00000000000000e0
   x28: 0x0000000000000010   fp: 0x000000016bde54b0
      lr: 0x000000010401ca04
    sp: 0x000000016bde53e0   pc: 0x000000010401c6c8
     cpsr: 0x80000000

iOS 崩溃报告的 Binary Images 部分

奔溃报告会有一部分列举了崩溃进程加载的所有 Binary Images。这通常是一串很长的列表。它强调了一个事实,即我们的应用程序有许多支持框架。大多数框架是私有框架。iOS 开发工具包似乎包含了大量 API,但这只是冰山一角。

这是一个示例列表,为便于演示而进行了编辑:

Binary Images:

0x104018000 - 0x10401ffff icdab_as arm64
  
/var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/icdab_as

0x104030000 - 0x104037fff libswiftCoreFoundation.dylib arm64
  <81f66e04bab133feb3369b4162a68afc>
  /var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/
Frameworks/libswiftCoreFoundation.dylib


0x104044000 - 0x104057fff libswiftCoreGraphics.dylib arm64
  
  /var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/
Frameworks/libswiftCoreGraphics.dylib

0x104078000 - 0x10407ffff libswiftCoreImage.dylib arm64
  <9433fc53f72630dc8c53851703dd440b>
  /var/containers/Bundle/Application/
1A05BC59-491C-4D0A-B4F6-8A98A804F74D/icdab_as.app/
Frameworks/libswiftCoreImage.dylib

0x104094000 - 0x1040cffff dyld arm64
  <06dc98224ae03573bf72c78810c81a78> /usr/lib/dyld

第一部分是图像已加载到内存中的位置。这里 icdab_as 已被加载到 0x104018000-0x10401ffff 范围内。

第二部分是二进制文件的名称。这里的名字是 icdab_as

第三部分是加载的二进制文件中的体系结构切片。我们通常希望在这里看到 arm64(ARM 64位)。

第四部分是二进制文件的UUID。这里 icdab_as 的UDID 是 b82579f401603481990d1c1c9a42b773

如果我们的 DSYM 文件 UUID 与二进制文件不匹配,则符号化将失败。

以下是使用 dwarfdump 命令在 DSYM 和应用程序二进制文件中看到的相应 UUID 的示例:

$ dwarfdump --uuid icdab_as.app/icdab_as
icdab_as.app.dSYM/Contents/Resources/DWARF/icdab_as

UUID: 25BCB4EC-21DE-3CE6-97A8-B759F31501B7
 (arm64) icdab_as.app/icdab_as

UUID: 25BCB4EC-21DE-3CE6-97A8-B759F31501B7
 (arm64)
icdab_as.app.dSYM/Contents/Resources/DWARF/icdab_as

第五部分是设备上显示的二进制文件的路径。

大多数二进制文件都有一个不言自明的名称。dyld 二进制文件是动态加载器。它位于所有堆栈回溯的底部,因为它负责在执行之前开始加载二进制文件。

动态加载器在准备二进制文件以执行时执行许多任务。如果我们的二进制引用了其他库,它将加载它们。如果没有,则无法加载我们的应用程序。这就是为什么即使在调用 main.m 中的任何代码之前也可能发生崩溃的原因。稍后,我们将研究如何诊断这些问题。

感谢你阅读本文!

你可能感兴趣的:([译]《iOS Crash Dump Analysis》- 崩溃报告(iOS部分))