Understanding and Analyzing Application Crash Reports 奔溃分析

947 阅读38分钟

Understanding and Analyzing Application Crash Reports

当应用程序崩溃时, 将创建崩溃报告并将其存储在设备上. 崩溃报告描述应用程序终止的条件, 在大多数情况下包括每个执行线程的完整回溯(调用栈), 并且通常对于应用程序中的调试问题非常有用. 应该查看这些崩溃报告, 以了解应用程序的崩溃, 然后尝试修复它们

使用回溯(调用栈)的崩溃报告需要符号化, 然后才能对其进行分析. 符号化用人可读的函数号和行号替换内存地址. 如果通过Xcode的设备窗口获取设备崩溃日志, 那么将在几秒钟后自动符号化. 否则需要自己将.crash文件符号化, 方法是将其导入Xcode Devices窗口

内存不足报告与其他崩溃报告的不同之处在于此类型的报告中没有回溯(调用栈). 当发生内存不足崩溃时, 必须调查内存使用模式以及对内存不足警告的响应

Acquiring Crash and Low Memory Reports

获取崩溃和内存不足报告

Debugging Deployed iOS Apps 讨论如何直接从iOS设备检索崩溃和内存不足报告

Analyzing Crash Reports讨论如何查看从TestFlight测试以及从应用商店下载应用的用户收集的汇总崩溃报告

Symbolicating Crash Reports

符号崩溃报告

符号化是将回溯地址解析为源代码方法或函数名称(称为符号)的过程, 如果不首先对崩溃报告进行符号化, 就很难确定崩溃发生的位置

注意:内存不足报告不需要符号化. 来自macOS的崩溃报告通常在生成时被符号化或部分符号化. 本节着重于用符号表示来自iOS, watchOS和tvOS的崩溃报告. 但是对于macOS总体过程是相似的

崩溃报告和符号化过程概述

Symbolicating Crash Reports

当编译器将源代码转换成机器代码时, 它还会生成调试符号, 这些符号将已编译二进制文件中的每条机器指令映射回其源代码行. 根据调试信息格式(DEBUG_INFORMATION_FORMAT)的构建设置, 这些调试符号存储在二进制文件中或随附的调试符号(dSYM)文件中. 默认情况下, 应用程序的调试版本将调试符号存储在已编译的二进制文件中, 而应用程序的发布版本将调试符号存储在随附的dSYM文件中以减小二进制文件的大小.

调试符号文件和应用程序二进制文件通过构建UUID在每个构建基础上捆绑在一起. 将为应用程序的每个内部版本生成一个新的UUID并唯一标识该内部版本. 即使功能相同的可执行文件是使用相同的编译器设置从相同的源代码重新构建的, 它也将具有不同的构建UUID. 来自后续版本的调试符号文件, 即使来自相同的源文件, 也不会与其他版本的二进制文件互操作

当存档应用程序以进行分发时, Xcode将收集应用程序二进制文件以及.dSYM文件, 并将它们存储在主文件夹内的某个位置. 可以在Xcode OrganizerArchived部分下找到所有已存档的应用程序.

重要 要符号化来自测试人员应用程序评论和客户的崩溃报告, 必须为分发的应用程序的每个内部版本保留存档

如果要通过App Store分发应用程序, 或使用Test Flight进行Beta测试, 则在将存档上传到iTunes Connect时可以选择包括dSYM文件. 在提交对话框中选中"包括您的应用程序的应用程序符号..." 要接收从TestFlight用户和选择共享诊断数据的客户收集的崩溃报告, 必须上传dSYM文件

重要 即使将存档上传到iTunes Connect时包含了dSYM文件也不会符号化从App Review收到的崩溃报告. 需要使用Xcode符号化从App Review收到的所有崩溃报告

当应用程序崩溃时, 会创建一个非符号化的崩溃报告并将其存储在设备上

用户可以按照调试已部署的iOS应用中(Debugging Deployed iOS Apps)的步骤直接从其设备检索崩溃报告. 如果是通过AdHocEnterprise发行版分发应用程序的, 则这是从用户那里获取崩溃报告的唯一方法

从设备中检索到的崩溃报告没有符号化, 需要使用Xcode进行符号化. Xcode使用与应用程序二进制文件关联的dSYM文件将回溯中的每个地址替换为其源代码中的原始位置. 结果是得到符号化的崩溃报告

如果用户选择与Apple共享诊断数据, 或者用户已通过TestFlight安装了应用程序的Beta版, 则崩溃报告将上传到App Store

App Store符号化崩溃报告, 并将其与类似的崩溃报告进行分组, 这种类似的崩溃报告的汇总称为崩溃点

带符号的崩溃报告可在Xcode的崩溃管理器中使用

Bitcode

Bitcode是已编译程序的中间表示, 当存档启用了Bitcode的应用程序时, 编译器会生成包含Bitcode而不是机器码的二进制文件, 二进制文件上传到App Store后, Bitcode将编译为机器代码. App Store将来可能会再次编译Bitcode, 以利用将来的编译器改进而无需提交人员采做其他操作

Bitcode编译过程概述

Symbolicating Crash Reports

