【译】Symbolicating an iOS Crash Report
通常,当您收到来自iTunes连接的崩溃报告或提供移动崩溃收集和报告的第三方服务(如Apteligent)时,该服务将负责为您提供符号化后的崩溃。如果你没有上传符号,你可能会发现自己有一个非符号化的崩溃,没有别的东西可以继续。这样的崩溃文件对于调试可能影响大量用户的问题并不是非常有用。
在这种情况下,您必须通过将回溯堆栈地址解析为符号来对崩溃报告进行符号化,以获取有关崩溃的有用信息。
幸运的是,完全有可能手动符号化崩溃报告。本文将概述您需要的信息,向您展示如何解释崩溃报告,并查看OSX和XCode上可用的一些工具来符号化崩溃。
崩溃报告中只有两个部分与符号化异常跟踪相关。第一个是 Exception Backtrace 部分。这显示了崩溃时应用程序的调用堆栈。此特定崩溃日志片段显示了我们的 ApteligentExampleApp 应用程序内部崩溃的回溯。
1 | Last Exception Backtrace: |
第二部分是崩溃报告底部的 Binary Images 部分,它为您提供了更多有用的信息。 本节列出了崩溃时加载的二进制文件。1
2Binary Images:
0x10002c000 - 0x1000dffff ApteligentExampleApp arm64 <3759a98e880336108b1a799afa3c1adc> /var/mobile/Applications/46FB38F8-0E69-459F-B96A-CEEA21B77D55/ApteligentExampleApp.app/ApteligentExampleApp
不幸的是,这个崩溃报告是未符号化的。 我们可以看到我们的应用程序崩溃的点(异常回溯中的第3行),但它缺少有助于开发人员调试问题的函数名称,函数参数和行号等详细信息。 为了将崩溃报告中的各种地址转换为可读的地址,我们需要将这些地址映射到符号。 为此,我们需要调试符号(dSYM文件)和符号化工具以及从崩溃报告本身收集的信息。
收集所需信息
这是一个需要符号化的示例行。 我们知道这是程序失败的重点,但地址本身并没有告诉我们任何有用的东西。1
3 ApteligentExampleApp 0x000000010003acc4 0x10002c000 + 60612
异常回溯中的这一行为您提供堆栈地址,应用程序虚拟内存中的二进制加载地址以及偏移量。 最后一个值只是堆栈地址和加载地址之间的差异。
更靠近崩溃报告的底部,您将看到 Binary Images 部分。 通常,崩溃的应用程序将位于列表的顶部。 此条目将为您提供加载地址(再次),此崩溃的dSYM UUID以及应用程序崩溃的系统体系结构。
1 | Binary Images: |
现在我们可以收集所有需要的东西来符号化这一行。 使用此数据,您可以符号化特定崩溃中的任何堆栈地址。
验证符号文件
dSYM 文件是一个 ELF 文件,其中包含应用程序的 DWARF 调试信息(以及其他内容)。 如果在XCode中设置了“带有 dSYM 文件的 DWARF ”选项,则编译器会生成 dSYM 文件,并将其存储在您的构建中。
如果要表示特定的崩溃,则需要找到匹配的 dSYM 文件。 最好使用某种归档机制将每个版本的 dSYM 和应用程序二进制文件存储到应用商店,因为将崩溃的 dSYM UUID 与正确的 dSYM 文件进行匹配非常重要。 如果 UUID 不完全匹配,则符号化结果不可靠。 崩溃报告将告诉您符号化所需的 dSYM。 如果您不确定您的 dSYM 是否与崩溃匹配,您可以使用 dwarfdump 检查 UUID。
1 | dwarfdump -u ApteligentExampleApp.dSYM |
获取偏移量
对于某些工具,您可能需要提供偏移量而不是加载地址。 如果给它一个加载地址(0x10002c000)和一个堆栈地址(0x10003acc4),ATOS 将为你处理偏移计算。 但是,dwarfdump 和 lldb 采用文件地址(0x10000ECC4),因此您需要考虑为这些工具设置偏移量。
从 dSYM 获取偏移量的一种方法是使用“otool”,它可以与 OSX 上的 XCode 开发人员工具一起使用。
您需要查找 LC_SEGMENT_64(arm64)或 LC_SEGMENT(armv7,armv7s)段和“vmaddr”条目。 对于iOS,对于32位通常为0x4000,对于64位架构通常为0x100000000,但这可能会发生变化。1
otool -l ApteligentExampleApp.dSYM > ApteligentExampleApp.otool.output
1 | Load command 3 |
现在我们已经编译好了所有需要的信息,可以开始符号化堆栈了.
符号化崩溃报告
使用 ATOS 符号化。
ATOS是Apple的控制台符号工具。 它将数字地址转换为二进制图像中的符号化字符串。
这是用于在OSX上快速获取符号化输出的最简单工具。 现在您已经从崩溃文件中收集了所有信息,您只需要插入地址并构建信息,您就应该获得符号化的行。 ATOS可以一次处理多个地址,因此如果您愿意,可以从堆栈跟踪中输入每个堆栈地址。1
2
3
4atos -arch <architecture> -o <binary filename> -l <load address> <stack address 1> <stack address 2> ...
对应
atos -arch arm64 -o ApteligentExampleApp.dSYM -l 0x10002c000 0x000000010003acc4
-[ApteligentExampleClass buggyFunction] (in ApteligentExampleApp.dSYM) (ApteligentExampleClass.m:181)
使用 lldb 符号化
lldb 是 OSX 上 XCode 中的默认调试器,可用于表示崩溃中的行。 lldb 再次包含在 OSX 的XCode 中,还有适用于 Linux,FreeBSD 和 Windows 的端口。 您可以从 llvm 项目站点 获取它。
如果您想了解有关lldb可以执行的操作的更多信息,可以点击此处阅读。
1 | (lldb) target create --arch arm64 ApteligentExampleApp.dSYM |
使用 Dwarfdump 符号化
Dwarfdump 是一个从 EL F对象转储 DWARF 调试信息的实用程序 - 对于 iOS,这通常是一个dSYM 文件。 Dwarfdump 是一个非常冗长的工具,通常用于调试 DWARF 调试信息生成器(如XCode 中的编译器)或验证符号化工具(如 ATOS)的输出。为了简单地表示崩溃转储,使用此工具是完全矫枉过正的,但有时您可能想要进一步深入了解DWARF调试信息。
Dwarfdump 使用文件地址(File Address 0x10000ECC4)来定位匹配的子程序“Debug Information Entry”。此条目具有 DWARF 属性,可为您提供有关子程序的信息。对于 Objective C,此子程序条目通常表示类中的函数,您可以获取行号,文件名以及类/函数名称等信息。
Dwarfdump 的一个版本随 XCode 开发人员工具一起提供,但您也可以从 libdwarf 项目页面 获取它.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31dwarfdump --lookup 0x10000ECC4 --arch arm64 ApteligentExampleApp.dSYM
----------------------------------------------------------------------
File: ApteligentExampleApp.dSYM (arm64)
----------------------------------------------------------------------
Looking up address: 0x000000010000ecc4 in .debug_info... found!
0x000516d2: Compile Unit: length = 0x00000e9f version = 0x0002 abbr_offset = 0x00000000 addr_size = 0x08 (next CU at 0x00052575)
0x000516dd: TAG_compile_unit [99] *
AT_producer( "Apple LLVM version 7.0.2 (clang-700.1.81)" )
AT_language( DW_LANG_ObjC )
AT_name( "/Users/kcrawford/src/apteligent-example-apps/ios/ApteligentExampleApp/ApteligentExampleClass.m" )
AT_stmt_list( 0x00008e5a )
AT_comp_dir( "/Users/kcrawford/src/apteligent-example-apps/ios" )
AT_APPLE_major_runtime_vers( 0x02 )
AT_low_pc( 0x000000010000d704 )
AT_high_pc( 0x000000010000f234 )
0x00051cbd: TAG_subprogram [116] *
AT_low_pc( 0x000000010000ec80 )
AT_high_pc( 0x000000010000ecd0 )
AT_frame_base( reg29 )
AT_object_pointer( {0x00051cdb} )
AT_name( "-[ApteligentExampleClass buggyFunction]" )
AT_decl_file( "/Users/kcrawford/src/apteligent-example-apps/ios/ApteligentExampleApp/ApteligentExampleClass.m" )
AT_decl_line( 179 )
AT_prototyped( 0x01 )
Line table dir : '/Users/kcrawford/src/apteligent-example-apps/ios/ApteligentExampleApp'
Line table file: 'ApteligentExampleClass.m' line 181, column 1 with start address 0x000000010000ecc4
Looking up address: 0x000000010000ecc4 in .debug_frame... not found.
注意:Xcode 10.2 之后不能指定架构,否则输出为空信息。如果发现输出为空,使用
dwarfdump –lookup 0x10000ECC4 ApteligentExampleApp.dSYM 。然后再从输出中找到你要的架构下的信息。
symbolicatecrash
到目前为止,我们已经研究了在崩溃中表示特定地址的工具,或者至多是 ATOS 中的一系列地址。 为了简化此过程,Apple 发布了一个带有 XCode 的脚本,可以完整地加速崩溃报告的符号化过程。 如果您有 dSYM,app 二进制文件和崩溃报告,这可能是最简单的符号化方法。 您不必担心任何地址 - 此脚本将解析整个故障转储文件并使用 ATOS 将所有地址解析为符号。
定位系统中的 “symbolicatecrash”
1
2cd /Applications/Xcode.app
find . -name symbolicatecrash如果不存在则导出 DEVELOPER_DIR 环境变量
1
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
拷贝 .app二进制文件、crash 报告、.dSYM 文件到临时文件(ex. ~/tmp)
- 执行如下脚本
1
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash -v ApteligentExampleApp.crash ApteligentExampleApp.app.dSYM/
如果一切顺利,脚本应该符号化整个崩溃文件并将结果输出到终端窗口。 此脚本不会执行任何您无法使用 ATOS 或其他工具手动执行的操作,但它可以更快地为您提供所需的内容。
开始实现自己的符号化平台
使用脚本批量处理,使用dwarfdump符号化自己App的 崩溃
下载测试脚本后 cd 到 test 目录执行命令:1
python sym.py BICrashAnalyzeDemo.crash BICrashAnalyzeDemo.app.dSYM
如果使用 dwarfdump 或者 lldb 命令,计算出来的File Address 地址为 0x0000000100006734 (stack address - load address + Slide Value(0x0000000100000000))。
Slide Value(32位虚拟地址): 0x00004000
Slide Value(64位虚拟地址): 0x0000000100000000 test中使用这个1
2
3dwarfdump --lookup 0x0000000100006734 --arch arm64 BICrashAnalyzeDemo.app.dSYM/Contents/Resources/DWARF/BICrashAnalyzeDemo
--arch arm64可省略
cd 到 test 目录执行如下命令,导出foo.crash中可看到符号化的非系统崩溃1
python analysis.py BICrashAnalyzeDemo.crash
使用atosl符号化系统的崩溃
系统崩溃需要找出崩溃设备所对应的系统库文件。
参考文章:
但是 dwarfdump 是基于 debug_info和 debug_frame解析,而系统库是没有这些的,可以使用 atosl,不过 facebook 的atosl 有些 bug,可能都安装不了,我自己实现了一个。
实现自己的 macho 解析工具
如下部分代码:是分析 macho 文件的代码,得到解析后的结构。根据 loadadress(crash 中的) - vmaddress(segment vmaddr)得到 slide,再使用 stackadress - slide 可以得到文件地址,再从解析出来 symbol table string 中找到地址所在的方法。
1 | int parse_load_command(char *data, long *offset, struct load_command *lc, struct thin_macho *tm, uint32_t magic_number){ |
请自行替换文件地址再运行。可以从 result.txt 中看到解析后的结构。可以看到和 machoview 解析出来的结构一致。
接下来:
1:对 symbol table string 列表的 value 排序,symbol table string中包含方法名。
2:使用上面提到的文件地址去排好序的 value 中查找,查找到小于 value 的 index,则崩溃的方法名存在于 index-1 项。
可以看到,找到的系统崩溃和 symbolicatecrash 符号化出来的 output.crash 中一致。
后续:自建crash 分析平台
自建 crash 分析平台的路还有很长,目前我也刚走到第三步。分为:
OS 平台:Ubuntu
1:安装 Ubuntu 虚拟机18.04LTS版本,只有这个版本才支持 dwarfdump。apt-get 安装 llvm-dwarfdump
使用命令:1
llvm-dwarfdump -lookup 0x0000000100006734 BICrashAnalyzeDemo.app.dSYM/Contents/Resources/DWARF/BICrashAnalyzeDemo
结果
可以看到和 macox 上 dwarfdump 结果差不多,上面的脚本改改能继续用来符号化 AppName 的崩溃。
2:安装自己实现的 atosl,符号化系统崩溃。
3:脚本批量符号化 crash 文件。
4:crash 分类
5:crash 额外信息添加