苹果的GCC是如何在可执行文件中存储DWARF信息的?

17

苹果的GCC是如何在可执行文件中存储DWARF信息的?

我使用gcc -gdwarf-2 (苹果的GCC)编译了一个二进制文件。然而,objdump -gobjdump -h都没有显示任何调试信息。

libbfd也没有找到任何调试信息。(我在binutils邮件列表上询问过这个问题 here.)

不过,我可以通过dsymutil(生成一个dSYM文件)来提取调试信息。之后,libbfd就可以读取这些调试信息了。

4个回答

61
在Mac OS X上,决定让链接器ld在链接程序时不处理所有的调试信息。调试信息通常是可执行程序大小的10倍,因此让链接器处理所有的调试信息并将其包含在可执行二进制文件中会严重影响链接时间。对于迭代开发-编译、链接、编译、链接、调试、编译、链接-这是一个真正的打击。
相反:
  • 编译器在.s文件中生成DWARF调试信息,汇编器将其输出到.o文件中
  • 链接器在可执行二进制文件中包含一个“调试映射”,告诉调试信息使用者在链接过程中所有符号的重新定位位置。
消费者(进行.o文件调试)从可执行文件中加载调试映射,并根据调试映射的指示按需处理所有.o文件中的DWARF,重新映射符号。

dsymutil 可以被视为调试信息链接器。它执行相同的过程——读取调试映射,从 .o 文件中加载 DWARF,重新定位所有地址——然后输出一个包含所有 DWARF 的单个二进制文件,这就是 dSYM 包。

一旦你有了一个 dSYM 包,你就拥有了普通的标准 DWARF,任何能够处理 Mach-O 二进制文件的 dwarf 阅读工具都可以处理。

还有一个额外的细节使得所有这些工作都能够实现,那就是包含在 Mach-O 二进制文件中的 UUIDs。每次链接器创建一个二进制文件时,它会在 LC_UUID 载入命令中发出一个 128 位的 UUID(例如 otool -hlvdwarfdump --uuid)。这个 UUID 唯一地标识了该二进制文件。当 dsymutil 创建 dSYM 时,它会包含该 UUID。只有当 dSYM 和可执行文件具有匹配的 UUID 时,调试器才会将它们关联起来,而不是通过可疑的文件修改时间戳等方式。

我们还可以使用UUID来定位二进制文件的dSYM。它们会出现在崩溃报告中,我们包含了一个Spotlight导入程序,您可以使用它来搜索它们,例如: mdfind“com_apple_xcode_dsym_uuids == E21A4165-29D5-35DC-D08D-368476F85EE1” 如果dSYM位于Spotlight索引位置。您甚至可以拥有公司的dSYM存储库和一个程序,可以根据UUID检索正确的dSYM - 也许是一个小型mysql数据库或类似的东西 - 这样您就可以在随机可执行文件上运行调试器,并立即获得该可执行文件的所有调试信息。UUID有一些非常棒的用途。
但无论如何,回答您最初的问题:未剥离的二进制文件具有调试映射,.o文件具有DWARF,当运行dsymutil时,它们将被合并以创建dSYM包。
如果您想查看调试映射条目,请执行nm -pa 可执行文件,它们都在那里。它们采用旧的stabs nlist记录形式 - 链接器已经知道如何处理stabs,因此最容易使用它们 - 但是您不会遇到太多麻烦,如果您不确定,可以参考一些stabs文档。

1
dsymutil如何知道.o文件的位置?我在manpage中没有看到告诉它的选项。还有,我需要将二进制编译为-g3吗?如果需要,我可以在dsymutil之后对其进行剥离吗?谢谢。 - mxcl
4
在可执行文件剥离之前,里面有带有.o文件名称的“调试映射”条目。使用 nm -pa binary | grep OSO 命令可以列出它们。它们采用旧的 stabs 调试格式(因为链接器已经知道如何处理该格式)。创建了 dSYM 文件后,可以将它们从可执行文件中剥离出来。在 Mac 平台上,您不需要使用 -g3-g 应该足够了。我认为 -g3 输出预处理宏信息,但 lldb 在 Mac OS X 上不会读取它(而且我也不知道 clang 是否输出它)。 - Jason Molenda
2
@JasonMolenda,太棒了,谢谢您的回答!除了您和this的个人经历写作外,我似乎找不到任何正式的资料,您知道是否有其他资料吗? - alloy
1
谢谢。嘿,我也写了你链接的那篇轶事性的文章。:) T从未以任何形式正式记录下来,因为它的受众非常小。所有相关的部分都是开源的(编译器、链接器、调试器),所以当然源代码都是可用的。DWARF标准委员会正在研究各种加速表和dwarf-in-.o-file提案。当一切都稳定下来并标准化后,我认为我们将看到工具向使用它们过渡。但这不是近期内会发生的事情。 - Jason Molenda
这解释了很多:D 当然,你是对的,受众非常小。 - alloy

2

看起来实际上并没有。

我跟踪了 dsymutil,它读取了所有的 *.o 文件。而且 objdump -h 也列出了其中的所有调试信息。

因此,这些信息似乎并没有复制到二进制文件中。


关于此事的一些相关评论也可以在这里找到。


2
似乎OSX有两种放置调试信息的方式:
  1. 在编译时使用的 .o 对象文件中。二进制文件存储对这些文件的引用(通过绝对路径)。

  2. 在名为 .dSYM 的单独捆绑包(目录)中

如果我使用苹果的Clang编译器使用 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 }
...

-1

苹果将调试信息存储在名为*.dSYM的单独文件中。您可以在这些文件上运行dwarfdump并查看DWARF调试信息条目。


1
不可以。你可以通过 dsymutil 创建 dSYM 文件。但我的问题是,调试信息在哪里?也就是说,dsymutil 是从哪里获取它的。但我已经有了答案(请参见我的回答)。它们实际上并不在二进制文件中,二进制文件引用了 *.o 文件,这也是 dsymutil 获取数据的地方。 - Albert
你的问题比较模糊。我理解为当在调试器中使用可执行文件时,调试信息存储在哪里。 - Bogatyr
是的,那是我的问题。答案是,它存储在*.o文件中。当您创建dSYM时,当然您会在dSYM中有另一个副本。但是在创建dSYM之前,没有dSYM。这就是我在问题中所说的。 (“但是我可以通过dsymutil提取调试信息。”)您不会自动获得dSYM。您必须调用dsymutil。(我认为,当您使用Xcode时,Xcode会自动执行此操作。)或者我错了吗? - Albert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接