由于二进制文件的最终编译是在App Store上进行的, 因此程序员的Mac将不包含从App Review或从其设备发送了崩溃报告的用户收到的崩溃报告所需的调试符号(dSYM)文件. 尽管在归档应用程序时会生成dSYM文件, 但该文件用于Bitcode二进制文件, 不能用于符号崩溃报告. App Store可以生成的dSYM文件. 您必须下载这些dSYM文件以表示从App Review或从其设备向您发送崩溃报告的用户收到的崩溃报告. 通过崩溃报告服务收到的崩溃报告将自动被符号化

重要: 由App Store编译的二进制文件将具有与最初提交的二进制文件不同的UUID

Downloading the dSYM files from Xcode

从Xcode下载dsYM文件

  1. Archives organizer下, 选择最初提交给App Store的归档
  2. 点击Download dSYMs按钮

Downloading the dSYM files from the iTunes Connect website

从iTunes Connect网站下载dsYM

  1. 打开App Details页面
  2. 点击Activity
  3. 在构建版本list里选择一个版本
  4. 点击Download dSYM链接

Translating 'hidden' symbol names back to their original names

将“隐藏”符号名称转换回其原始名称

将带有Bitcode的应用程序上载到App Store时, 可以通过取消选中“提交”对话框中的“上传应用程序的符号以从Apple接收符号报告”来选择不发送应用程序的符号. 如果选择不将应用程序的符号信息发送给Apple, Xcode会将应用程序的.dSYM文件中的符号替换为诸如 __hidden#109_之类的混淆符号, 然后再将其发送到iTunes Connect. Xcode在原始符号和“隐藏”符号之间创建一个映射, 并将此映射存储在应用程序存档内的.bcsymbolmap文件中, 每个.dSYM文件将具有一个对应的.bcsymbolmap文件

在符号化崩溃报告之前, 需要对从iTunes Connect下载的.dSYM文件中的符号进行模糊处理. 如果使用Xcode中的Download dSYMs按钮, 则将自动执行这种模糊处理. 但是如果使用iTunes Connect网站下载.dSYM文件, 请打开终端并使用以下命令对符号进行模糊处理(将示例路径替换为自己的存档和从iTunes Connect下载的dSYMs文件夹)

xcrun dsymutil -symbol-map ~/Library/Developer/Xcode/Archives/2017-11-23/MyGreatApp\ 11-23-17\,\ 12.00\ PM.xcarchive/BCSymbolMaps ~/Downloads/dSYMs/3B15C133-88AA-35B0-B8BA-84AF76826CE0.dSYM

对下载的dSYMs文件夹中的每个.dSYM文件运行此命令

Determining Whether a Crash Report is Symbolicated

确定崩溃报告是否符号化

崩溃报告可能没有符号化,完全符号化或部分符号化. 未符号化的崩溃报告将在回溯中不包含方法或函数名称. 而是在加载的二进制图像中具有可执行代码的十六进制地址. 在完全符号化的崩溃报告中, 回溯的每一行中的十六进制地址将替换为相应的符号. 在部分符号化的崩溃报告中仅回溯中的某些地址已被其相应符号代替

显然应该尝试完全符号化收到的任何崩溃报告, 因为它将提供有关崩溃的最深刻直白的意思. 部分符号化的崩溃报告可能包含足够的信息来理解崩溃, 这取决于崩溃的类型以及回溯的哪些部分已被成功符号化. 为符号化的崩溃报告很少有用

相同回溯的不同级别的符号化

Symbolicating Crash Reports

Symbolicating iOS Crash Reports With Xcode

使用Xcode符号化iOS奔溃信息

Xcode将自动尝试用符号表示它遇到的所有崩溃报告. 需要做的只是将崩溃报告添加到Xcode Organizer中

注意 没有.crash扩展名的文件, Xcode将不接受此崩溃报告. 如果收到的崩溃报告不带扩展名或扩展名为.txt, 请按照以下步骤将其重命名为.crash扩展名

  1. 将iOS设备连接到Mac
  2. 动Window菜单选择Devices
  3. 在左列的“设备”部分下,选择一个设备
  4. 单击右侧面板“设备信息”部分下的“查看设备日志”按钮
  5. 将崩溃报告拖到显示面板的左列
  6. Xcode将自动符号化崩溃报告并显示结果

为了符号化崩溃报告,Xcode需要能够找到以下内容

  • 崩溃的应用程序的二进制文件和dSYM文件
  • 应用程序链接到的所有自定义框架的二进制文件和dSYM文件. 对于使用应用程序从源构建的框架, 将其dSYM文件与应用程序的dSYM文件一起复制到存档中, 对于由第三方构建的框架, 需要向作者索要dSYM文件.
  • 崩溃时正在运行该应用程序的操作系统的符号. 这些符号包含特定OS版本(例如iOS 9.3.3)中包含的框架的调试信息. 操作系统符号是特定于体系结构的-适用于64位设备的iOS版本不会包含armv7的符号. Xcode将自动从连接到Mac的每个设备中复制OS符号

如果缺少这些则Xcode可能无法符号化崩溃报告, 或者可能仅部分符号化崩溃报告

Symbolicating Crash Reports With atos

使用终端工具atos符号化奔溃日志

