使用VC++生成调试信息ZhangTao,zhangtao.it@gmail.com, 译自 “Generating debug information with Visual C++”,Oleg Starodumov
引子当我们使用调试器来调试程序时,我们希望能够单步调试到源代码中,在代码中设置断点,观察变量的值(包括用户自定义的复杂类型的值)。但是可执行文件只含有原始的字节数据——机器指令和操作系统执行程序时所使用的头信息和表信息。操作系统加载并运行可执行文件后,它根据不同的需求使用不同片段的内存(栈、堆)存放数据,其中的存放的依然是原始的字节数据。那么,调试器如何知道当前CPU指令对应哪一行代码?如何知道堆栈中的地址对应哪一个函数的局部变量?答案是“调试信息”,调试信息是高级编程语言和运行程序的原始字节数据之间的桥梁。名词解释位置(location): 在不同的情况有不同的含义。对于函数而言,是函数首字节的地址;对于全局和静态变量而言,是内存中变量的首字节;对局部变量和函数参数而言,通常是该变量的首字节相对于函数堆栈的预先定义的基址的偏移。另外,其他类型的位置也可能出现,如:寄存器、TLS slot(参见:http://www./u2/38/94/silannyukun/blog/37069531.html)、元数据标记(metadata token, 参见http:///blogs/framesniper/archive/2005/04/12/1910.aspx)。 FPO (frame pointer omission): 帧指针省略,FPO用来链接CodeView或PDB符号。它在编译器没有用EBP寄存器生成标准堆栈桢(a standard stack frame) 的地方帮助调试器查找函数的参数和本地变量。调试信息的类型我们只讨论在Intel X86平台上的现有的由微软提供的调试器。
调试信息格式现在来探索调试信息是如何存储的。在过去的十年中,微软开发工具使用了几种不同的格式来包装调试信息。这里我们讨论COFF、CodeView和应用的最广泛的PDB(Program Database)格式。在讨论每种格式时,我们从下列几个特性着手:
COFFCOFF是这里要涉及的所有格式中最古老的一种,它只能保存三种调试信息: 公共函数和变量,源文件和代码行信息,FPO信息。COFF总是保存在可执行文件中,不能够单独保存在其他文件中。该格式的文档说明参见:微软可移植可执行和通用对象文件格式规范. CodeViewCodeView是较COFF更新的而且更复杂的一种格式,它可以存储除编辑和继续执行信息外的所有类型的调试信息。CodeView通常保存在可执行文件中,它也可从可执行文件中导出到一个单独的文件(.DGB文件)。CodeView文档不全,其文档可以在MSDN中的VC++5.0符号调试信息规范(Symbolic Debug Information Specification)中找到。 Program Database 程序数据库这是三种中最新的一种调试信息格式,可以存储所有类型的调试信息(包括编辑和继续执行信息),也支持增量编译(其余两种格式不支持)。程序数据库信息保存在一个单独的.PDB文件中。遗憾的是,微软没有提供程序数据库格式的文档,只提供特殊的编程接口DbgHelp 和DIA来访问它。目前,程序数据库格式有两个版本,第一版(PDB2.0)为VC6.0所用,第二版(PDB 7.0)被Visual Studio.NET采用。PDB 7.0不能向上兼容,也就是说:VC6.0不能读取PDB 7.0格式。 三种格式对比如下:
生成调试信息构造(build)过程一个典型的可执行文件的构造过程包含两步:编译和链接。首先,编译器分析源文件,生成机器指令(保存在.obj对象文件中);然后链接器将所有可用的对象文件合并到最终的可执行文件。在对象文件之外,链接器也会用到库文件(库文件也是其他一些对象文件的汇集)。整个构造过程如下图:
如果我们想要为可执行文件生成调试信息,也得经历两步:首先,编译器为每一个源文件创建调试信息;然后,链接器合并由编译器创建得调试信息,如下图:
缺省状态下,编译器和链接器不会产生调试信息。因此我们必须通过编译和链接选项来要求编译器和链接器生成调试信息,我们也可以指定生成哪些类型得调试信息,使用什么调试信息格式,将调试信息保存在什么地方。 接下来,我讨论具体得编译器和链接器选项。 Visual C++ 6.0编译器 Compiler有下列选项:
链接器Linker下列选项可用: /debug 告诉链接器生成调试信息,如果该选项不使用,则其他所有选项都无效 /debugtype 指定调试信息格式,可能的用法包括: /debugtype:coff COFF格式。注意:该选项下,调试信息中不包含源文件和代码行信息 /debugtype:cv CodeView或程序数据库格式。究竟是哪一种格式,由/pdb决定 /debugtype:both 同时使用COFF格式和CodeView/程序数据库格式 /pdb 决定是CodeView还是程序数据库格式。/pdb:none 表示CodeView格式,/pdb:filename(如/pdb:myexe.pdb)表示使用程序数据库格式,文件名为myexe.pdb。在/debugtype:coff 选项下,/pdb 选项无效。 /pdbtype 该选项只在一个或多个对象文件或库文件的调试信息也保存在一个单独的PDB文件中。/pdbtype:sept 选项可以使得调试信息各自保存在各自的PDB文件中,这样可以加快链接速度,不利的是调试信息分散,调试时需要多个PDB文件。相对的,/pdbtype:con 选项使得所有调试信息都保存在与可执行文件对应的最终的PDB文件中。 为便于理解各个选项的配对使用,请见下表:
Visual C++.NET (2002 and 2003)编译器 Compiler下列选项可用: /Z7 生成CodeView格式的调试信息,保存在对象文件中
链接器Linker下列选项可用: /debug告诉链接器生成调试信息,如果该选项不使用,则其他所有选项都无效。调试信息的格式总是程序数据库格式,保存在PDB文件中。缺省的,链接器使用可执行文件名生成PDB文件名。PDB文件名可包含所有调试信息的变量内容。 /pdb 指定PDB文件名. /pdbstripped 允许链接器生成附加的PDB文件,该文件的内容限定于:
注意: COFF 和 CodeView 格式不被 VC++.NET链接器支持。 静态库的调试信息由于没有连接过程,静态库的调试信息的生成比可执行文件要简单的多。不考虑编译器版本(VC6 或 VS.NET),我们可以使用(/Zd, /Z7, /Zi, /ZI)中一个选项通知编译器为静态库生成调试信息。 关键问题是将调试信息保存在什么地方。当使用/Z7或/Zd选项时,调试信息保存在.LIB文件中;当使用/Zi或/ZI选项时,调试信息保存在.PDB文件中(当然可以使用/Fd指定文件名)。 调试信息对可执行文件的大小的影响调试信息对可执行文件的大小的影响,决定于存储调试信息的地方,也间接的决定于所使用的格式。 COFF和CodeView格式下,调试信息保存在可执行文件中,因此可执行文件的大小将显著增长(通常要增长一倍以上,甚至更大)。 程序数据库格式下,调试信息单独保存,对可执行文件的大小几乎没有影响。在这种情况下,可执行文件需要保存一个头信息方便调试器对调试信息进行定位,因此需要增长大约几百个字节。 要避免可执行文件的膨胀,我们需要在使用/debug 同时,将/opt:ref 选项改为opt:noref。这样做,有一个另外的结果就是关闭了链接器的大小优化。如果要恢复大小优化,需要改回/opt:ref。 .DBG 文件使用一个小工具——Rebase——可以将CodeView格式的内容从可执行文件中导出,存入到DBG文件中。Rebase包含在Visual Studio中。除了用于导出DBG文件外,它还有其他的一些用途。如果用于导出DBG文件,其命令行格式为: rebase –b BaseAddr –x SymbolDir [-p] ExeName
例如:下面的命令行从DLL中导出调试信息到当前目录下的DBG文件中: rebase –b 0x60000000 –x . MyDll.dll 调试器和调试信息的格式通用的调试器支持的格式如下:
WinDbg 6.3 部分支持CodeView格式,它只能读取下列信息:
它可以单步进入源代码,看到调用堆栈,但无法观察变量的值(因此类型信息不被支持). 操作系统符号文件(symbols)Windows操作系统所公开的调试系统格式如下:
|
|