LLVM 在 Windows 上现已支持 PDB 调试信息
多年来,我们一直致力于将 clang 打造成 Windows 上开发软件的一流工具链。 我们曾 写过 相关内容 多次,并且我们已经实现了完整的 ABI 兼容性(除了 bug)。 一个一直难以实现兼容性的领域是调试信息,但在过去的两年里,我们取得了重大进展。 如果你只想了解 TL;DR,那么这里就是:如果你在 Windows 上使用 clang,现在可以获得 PDB 调试信息!
背景:CodeView 与 PDB
CodeView 是微软在 1980 年代中期发明的一种调试信息格式。 由于种种原因,其他调试器开发了一种独立的格式,称为 DWARF,它最终得到了标准化,现在被许多编译器和编程语言广泛支持。 CodeView 就像 DWARF 一样,定义了一组记录,描述了源代码行与代码地址之间的映射关系,以及程序使用的类型和符号。 调试器随后使用这些信息来让你可以通过函数名称设置断点,显示变量的值等。 但是 CodeView 只是部分有文档记录,最新的官方文档至少有 20 年的历史了。 虽然一些记录仍然保留着上面记录的格式,但其他记录已经发生了演变,并且引入了完全没有记录的新记录。
需要理解的是,CodeView 仅仅是一组记录。 用户说“显示 Foo 的值”时会发生什么? 调试器必须找到 描述 Foo 的记录。 现在事情开始变得复杂了。 启用了哪些优化? 使用了哪个版本的编译器? (这些可能很重要,因为不同版本的编译器之间可能存在一些 ABI 不兼容性,或者在尝试重建高度优化的代码中的回溯时,或者如果堆栈已经被破坏)。 程序中还有数十亿个其他符号,如何在不进行详尽的 O(n) 搜索的情况下找到名为 Foo 的符号? 如何支持增量链接,以便在只有少量代码实际发生变化时,重新生成调试信息不会花费很长时间? 如何通过对重复使用的字符串进行去重来节省空间? PDB 应运而生。
PDB(程序数据库),顾名思义,就是一个数据库。 它包含 CodeView,但也包含许多其他内容,这些内容允许以各种方式对 CodeView 记录进行索引。 这允许通过名称或地址快速查找类型和符号,相当于“表”的概念,用于单个输入文件,以及许多其他对用户来说几乎是不可见的但很大程度上决定了 Windows 上调试体验如此出色的因素。 但问题在于:虽然 CodeView 至少是有某种程度的文档记录,但 PDB 是完全没有文档记录。 并且它非常复杂。
我们被卡住了(还是没有?)
几年前,我们决定前进的方向是放弃对生成 CodeView 和 PDB 的任何希望,而是专注于两件事
- 让 clang-cl 在 Windows 上生成 DWARF 调试信息
- 将 LLDB 移植到 Windows 并教它了解 Windows ABI,这将比教 Visual Studio 和/或 WinDbg 能够解释 DWARF 要容易得多(假设这根本可行,因为所有操作都必须严格地通过 Visual Studio/WinDbg 扩展模型来完成)
不幸的是,我们开始意识到我们真的需要 PDB。 我们的目标始终是为嵌入在 Windows 生态系统中的开发者创造尽可能少的阻力。 像Windows 性能分析器和 vTune 这样的工具非常强大,并且是工程师现有工具集中的标准工具。 各组织已经建立了基础设施来归档 PDB 文件,并收集和分析崩溃转储。 使用 PDB 进行调试非常快速,因为调试器在启动时不需要索引符号,因为索引已经内置到文件格式中。 最后但并非最不重要的是,WinDbg 这样的工具已经非常适合事后调试,坦率地说,许多(甚至可能是大多数)Windows 开发者只会从他们的冷冰冰的死手中夺走 Visual Studio 调试器。
当我建议我们只是询问微软是否愿意帮助我们时,我得到了一些奇怪的眼神(轻描淡写地说)。 但最终我们还是这么做了,并且…他们同意了! 这表现为一些上传到微软 Github仓库的代码,我们只能靠自己去弄明白。 虽然他们只能上传一小部分的 PDB 代码(这意味着我们必须进行大量猜测和探索,而且代码也无法编译,因为其中一半缺失),但它填补了足够的空白,让我们能够完成剩下的工作。
经过大约一年半对这段代码的研究,不断地进行破解,再次研究代码,再次进行破解等等,我很自豪地说,lld(LLVM 链接器)最终可以生成可工作的 PDB。 所有基本功能,比如按行设置断点,或按名称设置断点,或查看变量,或搜索符号或类型,一切都正常工作(当然,除了 bug 之外)。
对于那些有兴趣深入研究 PDB 内部结构的人来说,我们还开发了一种专门用于此目的的工具。 它被称为llvm-pdbutil,它是微软自己的cvdump实用程序的精神上的对应工具。 它可以转储 PDB 的内部结构,将 PDB 转换为 yaml 格式,反之亦然,找到两个 PDB 之间的差异,等等。 llvm-pdbutil 的简要文档在这里,而 PDB 文件格式内部结构的详细描述在这里,其中包含我们过去两年学到的所有内容(仍在不断完善中,因为我必须在编写文档和实际制作 PDB 之间分配时间)。
欢迎 bug!
所以,您现在可以参与进来。我们已经用我们的 PDB 测试了简单的调试场景,但我们仍然认为这在调试信息质量方面处于 alpha 阶段。我们希望您尝试一下并在我们的 错误追踪器上报告问题。为了帮助您入门,请下载 Windows 上的最新 clang 快照。这里有两种简单的方法来测试这项新功能:
- 让 clang-cl 自动调用 lld
- clang-cl -fuse-ld=lld -Z7 -MTd hello.cpp
- 分别调用 clang-cl 和 lld。
- clang-cl -c -Z7 -MTd -o hello.obj hello.cpp
- lld-link -debug hello.obj
我们期待着大量错误报告!
我们要对微软在将代码上传到 github 仓库方面提供的帮助表示衷心的感谢,没有他们,我们不可能走到今天这一步。
为了让您对未来更加兴奋,值得重申的是,所有这一切都是无需依赖任何 Windows 特定 API、DLL 或库完成的。它是 100% 可移植的。您是否听到了交叉编译?
Zach Turner(代表 LLVM Windows 团队)