引言

大多数人知道 PDB 文件是用来帮助我们 debug 的,但也仅此而已。

本文主要介绍当你遇到 PDB 文件时(windows 开发中),你必须要知道的内容。

重要的事情说三遍

PDB 文件和源代码一样重要!!!

PDB 文件和源代码一样重要!!!

PDB 文件和源代码一样重要!!!

开始之前

首先定义两个概念:

  • 本地编译:在你本机开发环境中的编译。
  • 官方编译:在编译服务器上的编译。

这两种编译的区分很重要,因为调试本地编译往往很简单,但是问题往往出现在官方编译中。

官方编译至少需要有一个地方(Symbol Server)来存放编译出来的二进制及 PDB 文件。这样当某个版本发现任何问题,我们可以获取到对应的 PDB 文件进行调试。

没有匹配的 PDB 文件,调试器几乎不可能完成调试任务,或者你将付出高昂的代价才能解决问题。

更多关于 Symbol Server 的内容,参考 https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/june/bugslayer-symbols-and-crash-dumps

Visual Studio 和 WinDBG 知道如何访问 Symbol Server 并且如果二进制文件来自官方编译,那么调试器能够自动加载匹配的 PDF 文件。

在将 PDB 文件放在 Symbol Server 上之前,还需要做一件事:在官方编译出来的 PDB 文件上,通过 Source Server 工具,进行源文件索引(source indexing)。索引过程会嵌入版本控制命令用于拉取当前版本编译的源文件。这样,当你调试当前版本时,你就不用担心找不到版本的源文件。

更多关于 Source Server 的内容,参考 https://docs.microsoft.com/en-us/archive/msdn-magazine/2006/august/source-server-helps-you-kill-bugs-dead-in-visual-studio-2005

什么是 PDB 文件?

现在,我们可以开始介绍什么是 PDB 文件,以及调试器是如何查找 PDB 文件了。

实际的 PDB 文件格式是加密的,但是微软提供了 API,为调试器提供 PDB 文件的数据。

非托管的 C++ PDB 文件包含了以下信息:

  • 公有函数,私有函数和静态函数的地址
  • 全局变量的名称和地址
  • 参数和局部变量的名称以及栈上的偏移量
  • 类,结构体以及数据定义的类型信息
  • FPO(Frame Pointer Omission) 数据
  • 源文件的文件名及行信息。

而 .NET 的 PDB 文件只包含了两项内容:

  • 源文件的文件名及行信息
  • 局部变量的名称

其他所有信息已经存放在 .NET 元数据中,所以没有必要再 PDB 文件中冗余。

PDB 文件的加载

当模块被加载到进程的地址空间后,调试器会使用两种信息找到匹配的 PDB 文件。首先,当然是文件名。如果你加载 ZZZ.DLL,那么调试器就会查找 ZZZ.PDB。

更重要的是,调试器如何知道这就是匹配的 PDB 文件?这是通过比对内嵌于 PDB 文件和二进制文件中的 GUID 来确认的。

负责将 GUID 嵌入二进制和 PDB 文件的是编译器(.NET)或者链接器(C++)。想想,历史编译的版本如果没有保存 PDB 文件,你还能调试吗?答案是否定的,哪怕你没有修改源文件!你可能会想是否可以修改 PDB 文件的 GUID?很遗憾,答案也是否定的。

你可以查看二进制文件中的 GUID。使用 Visual Studio -> DUMPBIN 的命令行工具,你可以列出所有的 PE(Portable Executable) 文件内容。可以在 Visual Studio 的命令行工具中调用 DUMPBIN。

更多关于 DUMPBIN 的内容参考:https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detailhttps://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2

DUMPBIN 有很多命令行指令,其中显示 GUID 的指令是 /HEADERS。在输出内容中,对我们来说重要的是 Debug Directories 部分的内容:

Debug Directories

        Time Type        Size      RVA  Pointer
-------- ------- -------- -------- --------
6045C20E cv 60 00541AC8 5408C8 Format: RSDS, {DC80D058-127B-4379-B859-3F9F6978A4DB}, 1, C:ZZZ.pdb