atos命令将数字地址转换为其符号等效项. 如果有完整的调试符号信息, 那么atos的输出将包括文件名和源行号信息. atos命令可用于符号化未符号化或部分符号化的崩溃报告的回溯中的各个地址

使用atos符号化崩溃报告的一部分

  1. 在回溯中找到要符号化的某行, 在第二列中注意二进制图像的名称, 在第三列中注意地址
  2. 在崩溃报告底部的二进制图像列表中查找具有该名称的二进制图像, 注意二进制图像的体系结构和加载地址

崩溃报告中使用atos所需的信息

Symbolicating Crash Reports

  1. 找到二进制文件的dSYM文件, 可以使用Spotlight为二进制图像的UUID查找匹配的dSYM文件. 请参阅“符号故障排除”部分. dSYM文件是捆绑软件, 其中包含一个文件, 其中包含编译器在构建时生成的DWARF调试信息. 调用atos时必须提供此文件的路径而不是dSYM软件包的路径
  2. 利用以上信息可以使用atos命令在回溯中用符号表示地址。您可以指定多个符号地址,以空格分隔
atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>

遵循上述步骤的atos命令的示例用法以及所得的输出

$ atos -arch arm64 -o TheElements.app.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc
-[AtomicElementViewController myTransitionDidStop:finished:context:]

Symbolication Troubleshooting

符号故障排除

如果Xcode无法完全符号化崩溃报告, 则可能是因为Mac缺少应用程序二进制文件的dSYM文件,应用程序链接到的一个或多个框架的dSYM文件或应用程序正在运行的操作系统的设备符号,在它崩溃时候. 以下步骤显示了如何使用Spotlight确定在Mac上是否存在用于象征二进制图像中的回溯地址所需的dSYM文件

找到二进制图像的UUID

Symbolicating Crash Reports

  1. 在回溯中找到Xcode无法符号化的一行, 注意第二列中的二进制图像的名称
  2. 在崩溃报告底部的二进制图像列表中查找具有该名称的二进制图像, 此列表包含崩溃时加载到进程中的每个二进制映像的UUID

可以使用grep命令行工具快速找到二进制映像列表中的条目

$ grep --after-context=1000 "Binary Images:" <Path to Crash Report> | grep <Binary Name>
  1. 将二进制图像的UUID转换为为32个字符串, 以8-4-4-4-12(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXX)组分隔. 请注意所有字母都必须大写
  2. 使用mdfind命令行工具通过查询"com_apple_xcode_dsym_uuids =="(包括引号)来搜索UUID

使用mdfind命令行工具搜索给定UUID的dSYM文件

$ mdfind "com_apple_xcode_dsym_uuids == <UUID>"
  1. 如果Spotlight找到用于UUID的dSYM文件, 则mdfind将打印dSYM文件的路径, 并可能将其包含存档. 如果找不到用于UUID的dSYM文件, 则mdfind将退出而不打印任何内容

如果Spotlight为该二进制文件找到了dSYM文件,但Xcode无法在该二进制文件图像中符号化地址. 则应提交一个错误, 将崩溃报告和相关的dSYM文件附加到错误报告. 解决方法是可以使用atos手动将地址符号化

如果Spotlight找不到二进制映像的dSYM, 请确认仍然拥有崩溃的应用程序版本的Xcode存档, 并且该存档位于Spotlight可以找到它的某个位置(主目录中的任何位置都可以). 如果应用程序是在启用了Bitcode的情况下构建的, 请确保已从App Store下载了dSYM文件以进行最终编译

如果=认为二进制图像具有正确的dSYM, 则可以使用dwarfdump命令来打印匹配的UUID. 还可以使用dwarfdump命令来打印二进制文件的UUID

xcrun dwarfdump --uuid <Path to dSYM file>

注意 必须拥有最初提交给App Store的已崩溃应用程序版本的存档. dSYM文件和应用程序二进制文件专门基于每个构建而捆绑在一起. 即使使用相同的来源和构建配置来创建新的存档, 也不会生成可以与崩溃的构建版本互操作的dSYM文件.

如果不再拥有此存档, 则应提交保留该存档的应用程序的新版本. 然后将可以用符号化此新版本的崩溃报告

Analyzing Crash Reports

分析奔溃报告

本部分讨论在标准崩溃报告中找到的每个部分

Header

每个崩溃报告都有头部信息

崩溃报告中的头部摘录

Incident Identifier: B6FD1E8E-B39F-430B-ADDE-FC3A45ED368C
CrashReporter Key: f04e68ec62d3c66057628c9ba9839e30d55937dc
Hardware Model: iPad6,8
Process: TheElements [303]
Path: /private/var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
Identifier: com.example.apple-samplecode.TheElements
Version: 1.12
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.example.apple-samplecode.TheElements [402]
 
