符号文件定位器






4.73/5 (13投票s)
如何使用 Debug Interface Access (DIA) 应用程序接口来定位程序集引用的调试符号文件。

引言
我之前的文章旨在介绍最新的Microsoft Debug Interface Access (DIA)基础设施。文章的重点是程序数据库 (PDB) 文件,展示了 DIA 系列的一些函数,并提供了一个将 DIA 封装在一组对程序员友好的 C++ 虚拟接口中的项目。
在本文中,我将继续探索 Microsoft DIA 的潜力,重点关注可移植可执行文件。与上一篇文章一样,我将提供一个带有源代码的项目,将 DIA 接口封装在一组 C++ 虚拟接口中。下面的快照显示了本文介绍的控制台应用程序以及可以从我的示例中检索到的信息类型。
- 公共符号存储的位置
- 本地符号存储的位置
- 程序集引用的 PDB 符号文件的 GUID
- 程序集引用的 PDB 符号文件的完整路径
- 系统找到的 PDB 符号文件的实际路径
- 系统在搜索 PDB 符号文件时尝试的路径列表
背景
Debug Interface Access (DIA) 是一个客户端应用程序在处理存储在程序数据库 (PDB) 文件中的符号信息时应该使用的新应用程序接口。下图显示了现有不同 API 的概述。

出于历史原因,大多数处理符号文件的开发人员通常会使用广为人知的 DbgHelp.dll 接口。新的 DIA 接口不仅可以用于查询 PDB 文件,还可以用于检查可移植可执行文件(EXE、DLL、SCR、CPL、SYS…)并从中收集与调试相关的信息。
全局视图
作为提醒,下图显示了在生成代码和调试代码时涉及的各个部分的全局视图。编译器会创建一个程序集及其关联的调试符号文件。调试器打开程序集并尝试查找包含调试信息的文件。

符号文件搜索
在能够设置断点或查看任何变量之前,调试器首先要做的工作是查找与要调试的程序集关联的调试符号文件。如前所述,本机和托管程序集都嵌入了包含代码调试数据的 PDB 文件的 GUID 和完整路径名。
默认情况下,链接器会将 PDB 文件到镜像文件的完整限定名写入。如果您愿意,可以使用以下技术剥离 PDB 文件的路径,只保留 PDB 文件名(包括扩展名)。您是否查看过 Windows 镜像文件(例如 notepad.exe、kernel32.dll…)?据我所知,Microsoft 在构建其镜像时总是会剥离 PDB 的路径。这可以节省构建镜像文件时的一些字节,并隐藏镜像在生成时在生成计算机上的目录名称(例如,“d:\temp\version2\free_demo_build_whithout_some_features\.... xy.pdb”)。
当调试器附加到程序集时,它会读取其镜像文件并检查它是否已使用调试信息进行了编译。如果为真,它会读取镜像文件中找到的预期关联 PDB 文件的嵌入式 GUID。基于这些信息,它会在不同位置搜索并尝试找到 GUID 与要调试的镜像文件中的 GUID 相匹配的 PDB。在搜索过程中,调试引擎会在以下位置搜索:
- 镜像文件中嵌入的 PDB 文件的完整路径(如果可用)所指向的位置
- 镜像加载的目录
- 本地符号缓存(如果可用)
- 远程符号服务器(如果可用)。当在远程符号服务器上找到文件时,符号引擎会将 PDB 文件复制到本地符号缓存。(下次调试进程时,由于 PDB 文件将在本地符号缓存中找到,调试会话的启动将更快。)
架构
本文介绍的项目软件架构的构建概念与我上一篇文章中的相同。接口不能直接创建;它们只能被获取。这个概念的目的是让消费者摆脱任何内存管理和责任。

所有类都是虚拟的,因此不能直接实例化。
Using the Code
本文介绍的项目由两部分组成:
SymbolsParser
:C++ 项目 - 实现 SymbolsParser.dll,它是对一些 DIA 接口的包装器。ConsoleSymbolsParser
:C++ Win32 控制台项目 - 使用SymbolsParser
并显示有关程序集引用的 PDB 文件的一些信息。
打开可移植可执行文件 (PE) 文件分两步完成:
- 使用
ISymbolsParserFactory::Create()
函数实例化SymbolsParser
。ISymbolsParser* pISymbolsParser = ISymbolsParserFactory::Create();
- 使用
ISymbolsParser::Open()
函数打开特定的可执行文件。wstring sFile = L"c:\\windows\\system32\\eventvwr.exe"; IPeFile* pIPeFile = pISymbolsParser->OpenPeFile(sFile);
然后可以调用 IPeFile
函数。
- 使用
IPeFile::GetGuid()
函数检索引用的 PDB 文件的 GUID。// Retrieve the file GUID wstring sGuid = pIPeFile->GetGuid(); wcout << L"GUID:" << sGuid.c_str() << endl;
- 使用
IPeFile::GetBuiltinSymbolsPath()
函数检索引用的 PDB 文件的完整路径。// Retrieve the Symbols Path referenced by the File wcout << L"Built-in PDB Path:" << pIPeFile->GetBuiltinSymbolsPath().c_str() << endl;
- 使用
IPeFile::GetFoundSymbolsPath()
函数检索 PDB 文件被找到位置的完整路径。// Retrieve the Symbols Path found by the system wcout << L"Found PDB Path:" << pIPeFile->GetFoundSymbolsPath().c_str() << endl;
在查找被可执行文件引用的 PDB 文件的过程中,符号引擎会在不同位置进行搜索。使用 ISymbolsParser::GetSymbolsSearch()
接口,可以获得一个对象,该对象可用于枚举在此搜索过程中访问过的路径。
// Collect Search paths details
ISymbolsSearch* pISymbolsSearchPath = pISymbolsParser->GetSymbolsSearch();
typedef std::map<wstring, bool> Paths;
Paths paths = pISymbolsSearchPath->GetPaths();
Paths::iterator it = paths.begin();
for ( ;it!=paths.end();it++)
{
std::wstring s = it->first;
wcout << s.c_str() << endl;
}
通过获取指向 ISymbolsStore
接口的指针,还可以检索本地和远程符号服务器的位置。
// Obtain a pointer
ISymbolsStore* pIEnvironment = pISymbolsParser->GetSymbolsStore();
// Collect data
wcout << L"Public Symbols Store:" <<
pIEnvironment->GetPublicSymbolsStore().c_str() << endl;
wcout << L"Local Symbols Store:" <<
pIEnvironment->GetLocalSymbolsStore().c_str() << endl;
在适当的时候,必须使用 ISymbolsParser::Destroy()
函数释放 SymbolsParser
分配的资源。
环境
该项目已使用 Visual Studio 2008 开发,并且仅在 Windows Vista Ultimate 32 位系统上进行了测试。
建议
一旦获得 IPeFile
,就可以检索 IPdbFile
并继续收集有关程序集引用的 PDB 文件的更多详细信息。本项目未实现此桥接。

我将此桥接的实现留给读者作为练习。读者可以参考我之前的文章,其中介绍了 IPdbFile
接口。
链接
历史
- 2009 年 7 月 6 日:首次发布