知道了调试器如何确定匹配的 PDB 文件,下一步我们讨论调试器从哪里查找 PDB 文件。首先,调试器会在加载二进制文件的目录查找对应的 PDB 文件,如果没有找到,那么就查找PE 文件中 Debug Directories 内容里硬编码的 PDB 文件路径,在上面的输出示例中是 "C:ZZZ.pdb"(.NET 应用编译工具 MSBUILD会将 PDB 文件编译到 OBJ<Debug/Relase/...> 目录下,如果编译成功,再拷贝到 DEBUG 或者 RELEASE 目录)。如果在上述两个位置都没有找到,但是建立了 Symbol Server,那么调试器会在 Symbol Server 的缓存目录里继续查找。这种查找顺序也保证了本地编译和官方编译不会有冲突。

在 Visual Studio 中调试的时候,你可以在窗口 Modules 中的列 Symbol File 里看到 PDB 文件的位置。

对大多数应用来讲这种加载方式都没有问题。但是对于需要将程序集放入 GAC(Global Assembly Cache)的 .NET 应用,PDB 的加载就会变得有趣了。对于本地编译,调试器会在编译目录找到 PDB 文件,所以没什么问题。问题来源于当你想要在其他机器上调试本地编译版本。

在其他机器上调式,很多人会用 Gacutil.exe 将程序集放入 GAC,然后打开命令行在 "C:WINDOWSASSEMBLY" 下查找程序集的物理位置。但是基于 Any CPU 编译的程序集实际上会放入类似 "C:WindowsassemblyGAC_MSILExample1.0.0.0__682bc775ff82796a" 的路径。

上述路径中,Example 是程序集名称,1.0.0.0 是版本号,682bc775ff82796a 是公有秘钥令牌值(public key token value)。当你推断出这个路径后,你可以将 PDB 文件拷入这个目录然后调试器会加载它。

PDB 文件的内容

对于官方编译,因为有源文件索引工具,所以 PDB 文件中会存储版本控制命令,用于将源文件放入你配置的源文件缓存池。对于本地编译,PDB 文件中存储的是二进制文件对应的源文件的完整路径。换句话说,如果你使用 C:FOO 中的源文件 MYCODE.CPP,那么 PDB 文件中存储的就是 C:FOOMYCODE.CPP。

理论上,所有的官方编译会自动立马进行源文件索引,并将内容存储于 Symbol Server,以至于你都不用考虑源文件在哪。然而,有些开发团队在测试及其他环节中会考量编译结果是否满足使用的要求,在此之前,不会对 PDB 文件进行源文件索引。如果你确实需要调试未索引的版本,最好将源代码下载到本地时保证和编译服务器相同的磁盘和目录,否则,你可能会在调试时遇到麻烦。尽管 Visual Studio 调试器和 WinDBG 有配置源文件搜索路径的选项,但要配置正确并不容易。

引用

https://www.wintellect.com/pdb-files-what-every-developer-must-know/

