探索PDB的秘密

最近要在公司做一个关于Debug Interface Access SDK的技术分享,我又重新学习整理了这个库。 Debug Interface Access SDK简称为DIA,是微软提供的一个用来访问存储在PDB(Program Database)文件中信息的库。链接是https://msdn.microsoft.com/en-us/library/x93ctkx8.aspx。

PDB我们都知道,是代码编译生成二机制文件后,存储调试信息的文件。我们调试程序的时候就需要PDB文件,有了PDB文件里面的调试信息,就把地址翻译成有意义的变量名、函数名。一般我们不用直接跟PDB文件打交道,都是调试器自己去读取所需要的信息。 PDB文件里面包含了代码编译后的很多秘密,利用好DIA库能够对我们有很大的帮助。举个例子,有时候我们去接触一个陌生的代码库,可能它的代码量很大或者缺少文档,我们一开始无从下手。为了了解它的架构,我们会读一读代码,绘制一下它的类图、看一看类之间的关系、如何交互。我这里有个小工具,通过DIA库读取代码编译后生成的PDB文件,获取其中类的基类的关系信息,然后通过Graphviz绘制成的图片。如下截图就是工具生成Duilib这个库类的继承关系信息。 explore the secrets of pdb

利用工具比我们人工去分析代码有以下优点:

好了,我正式开始了解一下DIA库。它也不用特别的去下载,它在Visual Studio的安装目录里面,如图DIA SDK目录:

explore the secrets of pdb

DIA2Dump这个工程应该是可以编译成功的,我们运行起来看看,它是个命令行程序,命令行的用法说明如下图: explore the secrets of pdb

可以看到,这个工具可以输出模块、公共符号、全局符号、类型、代码文件等等信息。我们用-m输出一下模块信息,如下图: explore the secrets of pdb

可以看到模块信息显示了所有编译链接成这个模块的obj文件,还有导入模块信息。

我们用-f输出一下代码文件信息,如下图: explore the secrets of pdb

可以看到obj文件是由哪些代码文件编译而成的,以及这些文件的md5值。

我们用-lines输出一下代码行信息,如下图: explore the secrets of pdb

可以看到代码每一行里函数生成的指令的地址和大小。

更多DIA2Dump的用法你自己去探索。

以前我在360浏览器的时候,为了渠道的推广做安装包体积的优化。当时产品有个需求,精简浏览器的功能,只保留一些常用的功能,其他功能都裁剪掉。到技术实现的时候,我们就从DIA2Dump中得到了启发,通过DIA库的接口,我们统计一下每个目录里每个代码文件生成的二进制文件大小,看看那些功能模块不常用而占用的体积比较大,然后就把它裁剪掉。下面图片就是我们通过DIA获取到的代码文件生成二进制大小的信息,通过TreeMap工具展示dns这个目录下面的情况: explore the secrets of pdb

我们开始讲一下DIA的接口。DIA提供都是COM接口,大多数都是显式方法获取,无需QueryInterface。下面是几个主要的接口:

比较特别的是IDiaSymbol,它的方法不是都能返回有效值,因为不同的Symbol Tag的特性不一样。比如说如果一个IDiaSymbol的Symbol Tag是SymTagFunction,它可以调用get_virtualAddress方法来返回它在PE文件中的虚拟地址。如果是SymTagUDT,它可以调用get_virtualTableShape获得类的虚表信息。

此外,IDiaSymbol都有get_symTag和get_symIndexId方法。symIndexId在PDB中是唯一的。一个IDiaSymbol可以关联到另一个IDiaSymbol,能够获取IDiaSymbol的方法一般也有一个对应的获取相应IDiaSymbol ID的方法。比如get_classParent和get_classParentId。

IDiaSymbol本身有180多个方法,Symbol Tag也有30多种,那么就存在一个问题:对于获取到的一个IDiaSymbol,我们怎么知道它的哪些方法可以获得有效值?于是就有了IDiaPropertyStorage接口帮助枚举IDiaSymbol方法的有效值。

好吧,结束了。看完之后不知道你对DIA有了清晰的了解,有什么想法,欢迎与我探讨。