每个开发人员必须知道PDB文件知识

大多数开发人员都意识到PDB文件有助于您进行调试,但仅此而已。如果你不知道PDB文件是怎么回事,不要觉得很糟糕,因为虽然有文档在那里,但它分散在周围,而且大部分是为编译器和调试器编写器准备的。虽然编写编译器和调试器非常酷和有趣,但这可能不是你的工作。
我想做的是把每个在微软操作系统上进行开发的人都必须知道的PDB文件放在一个地方。这些信息也适用于本机开发人员和托管开发人员,不过我将提到一个特定于托管开发人员的技巧。我将从讨论PDB文件存储和内容开始。由于调试器使用PDB文件,我将详细讨论调试器如何为二进制文件找到正确的PDB文件。最后,我将讨论调试器在调试时如何查找源文件,并向您展示一个与调试器如何查找源代码相关的常用技巧。
在我们开始之前,我需要定义两个重要的术语。在开发计算机上执行的生成是私有生成。在生成计算机上完成的生成是公共生成。这是一个重要的区别,因为调试在本地生成的二进制文件很容易,总是公共生成导致问题。
所有开发人员需要知道的最重要的事情是:PDB文件和源代码一样重要!很多公司没有人能找到在生产服务器上运行的构建的PDB文件。如果没有匹配的PDB文件,您的调试挑战几乎是不可能的。通过大量的努力,可以在没有正确的PDB文件的情况下找到问题,但是如果您首先拥有正确的PDB文件,它将为您节省大量的资金。

正如Visual Studio的开发经理约翰坎宁安(John Cunningham)在2008年的PDC会议上所说的,“热爱、保持和保护您的pdb”。至少,每个开发商店都必须设置一个符号服务器。您还可以在Windows调试工具帮助文件中阅读Symbol Server文档本身。请查看这些资源以了解有关详细信息的更多信息。简而言之,符号服务器存储所有公共构建的pdb和二进制文件。这样,无论哪个构建有人报告崩溃或问题,您都有与调试器可以访问的公共构建完全匹配的PDB文件。Visual Studio和WinDBG都知道如何访问符号服务器,如果二进制文件来自公共构建,调试器将自动获取匹配的PDB文件。
大多数人在将PDB文件放入Symbol服务器之前还需要做一个准备步骤。这一步是在您的公共PDB文件上运行源服务器工具,这被称为源索引。索引嵌入了版本控制命令,以拉动在特定公共构建中使用的确切源文件。因此,在调试公共生成时,您永远不必担心找到该生成的源文件。如果您是一个一人或两人的团队,则有时可以不使用源服务器步骤。
我听到过一些团队对设置符号服务器的抱怨,他们的软件太大太复杂。我不得不承认,当我听到别人说“我的团队功能失调”时,你的软件绝不可能比微软做的任何事情都更大、更复杂。它们将所有产品的每个版本的索引和存储到一个符号服务器中。这意味着从Windows到Office,再到SQL,再到游戏,所有东西都存储在一个中心位置。我的猜测是,在雷德蒙德的34号楼只不过是存储所有这些文件的SAN驱动器,而大楼里的每个人都支持这些SAN。能够在微软内部调试任何东西都是非常令人惊奇的,而且您不必担心符号或源代码(前提是您对源代码树拥有适当的权限)。

在关键基础设施讨论结束后,让我来看看PDB中的内容以及调试器如何找到它们。PDB文件的实际文件格式是一个严格保密的秘密,但是微软提供了api来为调试器返回数据。本机C++ PDB文件包含相当多的信息:

  • 公共、私有和静态函数地址
  • 全局变量名称和地址
  • 参数和局部变量的名称和偏移在堆栈上的位置
  • 由类、结构和数据定义组成的类型数据
  • 帧指针省略(FPO)数据,这是在x86上进行本机堆栈遍历的关键
  • 源文件名及其行

