【C# 调试】.net中的 .pdb文件是什么,有什么用
阅读原文时间:2023年07月10日阅读:1

mscn:在 Visual Studio 调试器(C#)中指定符号 (.pdb) 和源文件

PDB全称Program Database,程序数据库 ( .pdb) 文件(也称为符号文件)将项目源代码中的标识符和语句映射到已编译应用中的相应标识符和说明。 这些映射文件将调试器链接到源代码,以进行调试。

这个文件会在我们调试的时候被使用到,这个东西可以理解为调试的时候应用程序和源文件之间的一个桥梁。正是归功于这个文件,我们才能在debug的时候看到程序当前执行相对应的代码和监视到一些变量。

.pdb 文件保存调试和项目状态信息,使用这些信息可以对应用的调试配置进行增量链接。 在调试时,Visual Studio 调试器使用 .pdb 文件来确定两项关键信息:

  • 要在 Visual Studio IDE 中显示的源文件名和行号。
  • 在应用中停止的断点位置。
  • 符号文件还会包含源文件的位置,以及要从中检索它们的服务器(可选),所以vs2022 可以同步符号文件获取源代码。(个人修改过了)
  • 每个源文件的MD5校验和都存储在.pdb文件中。
  • .pdb文件作用还包括Debug里的PDB是保存着调试和项目状态信息、有断言、堆栈检查、记录出错信息等代码。Release 里的PDB是记录:出什么错了+错误在哪行。所有的其他的数据都已经包含在了.NET Metadata中了; (个人修改过了)

调试器只会加载与在生成应用时创建的 .pdb 文件完全匹配的 .pdb 文件(即原始 .pdb 文件或副本) 。 这样的完全重复是必需的,因为即使代码本身未更改,应用的布局也可能会更改。

内容来源:https://docs.microsoft.com/zh-cn/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2022

因为微软并未公布PDB内部细节,只公开了一些API,所以对于这个文件一直是一个迷。

案例:pdb类型文件的作用之一:记录具体异常的关键信息,如文件路径和行号

1、用于本地代码调试。在本地调试的时候就用pdb(只是被程序员忽略,默认生成放在debug文件夹的子目录中),建立源代码和dll之间的联系,这样调试器才知道哪行代码设置断点、断言、变量、到底哪一行代码异常等。

2、用于公开库的参与本地调试。公开的库(dll)在别程序员电脑上运行时候,要加载对应库(dll).pdb文件。dll文件的代码才能参与本地调试。程序员才能在库中设置断点、才能查到出错代码的行号等。

3、调试是配置增量链接

PDB文件是在我们编译工程的时候产生的,它是和对应的模块(exe或dll)一起生成出来的。我们一般可能不会意识到PDB文件的重要性,因为如果只是我们本地进行开发,我们总是能够进行调适。这里我要引入两个概念:Private Build和Public Build1。Private Build指的是在开发机器上的编译,Public Build指的是在负责编译的机器上的编译。

正如上面我所说Private Build一般不会有问题,因为在编译出来的机器上进行调试所有必要的文件都在该在的地方。所有大部分不能调试的问题都发生在Public Build的情况下。

如果你的应用程序需要发布或者当作产品卖得,你就需要特别注意要保存你发布出去的那个版本的PDB文件和源文件。注意:你只有一次机会保存着发布出去的PDB文件,如果你弄丢了将无法找回(唯一性)。

每个源文件的MD5校验和都存储在.pdb文件中。

也许你会认为如果拿一份一模一样的源代码重新编译一个PDB文件,然后用来调试就行了。我也曾经这么认为过,直到有一天………

直接的原因是因为VS生成出来的二进制文件的Header部分里面包含了它对应的PDB的GUID,PDB也包含一个GUIID,这两个GUID实在编译的时候添加进去的。VS调试器在载入PDB的时候会去比对这个两个GUID,如果不一致,那么就不能使用。

当然上面那个原因只是一个表面现象,根本原因是既是两份一模一样的代码编译器编译出来的文件可能是不一样的。因为编译器在编译的时候会对代码进行优化,而同一份代码可能会有很多种优化的方法,它会根据当时的具体机器的环境等情况选择一个最快的生成方法。所以它生成出来的文件有可能是不一样的!所以如果连生成出来的文件都不一样,那么原来的那个PDB里面的符号对应的地址也就没有意义了。具体可以看:引用2

这里有一个非常简单的例子来演示。假设您正在构建的组件由一个函数和一个变量组成。

生成的文件内容是否像这样重要吗?
0000: MyFunc ()
0020: gGlobalVariable
还是这个?
0000: gGlobalVariable
0004: MyFunc ()

在功能上,这一点都不重要,但对于调试器来说,这是非常重要的。事实上,如果出错,可能会对调试会话造成严重破坏。在这个例子中,如果你使用来自构建#1的符号文件来尝试确定全局变量的值,当运行由构建#2构建的组件时,会发生什么?调试器将查阅符号文件并返回地址0020所引用的值。不幸的是,在组件构建#2中,全局变量不在那个地址。这里有一些值组成了MyFunc()的指令流。

以上内容来源:为什么 Visual Studio 要求调试器符号文件与构建它们的二进制文件完全匹配?|微软文档 (microsoft.com)

右建项目 属性》生成》常规》调试符号

自 C# 6.0 起,对于所有编译器版本而言,pdbonly 与 full 之间没有任何区别。 请选择 pdbonly。 若要更改 .pdb 文件的位置,请参阅 PdbFile

以下为有效值:
DebugType
Value     含义
full     使用当前平台的默认格式向 .pdb 文件发出调试信息:
Windows:Windows pdb 文件。
Linux/macOS:可移植 PDB 文件。
pdbonly     与 full 相同。 有关详细信息,请参阅下面的注释。
portable     使用跨平台可移植 PDB 格式向 .pdb 文件发出调试信息。
embedded     使用 可移植 PDB 格式向 .dll/.exe 自身(未生成 .pdb 文件)发出调试信息。

使用VS自带的DUMPBIN工具可以查看二进制文件所期望的PDB的GUID。基本用法就是DUMPBIN /HEADER 文件,具体用可可参考MSDN

查看PDB的GUID可以用下面这个工具,直接将PDB拉进去即可。http://www.codeproject.com/Articles/37456/How-To-Inspect-the-Content-of-a-Program-Database-P

PDB文件的查找策略

在运行调试》窗口》模块》附加加载信息。就可以pab查找的过程

1. 文件被执行或者被载入的地址

2. 就是硬编码在PE文件头中的那个地址。大家可以看到obj\<config>才是最原始生成的地址,只是之后被拷贝到了第一个地址中去了。

2.5 如果配置了符号服务器,第二步以后应该先去符号服务器的缓存目录下找,如果找不到再去符号服务器上去找。找到的话就会下载到缓存目录。

3. 第三部分是我VS中设置的一些符号查询的目录,因为我装过Reflector所以默认加了这几个目录在我的设置中。

4. Windows文件夹。

这里有一个比较有意思的现象就是,VS的查找策略都是会先找一个目录下的symbol\exe\project.pdb,然后exe\project.pdb,最后才找project.pdb。这个顺序有点出人意料。

可能有些人会觉得PDB文件的生成会对最终的应用程序的性能产生一定的影响,所以觉得在发布版中不应该生成PDB文件。

错!对于.NET应用程序来说,生成PDB文件不会影响编译器的优化,所以也完全不会影响应用的性能。只会对于生成的程序集中的一个DebuggableAttribute的属性产生影响。有兴趣的人可以阅读Do PDB Files Affect Performance?

1)选择工具>选项>调试>符号,并确保已选中“ Microsoft符号服务器(.NET框架相关的库)和NuGet.org符号服务器”选项。为符号高速缓存指定目录是避免再次下载相同符号,提高源码查看效率。