关于 PDB 文件你需要知道什么?的更多相关文章

  1. Microsoft Visual Studio PDB文件相关事宜

    Microsoft Visual Studio PDB:调试的符号文件,程序数据库 (PDB) 文件保存着调试和项目状态信息,使用这些信息可以对程序的调试配置: 当以 /ZI 或 /Zi(用于 C/C ...

  2. Visual Studio 不生成.vshost.exe和.pdb文件的方法【转】

    Visual Studio 不生成.vshost.exe和.pdb文件的方法[转] 使用Visual Studio编译工程时,默认设置下,即使选择了「Release」时也会生成扩展名为「.vshost ...

  3. windbg不识别pdb文件符号

    一开始配置完毕后 输入reload  但不识别 输入reload -f 还是不识别 输入reload -f 模块名 继续不识别 !sym noisy 查看 输入reload 发现有了一堆的查找路径 把 ...

  4. Visual Studio无法查找或打开 PDB 文件解决办法

    Visual Studio无法查找或打开 PDB 文件解决办法 用VS调试程序时,有时会在VS底部的“输出”框中提示“无法查找或打开 PDB 文件”.这该怎么解决呢? 下面,我们以VS2013为例,来 ...

  5. .pdb文件的使用方法

    1.Demo1:用DLL_01生成my.dll.my.pdb.my.lib文件. 2.Demo2:在DLL_01_APP_02中使用DLL_01的dll. 步骤: 1.vs2008打开DLL_01_A ...

  6. 2016-07-07: 重新编译时vc90.pdb不是创建此预编译头时使用的pdb文件

    使用VS2008在一个解决方案中包含多个项目时,当设置多个项目的中间目录为同一个目录时,在增量编译时出现"重新编译时vc90.pdb不是创建此预编译头时使用的pdb文件,请重新创建预编译头问 ...

  7. VS2013 编译程序时提示 无法查找或打开 PDB 文件

    "Draw.exe"(Win32):  已加载"C:\Users\YC\Documents\Visual Studio 2013\Projects\Draw\Debug\ ...

  8. PDB文件:每个开发人员都必须知道的

    PDB Files: What Every Developer Must Knowhttp://www.wintellect.com/CS/blogs/jrobbins/archive/2009/05 ...

  9. Visual Studio 不生成.vshost.exe和.pdb文件的方法

    使用Visual Studio编译工程时,默认设置下,即使选择了「Release」时也会生成扩展名为「.vshost.exe」和「.pdb」的文件. 一.先解释一下各个文件的作用: .pdb文件: 程 ...

  10. 解决“C:\Windows\System32\ntdll.dll”。无法查找或打开 PDB 文件问题

    这些提示的问题完全没有必要去理会,因为一般情况下你点击本地windows调试,会报出这样问题很正常. 网上一些介绍什么要去选项卡栏勾选window连接器什么鬼,不建议用该方式,一旦你勾选那个方式虽然不 ...

随机推荐

  1. C# 之 async / await

    直接看一个例子 private async void button1_Click(object sender, EventArgs e) { var t = Task.Run(() => { T ...

  2. SSH 密钥认证

    目录 SSH协议概述 SSH 和 Telnet 的区别 SSH 相关命令 SSH 验证方式 基于密钥的安全认证 SSH 优化 expect 脚本免交互登录 sshpass 免交互登录 SSH协议概述 ...

  3. java 提供了哪些IO方式

    今天听了杨晓峰老师的java 36讲,感觉IO这块是特别欠缺的,所以讲义摘录如下: 欢迎大家去订阅: 本文章转自:https://time.geekbang.org/column/article/83 ...

  4. 使用DTK创建模糊背景窗口并自定义阴影效果

    DTK是deepin开发的基于Qt的开发套件,提供了大量的具有独特风格的美化控件,也提供了很多非常方便的API,下边我们用DTK实现一个模糊窗口,并设置其阴影效果. 使用场景 一切需要模糊窗口作为美化 ...

  5. codeforces 1029E Tree with Small Distances【思维+贪心】 【非原创】

    题目:戳这里 学习博客:戳这里 题意:给一个树加最少的边,使得1到所有点的距离小于等于2. 解题思路:分析样例3可以看出,如果一个点到1的距离大于2,那么建立1到该点的父亲节点的边将比直接与该点建边更 ...

  6. _.shuffle、_.debounce中下划线对象的理解

    Vue 官方教程中有_.shuffle._.debounce,不明白"_"是怎么来的,有什么意义? Lodash 和 Underscorejs 都有相关解释

  7. Unknown command '\b'. 关于Mysql导入外部数据库脚本报错的解决

    来自网络转载 还是字符集的问题 使用source导入外部sql文件: mysql> source F:\php\bookorama.sql;--------------source F:---- ...

  8. Single Round Math sdut3260高精度除以低精度

    做高精度除法,从高位开始除..高位除剩下的我们就*10扔给低一位处理,最终余数是在最低位取模得到的 高精除以高精,我们可以这么做,让除数在后面补零,刚好小于被除数,作若干次减法,减的次数加到商里面 然 ...

  9. vuepress & ReferenceError: window is not defined

    vuepress & ReferenceError: window is not defined bug Client Compiled successfully in 15.35s Serv ...

  10. Swift Playground All In One

    Swift Playground All In One Swift 5.3 Playgrounds in Xcode Xcode 11.5 https://developer.apple.com/vi ...