一个.NET PDB只包含两部分信息:源文件名及其行和本地变量名。所有其他信息都已在.NET元数据中,因此无需在PDB文件中重复相同的信息。
将模块加载到进程地址空间时,调试器使用两条信息来查找匹配的PDB文件。第一个显然是文件名。如果加载ZZZ.DLL,调试器将查找ZZZ.PDB。最重要的部分是调试器如何知道这是与此二进制文件完全匹配的PDB文件。这是通过嵌入在PDB文件和二进制文件中的GUID完成的。如果GUID不匹配,您肯定不会在源代码级别调试模块。
.NET编译器和本机的链接器将此GUID放入二进制和PDB中。既然编译的行为创建了这个GUID,那么请停下来考虑一下。如果您有昨天的版本,但没有保存PDB文件,您是否可以再次调试二进制文件?不!这就是为什么保存每次生成的PDB文件如此重要的原因。因为我知道你在想,所以我会继续回答你脑海中已经形成的问题:不,没有办法更改GUID。
但是,可以查看二进制文件中的GUID值。使用Visual Studio附带的命令行工具DUMPBIN,可以列出可移植可执行文件(PE)的所有部分。要运行DUMPBIN,请从程序菜单中打开Visual Studio 2008命令提示符,因为需要设置PATH环境变量才能找到DUMPBIN EXE。顺便说一下,如果你对DUMPBIN向你展示的信息感兴趣的话,我强烈推荐Matt Pietrek在2002年2月和2002年3月的MSDN杂志上关于PE文件的权威文章。

DUMPBIN有很多命令行选项,但是显示构建GUID的选项是/HEADERS。Pietrek文章将解释输出,但对我们来说重要的一点是调试目录输出:

Debug Directories
Time Type Size RVA Pointer
——– —— ——– ——– ——–
4A03CA66 cv 4A 000025C4 7C4 Format: RSDS,
  {4B46C704-B6DE-44B2-B8F5-A200A7E541B0}, 1,
C:junkstuffHelloWorldobjDebugHelloWorld.pdb

根据调试器如何确定正确匹配的PDB文件的知识,我想讨论调试器在哪里查找PDB文件。调试时,通过查看Visual Studio模块窗口的符号文件列,可以看到所有这些命令都是自己加载的。首先搜索的是加载二进制文件的目录。如果PDB文件不存在,调试器将查找嵌入PE文件中的调试目录中的硬编码生成目录。如果查看上面的输出,就会看到完整的路径C:JUNKSTUFFHELLOWORLDOBJDEBUGHELLOWORD.PDB。(用于生成.NET应用程序的MSBUILD任务实际上生成到OBJ<CONFIG>目录,并仅在成功生成时将输出复制到调试或发布目录。)如果PDB文件不在前两个位置,并且在计算机上为设置了符号服务器,则调试器将在符号服务器缓存目录中查找。最后,如果调试器在Symbol Server缓存目录中找不到PDB文件,它将在Symbol Server本身中查找。此搜索顺序就是为什么本地生成和公共生成部件从不冲突的原因。
调试器搜索PDB文件的方式对几乎所有要开发的应用程序都很好。PDB文件加载更有趣的地方是那些需要您将程序集放入全局程序集缓存(GAC)中的.NET应用程序。我特别关注的是你的SharePoint和你对web部件的残忍,但是还有其他的。对于本地计算机上的私有构建,使用起来很容易,因为调试器将在构建目录中找到PDB文件,如我上面所述。当您需要在另一台计算机上调试或测试私有构建时,就会出现问题。

在另一台机器上,我看到许多开发人员在使用GACUTIL将程序集放入GAC后所做的工作是打开一个命令窗口,在C:windowssembly中搜索程序集在磁盘上的物理位置。尽管将来可能会发生更改,但为任何CPU编译的程序集实际上都位于如下目录中:C:WindowsassemblyGAC_MSILExample1.0.0.0__682bc775ff82796a

