探索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这个库类的继承关系信息。

QQ20170115170317

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

  • 速度快,几分钟之内就可以看到结果。
  • 信息完整。c++代码里面有很多接口类,我们自己去看代码,找到接口背后的实现类是什么比较麻烦。如果一个接口有多个实现,我们可能会遗漏一些信息。

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

  • bin目录是DIA库的dll文件,初始化DIA COM接口失败的时候,可能需要注册一下msdia×.dll
  • include目录则是DIA库的头文件,lib目录则是DIA库的lib文件,没有什么好讲的。
  • Samples目录里面有个DIA2Dump工程,这个工程有一些DIA库的使用例子,值得看看。

QQ20170115171109

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

QQ20170115170738

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

QQ20170115171424

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

我们用-f输出一下代码文件信息,如下图:

QQ20170115171630

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

我们用-lines输出一下代码行信息,如下图:

QQ20170115171843

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

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

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

QQ20170115173747

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

  • IDiaDataSource,从PDB文件中加载数据,可获取IDiaSession接口
  • IDiaSession,获取信息的枢纽,它几乎可以获取到其他的所有的接口
  • IDiaEnum*,IDiaEnumSourceFiles、IDiaEnumLineNumbers、IDiaEnumSymbols,信息枚举接口,可以枚举对应的IDiaSourceFile、IDiaLineNumber、IDiaSymbol
  • IDiaSourceFile,可以获取代码文件信息
  • IDiaLineNumber,可以获取代码文件所在行的信息
  • IDiaSymbol,具有不同的Symbol Tag,可获取大多数符号信息

比较特别的是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有了清晰的了解,有什么想法,欢迎与我探讨。

《探索PDB的秘密》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注