[关闭]
@qidiandasheng 2022-08-23T01:24:37.000000Z 字数 5985 阅读 533

iOS符号(😁)

iOS调试


符号类型

.debug:调试符号,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译驱动程序时才会得到这张表。
.symtab:符号表,它存放在程序中定义和引用的函数和全局变量信息。

格式:

可执行文件中Symbol Table存储着全局符号和局部符号; DWARF存储着符号的行号信息。

编译步骤:

Xcode编译实际的操作步骤是:生成带有 DWARF 调试信息的可执行文件 -> 提取可执行文件中的调试信息打包成 dSYM -> 去除符号化信息。去除符号是单独的步骤,使用的是 strip 命令。

调试符号

Generate Debug Symbols

GCC_GENERATE_DEBUGGING_SYMBOLS,生成调试符号,当Generate Debug Symbols选项设置为YES时,每个源文件在编译成.o文件时,编译参数多了-g-gmodules两项。

截屏2022-01-08 下午9.09.05.png-510kB

对应的目标文件,会多一些调试信息section:

截屏2022-01-08 下午9.10.36.png-428.6kB

Generate Debug Symbols设置为NO的时候,在Xcode中设置的断点不会中断。但是在程序中打印[NSThread callStackSymbols],依然可以看到类名和方法名。

Debug Information Level

CLANG_DEBUG_INFORMATION_LEVEL,这个配置项表示调试信息的等级,默认为Compiler default

另一个选项是Line tables only,这种类型的调试信息允许获得带有函数名、文件名和行号的函数调用栈,但是不包含其他数据(比如局部变量和函数参数)。

Strip符号

Deployment Postprocessing

Deployment Postprocessing如果为 YES,在编译生成目标文件之后要进行后续的Strip处理;如果为 NO,则不会有后续处理;使用 Xcode Archive 进行编译,Deloyment Postprocessing 的值恒为YES;

Strip Linked Product

Strip Linked Product如果为 YES,则进行Strip符号;如果为 NO,则不进行Strip符号。如果Deployment Postprocessing设置为NO,则此选项不生效。

Strip Linked Product为YES时我们可以看到Xcode在编译完成可执行文件后,有对可执行文件进行Strip的操作:

截屏2022-01-08 下午9.57.22.png-105.7kB

Strip Style

Deployment PostprocessingStrip Linked Product都为YES才生效;去除的是可执行文件中的符号。

截屏2022-01-08 下午10.08.58.png-168kB

截屏2022-01-08 下午10.18.01.png-572.9kB

截屏2022-01-08 下午10.12.07.png-160.6kB

截屏2022-01-08 下午10.22.26.png-674.2kB

截屏2022-01-08 下午10.06.11.png-204.1kB

截屏2022-01-08 下午10.25.34.png-627kB

llvm-strip

对可执行文件进行符号的strip,使用的是llvm的llvm-strip命令,比如之前的几种Strip Style的差别只是调用llvm-strip的参数不一样而已。

  1. /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip -S -T /Users/dasheng/Library/Developer/Xcode/DerivedData/HelloDemo-adntqoddwxvflvcgqagptmjkooku/Build/Intermediates.noindex/ArchiveIntermediates/HelloDemo-Example/InstallationBuildProductsLocation/Applications/HelloDemo_Example.app/HelloDemo_Example
  1. /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip -x -T /Users/dasheng/Library/Developer/Xcode/DerivedData/HelloDemo-adntqoddwxvflvcgqagptmjkooku/Build/Intermediates.noindex/ArchiveIntermediates/HelloDemo-Example/InstallationBuildProductsLocation/Applications/HelloDemo_Example.app/HelloDemo_Example
  1. /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/strip /Users/dasheng/Library/Developer/Xcode/DerivedData/HelloDemo-adntqoddwxvflvcgqagptmjkooku/Build/Intermediates.noindex/ArchiveIntermediates/HelloDemo-Example/InstallationBuildProductsLocation/Applications/HelloDemo_Example.app/HelloDemo_Example

具体的strip参数可参考llvm-strip文档

--strip-debug, -d, -g, -S

Remove all debug sections from the output.

--discard-all, -x

Remove most local symbols from the output. Different file formats may limit this to a subset of the local symbols. For example, file and section symbols in ELF objects will not be discarded. Additionally, remove all debug sections.

-T

Remove Swift symbols.


注意

动态库和静态库不能去除全部符号(Strip All Symbols),要保留全局符号(选择Non-Global Symbols),他们是库和其他库链接时沟通的桥梁;失去了全局符号,动态库和静态库就成为了黑盒。

去除符号的操作对于 dSYM 文件中的符号信息没有影响;对于动态库和可执行二进制文件,可以将符号尽可能去除掉减少二进制体积的大小。需要符号进行符号化崩溃日志时,再从 dSYM 文件中找对应符号。

解析符号表

为什么崩溃日志需要解析

如果我们线上app发生了崩溃,会有崩溃日志,但是因为我们的可执行文件里的符号依旧被strip掉了,所以你看到的调用堆栈都是二进制地址,而不是可读的函数名称。