示例是程序集的名称,1.0.0.0是版本号,682bc775ff82796a是公钥标记值。推导出实际目录后,可以将PDB文件复制到该目录,调试器将加载该目录。
如果你现在对像这样挖掘GAC感到有点不安,你应该,因为它是不受支持和脆弱的。有一个更好的方法,似乎几乎没人知道DEVPATH。其思想是,您可以在.NET中设置一些设置,它会将您指定的目录添加到GAC中,因此您只需将程序集和它的PDB文件放入该目录,这样调试就容易得多。仅在开发计算机上设置DEVPATH,因为存储在指定目录中的任何文件都不会像在真正的GAC中那样进行版本检查。
顺便说一下,如果你在任何一个互联网搜索引擎中搜索DEVPATH,其中一个最热门的条目是Suzanne Cook的一篇过时的博客文章,她说微软正在摆脱DEVPATH。这不再是事实。和其他博客一样,看看苏珊博客上的日期:2003年。这相当于互联网时代的1670年。
要使用DEVPATH,首先要创建一个目录,该目录对所有帐户都具有读访问权限,至少对开发帐户具有写访问权限。此目录可以位于计算机上的任何位置。第二步是设置一个系统范围的环境变量DEVPATH,其值是您创建的目录。有关DEVPATH的文档并没有说明这一点,但是在执行下一步之前,请先设置DEVPATH环境变量。

要告诉.NET运行时已设置DEVPATH,需要根据应用程序的需要将以下内容添加到APP.CONFIG、WEB.CONFIG或MACHINE.CONFIG中:

<configuration>
   <runtime>
      <developmentMode developerInstallation=”true”/>
   </runtime>
</configuration>

一旦打开开发模式,您就会知道在进程中缺少DEVPATH环境变量或者您设置的路径不存在,如果应用程序在启动时死机,错误消息表示完全非直观的:“注册表的无效值”。如果确实要在MACHINE.CONFIG中使用DEVPATH,请格外小心,因为计算机上的每个进程都会受到影响。在一台机器上导致所有.NET应用程序失败不会赢得办公室里的许多朋友。
每个开发人员需要知道的关于PDB文件的最后一项是源文件信息如何存储在PDB文件中。对于已在其上运行源索引工具的公共构建,存储是将源文件获取到所设置的源缓存中的版本控制命令。对于私有构建,存储的是编译器用来生成二进制文件的源文件的完整路径。换句话说,如果在C:FOO中使用源文件MYCODE.CPP,那么PDB文件中嵌入的是C:FOOMYCODE.CPP。这可能是你已经怀疑过的,但我只是想说清楚。
理想情况下,所有的公共构建都会立即自动被源代码索引并存储在符号服务器中,因此如果您甚至不必再考虑源代码在哪里的话。然而,一些团队在PDB文件中不做源索引,直到他们做了冒烟测试或其他祝福,看看该构建是否足够好让其他人使用。这是一个非常合理的方法,但是如果必须在生成的源代码被索引之前调试生成,最好将该源代码拉到与生成计算机使用的驱动器和目录结构完全相同的位置,否则在源代码级别调试时可能会遇到一些问题。虽然Visual Studio调试器和WinDBG都有设置源搜索目录的选项,但我发现很难找到正确的方法。
对于较小的项目,这没有问题,因为您的源代码总是有足够的空间。生活更困难的地方是在更大的项目上。如果你有30MB的源代码,而你的C:驱动器上只剩下20MB的磁盘空间,你该怎么办?有办法控制PDB文件中存储的路径不是很好吗?
虽然我们不能编辑PDB文件,但是有一个简单的技巧可以控制PDB文件中的路径:SUBST.EXE。subt所做的是将路径与驱动器号相关联。如果您将源代码下拉到C:DEV并执行“SUBST R:C:DEV”,那么如果您键入“DIR C:DEV”,R:drive现在将在其顶层显示相同的文件和目录。您还将在资源管理器中看到R:drive作为新驱动器。也可以通过将驱动器映射到资源管理器中的共享目录来实现驱动器到路径的影响我个人更喜欢subt方法,因为它不需要机器上的任何共享。虽然有些人认为可以通过<DRIVE>$共享,但有些组织禁用了该功能。
在生成计算机上要做的是设置一个启动项,该启动项执行特定的SUBST命令。当生成系统帐户登录时,它将提供新的驱动器号,这是您进行生成的位置。通过对PDB文件中嵌入的驱动器和根目录的完全控制,在测试机上设置源代码所需做的全部工作就是在任何需要的地方将其拉下来,并使用与生成机使用的驱动器号相同的驱动器号执行子命令。现在调试器中不再考虑源匹配了。

posted on 2019-11-28 16:28  活着的虫子  阅读(1500)  评论(0编辑  收藏  举报

导航