`
javatoyou
  • 浏览: 1013670 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Windows程序调试系列: 使用VC++生成调试信息

阅读更多

Windows程序调试系列: 使用VC++生成调试信息

ZhangTao,zhangtao.it@gmail.com, 译自 “Generating debug information with Visual C++”,Oleg Starodumov

引子

当我们使用调试器来调试程序时,我们希望能够单步调试到源代码中,在代码中设置断点,观察变量的值(包括用户自定义的复杂类型的值)。但是可执行文件只含有原始的字节数据——机器指令和操作系统执行程序时所使用的头信息和表信息。操作系统加载并运行可执行文件后,它根据不同的需求使用不同片段的内存(栈、堆)存放数据,其中的存放的依然是原始的字节数据。那么,调试器如何知道当前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用来链接CodeViewPDB符号。它在编译器没有用EBP寄存器生成标准堆栈桢(a standard stack frame) 的地方帮助调试器查找函数的参数和本地变量。

调试信息的类型

我们只讨论在Intel X86平台上的现有的由微软提供的调试器。

信息的类型

描述

公共函数和变量

用于描述在多个的编译单元(源代码文件)中可见的函数和变量,调试信息保存每个函数和变量的位置(location)和名称。

私有函数和变量

用于描述除公共函数和变量以外的所有函数和变量,包括静态函数、静态和局部变量、函数参数),调试信息保存每个函数和变量的位置、大小和名称。

源文件和代码行信息

用于将每一行代码映射到可执行文件的某个位置上。当然,某些代码行不能做映射,如注释行,这样的代码行在调试信息中不做体现。

类型信息

用于存储每一个函数和变量的类型信息。对于变量或函数参数,类型信息能够告诉调试器它是整型还是字符串类型,或是用户自定义的类型。对于函数,类型信息记载了参数的个数、调用转换和返回值的类型。

FPO信息

对于做了FPO优化的函数,调试信息保存了一些数据来帮助调试器确定函数堆栈帧的大小,甚至在帧指针无效时也能工作。 如果没有FPO信息,调试器无法正确显示被优化的程序的调用堆栈。

编辑和继续执行信息

用于帮助Visual Studio IDE在调试时实现编辑和继续执行的功能

调试信息格式

现在来探索调试信息是如何存储的。在过去的十年中,微软开发工具使用了几种不同的格式来包装调试信息。这里我们讨论COFFCodeView和应用的最广泛的PDBProgram 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对象文件中);然后链接器将所有可用的对象文件合并到最终的可执行文件。在对象文件之外,链接器也会用到库文件(库文件也是其他一些对象文件的汇集)。整个构造过程如下图:

Build Process 1

如果我们想要为可执行文件生成调试信息,也得经历两步:首先,编译器为每一个源文件创建调试信息;然后,链接器合并由编译器创建得调试信息,如下图:

Build Process 2

缺省状态下,编译器和链接器不会产生调试信息。因此我们必须通过编译和链接选项来要求编译器和链接器生成调试信息,我们也可以指定生成哪些类型得调试信息,使用什么调试信息格式,将调试信息保存在什么地方。

接下来,我讨论具体得编译器和链接器选项。

Visual C++ 6.0
编译器 Compiler

有下列选项:
/Zd
生成COFF格式的调试信息,保存在对象文件中
/Z7
生成CodeView格式的调试信息,保存在对象文件中
/Zi
生成程序数据库格式的调试信息,保存在.PDB文件中
/ZI
/Zi 基本一致, 唯一不同的是调试信息中包含编辑和继续执行信息
缺省时,/Zi /ZI 选项生成的PDB文件名为VC60.PDB,也可以使用/Fd指定文件名。

选项

格式

存储文件

内容

/Zd

COFF

.OBJ

  • 公共函数和变量
  • 源文件和代码行信息
  • FPO信息

/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.PDBVC71.PDB,也可以使用/Fd指定文件名。
注意: VC++.NET 编译器不支COFF

选项

格式

存储

内容

/Z7

CodeView

.OBJ

  • 公共函数和变量
  • 私有函数和变量
  • 源文件和代码行信息
  • 类型信息
  • FPO信息

/Zd

Program Database

.PDB

  • 公共函数和变量
  • 源文件和代码行信息
  • FPO信息

/Zi

Program Database

.PDB

  • 公共函数和变量
  • 私有函数和变量
  • 源文件和代码行信息
  • 类型信息
  • FPO信息

/ZI

Program Database

.PDB

  • 公共函数和变量
  • 私有函数和变量
  • 源文件和代码行信息
  • 类型信息
  • FPO信息
  • 编辑和继续执行信息
链接器Linker

下列选项可用:

/debug告诉链接器生成调试信息,如果该选项不使用,则其他所有选项都无效。调试信息的格式总是程序数据库格式,保存在PDB文件中。缺省的,链接器使用可执行文件名生成PDB文件名。PDB文件名可包含所有调试信息的变量内容。

/pdb 指定PDB文件名.

/pdbstripped 允许链接器生成附加的PDB文件,该文件的内容限定于:

  • 公共函数和变量
  • FPO信息

注意: COFF CodeView 格式不被 VC++.NET链接器支持。

静态库的调试信息

由于没有连接过程,静态库的调试信息的生成比可执行文件要简单的多。不考虑编译器版本(VC6 VS.NET),我们可以使用(/Zd, /Z7, /Zi, /ZI)中一个选项通知编译器为静态库生成调试信息。

关键问题是将调试信息保存在什么地方。当使用/Z7/Zd选项时,调试信息保存在.LIB文件中;当使用/Zi/ZI选项时,调试信息保存在.PDB文件中(当然可以使用/Fd指定文件名)

调试信息对可执行文件的大小的影响

调试信息对可执行文件的大小的影响,决定于存储调试信息的地方,也间接的决定于所使用的格式。

COFFCodeView格式下,调试信息保存在可执行文件中,因此可执行文件的大小将显著增长(通常要增长一倍以上,甚至更大)。

程序数据库格式下,调试信息单独保存,对可执行文件的大小几乎没有影响。在这种情况下,可执行文件需要保存一个头信息方便调试器对调试信息进行定位,因此需要增长大约几百个字节。

要避免可执行文件的膨胀,我们需要在使用/debug 同时,将/opt:ref 选项改为opt:noref。这样做,有一个另外的结果就是关闭了链接器的大小优化。如果要恢复大小优化,需要改回/opt:ref

.DBG 文件

使用一个小工具——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格式,它只能读取下列信息:

  • 公共函数和变量
  • FPO信息
  • 源文件和代码行信息

它可以单步进入源代码,看到调用堆栈,但无法观察变量的值(因此类型信息不被支持).

操作系统符号文件(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程序调试系列: 使用VC++生成调试信息

    吕鑫:《VC++就业培训宝典之MFC视频教程》第一章 第一节 Visual C++6.0安装及界面介绍

    Visual C++6.0 IDE环境介绍,即开发和调试环境介绍。 VC开发环境,讲解如何新建工程、关闭工程和打开工程等,并编写代码并编译生成执行文件,讲解程序运行和测试的方法

    VC++常见安装调试错误

    程序设计到多线程,VC++6.0默认设置可作以下修改: project-&gt;settings-&gt;C/C++-&gt;Category中选Code Generation-&gt;Use Run-time Library选Debug MultiThreaded或MultiThreaded 在创建MFC项目时, 不使用MFC AppWizard...

    vc++6.0调试方法 还包括一些编译基础知识

    调试程序可以帮助了解程序是怎样运行的。 1、如何快速地规范代码缩进格式 选中所需要规范的代码,按shift+F8 2、如何在Release状态下进行调试 Project-&gt;Setting…-&gt;Project Settings对话框,选择Release状态。“C/...

    VC++6.0中如何编译运行及调试C语言程序

    VC++6.0中如何编译运行及调试C语言程序

    vc++ 应用源码包_3

    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...

    自动配置VC++与DDK2000驱动开发环境的软件

    &lt;br&gt; 软件功能: 在Windows2000下使用VC++6.0结合DDK2000开发驱动程序,无论是编写、调试、编译都十分方便。该程序能够生成VC++中为驱动程序项目设置好的.dsw文件和.dsp文件。 &lt;br&gt; &lt;br&gt;使用说明...

    vc++ 应用源码包_1

    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...

    VC 演示一种直线生成的算法.rar

    VC 6.0 演示一种直线生成的算法,演示了三种直线生成算法 :1、中点算法生成直线;2、bresenham算法生成直线;3、DDA算法 生成直线。在运行的实例窗口中,单击“直线生成算法”菜单,可通过弹出的菜单,选择不同的...

    调试加壳程序的dmp文件

    加壳后的程序crash生成的dmp文件不好在VC++上调试,这里是一个转换程序,转换加壳后的dmp到加壳前的dmp。以便更方便调试它。 用法如下: DmpMatch.exe xxx.dmp my_tmd.exe 第一个参数是dmp文件名,第二个参数是未...

    VC++的使用方法

    一份详细介绍VC++6.0的课件,内容包括如何组建工程项目、如何调试、如何测试、最终生成完整的应用程序。

    VC++实现霍夫曼编码

    通过Huffman编码基本流程, 学习二叉树数据结构和生成方法,并用VC++调试Huffman编码程序。

    利用伪随机数理论生成均匀分布的高斯白噪声(VC++程序)

    利用伪随机数理论生成均匀分布的高斯白噪声,并绘制出白噪声的时域图,本程序在VC6.0中调试通过

    VC++ 寝室管理系统

    它不但具有程序框架自动生成、灵活方便的类管理、代码编写和界面设计集成交互操作、可开发多种程序等优点,而且通过简单的设置就可使其生成的程序框架支持数据库接口、OLE2,WinSock网络、3D控制界面。 它以拥有...

    vc++ 应用源码包_2

    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...

    vc++ 应用源码包_5

    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...

    vc++ 应用源码包_6

    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...

    Windows编程循序渐进.part2

    11.7 实例:Windows二级文件系统 209 11.7.1 设计实例 209 11.7.2 具体实现 211 11.8 实例:手柄测试器 214 11.8.1 DirectInput手柄输入 214 11.8.2 设计实例 216 第三篇 Windows系统程序设计篇 第12章 进程...

    Windows编程循序渐进.part3

    11.7 实例:Windows二级文件系统 209 11.7.1 设计实例 209 11.7.2 具体实现 211 11.8 实例:手柄测试器 214 11.8.1 DirectInput手柄输入 214 11.8.2 设计实例 216 第三篇 Windows系统程序设计篇 第12章 进程...

    VC++编程技术与难点剖析(光盘源代码等)

     所有工程都以VC++ 6.0编写,在Windows 2000专业版和Windows 98第2版中调试通过。所有以 MFC编写的工程都以动态链接MFC DLL库的方式编译。每个工程目录下除包含源代码以外,还提供 了一个该工程在_DEBUG方式下编译...

Global site tag (gtag.js) - Google Analytics