微软以不断发明新的专有文件格式而闻名,而.pdb文件的程序数据库格式就是其中之一。许多软件开发人员都在拼命寻找关于PDB内部的更多信息。好消息:搜索结束了!本文将PDB格式记录到最后一位,并提供一个Win32实用程序,该实用程序将复合PDB文件拆分为单独的文件。
软件开发和调试工具的编写人员经常面临着显示有关Windows 2000系统模块的符号信息的任务。例如,每个好的反汇编程序都不应该只显示原始的数字和地址,而应该尝试将它们解析为有意义的名称。否则,用户很难弄清楚反汇编代码的实际用途。要采取的第一步(也是最简单的一步)是使用隐藏在被检查模块的可移植可执行文件(PE)文件中的符号信息,以及它通过动态链接引用的所有模块。然而,这些信息并不足以完全理解反汇编代码,因为这些信息只涉及模块之间的接触点。要掌握内部(非导出)函数的语义,首先了解函数的名称通常非常有帮助。还必须知道它调用的从属函数的名称和它访问的全局变量。幸运的是,微软在Windows2000操作系统中以符号文件的形式发布了这些重要信息,这些文件的扩展名是.dbg和.pdb。

.dbg and .pdb Symbol Files

在安装Windows 2000、微软Visual C/C++和平台SDK之后,您仍然会丢失它们,必须在单独的步骤中设置的符号文件。请注意,符号文件必须始终与操作系统的“已更正的服务磁盘”(CSD)级别匹配。也就是说,在每次安装Service Pack和热修复程序之后,也必须始终更新符号文件。通常,符号安装程序会附带新的操作系统文件。安装程序名为symbolsx.exe,符号文件位于关联的存档文件symbols.cab中。首次运行安装程序后,您会发现包含Windows 2000操作系统的硬盘丢失了400MB到500MB的可用空间。默认情况下,符号文件安装在名为Symbols的目录树中,该目录树包含在Windows 2000系统根目录中(例如,C:\WINNT\Symbols)。

对于每个模块文件扩展名,将创建一个单独的子目录,并且每个系统模块都有两个文件扩展名为.dbg和.pdb的符号文件。如果您以前使用过WindowsNT4.0,您可能会想为什么现在每个Windows2000需要两个符号文件,而不是一个.dbg文件。原因是微软已经将所谓的“公共符号”移动到一个叫做程序数据库(Program Database,PDB)的单独文件中。基本上,PDB文件是由几个独立的流组成的复合文件。您可以将PDB复合文件视为单个文件中的简单平面文件系统,其中的流与文件系统承载的文件相对应。其中一个流包含一系列可变长度的记录,这些记录描述了在关联模块中定义的符号。

在上一个示例中,假定Windows 2000安装在目录C:\ WINNT中,符号根目录为C:\ WINNT\Symbols,则Windows 2000内核模块ntoskrnl.exe的符号文件将安装为C:\ WINNT\Symbols\exe\ntoskrnl.dbg和C:\ WINNT\Symbols\exe\ntoskrnl.pdb。同样,ntdll.dll符号文件的路径将是C:\ WINNT\Symbols\dll\ntdll.dbg和C:\ WINNT\Symbols\dll\ntdll.pdb。这些文件的主要目的之一是允许调试器或反汇编程序查找可归属于模块中给定二进制地址的最近符号名。例如,如果反汇编程序发现要显示的下一个汇编语言指令是call72A05A2Eh,那么最好为用户提供与函数入口点0x72A05A2E相关联的实名,例如call_pMemAlloc@4。

PDB文件布局全局

在本文中,我将介绍更一般的任务,即解析PDB文件并将其分解为各个部分,而不考虑其各种流的内容。这里提供的示例应用程序是一个简单的Win32控制台模式程序,它期望PDB文件的路径,并将其组成部分存储在单独的文件中。根据命令行中指定的选项开关,可以提取以下信息:

  • PDB文件头,包含有关文件的一般信息
  • 分配位表,指定文件的哪些部分正在使用
  • 根流,包含有关所有数据流的特定信息
  • 根流中列出的所有数据流

PDB文件最基本的结构属性是将其细分为大小相等的页面。最常用的页面大小是1KB(1024字节),但我的研究表明2K和4K页面也是合法的。(可以通过检查Microsoft的dbghelp.dll如何处理PDB文件来验证这一点。)PDB流是包含一致信息的文件页序列。流最基本的特性是它的页面可以按任意顺序位于文件中的任何位置。
当一个流被读或写时,流目录负责告诉应用程序哪些页面需要按哪个顺序访问。此目录本身存储在名为根流的流中。此外,嵌入的分配位表会跟踪已使用和未使用的页。由于流的重新排列,一旦PDB文件中出现“洞”,此表就必不可少。如果流被重写到PDB文件的末尾,释放它之前占用的页,则分配位表反映在使用新页时前页是空闲的。该方案是从简单的操作系统(如MS-DOS)及其胖文件系统借鉴而来的,其中有一个类似的表指定将哪些磁盘扇区分配给文件。
下图显示了1-KB、2-KB和4-KB模式下PDB文件的典型基本布局。应该使用哪个页面大小取决于要存储在流中的数据。如果页面大小增加,分配位表和根流就会变小。另一方面,较大的页面大小会导致更多的页面悬置;也就是说,如果流大小不是页面大小的确切倍数,则会浪费更多的文件字节。同样的问题也出现在文件系统中,在文件系统中,必须正确选择磁盘扇区大小,以避免过度的扇区挂起。大多数PDB文件,如Windows 2000符号文件和微软Visual C/C++ 6所生成的调试信息,都采用1-KB方案,如下图左侧所示。

 

 在1-KB页面模式下,PDB文件中最多可以存储64MB,这只是将分配位的数量乘以页面大小的结果。稍后我将展示,PDB页码存储为基于零的16位数量。因此,分配位表永远不会包含超过0[ts]10000位(8KB)。
PDB头总是占据第一个文件页,后面跟着一个或多个包含分配位的页。头的结构定义如清单1所示。前44个字节组成ID字符串PDB_SIGNATURE_200,指定文件类型和版本。撰写本文时,最新版本为2,这是Windows 2000符号文件和Visual C/C++调试信息所使用的版本。dPageSize成员指示应用于文件中所有页的页大小,wStartPage是分配位之后的第一个数据页的从零开始的页码。分配位表的大小始终可以通过从wStartPage中减去1(对于头页)并将结果乘以dPageSize来计算。wFilePages成员指定PDB文件中存储的页数,并且应该始终与文件大小(字节)除以页面大小相匹配。

Listing 1PDB File Header

#define PDB_SIGNATURE_200 
    "Microsoft C/C++ program database 2.00\r\n\x1AJG\0"

#define PDB_SIGNATURE_   44 //size of signature (bytes)

//-----------------------------------------------------------------
typedefstruct_PDB_HEADER
{
BYTE abSignature [PDB_SIGNATURE_];
//PDB_SIGNATURE_200 DWORD dPageSize; //0x0400, 0x0800, 0x1000 WORD wStartPage; //0x0009, 0x0005, 0x0002 WORD wFilePages; //file size / dPageSize PDB_STREAM RootStream; //stream directory WORD awRootPages []; //pages containing PDB_ROOT }
PDB_HEADER,
*PPDB_HEADER, **PPPDB_HEADER;

标签: none

添加新评论