如何检查程序数据库 (PDB) 文件的内容






4.87/5 (62投票s)
了解在使用 Visual Studio 或 WinDbg 调试应用程序时,您日常使用的文件的相关信息。
引言
作为 Windows 软件开发人员,我们都广泛使用 Visual Studio 和/或 WinDbg 来单步调试代码、设置断点、监视变量以及执行与应用程序调试相关的许多其他有用任务。我们知道存在一个内部机制,用于使调试器能够将源代码映射到二进制文件并进入许多可用的运行时库。为此,调试器会使用托管代码和非托管代码的程序数据库 (PDB) 文件。托管代码的 PDB 包含的调试信息较少,因为这些信息位于 PE 部分的元数据节中。
本文有几个目标
- 展示 PDB 文件的存在以及调试器如何使用它们。
- 展示用于检索其内容现有技术。
- 就 PDB 文件在调试中的重要性以及其中嵌入的信息类型给出一个概念。
- 展示一个项目,该项目在晦涩的 DIA 类之上实现了一个方便的 C++ 包装器,以及一个 PDB 检查器前端。这是专门介绍 PDB 及其可执行文件对应物的系列文章的第一部分。本文集中讨论这些 PDB 文件的一个方面,即引用的模块。
背景
正如 John Robbin 在下面提到的文章中所解释的那样,“本机 C++ PDB 文件包含大量信息:
public
、private
和static
函数地址- 全局变量名称和地址
- 参数和局部变量的名称以及在堆栈中找到它们的位置
- 源文件名及其行号等……”
“.NET PDB 文件仅包含两类信息:(来自 John Robbin 在下面提到的文章)
- 源文件名
- 文件名及其行号和局部变量名
所有其他信息已包含在 .NET 元数据中,因此无需在 PDB 文件中重复相同的信息。
对于不熟悉 Windows 调试接口访问、程序数据库 (PDB) 和此处介绍的基本概念的读者,请参考以下几个重要链接:
- PDB 文件:每个开发人员都必须知道的事项 (John Robbin)
- 调试接口访问 SDK
- Dia2dump 示例
- DIA SDK 问题
当使用调试信息编译时,可执行文件包含对关联 PDB 文件的两个引用:
- 与放置在预期 PDB 文件中的 GUID 匹配的 GUID
- 将在调试会话期间使用的关联 PDB 文件的完整路径
当启动要调试的程序时,调试器会进入可执行文件并尝试找到正确的 PDB 文件以继续调试会话。上面的链接解释了这些内容以及如何设置符号服务器。
Using the Code
此处介绍的 PDB 项目包含三个部分:
- PdbParser:C++ 项目 - 实现 PdbParser.dll,它是 DIA 接口的包装器。
- PdbInspectorConsole:C++ Win32 控制台项目 - 使用
PdbParser
并显示 PDB 文件中引用的模块。 - PdbInspector:C++ MFC 项目 - 使用
PdbParser
并显示 PDB 文件中引用的模块以及一些与模块相关的可用详细信息。
环境
该项目仅在 Windows Vista Ultimate 32 位上开发和测试。
类层次结构
如前所述,Microsoft DIA SDK 是一个基于 COM 的接口,用于处理 PDB 文件。此 SDK 的问题在于它包含大量的接口和函数。这里提供的 PdbParser
封装了这些细节,并提供了一组面向任务的简单接口。在此版本中,PdbPaser
专注于模块的收集。PdbParser
被组织成一组抽象层。打开 PDB 文件分两步完成:
- 使用
IPdbParserFactory::Create()
函数实例化PdbParser
IPdbParser* pIPdbParser = IPdbParserFactory::Create();
IPdbParser::Open()
函数打开特定文件IPdbParser* pIPdbParser = IPdbParserFactory::Create();
IPdbFile* pIPdbfile = pIPdbParser->OpenFile(L"test.pdb");
为了检索 PDB 文件中引用的特定模块的详细信息,您需要经过另外三个步骤:
- 使用
IPdbFile::GetModules()
函数收集模块。 - 使用
IPdbModule::GetModuleDetails()
函数收集特定模块的详细信息。 - 使用可用的
IPdbModuleDetails
函数。
//Traverse the Modules
vector<ipdbmodule*> vModules = pIPdbfile->GetModules();
vector<ipdbmodule*>::iterator it = vModules.begin();
for( ;it!=vModules.end();it++)
{
IPdbModule* pIPdbModule = *it;
wprintf(L"%ws\n", pIPdbModule->GetName().c_str());
}
为了检索特定模块的源文件名,需要经过三个步骤:
- 使用
IPdbFile::GetModules()
函数收集模块。 - 使用
IPdbModule::GetSourceFiles()
函数收集特定模块引用的文件。 - 使用
IPdbSourceFile::GetFileName()
函数。
//Traverse the Source file Names collection.
std::vector<ipdbsourcefile*> vSources = pIPdbModule->GetSourceFiles();
std::vector<ipdbsourcefile*>::iterator it = vSources.begin();
for( ;it!=vSources.end(); it++)
{
IPdbSourceFile* pIPdbSourceFile = *it;
wprintf(L"%ws\n", pIPdbSourceFile->GetFileName().c_str());
}
在适当的时候,使用最后一个步骤释放 PdbParser
分配的资源。
- 使用
IPdbParserFactory::Destroy()
函数释放分配的资源。
IPdbParserFactory::Destroy();
下图显示了访问器式层次结构。
历史
- 2009 年 6 月 19 日 - 本项目重点在于模块的枚举及其部分详细信息。
- 2009 年 6 月 23 日 - 添加了源文件名枚举。
- 2009 年 7 月 2 日 - 修正了打开/关闭问题;添加了
IsStripped()
方法。 - 20.08.2011
- 添加了对 PDB 文件拖放到 UI 的支持。
- 移除了控制台演示。
- 更新了我的网址。
- 30.08.2011
- 显示编译器名称和版本。
- 显示校验和类型和值。
- 26.06.2013
- 在 Windows 7-64 位上使用 VStudio 2k8 在调试和发布模式下构建和测试。