dSYM符号文件

上面我们说过没有对应的符号是因为没有符号被strip掉了,那我们只要保存好这份被strip掉的符号不就行了。在xcode的build setting里可以生成这份被strip掉的符号文件,也就是dSYM符号文件,如下图设置即可:

截屏2022-01-10 下午10.00.33.png-117.5kB

截屏2022-01-10 下午10.02.48.png-204.3kB

截屏2022-01-10 下午10.05.42.png-42.5kB

解析crash文件

这里我真机跑了一个crash代码,然后连接电脑导出.crash文件。

同时在/Users/dasheng/Library/Developer/Xcode/DerivedData/HelloDemo-adntqoddwxvflvcgqagptmjkooku/Build/Products/Release-iphoneos目录下找到对应的.dSYM文件。

然后在/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash找到解析工具symbolicatecrash

把以上三个文件放到同一级目录:

截屏2022-01-10 下午10.35.58.png-24kB

解析原理

dSYM目录结构(右键打开包内容),显示如下图:

截屏2022-01-10 下午10.39.18.png-55.9kB

其中真正保存保存数据的是 DWARF 文件(DWARF结构), DWARF(Debuging With Arbitrary Format)是ELF 和 Mach-O 等文件格式中用来存储和处理调试信息的标准格式。 DWARF 中的数据是高度 压 缩 的 , 可以通过dwarfdump命令提取可读信息,比如提取关键的调试信息.debug_info.debug_line

  1. dwarfdump --debug-info HelloDemo_Example.app.dSYM > debuginfo.txt

如下图我在类DSViewController24行声明了NSMutableArray变量:

截屏2022-01-10 下午10.45.07.png-264.2kB

然后在debuginfo.txt,能看到响应的信息:

截屏2022-01-10 下午10.48.28.png-380kB

解析流程

我们打开.carsh文件,如下图所示红框内为崩溃的堆栈:

截屏2022-01-10 下午11.11.28.png-635.6kB

然后再往下就是对应线程的调用堆栈,我们往下拉找到HelloDemo_Example的堆栈地址,红框内地址部分第一列为运行时的堆栈地址,第二列为进程运行时的起始地址,第三列为运行时的偏移地址:

截屏2022-01-10 下午11.12.22.png-638.7kB

HelloDemo_Example的所有行其实地址都相同,如图起始地址为0x100930000

我们可以看到第一张图有两个地址是相近的,这两个地址都属于HelloDemo_Example,其中0x100937928,我们下面能看到偏移量为31016 = 0x7928。因为起始地址相同,另一个地址0x10093781c的偏移量为0x10093781c-0x100930000 = 30748 = 0x781C

以上地址均为 app 发生崩溃时的运行地址,根据虚拟内存偏移地址不变的原理,只要知道符号表 TEXT 段的起始地址,加上偏移量就能得到崩溃地址对应符号表中的地址。
使用如下命令输出符号表中TEXT段起始位置为0x0000000100000000

  1. otool -l HelloDemo_Example.app.dSYM/Contents/Resources/DWARF/HelloDemo_Example | grep __TEXT -C 5
  2. --
  3. nsects 0
  4. flags 0x0
  5. Load command 3
  6. cmd LC_SEGMENT_64
  7. cmdsize 1032
  8. segname __TEXT
  9. vmaddr 0x0000000100000000
  10. vmsize 0x0000000000030000
  11. fileoff 98304
  12. filesize 116
  13. maxprot 0x00000005
  14. initprot 0x00000005
  15. nsects 12
  16. flags 0x0
  17. Section
  18. sectname __text
  19. segname __TEXT
  20. addr 0x0000000100007748
  21. size 0x000000000001d240
  22. offset 0
  23. align 2^2 (4)
  24. reloff 0
  25. --

使用符号表基址+之前获取的崩溃堆栈偏移量,0x0000000100000000+0x7928 = 0x1000079280x0000000100000000+0x781C = 0x10000781C

使用以下命令解析对应地址在符号表中的符号,如图为解析内容,我们能看到对应的文件以及函数。

  1. dwarfdump HelloDemo Example.app.dSYM --lookup 0x10000781C

截屏2022-01-10 下午11.35.35.png-1304.1kB

获取该地址对应的准确行数,这需要借助debug_line文件,使用以下代码得到debugline.txt,然后我们就能找到地址0x10000781C准确的行数了,如下图所示:

  1. dwarfdump --debug-line HelloDemo_Example.app.dSYM > debugline.txt

截屏2022-01-10 下午11.44.26.png-175kB

问题

为何去除符号之后还可恢复线程堆栈符号

因为iOS是一门动态语言,我们可以通过类名、方法名等字符串动态的执行方法之类。因此可执行文件中一定会存有类名和方法名(不是在符号表中),所以恢复方案就是:

根据以上原理我们知道因为OC是动态语言所以可以恢复Objective-C的函数符号,但如果C++函数符号被Strip后,我们是没有办法恢复其符号的。

参考

llvm-strip
iOS 符号二三事
DWARF数据结构

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注