背景
在App上线之后,我们通常使用一些工具比如bugly
来收集线上的crash
,收集上的函数调用栈都是一些函数地址,我们根本无法是哪个文件的下的哪段代码引起的crash
,如果想要知道是哪个函数引起的crash
,我们需要上传编译之后产生的dSYM
文件,为什么需要dSYM
文件呢?
dSYM概念
什么是DWARF
DWARF
是一种被众多编译器和调试器使用的用于支持源码级别
调试的调试文件格式
,该格式是一个固定的数据格式
什么是dSYM
dSYM
就是按照DWARF
格式保存调试信息的文件
,也就是说,dSYM
是一个文件。
探索dSYM
带调试符号的.o
1, 我们首先使用 clang
将 test.m
编译为.o
文件,test.m
的内容如下所示
clang -g -c test.m -o test.o
复制代码
2,我们使用 objcdump
查看 test.o
的header
信息
objdump --macho --private-headers test.o
Section
sectname __debug_str
segname __DWARF
addr 0x0000000000000158
size 0x00000000000000a9
offset 1616
align 2^0 (1)
reloff 0
nreloc 0
type S_REGULAR
attributes DEBUG
reserved1 0
reserved2 0
复制代码
我们可以看到,在编译的时候,调试信息存在了名为__DWARF
的段中。
带调试符号的可执行文件
1,我们继续使用clang
,先生成带调试符号的可执行文件,不再使用-c
参数
clang -g test.m -o test
复制代码
2,查看其段信息
bel@beldeMacBook-Pro dSYM % objdump --macho --private-headers test
test:
Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
MH_MAGIC_64 X86_64 ALL 0x00 EXECUTE 16 1048 NOUNDEFS DYLDLINK TWOLEVEL PIE
Load command 0
cmd LC_SEGMENT_64
cmdsize 72
segname __PAGEZERO
vmaddr 0x0000000000000000
vmsize 0x0000000100000000
fileoff 0
filesize 0
maxprot ---
initprot ---
nsects 0
flags (none)
Load command 1
cmd LC_SEGMENT_64
cmdsize 232
segname __TEXT
...
Section
sectname __text
segname __TEXT
...
Section
sectname __unwind_info
segname __TEXT
...
Load command 2
cmd LC_SEGMENT_64
cmdsize 152
segname __DATA_CONST
...
Section
sectname __objc_imageinfo
segname __DATA_CONST
...
Load command 3
cmd LC_SEGMENT_64
cmdsize 152
segname __DATA
...
Section
sectname __data
segname __DATA
...
Load command 4
cmd LC_SEGMENT_64
cmdsize 72
segname __LINKEDIT
...
Load command 5
cmd LC_DYLD_INFO_ONLY
cmdsize 48
rebase_off 0
rebase_size 0
bind_off 0
bind_size 0
weak_bind_off 0
weak_bind_size 0
lazy_bind_off 0
lazy_bind_size 0
export_off 49152
export_size 80
Load command 6
cmd LC_SYMTAB
cmdsize 24
symoff 49240
nsyms 23
stroff 49608
strsize 192
Load command 7
cmd LC_DYSYMTAB
cmdsize 80
....
Load command 8
cmd LC_LOAD_DYLINKER
cmdsize 32
name /usr/lib/dyld (offset 12)
Load command 9
cmd LC_UUID
cmdsize 24
uuid 2890FF4F-897E-30B1-A90C-48A58B0B0146
Load command 10
cmd LC_BUILD_VERSION
cmdsize 32
platform macos
sdk 11.3
minos 11.0
ntools 1
tool ld
version 650.9
Load command 11
cmd LC_SOURCE_VERSION
cmdsize 16
version 0.0
Load command 12
cmd LC_MAIN
cmdsize 24
entryoff 16256
stacksize 0
Load command 13
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
....
复制代码
此时,我们在Mach O
文件中没有找到__DWARF
相关的信息,
3,查看其符号表
nm -pa test
bel@beldeMacBook-Pro dSYM % nm -pa test
0000000000000000 - 00 0000 SO /Users/bel/Desktop/dSYM/
0000000000000000 - 00 0000 SO test.m
0000000060e97f85 - 03 0001 OSO /var/folders/d2/_hhc47fd0cvd4b7n2hrcpxt80000gn/T/test-b74f6a.o
0000000100003f60 - 01 0000 BNSYM
0000000100003f60 - 01 0000 FUN _test
0000000000000010 - 00 0000 FUN
0000000000000010 - 01 0000 ENSYM
0000000100003f70 - 01 0000 BNSYM
0000000100003f70 - 01 0000 FUN _test_1
0000000000000010 - 00 0000 FUN
0000000000000010 - 01 0000 ENSYM
0000000100003f80 - 01 0000 BNSYM
0000000100003f80 - 01 0000 FUN _main
0000000000000035 - 00 0000 FUN
0000000000000035 - 01 0000 ENSYM
0000000000000000 - 00 0000 GSYM _global
0000000000000000 - 01 0000 SO
0000000100000000 T __mh_execute_header
0000000100008000 D _global
0000000100003f80 T _main
0000000100003f60 T _test
0000000100003f70 T _test_1
U dyld_stub_binder
复制代码
在其符号表里面,我们看到我们使用的_test
,_test_1
等符号,
小结
编译器在编译的时候,会将调试信息存放到__DWARF段
中,在进行链接的时候,将__DWARF
段删除,并将符号信息存放到__DWARF
段中。
生成dSYM文件
1,我们使用编译的 -g1
参数,生成dSYM
文件
clang -g1 test.m -o test
复制代码
2,使用 dwarfdump
命令,查看dSYM
文件
bel@beldeMacBook-Pro dSYM % dwarfdump test.dSYM
test.dSYM/Contents/Resources/DWARF/test: file format Mach-O 64-bit x86-64
.debug_info contents:
0x00000000: Compile Unit: length = 0x00000063, format = DWARF32, version = 0x0004, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000067)
0x0000000b: DW_TAG_compile_unit
DW_AT_producer ("Apple clang version 12.0.5 (clang-1205.0.22.9)")
DW_AT_language (DW_LANG_ObjC)
DW_AT_name ("test.m")
DW_AT_LLVM_sysroot ("/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
DW_AT_APPLE_sdk ("MacOSX.sdk")
DW_AT_stmt_list (0x00000000)
DW_AT_comp_dir ("/Users/bel/Desktop/dSYM")
DW_AT_APPLE_major_runtime_vers (0x02)
DW_AT_low_pc (0x0000000100003f60)
DW_AT_high_pc (0x0000000100003fb5)
0x00000033: DW_TAG_subprogram
DW_AT_low_pc (0x0000000100003f60)
DW_AT_high_pc (0x0000000100003f66)
DW_AT_name ("test")
0x00000044: DW_TAG_subprogram
DW_AT_low_pc (0x0000000100003f70)
DW_AT_high_pc (0x0000000100003f76)
DW_AT_name ("test_1")
0x00000055: DW_TAG_subprogram
DW_AT_low_pc (0x0000000100003f80)
DW_AT_high_pc (0x0000000100003fb5)
DW_AT_name ("main")
0x00000066: NULL
复制代码
我们可以看到,在dSYM
文件中,按照一定的格式来保存调试符号,里面包含了虚拟内存地址值
和符号名称
。
如何把虚拟内存地址还原成符号
含有调试符号的调用栈
我们打开Xcode
,新建一个工程,让App
在启动3秒后,就crash
,代码如下所示:
其crsah信息如下:
crash
发生在-[ViewController warfTest]
方法里面。
不含有调试符号的调用栈
在默认情况下,我们在Release
模式下进行App打包
,Xcode
会使用strip
命令,将本地符号
脱掉,在导出ipa
包的时候生成dSYM
文件,在Debug
模式下,我们需要将 Deployment Postprocessing
设置为Yes
。在每次编译完之后,就会执行strip
进行脱符号
此时其crash
日志为:
从日志里面,我们就不能定位是哪个函数引起的crash了。
编译运行后,生成dSYM
此时,在Debug
模式下,编译只完成了使用 strip
去脱符号,我们将 PROJECT
-> Build Settings
-> Build Options
-> Debug information Format
设置为 DWARF with dSYM File
,并将Deployment Postprocessing
设置为NO
(不进行脱符号), 这样,我们就可以得到dSYM
文件
根据虚拟地址和dSYM还原调试符号
此时我们重新运行工程,然后就crash,
在序号为3的那一行,内存地址值为 0x000000010a107eb4
,在运行过程中,其等于
ASLR + 函数的虚拟地址值
。
首先我们要获取这次运行的ASLR
值:
(lldb) image list
[ 0] 3B857210-92BA-359F-A96E-856AC9E5CB8F 0x000000010a107000 /Users/bel/Library/Developer/Xcode/DerivedData/dSYMProject-ccrqorgatcanaacgnyihzhjcjcwi/Build/Products/Debug-iphonesimulator/dSYMProject.app/dSYMProject
/Users/bel/Library/Developer/Xcode/DerivedData/dSYMProject-ccrqorgatcanaacgnyihzhjcjcwi/Build/Products/Debug-iphonesimulator/dSYMProject.app.dSYM/Contents/Resources/DWARF/dSYMProject
复制代码
可以看出 ASLR = 0x000000010a107000 - 0x0000000100000000(vmsize)
,那就可以得出该函数的虚拟地址值
为 0x000000010a107eb4 - 0x00000000a107000 = 0x100000EB4
在 dSYM
文件中查找该地址对应的符号:
bel@beldeMacBook-Pro ~ % dwarfdump --lookup 0x100000EB4 /Users/bel/Library/Developer/Xcode/DerivedData/dSYMProject-ccrqorgatcanaacgnyihzhjcjcwi/Build/Products/Debug-iphonesimulator/dSYMProject.app.dSYM
/Users/bel/Library/Developer/Xcode/DerivedData/dSYMProject-ccrqorgatcanaacgnyihzhjcjcwi/Build/Products/Debug-iphonesimulator/dSYMProject.app.dSYM/Contents/Resources/DWARF/dSYMProject: file format Mach-O 64-bit x86-64
0x000474bb: Compile Unit: length = 0x0000023d, format = DWARF32, version = 0x0004, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x000476fc)
0x000474c6: DW_TAG_compile_unit
DW_AT_producer ("Apple clang version 11.0.3 (clang-1103.0.32.62)")
DW_AT_language (DW_LANG_ObjC)
DW_AT_name ("/Users/bel/Desktop/dSYMProject/dSYMProject/ViewController.m")
DW_AT_stmt_list (0x0000a891)
DW_AT_comp_dir ("/Users/bel/Desktop/dSYMProject")
DW_AT_APPLE_major_runtime_vers (0x02)
DW_AT_low_pc (0x0000000100000ce0)
DW_AT_high_pc (0x0000000100000ef6)
0x000475dd: DW_TAG_subprogram
DW_AT_low_pc (0x0000000100000e70)
DW_AT_high_pc (0x0000000100000ef6)
DW_AT_frame_base (DW_OP_reg6 RBP)
DW_AT_object_pointer (0x000475f6)
DW_AT_name ("-[ViewController warfTest]")
DW_AT_decl_file ("/Users/bel/Desktop/dSYMProject/dSYMProject/ViewController.m")
DW_AT_decl_line (26)
DW_AT_prototyped (true)
Line info: file 'ViewController.m', line 29, column 18, start line 26
bel@beldeMacBook-Pro ~ %
复制代码
这样,我们能得出结论: 0x000000010a107eb4
代表的是 ViewController.m
文件中的 第29行
的-[ViewController warfTest]
方法。这样我们就能够根据地址值
和dSYM
文件,将调试符号进行还原。
总结
1,dSYM
是一个DWARF
格式的文件,DWARF
是保存调试信息的一种格式。
2,Deployment Postprocessing == YES
且strip == All Symbols
时,在编译的时候会将所有的本地符号
脱掉。
3,Deployment Postprocessing == NO
且 Debug Information Format == DWAR with dSYM File
时,会在编译后生成 dSYM
。
4,根据 偏移后的内存地址值 和 ASLR
可以得到 函数的真实虚拟地址值
,根据真实虚拟地址值
可以在dSYM
文件中获取其调试信息。