Windows程序调试系列: 使用VC++生成调试信息
引子
当我们使用调试器来调试程序时,我们希望能够单步调试到源代码中,在代码中设置断点,观察变量的值(包括用户自定义的复杂类型的值)。但是可执行文件只含有原始的字节数据——机器指令和操作系统执行程序时所使用的头信息和表信息。操作系统加载并运行可执行文件后,它根据不同的需求使用不同片段的内存(栈、堆)存放数据,其中的存放的依然是原始的字节数据。那么,调试器如何知道当前CPU指令对应哪一行代码?如何知道堆栈中的地址对应哪一个函数的局部变量?答案是“调试信息”,调试信息是高级编程语言和运行程序的原始字节数据之间的桥梁。
名词解释
位置(location):在不同的情况有不同的含义。对于函数而言,是函数首字节的地址;对于全局和静态变量而言,是内存中变量的首字节;对局部变量和函数参数而言,通常是该变量的首字节相对于函数堆栈的预先定义的基址的偏移。另外,其他类型的位置也可能出现,如:寄存器、TLS slot(参见:http://www.blogcn.com/u2/38/94/silannyukun/blog/37069531.html)、元数据标记(metadata token, 参见http://naoku.net/blogs/framesniper/archive/2005/04/12/1910.aspx)。
FPO (frame pointer omission): 帧指针省略,FPO用来链接CodeView或PDB符号。它在编译器没有用EBP寄存器生成标准堆栈桢(a standard stack frame) 的地方帮助调试器查找函数的参数和本地变量。
调试信息的类型
我们只讨论在Intel X86平台上的现有的由微软提供的调试器。
信息的类型
|
描述
|
公共函数和变量
|
用于描述在多个的编译单元(源代码文件)中可见的函数和变量,调试信息保存每个函数和变量的位置(location)和名称。
|
私有函数和变量
|
用于描述除公共函数和变量以外的所有函数和变量,包括静态函数、静态和局部变量、函数参数),调试信息保存每个函数和变量的位置、大小和名称。
|
源文件和代码行信息
|
用于将每一行代码映射到可执行文件的某个位置上。当然,某些代码行不能做映射,如注释行,这样的代码行在调试信息中不做体现。
|
类型信息
|
用于存储每一个函数和变量的类型信息。对于变量或函数参数,类型信息能够告诉调试器它是整型还是字符串类型,或是用户自定义的类型。对于函数,类型信息记载了参数的个数、调用转换和返回值的类型。
|
FPO信息
|
对于做了FPO优化的函数,调试信息保存了一些数据来帮助调试器确定函数堆栈帧的大小,甚至在帧指针无效时也能工作。 如果没有FPO信息,调试器无法正确显示被优化的程序的调用堆栈。
|
编辑和继续执行信息
|
用于帮助Visual Studio IDE在调试时实现编辑和继续执行的功能
|
调试信息格式
现在来探索调试信息是如何存储的。在过去的十年中,微软开发工具使用了几种不同的格式来包装调试信息。这里我们讨论COFF、CodeView和应用的最广泛的PDB(Program Database)格式。在讨论每种格式时,我们从下列几个特性着手:
- 哪些类型的调试信息可以通过该格式保存?
- 调试信息究竟保存在哪里(在可执行文件中,还是单独的一个文件)?
- 该格式是否有文档说明?
COFF
COFF是这里要涉及的所有格式中最古老的一种,它只能保存三种调试信息: 公共函数和变量,源文件和代码行信息,FPO信息。COFF总是保存在可执行文件中,不能够单独保存在其他文件中。该格式的文档说明参见:微软可移植可执行和通用对象文件格式规范.
CodeView
CodeView是较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格式。
三种格式对比如下:
格式
|
是否有文档
|
存储
|
公共函数和变量
|
私有函数和变量
|
源文件和代码行信息
|
类型信息
|
FPO 信息
|
编辑和继续执行信息
|
COFF
|
有
|
可执行文件中
|
+
|
-
|
+
|
-
|
+
|
-
|
CodeView
|
部分
|
可执行文件中
或.DBG文件中
|
+
|
+
|
+
|
+
|
+
|
-
|
Program Database
|
无
|
.PDB文件中
|
+
|
+
|
+
|
+
|
+
|
+
|
生成调试信息
构造(build)过程
一个典型的可执行文件的构造过程包含两步:编译和链接。首先,编译器分析源文件,生成机器指令(保存在.obj对象文件中);然后链接器将所有可用的对象文件合并到最终的可执行文件。在对象文件之外,链接器也会用到库文件(库文件也是其他一些对象文件的汇集)。整个构造过程如下图:
如果我们想要为可执行文件生成调试信息,也得经历两步:首先,编译器为每一个源文件创建调试信息;然后,链接器合并由编译器创建得调试信息,如下图:
缺省状态下,编译器和链接器不会产生调试信息。因此我们必须通过编译和链接选项来要求编译器和链接器生成调试信息,我们也可以指定生成哪些类型得调试信息,使用什么调试信息格式,将调试信息保存在什么地方。
接下来,我讨论具体得编译器和链接器选项。
Visual C++ 6.0
编译器 Compiler
有下列选项:
/Zd 生成COFF格式的调试信息,保存在对象文件中
/Z7 生成CodeView格式的调试信息,保存在对象文件中
/Zi 生成程序数据库格式的调试信息,保存在.PDB文件中
/ZI 与 /Zi 基本一致, 唯一不同的是调试信息中包含编辑和继续执行信息
缺省时,/Zi 和 /ZI 选项生成的PDB文件名为VC60.PDB,也可以使用/Fd指定文件名。
选项
|
格式
|
存储文件
|
内容
|
/Zd
|
COFF
|
.OBJ
|
|
/Z7
|
CodeView
|
.OBJ
|
- 公共函数和变量
- 私有函数和变量
- 源文件和代码行信息
- 类型信息
- FPO信息
|
/Zi
|
Program Database
|
.PDB
|
- 公共函数和变量
- 私有函数和变量
- 源文件和代码行信息
- 类型信息
- FPO信息
|
/ZI
|
Program Database
|
.PDB
|
- 公共函数和变量
- 私有函数和变量
- 源文件和代码行信息
- 类型信息
- FPO信息
- 编辑和继续执行信息
|
链接器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文件中。
为便于理解各个选项的配对使用,请见下表:
/debugtype
|
/pdb
|
格式
|
存储
|
coff
|
/pdb:none (无效)
|
COFF
|
在可执行文件中
|
coff
|
/pdb:filename (无效)
|
COFF
|
在可执行文件中
|
cv
|
/pdb:none
|
CodeView
|
在可执行文件中
|
cv
|
/pdb:filename
|
Program Database
|
.PDB 文件
|
both
|
/pdb:none
|
COFF and CodeView
|
在可执行文件中
|
both
|
/pdb:filename
|
COFF and Program Database
|
COFF 信息在可执行文件中, 程序数据库信息在 .PDB 文件中
|
Visual C++.NET (2002 and 2003)
编译器 Compiler
下列选项可用:
/Z7 生成CodeView格式的调试信息,保存在对象文件中
/Zd, /Zi 和 /ZI都表示生成程序数据库格式的调试信息,保存在.PDB文件中. 不同之处是调试信息的内容(见下表)。
缺省时,/Zd,/Zi 和 /ZI 选项生成的PDB文件名为VC70.PDB或VC71.PDB,也可以使用/Fd指定文件名。
注意: VC++.NET 编译器不支COFF。
选项
|
格式
|
存储
|
内容
|
/Z7
|
CodeView
|
.OBJ
|
- 公共函数和变量
- 私有函数和变量
- 源文件和代码行信息
- 类型信息
-
FPO信息
|
/Zd
|
Program Database
|
.PDB
|
|
/Zi
|
Program Database
|
.PDB
|
- 公共函数和变量
- 私有函数和变量
- 源文件和代码行信息
- 类型信息
-
FPO信息
|
/ZI
|
Program Database
|
.PDB
|
- 公共函数和变量
- 私有函数和变量
- 源文件和代码行信息
- 类型信息
-
FPO信息
- 编辑和继续执行信息
|
链接器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。
使用一个小工具——Rebase——可以将CodeView格式的内容从可执行文件中导出,存入到DBG文件中。Rebase包含在Visual Studio中。除了用于导出DBG文件外,它还有其他的一些用途。如果用于导出DBG文件,其命令行格式为:
rebase –b BaseAddr –x SymbolDir [-p] ExeName
选项
|
描述
|
<nobr><p><span>-b BaseAddr</span></p>
</nobr> |
指定可执行文件的基地址,如果你不想更改基地址,就指定当前可执行文件所使用的地址
|
<nobr><p><span>-x SymbolDir</span></p>
</nobr> |
制定存放.DBG文件的目录, 使用“.”表示当前目录
|
-p
|
如果该选项被使用,DBG文件只包含公共函数和变量和FPO信息
|
例如:下面的命令行从DLL中导出调试信息到当前目录下的DBG文件中: rebase –b 0x60000000 –x . MyDll.dll
调试器和调试信息的格式
通用的调试器支持的格式如下:
调试器
|
COFF
|
CodeView
|
Program Database (2.0)
|
Program Database (7.0)
|
Visual Studio.NET
|
-
|
+
|
+
|
+
|
Visual C++ 6.0
|
+
|
+
|
+
|
-
|
WinDbg 6.3
|
+
|
部分支持
|
+
|
+
|
WinDbg 6.3 部分支持CodeView格式,它只能读取下列信息:
它可以单步进入源代码,看到调用堆栈,但无法观察变量的值(因此类型信息不被支持).
操作系统符号文件(symbols)
Windows操作系统所公开的调试系统格式如下:
操作系统
|
格式
|
<nobr><p><span>Windows NT 4.0</span></p>
</nobr> |
CodeView (.DBG files)
|
<nobr><p><span>Windows 2000</span></p>
</nobr> |
CodeView (.DBG files) and Program Database (2.0)
|
<nobr><p><span>Windows XP</span></p>
</nobr>(including SP1 and SP1a) |
Program Database (2.0)
|
<nobr><p><span>Windows XP SP2</span></p>
</nobr> |
Program Database (7.0)
|
<nobr><p><span>Windows 2003 Server</span></p>
</nobr> |
Program Database (2.0)
|
分享到:
相关推荐
### 使用VC++生成调试信息详解 #### 一、引言 在进行Windows程序开发时,开发者经常需要通过调试工具来定位程序中的问题。这通常涉及到单步执行代码、设置断点以及观察变量值等操作。然而,直接从可执行文件中获取...
它提供了编辑器、调试器、编译器等一系列工具,使得开发者能够高效地构建Windows应用程序。 描述中的信息表明这个项目不仅使用了VC++,还通过了g++编译器的验证。g++是GCC(GNU Compiler Collection)的一部分,是...
### VC++生成可执行文件及Release与Debug的区别 #### 一、VC++生成可执行文件的基础概念 在VC++环境中,生成可执行文件的过程通常涉及多个步骤,包括编译源代码、链接对象文件以及处理资源文件等。在这个过程中,...
7. 测试和调试:条形码生成器需要经过严格的测试,确保生成的条形码能被各种扫描设备正确读取。开发者需要进行单元测试和集成测试,排查可能的错误。 在提供的压缩包文件中,"条形码生成器源程序"可能是包含了整个...
当我们在使用Visual C++进行程序开发时,可能会遇到各种调试提示和错误信息,特别是与MFC(Microsoft Foundation Classes)相关的错误。MFC是微软提供的一个C++类库,用于构建Windows应用程序,它封装了Windows API...
MFC是微软为Windows平台提供的一个C++类库,它简化了Windows API的使用,使得开发者能够更高效地编写应用程序。 首先,要理解生成静态网页的基本步骤。静态网页是由HTML、CSS和JavaScript等静态文件组成的,不依赖...
在VC++6.0环境中,开发者可以使用MFC Wizard自动生成对话框和相关的类,然后根据需求添加和修改代码。源代码的学习可以帮助开发者了解如何在MFC环境下处理网络通信,包括套接字的创建、连接、发送、接收以及异常处理...
这篇资源提供了使用VC++生成DLL的MD5加密算法代码,这对于开发人员来说是十分有价值的。下面将详细介绍这个过程中的关键知识点: 1. **MD5算法**:MD5算法是一种单向哈希函数,它具有快速、稳定和不可逆的特点。...
《VC++就业培训宝典之MFC视频教程》第一章第一节主要介绍了Visual C++6.0这一经典的编程环境,这是微软推出的用于开发Windows应用程序的强大工具。本节内容主要围绕着IDE(集成开发环境)展开,旨在帮助初学者快速...
这个压缩包"vc++生成Windows快捷方式文件.zip"包含了一些源代码文件,用于演示如何使用VC++来创建和分析Windows快捷方式。 首先,我们来看看压缩包内的文件: 1. AnalyseShortCut.cpp:这个文件很可能包含了实现...
8. **使用调试信息**:通过编译器生成调试信息,可以在调试时获得更丰富的符号信息,如变量名、函数名等,便于理解代码。 9. **性能分析**:虽然VC++6.0的性能分析工具相对现代IDE较为简单,但仍然可以提供基本的...
在VC++环境中开发录音并生成wav文件的程序是一项常见的任务,尤其在音频处理或多媒体应用中。WAV(Waveform Audio File Format)是Microsoft开发的一种无损音频文件格式,广泛用于存储原始音频数据。以下是对如何...
标题 "倍福VC++显示器程序编码翻译" 涉及的核心技术主要是在使用Microsoft的Visual C++(简称VC++)编程环境下,为贝克霍夫(Beckhoff)的工业自动化设备开发显示器程序,并进行代码的编译和翻译。这个过程涉及到的...
在VC++ 2005中,你可以创建各种类型的项目,如控制台应用程序、Windows应用程序、DLL动态链接库等。通过“文件”>“新建”>“项目”,选择合适的模板,输入项目名称和位置,即可创建新项目。 **4. C++语言基础** ...
总的来说,通过VC++ 6.0和dbghelp.dll生成DMP内存转储文件,能够帮助开发者在无法复现问题的情况下,从DMP文件中获取关键信息,从而定位并修复程序中的错误,提高软件的稳定性和可靠性。对于学习和实践C++的调试技术...
8. 并行和多线程调试:在处理大量图像数据时,可能涉及到多线程或多进程。这时,确保线程安全和同步机制正确至关重要,VC++调试器提供了强大的多线程调试功能。 9. 性能优化:当处理大型图像或实时图像流时,性能是...
2. **集成开发环境**:VC++6.0不仅是一个C++语言的IDE,还提供了一系列强大的编程工具,使得在Windows环境下编写应用程序变得更加简便快捷。它支持MFC(Microsoft Foundation Classes)库,简化了面向对象编程,提高...
通过以上步骤,我们可以熟练地使用VC++6.0进行C语言的编程实践,不仅能够掌握基本操作,还能提升程序设计和调试的技能。在实际操作中,不断实践和积累经验,将有助于我们更好地理解和运用C语言,以及深入理解面向...