如何自建 Crash 平台

【译】Symbolicating an iOS Crash Report

通常,当您收到来自iTunes连接的崩溃报告或提供移动崩溃收集和报告的第三方服务(如Apteligent)时,该服务将负责为您提供符号化后的崩溃。如果你没有上传符号,你可能会发现自己有一个非符号化的崩溃,没有别的东西可以继续。这样的崩溃文件对于调试可能影响大量用户的问题并不是非常有用。

在这种情况下,您必须通过将回溯堆栈地址解析为符号来对崩溃报告进行符号化,以获取有关崩溃的有用信息。

幸运的是,完全有可能手动符号化崩溃报告。本文将概述您需要的信息,向您展示如何解释崩溃报告,并查看OSX和XCode上可用的一些工具来符号化崩溃。

崩溃报告中只有两个部分与符号化异常跟踪相关。第一个是 Exception Backtrace 部分。这显示了崩溃时应用程序的调用堆栈。此特定崩溃日志片段显示了我们的 ApteligentExampleApp 应用程序内部崩溃的回溯。

1
2
3
4
5
Last Exception Backtrace:
0 CoreFoundation 0x000000018708b100 0x186f80000 + 1093888
1 libobjc.A.dylib 0x00000001939441fc 0x19393c000 + 33276
2 CoreFoundation 0x000000018708b040 0x186f80000 + 1093696
3 ApteligentExampleApp 0x000000010003acc4 0x10002c000 + 60612

第二部分是崩溃报告底部的 Binary Images 部分,它为您提供了更多有用的信息。 本节列出了崩溃时加载的二进制文件。

1
2
Binary 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
2
Binary Images:
0x10002c000 - 0x1000dffff +ApteligentExampleApp arm64 <3759a98e880336108b1a799afa3c1adc>

现在我们可以收集所有需要的东西来符号化这一行。 使用此数据,您可以符号化特定崩溃中的任何堆栈地址。

验证符号文件

dSYM 文件是一个 ELF 文件,其中包含应用程序的 DWARF 调试信息(以及其他内容)。 如果在XCode中设置了“带有 dSYM 文件的 DWARF ”选项,则编译器会生成 dSYM 文件,并将其存储在您的构建中。

如果要表示特定的崩溃,则需要找到匹配的 dSYM 文件。 最好使用某种归档机制将每个版本的 dSYM 和应用程序二进制文件存储到应用商店,因为将崩溃的 dSYM UUID 与正确的 dSYM 文件进行匹配非常重要。 如果 UUID 不完全匹配,则符号化结果不可靠。 崩溃报告将告诉您符号化所需的 dSYM。 如果您不确定您的 dSYM 是否与崩溃匹配,您可以使用 dwarfdump 检查 UUID。

1
2
3
dwarfdump -u ApteligentExampleApp.dSYM
UUID: 3759A98E-8803-3610-8B1A-799AFA3C1ADC (arm64)
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
2
3
4
5
Load command 3
cmd LC_SEGMENT_64
cmdsize 1032
segname __TEXT
vmaddr 0x0000000100000000

现在我们已经编译好了所有需要的信息,可以开始符号化堆栈了.

符号化崩溃报告

使用 ATOS 符号化。

ATOS是Apple的控制台符号工具。 它将数字地址转换为二进制图像中的符号化字符串。

这是用于在OSX上快速获取符号化输出的最简单工具。 现在您已经从崩溃文件中收集了所有信息,您只需要插入地址并构建信息,您就应该获得符号化的行。 ATOS可以一次处理多个地址,因此如果您愿意,可以从堆栈跟踪中输入每个堆栈地址。