2)在“工具” >“选项” >“调试” >“常规”中禁用【启用“仅我的代码”】

3)在“工具” >“环境” >“文档” >勾选【在解决方案的管理器中显示杂项】点击完成,再次运行调试的时候,编译器就会从https://raw.githubusercontent.com/下载Dll库的源代码(仅限.net core)

在github.com页面点击raw,就是raw.githubusercontent.com界面,raw.githubusercontent.com返回存储在github中的文件的原始内容(纯文本)。例如:SpinLock.cs

Visual Studio 提供两种自动符号加载模式:

  • 自动加载所有模块的符号,除非排除:如标题所示,除非通过单击"指定排除的模块"将模块添加到排除列表中,否则 Visual Studio 将尝试为进程中的所有模块加载符号。如果您希望为进程中的几乎所有内容加载符号,或者由于内存或调试启动性能原因,您不希望加载少数非常大的符号,则通常需要此设置。
  • 仅指定模块:默认情况下,此设置将加载磁盘上二进制文件旁边的符号,但不会尝试加载任何其他模块的符号,除非您通过单击"指定模块"将它们添加到包含列表中。例如,如果您想使用手动加载,但始终加载名称中带有"Microsoft"的任何内容的符号,则可以输入"*Microsoft*"

vs2022 默认是加载所有符号的,这大型项目中式很影响调试的。所以在大型项目中要设置成按需加载符号,如下操作:

  • 符号路径未指向正确的位置

  • 符号文件来自与进程中加载的模块版本不同的模块版本

  • Visual Studio 要求符号文件来自与模块完全相同的内部版本。它无法加载来自不同版本的符号,即使源代码相同

  • [仅限托管]"仅我的代码"设置阻止调试器加载符号文件

更多符号相关内容请查看

了解符号文件和 Visual Studio 的符号设置 - Azure DevOps 博客 (microsoft.com)

符号文件位置
符号缓存

自动符号加载