iOS Crash 符号化解析

| /

在公司的时候,通常 Crash 平台都做的很好,帮我们做好了符号化解析,一些三方平台也会提供类似的能力。

但我们还是会遇到需要自己做符号化解析的场景,比如某些线下包没有解析成功,又比如离开了公司提供的环境的时候等等。

所以我们还是有必要了解如何做符号化解析。

准备工作

先准备好原始崩溃栈和符号表文件 xxx.dSYM

另:有关对应关系,可以通过崩溃日志的Binary Images看到架构和UUID,检查UUIDdSYM是否一致。

我们通常看到的崩溃栈的每行如下:

1
2
11 XXXAPP 0x00000001001e7ff4 0x100078000 + 1507316
12 XXXAPP 0x00000001001e7ff4 0x100078000 + 1507316

先明确下后3列的含义,分别是 运行时地址运行时基地址崩溃处距离进程起始地址的偏移量

3者的关系是 运行时地址 = 运行时基地址+ 偏移量

留意前2个是16进制,偏移量是10进制,做运算时要注意,如上文中的:
0x00000001001e7ff4 = 0x100078000 + 1507316(0x16FFF4)

虚拟内存偏移量,拿到符号表TEXT段的起始地址,再加上偏移量,就能得到符号表中的堆栈地址:
符号表堆栈地址 = 符号表基地址 + 偏移量

符号表TEXT段起始地址获取方式:

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
$ otool -l xxx.dSYM/Contents/Resources/DWARF/xxx

...
Load command 3
cmd LC_SEGMENT
cmdsize 736
segname __TEXT
vmaddr 0x00004000
vmsize 0x00c3c000
fileoff 0
filesize 0
maxprot 0x00000005
initprot 0x00000005
nsects 10
flags 0x0
...
Load command 3
cmd LC_SEGMENT_64
cmdsize 952
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000d94000
fileoff 0
filesize 0
maxprot 0x00000005
initprot 0x00000005
nsects 11
flags 0x0
...

留意,这里也区分了架构,留意崩溃所发生的架构:armv7 对应 LC_SEGMENTarm64 对应 LC_SEGMENT_64。而 vmaddr 部分就是我们要的符号表基地址。
所以我们有时候在网上看到教程里提到让我们用 0x0000000100000000 做计算,原因就在这里。

单行解析

常见的有两种方案:

dwarfdump

1
2
$ export dSYMPath="$(find ~/Library/Developer/Xcode -iname '*.dSYM' -print0 | xargs -0 dwarfdump -u | grep 62304F1D-278A-489D-B66E-77199CDDDD4B | sed -E 's/^[^/]+//' | head -n 1)"; 
$ dwarfdump --arch=armv7 --lookup 0x1001e7ff4 "$dSYMPath"

这段代码其实是很多三方平台里会自动提示给你的,简单来说就是使用 dwarfdump 命令。其中 grep 的就是 UUID,第1行目的是得到 dSYMP 的路径。

--arch 对应架构,--lookup 后面的就是我们要计算的对应符号表的位置,即:符号表堆栈地址 = 符号表基地址 + 偏移量
假如是 arm64,则这个计算就是:0x0000000100000000 + 1507316 = 0x10016FFF4

atos

还有一种方案是使用 atos 命令,而这个命令提供了2种方式。

第1种和 dwarfdump 类似,需要预先计算出符号表偏移量,计算方式同上。

1
$ xcrun atos -o xxx.dSYM/Contents/Resources/DWARF/xxx -arch armv7 0x98040

第2种则不用我们自己自己计算。

1
$ xcrun atos -o xxx.dSYM/Contents/Resources/DWARF/xxx -l 0x100078000 0x00000001001e7ff4 -arch arm64

其中 -l 指定的是代码 运行时基地址,即崩溃栈的 + 前面的地址,后面跟着的是崩溃发生的 运行时地址,即崩溃栈显示的第1个地址。

整体解析

symbolicatecrash

事实上,单行解析效率不高,很多时候,可能更多的是整个 crash 文件解析,我们可以使用 Xcode 提供的 symbolicatecrash 工具。

1
2
$ export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
$ symbolicatecrash ./*.crash ./*.app.dSYM>symbol.crash

symbolicatecrash 命令的2个入参,分别是 crash 文件和 dSYM

留意:

  • symbolicatecrash 需要完整的路径,/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash

  • 第1行 export DEVELOPER_DIR,一般情况下是需要的,否则会出现如下报错:

    1
    Error: "DEVELOPER_DIR" is not defined at ./symbolicatecrash line xx

说明

本文仅仅介绍了工具使用相关的技巧,有关符号化更深层次的含义、一些系统库一般不容易符号化的场景如何处理、SwiftC++的场景又会遇到哪些问题,本文没有详细描述。