1
2
3
4
atos -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
2
3
4
5
(lldb) target create --arch arm64 ApteligentExampleApp.dSYM
Current executable set to ApteligentExampleApp.dSYM' (arm64).
(lldb) image lookup --address 0x10000ECC4
Address: ApteligentExampleApp.dSYM[0x000000010000ecc4] (ApteligentExampleApp.dSYM.__TEXT.__text + 29916)
Summary: ApteligentExampleApp.dSYM`-[ApteligentExampleClass buggyFunction] + 68 at ApteligentExampleClass.m:181

使用 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
31
dwarfdump --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
    2
    cd /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
3
dwarfdump --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 解析工具

demo 地址

如下部分代码:是分析 macho 文件的代码,得到解析后的结构。根据 loadadress(crash 中的) - vmaddress(segment vmaddr)得到 slide,再使用 stackadress - slide 可以得到文件地址,再从解析出来 symbol table string 中找到地址所在的方法。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
int parse_load_command(char *data, long *offset, struct load_command *lc, struct thin_macho *tm, uint32_t magic_number){
int result = 0;
switch (lc->cmd){
case LC_UUID:
{
struct uuid_command command = {0};
memcpy(&command, data + *offset, sizeof(struct uuid_command));
int i = 0;

FilePrint( "-LC-UUID :");
while(i < 16){
tm->uuid[i] = command.uuid[i];
FilePrint( "%u",tm->uuid[i]);
i++;
}

FilePrint( "\n");
debug("uuid cmdsize = %u, lc cmdsize = %u",command.cmdsize,lc->cmdsize);
}
break;
case LC_SEGMENT:
{
struct segment_command command = {0};
memcpy(&command, data + *offset, sizeof(struct segment_command));
if(strcmp(command.segname, "__DWARF") == 0){//暂时不处理app 代码 crash
debug(" is __DWARF skip ");
// *offset += lc->cmdsize;
// return 0;
}
if (strcmp(command.segname, "__TEXT") == 0) {
tm->vmaddr32 = command.vmaddr;
}
int sectionOff = sizeof(struct segment_command);
for (int i = 0; i<command.nsects; i++) {
struct dwarf_section_t *dwarf_s = {0};
dwarf_s = malloc(sizeof(struct dwarf_section_t));
if (dwarf_s == NULL){
printf( "Can not malloc dwarf_s");
return -1;
}
memset(dwarf_s, 0, sizeof(struct dwarf_section_t));
memcpy(&dwarf_s->mach_section, data + *offset + sectionOff, sizeof(dwarf_s->mach_section));
sectionOff += sizeof(dwarf_s->mach_section);

struct dwarf_section_t *dwarf_sec = tm->dwarf_section;
if(!dwarf_sec) {
tm->dwarf_section = dwarf_s;
} else {
while (dwarf_sec) {
if(dwarf_sec->next == NULL) {
dwarf_sec->next = dwarf_s;
break;
} else {
dwarf_sec = dwarf_sec->next;
}
}
}
tm->section_count ++;
}
}
break;
case LC_SEGMENT_64:
{
struct segment_command_64 command = {0};
memcpy(&command, data + *offset, sizeof(struct segment_command_64));
if(strcmp(command.segname, "__DWARF") == 0){//暂时不处理app 代码 crash
debug(" is __DWARF skip ");
// *offset += lc->cmdsize;
// return 0;
}
if (strcmp(command.segname, "__TEXT") == 0) {
tm->vmaddr64 = command.vmaddr;
}

int sectionOff = sizeof(struct segment_command_64);
for (int i = 0; i<command.nsects; i++) {
struct dwarf_section_64_t *dwarf_s = {0};
dwarf_s = malloc(sizeof(struct dwarf_section_64_t));
if(dwarf_s == NULL) {
printf("no memory malloc dwarf_s \n");
return -1;
}
memset(dwarf_s, 0, sizeof(struct dwarf_section_64_t));
memcpy(&dwarf_s->mach_section, data + *offset + sectionOff, sizeof(dwarf_s->mach_section));
sectionOff += sizeof(dwarf_s->mach_section);
struct dwarf_section_64_t *dwarf_sec = tm->dwarf_section_64;
if(!dwarf_sec) {
tm->dwarf_section_64 = dwarf_s;
} else {
while (dwarf_sec) {
if(dwarf_sec->next == NULL) {
dwarf_sec->next = dwarf_s;
break;
} else {
dwarf_sec = dwarf_sec->next;
}
}
}
tm->section_count ++;
}
}
break;
case LC_SYMTAB:
{
struct symtab_command command = {0};
memcpy(&command, data + *offset, sizeof(struct symtab_command));
tm->nsyms = command.nsyms;
tm->strsize = command.strsize;
tm->stroff = command.stroff;
tm->strings = data + command.stroff;
if(magic_number == MH_MAGIC_64){
tm->symbollist = malloc(command.nsyms * sizeof(struct symbol_t));
if(tm->symbollist == NULL) {
printf( "no memory symbollist \n");
return -1;
}
memset(tm->symbollist, '\0', command.nsyms * sizeof(struct symbol_t));
struct symbol_t *current = tm->symbollist;
int i = 0;
uint32_t listOffset = 0;
for (; i < command.nsyms; i++) {
memcpy(&current->sym.sym64, data + command.symoff + listOffset, sizeof(struct nlist_64));
uint32_t n_strx = current->sym.sym64.n_un.n_strx;
if(n_strx < 0){
current->name = "";
}else if(n_strx > command.strsize){
current->name = "bad string index";
}else {
current->name = tm->strings + n_strx;
}

listOffset += sizeof(struct nlist_64);
current++;
}
} else {
uint32_t listOffset = 0;
tm->symbollist = malloc(command.nsyms * sizeof(struct symbol_t));
if(tm->symbollist == NULL){
printf( "no memory symbollist \n");
return -1;

}
memset(tm->symbollist, '\0', command.nsyms * sizeof(struct symbol_t));
struct symbol_t *current = tm->symbollist;
int i = 0;
for (i = 0; i < command.nsyms; i++) {
memcpy(&current->sym.sym32, data + command.symoff + listOffset, sizeof(struct nlist));
if(current->sym.sym32.n_un.n_strx < 0){
current->name = "";
}else if(current->sym.sym32.n_un.n_strx > command.strsize){
current->name = "bad string index";
}
else {
current->name = tm->strings + current->sym.sym32.n_un.n_strx;
}
listOffset += sizeof(struct nlist);
current++;
}
}

result = 0;

}
break;
default:
break;
}
*offset += lc->cmdsize;
return result;
}
//parse
int parse_macho(struct thin_macho *tm) {
char *macho_str = tm->data;
int num_load_cmds = 0;
long offset = 0;
size_t header_size = 0;
uint32_t magic_number = 0;
memcpy(&magic_number, macho_str, sizeof(uint32_t));
switch (magic_number) {
case MH_MAGIC:
{
struct mach_header mh = {0};
header_size = sizeof(struct mach_header);
memcpy(&mh, macho_str + offset, header_size);
num_load_cmds = mh.ncmds;
tm->cputype = mh.cputype;
tm->cpusubtype = mh.cpusubtype;
}
break;
case MH_MAGIC_64:
{
struct mach_header_64 mh64 = {0};
header_size = sizeof(struct mach_header_64);
memcpy(&mh64, macho_str + offset, header_size);
num_load_cmds = mh64.ncmds;
tm->cputype = mh64.cputype;
if(tm->cputype == CPU_TYPE_ARM64 && tm->cpusubtype == CPU_SUBTYPE_ARM64_ALL) {
tm->is_64 = 1;
}
tm->cpusubtype = mh64.cpusubtype;
}
break;
case MH_CIGAM:
printf("TODO: MH_CIGAM\n");
break;
case MH_CIGAM_64:
printf("TODO: MH_CIGAM_64\n");
break;
case FAT_MAGIC:
case FAT_CIGAM:
printf( "fat in fat?\n");
break;
default:
debug("not found magic_num = %u",magic_number);
printf( "magic_number invalid.");
break;
}
offset += header_size;
struct load_command lc = {0};
int i = 0;

while (i < num_load_cmds) {
memcpy(&lc, macho_str + offset, sizeof(struct load_command));
int lc_result = parse_load_command(macho_str, &offset, &lc, tm, magic_number);
if(lc_result == -1) {
return -1;
}
i++;
}
// sortSymbolAddr(tm);print part
InsertSort(tm->symbollist,tm->nsyms, tm->is_64);
return 0;
}


int parse_thin(FILE *fp,uint32_t magic_num, struct target_file *tf) {
tf->numofarchs = 1;
tf->thin_machos = malloc(1 * sizeof(struct thin_macho*));
if (tf->thin_machos == NULL){
return -1;
}
memset(tf->thin_machos, '\0', 1 * sizeof(struct thin_macho*));
fseek(fp, 0L, SEEK_END);
long int size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
tf->thin_machos[0] = malloc(sizeof(struct thin_macho));
if(tf->thin_machos[0] == NULL) {
return -1;
}
memset(tf->thin_machos[0], '\0', sizeof(struct thin_macho));
tf->thin_machos[0]->data = malloc(size);
if(tf->thin_machos[0]->data == NULL){
return -1;
}
memset(tf->thin_machos[0]->data, '\0', size);
long numofbytes = 0;
numofbytes = fread(tf->thin_machos[0]->data, sizeof(char), size, fp);
assert(numofbytes == size);
if(numofbytes == size){
int result = parse_macho(tf->thin_machos[0]);
if(result == -1) {
return -1;
}
return 0;
}else{
return -1;
}
}

int parse_fat(FILE *fp,uint32_t magic_num, struct target_file *tf) {
long rc = 0;
struct fat_header fh = {0};
uint32_t nfat_arch = 0;
debug("sizeof(struct fat_header) is = %lu",sizeof(struct fat_header));
if((rc = fread(&fh, sizeof(struct fat_header), 1, fp)) != 0) {
if(magic_num == FAT_CIGAM){
FilePrint("-magic_num : FAT_CIGAM\n");
uint32_endian_convert(&fh.nfat_arch);
} else {
FilePrint("-magic_num : FAT_MAGIC\n");
}
nfat_arch = fh.nfat_arch;
FilePrint("-arch count : %u \n",nfat_arch);
}
tf->numofarchs = nfat_arch;
tf->thin_machos = malloc(sizeof(struct thin_macho *));
if(tf->thin_machos == NULL) {
printf( "parse_fat Can not malloc thin_machos*");
return -1;
}
memset(tf->thin_machos, '\0', nfat_arch * sizeof(struct thin_macho *));
uint32_t i = 0;
struct fat_arch fa = {0};
while (i < nfat_arch) {
tf->thin_machos[i] = malloc(sizeof(struct thin_macho));
if(tf->thin_machos[i] == NULL) {
printf( "parse_fat Can not malloc thin_machos[%d]",i);
return -1;
}
memset(tf->thin_machos[i], '\0', sizeof(struct thin_macho));
debug("sizeof(struct fat_arch) is = %lu \n sizeof(char) = %lu",sizeof(struct fat_arch),sizeof(char));
if((rc = fread(&fa, sizeof(struct fat_arch), 1, fp)) == 1) {
if(magic_num == FAT_CIGAM){
integer_t_endian_convert(&fa.cputype);
integer_t_endian_convert(&fa.cpusubtype);
uint32_endian_convert(&fa.offset);
uint32_endian_convert(&fa.size);
uint32_endian_convert(&fa.align);
}
tf->thin_machos[i]->data = malloc(fa.size);
if (tf->thin_machos[i]->data == NULL){
printf( "Can not malloc data");
return -1;
}
memset(tf->thin_machos[i]->data, '\0', fa.size);
long cur_pos = ftell(fp);
fseek(fp, fa.offset, SEEK_SET);
long numofbytes = 0;
numofbytes = fread(tf->thin_machos[i]->data, sizeof(char), fa.size, fp);
if(numofbytes != fa.size) {
printf( "parse_fat read macho data error. i = %u \n",i);
return -1;
}
fseek(fp, cur_pos, SEEK_SET);//获取完 data。回到原处
} else {
printf( "read fat arch error\n");
return -1;
}
int result = parse_macho(tf->thin_machos[i]);
if(result == -1){
return -1;
}
i++;
}
return 0;
}

struct target_file *parse_file(const char *filename){
FILE *fp = fopen(filename, "rb");
if (fp == NULL){
printf( "Can not open file %s for read.\n", filename);
return NULL;
}
struct target_file *tf = malloc(sizeof(struct target_file));
if (tf == NULL){
printf( "Can not malloc target_file");
return NULL;
}
//申请内存
memset(tf, '\0', sizeof(struct target_file));
long rc = 0;
uint32_t magic_num = 0;
int parse_result = -1;
//前四字节为魔数
if((rc = fread(&magic_num, sizeof(uint32_t), 1, fp)) != 0)
{
fseek(fp, 0L, SEEK_SET);
switch (magic_num) {
case MH_MAGIC:
debug("MH_MAGIC");
parse_result = parse_thin(fp,MH_MAGIC,tf);
break;
case MH_MAGIC_64:
debug("MH_MAGIC_64");
parse_result = parse_thin(fp,MH_MAGIC_64,tf);
break;
case MH_CIGAM_64://macho 和 平台编码不一致
debug("MH_CIGAM_64");
printf("MH_CIGAM_64: %x\n", MH_CIGAM_64);
break;
case FAT_MAGIC://大端序
debug("FAT_MAGIC");
parse_result = parse_fat(fp,FAT_MAGIC,tf);
break;
case FAT_CIGAM:
debug("FAT_CIGAM");
parse_result = parse_fat(fp,FAT_CIGAM,tf);
break;
default:
debug("not found magic_num = %u",magic_num);
printf( "magic_number invalid.");
break;
}

}
fclose(fp);
if(parse_result == -1){
return NULL;
}
return tf;
}

int symbolicate(const char* arch, const char *executable, const char *loadAddr, char *addresses[]){
char *filename = strrchr(executable, '/');
if(filename == NULL){
filename = (char *)executable;
} else {
filename = filename + 1;
}
//开始读取二进制文件
debug("about to analysis file.");
struct target_file *tf = parse_file(executable);
int result = findAddressInMacho(tf, arch,loadAddr, addresses);
free_target_file(tf);
return result;
}

请自行替换文件地址再运行。可以从 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 额外信息添加