Date/Time: 2016-08-22 10:43:07.5806 -0700
Launch Time: 2016-08-22 10:43:01.0293 -0700
OS Version: iPhone OS 10.0 (14A5345a)
Report Version: 104
  • Incident Identifier 报告的唯一标识符, 两个报告永远不会共享相同的事件标识符
  • CrashReporter Key 每个设备的匿名标识符, 来自同一设备的两个报告将包含相同的值
  • Beta Identifier 崩溃的应用程序的设备和供应商的组合的唯一标识符, 来自同一供应商和同一设备的两个应用程序报告将包含相同的值, 该字段仅出现在为通过TestFlight分发的应用程序生成的崩溃报告中,并替换了CrashReporter Key字段
  • Process 崩溃的进程的可执行文件名. 这与应用程序的信息属性列表中CFBundleExecutable键的值匹配
  • Version 崩溃的进程版本, 此字段的值是崩溃的应用程序的CFBundleVersion和CFBundleVersionString`的关联值
  • Code Type 崩溃的进程的目标体系结构。这将是ARM-64, ARM, x86-64或x86其中之一
  • Role 终止时分配给进程的task_role
  • OS Version 发生崩溃的操作系统版本, 包括内部版本号

Exception Information

异常信息

不要与Objective-C/C++异常混淆(尽管其中一种可能是导致崩溃的原因), 本节列出了Mach异常类型和相关字段, 它们提供了有关崩溃性质的信息. 并非所有字段都会出现在每个崩溃报告中

由于未捕获的Objective-C异常而终止进程时生成的崩溃报告中的“异常代码”的摘要

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

进程终止是由于取消引用NULL指针而产生的崩溃报告中的“异常代码”的摘要

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 0

下面介绍了此部分可能出现的字段

  • Exception Codes 有关异常的处理器特定信息, 该异常编码为一个或多个64位十六进制数字. 通常此字段不会显示, 因为崩溃报告器会解析异常代码, 以将其作为其他字段中易于理解的描述显示
  • Exception Subtype 易于理解的异常代码名称
  • Exception Message 从异常代码中提取的其他可读信息
  • Exception Note 非特定于一种异常类型的附加信息. 如果此字段包含SIMULATED(这不是崩溃), 则该进程没有崩溃. 但是应系统(通常是watchdog)的请求而终止
  • Termination Reason 进程终止时指定的退出原因信息. 进程内部和外部的关键系统组件都会在遇到致命错误(例如错误的代码签名, 缺少的依赖库或在没有适当权利的情况下访问隐私敏感信息)时终止进程. macOS Sierra, iOS 10, watchOS 3和tvOS 10已采用新的基础架构来记录这些错误, 并且由这些操作系统生成的崩溃报告在“终止原因”字段中列出了错误消息.
  • Triggered by Thread 引发异常的线程

以下介绍了一些最常见的异常类型

Bad Memory Access [EXC_BAD_ACCESS // SIGSEGV // SIGBUS]

错误的内存访问

该进程尝试访问无效的内存, 或者尝试以内存保护级别不允许的方式访问内存(例如, 写入只读内存), 异常子类型字段包含描述错误的kern_return_t和被错误访问的内存地址

以下是一些调试错误的内存访问崩溃的提示

  • 如果objc_msgSendobjc_release在崩溃的线程的Backtrace顶部附近, 则该进程可能已尝试向已释放对象发送消息. 应该使用Zombies仪器来分析应用程序, 以更好地了解此崩溃的情况
  • 如果gpus_ReturnNotPermittedKillClient在崩溃的线程的Backtrace顶部附近,则该进程被终止, 因为它试图在后台使用OpenGL ESMetal进行渲染
  • 在启用了地址清理程序(Address Sanitizer)的情况下运行应用程序, 地址清理器在编译后的代码中为内存访问添加了其他工具, 在应用程序运行时, Xcode会警告是否以可能导致崩溃的方式访问内存

Abnormal Exit [EXC_CRASH // SIGABRT]

异常退出

进程异常退出. 这种异常类型导致崩溃的最常见原因是未捕获的Objective-C/C++异常以及对abort()的调用

如果扩展程序花费太多时间进行初始化(监视程序终止), 则该应用程序扩展将终止于此异常类型. 如果某个扩展由于启动时挂起而被杀死, 则生成的崩溃报告的异常子类型将为LAUNCH_HANG. 由于扩展没有main函数, 因此初始化和花费在扩展库和从属库中的静态构造函数和+load方法中花费的任何初始化时间都将发生. 应该尽可能多地推迟这些工作

Trace Trap [EXC_BREAKPOINT // SIGTRAP]

追踪陷阱(打断点)

与异常退出类似, 此异常旨在使附加的调试器有机会在执行的特定点中断进程. 可以使用__builtin_trap()函数从自己的代码中触发此异常. 如果未连接调试器则该进程将终止并生成崩溃报告

较低级别的库(例如libdispatch)将在遇到致命错误时捕获该进程. 可以在崩溃报告的Additional Diagnostic Information部分或设备的控制台中找到有关该错误的其他信息

如果在运行时遇到以下情况, 则Swift代码将以此异常类型终止例如

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

查看Backtrace以确定在哪里遇到了异常情况. 其他信息也可能已记录到设备的控制台中. 应该在崩溃位置修改代码以妥善处理运行时故障. 例如使用“可选绑定”而不是强制解包可选类型

Illegal Instruction [EXC_BAD_INSTRUCTION // SIGILL]

非法指令

进程试图执行非法或未定义的指令. 该进程可能试图通过配置错误的函数指针跳转到无效地址

在Intel处理器上, ud2操作码会导致EXC_BAD_INSTRUCTION异常, 但通常用于捕获进程以进行调试. 如果在运行时遇到异常情况, 英特尔处理器上的Swift代码将以该异常类型终止

Quit [SIGQUIT]

退出

该进程是在另一个具有管理其生命周期权限的进程的请求下终止的. SIGQUIT并不意味着该进程已崩溃, 但它确实可能以可检测的方式发生了异常

在iOS上键盘扩展如果加载时间太长, 主应用程序将退出键盘扩展. 崩溃报告中显示的Backtrace不太可能指向负责的代码. 最有可能的是扩展的启动路径上的其他一些代码花了很长时间才能完成, 超过了限定的时间. 并且在退出扩展时执行移至了Backtraces中显示的代码上. 应该对扩展进行概要分析以更好地了解启动期间大部分工作的发生位置, 并将该工作移至后台线程或将其推迟到以后(加载扩展之后)

Killed [SIGKILL]

杀死

该进程应系统的要求终止. 查看终止原因字段(Termination Reason)以更好地了解终止原因

终止原因字段将包含名称空间, 后跟一个code. 以下code特定于watchOS

  • 终止代码0xc51bad01表示watch应用已终止, 因为它在执行后台任务时占用了过多的CPU时间. 要解决此问题请优化执行后台任务的代码以提高CPU效率, 或者减少应用程序在后台运行时执行的工作量
  • 终止代码0xc51bad02表示watch应用程序已终止, 因为它未能在分配的时间内完成后台任务. 要解决此问题请减少应用程序在后台运行时执行的工作量
  • 终止代码0xc51bad03表示watch应用程序无法在分配的时间内完成后台任务, 并且系统总体上非常忙, 以至于该应用程序可能没有收到太多的CPU时间来执行后台任务. 尽管应用程序可以通过减少在后台任务中执行的工作量来避免该问题, 但0xc51bad03并不表示该应用程序做错了什么. 该应用更有可能由于整体系统负载而无法完成其工作

Guarded Resource Violation [EXC_GUARD]

受保护资源违规

该进程违反了受保护的资源保护. 系统库可能会将某些文件描述符标记为受保护, 然后对这些描述符的正常操作将触发EXC_GUARD异常(当要对这些文件描述符进行操作时, 系统将使用特殊的“受保护”专用API). 这可以帮助快速查找问题, 例如关闭由系统库打开的文件描述符. 如果某个应用程序关闭了用于访问支持Core Data存储的SQLite文件的文件描述符, 则Core Data随后会神秘地崩溃. 保护异常可以更快地发现这些问题从而使它们更易于调试

来自较新版本的iOS的崩溃报告在Exception SubtypeException Message字段中包含有关导致EXC_GUARD异常的操作的易于理解的详细信息. 在来自macOS或更旧版本的iOS的崩溃报告中, 此信息被编码为第一个Exception Code作为位字段其分解如下:

  • Guard Type 受保护资源的类型. 值0x2表示资源是文件描述符
  • Flavor 引发违规的条件
    • 如果设置了第一(1<<0)位, 则该进程尝试在受保护的文件描述符上调用close()
    • 如果设置了第二(1<<1)位, 则进程尝试使用受保护文件描述符上的F_DUPFDF_DUPFD_CLOEXEC命令调用dup(), dup2()fcntl().
    • 如果设置了第三(1<<2)位, 则该进程尝试通过套接字发送受保护的文件描述符
    • 如果设置了第五(1<<4)位, 则进程尝试写入受保护的文件描述符
  • File Descriptor 进程尝试修改的受保护文件描述符

Resource Limit [EXC_RESOURCE]

资源限制

该进程超出了资源消耗限制. 这是来自操作系统的通知. 通知该进程使用了​​太多资源. 确切的资源列在Exception Subtype字段中. 如果Exception Note字段包含NON-FATAL CONDITION, 那么即使生成了崩溃报告, 该进程也不会被终止

  • 异常子类型MEMORY表示进程已超出系统施加的内存限制.
  • 异常子类型WAKEUPS表示该进程中的线程每秒被唤醒太多次, 这迫使CPU频繁唤醒并消耗电池寿命
    • 通常这是由线程间通信(通常使用peformSelector:onThread:dispatch_async)引起的, 这种情况不经意地发生的次数比应有的多. 由于触发此异常的通信种类经常发生, 因此通常会有多个后台线程具有非常相似的Backtraces(回溯)-指示通信发生源

Other Exception Types

其他异常类型

某些崩溃报告可能包含未命名的异常类型, 该异常类型将以十六进制值(例如00000020)打印. 如果收到这些崩溃报告之一, 请直接查看Exception Codes字段以了解更多信息

  • 0xbaaaaaad
    • 异常代码0xbaaaaaad表示该日志是整个系统的堆栈快照, 而不是崩溃报告. 要拍摄快照, 请同时按侧面按钮和两个音量按钮. 这些日志通常是用户意外创建的, 它们并不表示错误
  • 0x8badf00d
    • 异常代码0x8badf00d表示应用程序已被iOS终止, 因为发生了watchdog超时. 该应用程序启动, 终止或响应系统事件花费的时间太长. 一个常见的原因是在主线程上进行同步网络连接. 线程0上的任何操作都需要移至后台线程, 或进行不同的处理以免阻塞主线程
  • 0xc00010ff
  • 0xdead10cc
    • 异常代码0xdead10cc表示应用程序已被操作系统终止. 因为该应用程序在挂起期间一直处于文件锁定或sqlite数据库锁定状态. 如果您的应用程序在挂起时对锁定的文件或sqlite数据库执行操作, 则它必须请求额外的后台执行时间来完成这些操作并在挂起之前放弃锁定
  • 0x2bad45ec
    • 异常代码0x2bad45ec表示应用程序已由于安全违规而被iOS终止. 终止描述信息“在安全模式下检测到进程在执行不安全绘图时”表示该应用尝试在不允许的情况下(例如在屏幕锁定时)在屏幕上进行绘制. 用户可能不会注意到此终止, 因为此终止发生时屏幕已关闭或显示了锁定屏幕

注意 使用应用程序切换器终止已暂停的应用程序不会生成崩溃报告. 应用暂停后, 它随时可以通过iOS终止, 因此不会生成崩溃报告

Additional Diagnostic Information

其他诊断信息

本节包括特定于终止类型的其他诊断信息,其中包括

  • Application Specific Information 在进程终止之前捕获的框架错误消息
  • Kernel Messages 有关代码签名问题的详细信息
  • Dyld Error Messages 动态链接器发出的错误消息

从macOS Sierra, iOS 10, watchOS 3和tvOS 10开始, 现在大多数信息在Exception Information下的Termination Reason字段中

由于找不到与之链接的框架而终止进程时生成的崩溃报告中的特定于应用程序的信息部分的摘要

Dyld Error Message:
Dyld Message: Library not loaded: @rpath/MyCustomFramework.framework/MyCustomFramework
  Referenced from: /private/var/containers/Bundle/Application/CD9DB546-A449-41A4-A08B-87E57EE11354/TheElements.app/TheElements
  Reason: no suitable image found.

由于进程无法快速加载其初始视图控制器而终止的进程崩溃报告中的特定于应用程序的信息部分的摘要

Application Specific Information:
com.example.apple-samplecode.TheElements failed to scene-create after 19.81s (launch took 0.19s of total time limit 20.00s)
 
Elapsed total CPU time (seconds): 7.690 (user 7.690, system 0.000), 19% CPU
Elapsed application CPU time (seconds): 0.697, 2% CPU

Backtraces

回溯(调用栈)

崩溃报告中最有趣的部分是进程终止时每个进程线程的回溯. 这些跟踪中的每一个都与在调试器中暂停过程时看到的类似

完全符号化的崩溃报告的Backtrace部分的摘要

Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   TheElements                   	0x000000010006bc20 -[AtomicElementViewController myTransitionDidStop:finished:context:] (AtomicElementViewController.m:203)
1   UIKit                         	0x0000000194cef0f0 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
2   UIKit                         	0x0000000194ceef30 -[UIViewAnimationState animationDidStop:finished:] + 160
3   QuartzCore                    	0x0000000192178404 CA::Layer::run_animation_callbacks(void*) + 260
4   libdispatch.dylib             	0x000000018dd6d1c0 _dispatch_client_callout + 16
5   libdispatch.dylib             	0x000000018dd71d6c _dispatch_main_queue_callback_4CF + 1000
6   CoreFoundation                	0x000000018ee91f2c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
7   CoreFoundation                	0x000000018ee8fb18 __CFRunLoopRun + 1660
8   CoreFoundation                	0x000000018edbe048 CFRunLoopRunSpecific + 444
9   GraphicsServices              	0x000000019083f198 GSEventRunModal + 180
10  UIKit                         	0x0000000194d21bd0 -[UIApplication _run] + 684
11  UIKit                         	0x0000000194d1c908 UIApplicationMain + 208
12  TheElements                   	0x00000001000653c0 main (main.m:55)
13  libdyld.dylib                 	0x000000018dda05b8 start + 4
 
Thread 1:
0   libsystem_kernel.dylib        	0x000000018deb2a88 __workq_kernreturn + 8
1   libsystem_pthread.dylib       	0x000000018df75188 _pthread_wqthread + 968
2   libsystem_pthread.dylib       	0x000000018df74db4 start_wqthread + 4
 
...

第一行列出了线程号和当前正在执行的调度队列的标识符. 其余各行列出了有关回溯中各个堆栈帧的详细信息, 从左到右:

  • 堆栈帧号. 堆栈帧按调用顺序显示, 其中零帧是停止执行时正在执行的函数, 第一帧是在零帧中调用该函数的函数依此类推
  • 堆栈帧执行函数所在的二进制文件的名称
  • 对于第零帧, 执行暂停时正在执行的机器指令的地址. 对于其余的堆栈帧, 当控制权返回到堆栈帧时, 下一条将执行的机器指令的地址
  • 在带符号的崩溃报告中, 函数在堆栈框架中的方法名称

Exceptions

Objective-C中的异常用于指示在运行时检测到的编程错误, 例如访问索引超出范围的数组, 尝试对不可变对象进行改变, 未实现协议的必需方法或发送以下消息, 接收器无法识别

注意 向先前释放的对象发送消息可能会引发NSInvalidArgumentException, 而不是因发生内存访问冲突而使程序崩溃. 当在先前由已释放对象占用的内存中分配新对象时, 会发生这种情况. 如果由于未捕获的NSInvalidArgumentException而导致应用程序崩溃(请在异常回溯中查找-[NSObject(NSObject) didNotRecognizeSelector:]), 请考虑使用Zombies工具对应用程序进行性能分析, 以消除导致内存管理不当的可能性

如果未捕获到异常, 则该异常将被称为未捕获的异常处理程序的函数拦截. 默认的未捕获异常处理程序将异常消息记录到设备的控制台, 然后终止该过程. 如下代码所示在Last Exception Backtrace部分下, 只有异常回溯被写入到生成的崩溃报告中. 代码中显示了异常消息. 如果收到带有上次异常回溯的崩溃报告, 则应从原始设备获取控制台日志以更好地了解导致异常的条件

Last Exception Backtrace:
(0x18eee41c0 0x18d91c55c 0x18eee3e88 0x18f8ea1a0 0x195013fe4 0x1951acf20 0x18ee03dc4 0x1951ab8f4 0x195458128 0x19545fa20 0x19545fc7c 0x19545ff70 0x194de4594 0x194e94e8c 0x194f47d8c 0x194f39b40 0x194ca92ac 0x18ee917dc 0x18ee8f40c 0x18ee8f89c 0x18edbe048 0x19083f198 0x194d21bd0 0x194d1c908 0x1000ad45c 0x18dda05b8)

带有最后一个异常回溯仅包含十六进制地址的崩溃日志必须用符号表示以产生可用的回溯

Last Exception Backtrace:
0   CoreFoundation                	0x18eee41c0 __exceptionPreprocess + 124
1   libobjc.A.dylib               	0x18d91c55c objc_exception_throw + 56
2   CoreFoundation                	0x18eee3e88 -[NSException raise] + 12
3   Foundation                    	0x18f8ea1a0 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 272
4   UIKit                         	0x195013fe4 -[UIViewController setValue:forKey:] + 104
5   UIKit                         	0x1951acf20 -[UIRuntimeOutletConnection connect] + 124
6   CoreFoundation                	0x18ee03dc4 -[NSArray makeObjectsPerformSelector:] + 232
7   UIKit                         	0x1951ab8f4 -[UINib instantiateWithOwner:options:] + 1756
8   UIKit                         	0x195458128 -[UIStoryboard instantiateViewControllerWithIdentifier:] + 196
9   UIKit                         	0x19545fa20 -[UIStoryboardSegueTemplate instantiateOrFindDestinationViewControllerWithSender:] + 92
10  UIKit                         	0x19545fc7c -[UIStoryboardSegueTemplate _perform:] + 56
11  UIKit                         	0x19545ff70 -[UIStoryboardSegueTemplate perform:] + 160
12  UIKit                         	0x194de4594 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1352
13  UIKit                         	0x194e94e8c -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 268
14  UIKit                         	0x194f47d8c _runAfterCACommitDeferredBlocks + 292
15  UIKit                         	0x194f39b40 _cleanUpAfterCAFlushAndRunDeferredBlocks + 560
16  UIKit                         	0x194ca92ac _afterCACommitHandler + 168
17  CoreFoundation                	0x18ee917dc __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
18  CoreFoundation                	0x18ee8f40c __CFRunLoopDoObservers + 372
19  CoreFoundation                	0x18ee8f89c __CFRunLoopRun + 1024
20  CoreFoundation                	0x18edbe048 CFRunLoopRunSpecific + 444
21  GraphicsServices              	0x19083f198 GSEventRunModal + 180
22  UIKit                         	0x194d21bd0 -[UIApplication _run] + 684
23  UIKit                         	0x194d1c908 UIApplicationMain + 208
24  TheElements                   	0x1000ad45c main (main.m:55)
25  libdyld.dylib                 	0x18dda05b8 start + 4

注意 如果发现没有捕获到由应用程序设置的异常处理域中引发的异常, 请在构建应用程序或库时确认未指定-no_compact_unwind标志

64位iOS使用zero-cost异常实现. 在zero-cost系统中, 每个函数都有额外数据, 这些数据描述了在函数上引发异常时如何展开堆栈. 如果在没有展开数据的堆栈帧上引发异常, 那么异常处理将无法继续, 并且进程将停止. 可能在堆栈的更上方有一个异常处理程序, 但是如果帧的展开数据没有, 那么就没有办法从引发异常的堆栈帧到达那里. 指定-no_compact_unwind标志意味着没有该代码的展开表, 因此不能在这些函数之间引发异常

另外如果在应用程序或库中包含普通C代码, 则可能需要指定-funwind-tables标志以包括该代码中所有函数的展开表

Thread State

本节列出了崩溃的线程的线程状态. 这是寄存器列表及其在执行暂停时的值. 阅读崩溃报告时不必了解线程状态, 但是可以使用此信息来更好地了解崩溃的情况

来自ARM64设备的崩溃报告中的线程状态

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x0000000000000000   x1: 0x000000019ff776c8   x2: 0x0000000000000000   x3: 0x000000019ff776c8
    x4: 0x0000000000000000   x5: 0x0000000000000001   x6: 0x0000000000000000   x7: 0x00000000000000d0
    x8: 0x0000000100023920   x9: 0x0000000000000000  x10: 0x000000019ff7dff0  x11: 0x0000000c0000000f
   x12: 0x000000013e63b4d0  x13: 0x000001a19ff75009  x14: 0x0000000000000000  x15: 0x0000000000000000
   x16: 0x0000000187b3f1b9  x17: 0x0000000181ed488c  x18: 0x0000000000000000  x19: 0x000000013e544780
   x20: 0x000000013fa49560  x21: 0x0000000000000001  x22: 0x000000013fc05f90  x23: 0x000000010001e069
   x24: 0x0000000000000000  x25: 0x000000019ff776c8  x26: 0xee009ec07c8c24c7  x27: 0x0000000000000020
   x28: 0x0000000000000000  fp: 0x000000016fdf29e0   lr: 0x0000000100017cf8
    sp: 0x000000016fdf2980   pc: 0x0000000100017d14 cpsr: 0x60000000

Binary Images

本节列出了终止时在进程中加载​​的二进制文件

奔溃报告二进制文件部分的内容

Binary Images:
0x100060000 - 0x100073fff TheElements arm64 <2defdbea0c873a52afa458cf14cd169e> /var/containers/Bundle/Application/888C1FA2-3666-4AE2-9E8E-62E2F787DEC1/TheElements.app/TheElements
...

二进制图像每行信息包括如下内容

  • 进程内的二进制图像的地址空间
  • 二进制文件的二进制文件名称或捆绑包标识符(仅适用于macOS), 在来自macOS的崩溃报告中, 如果二进制文件是操作系统的一部分, 则前缀是(+)
  • (仅适用于macOS)二进制文件的简短版本字符串和捆绑版本, 用破折号分隔
  • (仅限iOS)二进制文件的架构. 二进制文件可能包含多个“片段”, 每个“片段”都支持一种架构. 这些片中只有一个被加载到进程中
  • 一个唯一标识二进制文件的UUID. 该值随二进制文件的每次构建而变化, 并在符号化崩溃报告时用于定位相应的dSYM文件
  • 磁盘上二进制文件的路径

Understanding Low Memory Reports

当检测到内存不足的情况时, iOS中的虚拟内存系统将依靠应用程序的协作来释放内存. 低内存通知将作为释放内存的请求发送到所有正在运行的应用程序和进程, 以减少使用的内存量. 如果仍然存在内存压力, 系统可能会终止后台进程以减轻内存压力. 如果可以释放足够的内存, 则应用程序将继续运行. 如果没有, 应用程序将被iOS终止, 并且会生成内存不足报告并将其存储在设备上

低内存报告的格式与其他崩溃报告不同, 因为应用程序线程没有回溯. 低内存报告以与崩溃报告的标头相似的标头开头. 标头后是一组字段, 这些字段列出了系统范围的内存统计信息. 记下Page Size字段的值, 低内存报告中每个进程的内存使用情况以内存页数为单位进行报告

内存不足报告中最重要的部分是进程表. 这个表列出了生成低内存报告时所有正在运行的进程, 包括系统守护程序. 如果某个进程被终止, 则原因将列在**[reason]**列下. 出于多种原因可能会中止流程

  • [per-process-limit]
    • 该进程超出了系统施加的内存限制, 系统为所有应用程序建立驻留内存的每个进程做了限制. 超过此限制将使进程终止

注意 扩展具有更低的进程内存限制. 某些技术例如地图视图和SpriteKit, 会带来较高的基线内存成本, 并且可能不适合在扩展中使用

  • [vm-pageshortage]/[vm-thrashing]/[vm]
    • 该进程由于内存压力而终止
  • [vnode-limit]
    • 打开的文件太多

注意 当虚拟节点(vnode)快耗尽时, 系统避免杀死最前端的应用程序. 这意味着应用程序在后台时可能会终止, 即使它不是多余的虚拟节点使用量的来源

  • [highwater]
    • 系统守护程序为了使用内存而越过了高水位线
  • [jettisoned]
    • 该过程由于其他一些原因而被终止

如果没有在应用程序/扩展进程旁边看到原因, 则表明崩溃的原因不是内存不足. 查找.crash文件(在上一节中进行了介绍)以获取更多信息

当看到内存不足崩溃时, 不要担心终止时代码的哪个部分正在执行, 应该调查内存使用模式以及对内存不足警告的响应. 在应用程序中查找内存问题列出了有关如何使用泄漏工具发现内存泄漏以及如何使用分配工具的标记堆功能避免遗弃内存的详细步骤.

重要 内存泄漏和分配工具不会跟踪所有内存使用情况. 需要使用VM Tracker仪器(包含在Instruments Allocations template中)运行应用程序, 以查看总内存使用量. 默认情况下VM Tracker被禁用. 要使用VM Tracker来分析应用程序, 请点击instrument, 选中“自动快照”标志或手动按“立即快照”按钮

理解如有错误望指正 转载请说明出处