苹果的GCC是如何在可执行文件中存储DWARF信息的?
我使用gcc -gdwarf-2
(苹果的GCC)编译了一个二进制文件。然而,objdump -g
和objdump -h
都没有显示任何调试信息。
libbfd也没有找到任何调试信息。(我在binutils邮件列表上询问过这个问题 here.)
不过,我可以通过dsymutil
(生成一个dSYM文件)来提取调试信息。之后,libbfd就可以读取这些调试信息了。
ld
在链接程序时不处理所有的调试信息。调试信息通常是可执行程序大小的10倍
,因此让链接器处理所有的调试信息并将其包含在可执行二进制文件中会严重影响链接时间。对于迭代开发-编译、链接、编译、链接、调试、编译、链接-这是一个真正的打击。DWARF
调试信息,汇编器将其输出到.o文件中dsymutil
可以被视为调试信息链接器。它执行相同的过程——读取调试映射,从 .o 文件中加载 DWARF,重新定位所有地址——然后输出一个包含所有 DWARF 的单个二进制文件,这就是 dSYM 包。
一旦你有了一个 dSYM 包,你就拥有了普通的标准 DWARF,任何能够处理 Mach-O 二进制文件的 dwarf 阅读工具都可以处理。
还有一个额外的细节使得所有这些工作都能够实现,那就是包含在 Mach-O 二进制文件中的 UUIDs。每次链接器创建一个二进制文件时,它会在 LC_UUID 载入命令中发出一个 128 位的 UUID(例如 otool -hlv
或 dwarfdump --uuid
)。这个 UUID 唯一地标识了该二进制文件。当 dsymutil
创建 dSYM 时,它会包含该 UUID。只有当 dSYM 和可执行文件具有匹配的 UUID 时,调试器才会将它们关联起来,而不是通过可疑的文件修改时间戳等方式。
mdfind“com_apple_xcode_dsym_uuids == E21A4165-29D5-35DC-D08D-368476F85EE1”
如果dSYM位于Spotlight索引位置。您甚至可以拥有公司的dSYM存储库和一个程序,可以根据UUID检索正确的dSYM - 也许是一个小型mysql数据库或类似的东西 - 这样您就可以在随机可执行文件上运行调试器,并立即获得该可执行文件的所有调试信息。UUID有一些非常棒的用途。dsymutil
时,它们将被合并以创建dSYM包。nm -pa 可执行文件
,它们都在那里。它们采用旧的stabs nlist
记录形式 - 链接器已经知道如何处理stabs,因此最容易使用它们 - 但是您不会遇到太多麻烦,如果您不确定,可以参考一些stabs文档。看起来实际上并没有。
我跟踪了 dsymutil
,它读取了所有的 *.o
文件。而且 objdump -h
也列出了其中的所有调试信息。
因此,这些信息似乎并没有复制到二进制文件中。
关于此事的一些相关评论也可以在这里找到。
在编译时使用的 .o
对象文件中。二进制文件存储对这些文件的引用(通过绝对路径)。
在名为 .dSYM 的单独捆绑包(目录)中
g++ -g main.cpp -o foo
进行编译,我将得到名为 foo.dSYM
的捆绑包。然而,如果我使用CMake,则会在对象文件中获取调试信息。我猜这是因为它执行了一个单独的 gcc -c main.cpp -o main.o
步骤?$ dsymutil -dump-debug-map main
---
triple: 'x86_64-apple-darwin'
binary-path: main
objects:
- filename: /Users/tim/foo/build/CMakeFiles/main.dir/main.cpp.o
timestamp: 1485951213
symbols:
- { sym: __ZNSt3__111char_traitsIcE11eq_int_typeEii, objAddr: 0x0000000000000D50, binAddr: 0x0000000100001C90, size: 0x00000020 }
- { sym: __ZNSt3__111char_traitsIcE6lengthEPKc, objAddr: 0x0000000000000660, binAddr: 0x00000001000015A0, size: 0x00000020 }
- { sym: GCC_except_table3, objAddr: 0x0000000000000DBC, binAddr: 0x0000000100001E2C, size: 0x00000000 }
- { sym: _main, objAddr: 0x0000000000000000, binAddr: 0x0000000100000F40, size: 0x00000090 }
- { sym: __ZNSt3__124__put_character_sequenceIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_PKS4_m, objAddr: 0x00000000000001F0, binAddr: 0x0000000100001130, size: 0x00000470 }
- { sym: ___clang_call_terminate, objAddr: 0x0000000000000D40, binAddr: 0x0000000100001C80, size: 0x00000010 }
- { sym: GCC_except_table5, objAddr: 0x0000000000000E6C, binAddr: 0x0000000100001EDC, size: 0x00000000 }
- { sym: __ZNSt3__116__pad_and_outputIcNS_11char_traitsIcEEEENS_19ostreambuf_iteratorIT_T0_EES6_PKS4_S8_S8_RNS_8ios_baseES4_, objAddr: 0x0000000000000680, binAddr: 0x00000001000015C0, size: 0x000006C0 }
- { sym: __ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, objAddr: 0x00000000000000E0, binAddr: 0x0000000100001020, size: 0x00000110 }
- { sym: GCC_except_table2, objAddr: 0x0000000000000D7C, binAddr: 0x0000000100001DEC, size: 0x00000000 }
- { sym: __ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc, objAddr: 0x0000000000000090, binAddr: 0x0000000100000FD0, size: 0x00000050 }
- { sym: __ZNSt3__111char_traitsIcE3eofEv, objAddr: 0x0000000000000D70, binAddr: 0x0000000100001CB0, size: 0x0000000B }
...
苹果将调试信息存储在名为*.dSYM的单独文件中。您可以在这些文件上运行dwarfdump并查看DWARF调试信息条目。
dsymutil
创建 dSYM 文件。但我的问题是,调试信息在哪里?也就是说,dsymutil
是从哪里获取它的。但我已经有了答案(请参见我的回答)。它们实际上并不在二进制文件中,二进制文件引用了 *.o
文件,这也是 dsymutil
获取数据的地方。 - Albert*.o
文件中。当您创建dSYM时,当然您会在dSYM中有另一个副本。但是在创建dSYM之前,没有dSYM。这就是我在问题中所说的。 (“但是我可以通过dsymutil
提取调试信息。”)您不会自动获得dSYM。您必须调用dsymutil
。(我认为,当您使用Xcode时,Xcode会自动执行此操作。)或者我错了吗? - Albert
dsymutil
如何知道.o
文件的位置?我在manpage中没有看到告诉它的选项。还有,我需要将二进制编译为-g3
吗?如果需要,我可以在dsymutil
之后对其进行剥离吗?谢谢。 - mxclnm -pa binary | grep OSO
命令可以列出它们。它们采用旧的 stabs 调试格式(因为链接器已经知道如何处理该格式)。创建了 dSYM 文件后,可以将它们从可执行文件中剥离出来。在 Mac 平台上,您不需要使用-g3
,-g
应该足够了。我认为-g3
输出预处理宏信息,但 lldb 在 Mac OS X 上不会读取它(而且我也不知道 clang 是否输出它)。 - Jason Molenda