说说dYSM如何恢复Crash信息

231 阅读6分钟

看看MachO到底干了什么

通过命令clang test.m -o test.m文件直接编译为可执行文件

再通过命令objdump --macho -d test来查看MachO的代码段到底在做什么

image.png

可以看到MachO根据系统分配的虚拟内存地址,一行一行的来执行机器指令,里面显示的汇编代码则是帮助我们来理解指令到底在做什么

同样可以看看目标文件到底在做什么,通过指令clang -c test.m -o test.o先来生成目标文件

还是通过指令objdump --macho -d test.o来查看代码段是什么

image.png

通过对比目标文件里并未变为虚拟内存地址而是只是有一些偏移,但是目标文件在链接的时候需要系统来告诉它真实的偏移,通过命令objdump --macho -reloc test.o 查看重定位符号表来得知哪里是需要重定位的

image.png

同样在重定位符号表里看到我们定义的变量global30、28、20的地址处也需要重定位

那么在跳转到_test的时候是如何得到_test地址的呢?

image.png

大端模式: 最高有效位存放在低位地址

小端模式: 最高有效位存放在高位地址

先看到指令那行,小端模式下先得到偏移值0xFFFFF3,得到偏移值补码为0xD再乘以一条指令的字节数4得到0x34,跳转指令的地址-偏移值即为0x100003f98 - 0x34 = 0x100003f64

所以MachO就是根据这些数据和偏移来做事情

dSYM

在程序发生崩溃时可以看到一些信息来告诉我们哪里发生了问题,但是这些信息是从哪里来的呢?这些信息就是来自于dSYM文件,这里存放的就是调试符号的信息,dSYM文件就是保存按照DWARF格式保存调试信息的文件。DWARF是一种被众多编译器和调试器使用的用于支持源代码级别调试的调试文件格式。

clang -g -c test.m -o test.o这里的-g是要生成调试信息

接着来查看一下头部信息objdump --macho --private-headers test.o

image.png

可以看到出现了一些名为__DWARF的段

那么在链接过程生成可执行文件后这些段便不再存于这里了,将移动至符号表内

同样为了验证这个说法,先使用命令objdump --macho --private-headers test来查看

image.png

这里再没有发现有__DWARF的段存在,那么现在查看一下符号表nm -pa test

image.png

所有的调试符号都在符号表里可以看到,符号表内有所有的符号不止是调试符号

刚才在生成可执行文件的时候已经生成了dSYM文件,现在来看看这个文件里到底存储了什么

dwarfdump test.dSYM

image.png

对比之前查看的符号表,它是把符号表里的信息以一种特定的格式重新生成了这个dSYM文件,每个符号的名字以及所在文件还有行数所有信息都有,这也就是发生崩溃的时候为什么能查看到相关的信息

上述过程可以总结为以下:

  • 读取debug map
  • 从.o文件中加载__DWARF段的信息
  • 重定位所有地址
  • 将全部的DWARF打包为dSYM bundle

利用工程来验证dSYM是如何恢复符号的

创建一个工程里面只写一个可以造成崩溃的代码

image.png

这个应该是很熟悉的画面了,这里是已经还原过的场景,因为我们工程保存了相关的符号信息,这时应该设置为脱符号,但是笔者在这里发生了一点问题发现并没有真正脱去符号,如果有读者知晓还望告知

这里本应该在设置Deployment PostprocessingYes以及strip linked productYes,strip styleall symbols后就会脱去符号,但是我发现并没有,所以这里设置为release

image.png

本应该显示-[ViewController viewDidLoad]的地方此时已经无法看到相关信息,那么我们在上传正式版本后类似于bugly这样的软件是如何恢复调用栈的呢?它是利用了dSYM文件

  • 首先看到这里的地址0x102b6a134,这个地址是虚拟地址值再加上ASLR

  • ASLRdyld给加上的一个随机值,这里可以通过下图知道是0x102b68000 - 0x100000000

image.png

  • 那么在dSYM文件中的地址应该是0x102b6a134 - 0x2b68000 = 0x100002134

接下来在dSYM文件中来查看这个地址中到底是什么即可

dwarfdump --lookup 0x100002134 TestInject.app.dSYM

image.png

可以看到就是我们想要的,这也就是如何通过dSYM文件来定位确认出现崩溃的代码在哪里的过程

第三方库的断点信息

我们在平常使用第三方库的时候大多是直接以源码的形式来引入的,这样对于调试是很方便的,断点调试信息都可以直接在三方库源码中来看到,但是现在如果只给出framework的话是否能进行调试呢?

image.png

在主工程中调用framework中的方法,并对这个方法打上断点

另外framework在引入主工程时只给出framework的方式来做

image.png

image.png

这里可以看到断点已经成功,继续进入下一步,按照断点的确进入了源码

image.png

其实它也是根据符号表中的信息中源码文件的位置来定位到源码,接下来先来看一下framework的符号表信息

nm -pa TestFramework.framework/TestFramework

image.png

可以看到这个路径就存放的是源码文件

那么上述问题可以换个角度来验证它,这里可以把framework先移动到别的路径后再次进行编译,然后再重新运行再打断点来观察

首先可以看到framework的符号表信息中路径已经发生了改变

image.png

这时重复打断点的工作后发现此时无法再像之前定位到源码中

image.png

那么如果这个猜想是正确的话只需要在这个路径放入源码文件即可,这里尝试后发现的确可以实现,那么根据上述内容可以设想在构建组件二进制化的时候可以考虑将源码文件固定在某个路径中来实现不再来回切换二进制文件和源码来实现。

总结:

  • lldb的调试信息是根据符号表里给出源码的路径来找到源码文件进行调试的