cc682/NetRoc
http://netroc682.spaces.live.com/
符号文件:概述
当应用程序、库、驱动或操作系统被链接时,链接器在创建.exe和.dll文件时还会创建一些其它文件,即符号文件。
符号文件包含很多在运行时并不需要的数据,但是这些数据在调试过程中是非常有用的。
典型来说,符号文件可能包含:
- 全局变量
- 局部变量
- 函数名和它们的入口点地址
- 帧指针省略记录(Frame pointer omission (FPO) records)
- 源码行号
这些数据都被称为符号。例如,一个符号文件myprogram.pdb 可能包含了数千个符号,包括全局变量、函数名以及数千个局部变量。通常,软件同时生成两个版本的符号文件:包含公有和私有符号的完整符号文件,和一个只包含共有符号的有限制的文件。详细信息,查看公有和私有符号。
调试时,要确认调试器能访问和调试目标关联的符号文件。交互式的调试和调试崩溃转储文件都需要符号。需要得到要调试的代码的合适的符号并加载到调试器中。
Windows符号
Microsoft Windows NT和Windows 2000将他们的符号保存在扩展名为.pdb和.dbg的文件中。Windows XP和之后版本的Windows仅使用.pdb文件。Windows驱动程序可以使用其中之一。
编译器和链接器控制符号格式。Visual C++ 5.0链接器同时创建.pdb和.dbg符号文件——.dbg文件实质上指向.pdb文件。Visual C++ 6.0和之后的Visual Studio版本的链接器将所有符号都放入.pdb文件中。
所有基于Windows-NT的操作系统和驱动都构建有两个版本。发布版 (或零售版)的二进制文件相对较小,而 调试版 (checked build或debug build) 二进制文件要大一些,并带有更多调试符号。这些版本都有两种符号文件。当在Windows上调试目标时,必须有和目标机器上的Windows版本匹配的符号文件。
下表列出了在标准Windows符号树中包含的目录:
目录
|
包含的符号文件
|
ACM
|
Microsoft音频压缩管理器(Microsoft Audio Compression Manager )文件
|
COM
|
可执行文件(.com)
|
CPL
|
控制面板程序
|
DLL
|
动态链接库文件(.dll)
|
DRV
|
驱动文件(.drv)
|
EXE
|
可执行文件(.exe)
|
SCR
|
屏幕保护程序文件
|
SYS
|
驱动文件(.sys)
|
数据类型
所有代码中用typedef定义的结构都会包含,只要在程序中实际用过它们。但是,在头文件中定义但是没有实际使用到的符号不会包含在.pdb中,并且调试器不能访问它们。如果要让这样的类型在调试器中可用,可以将它们作为typedef声明的输入。例如,如果代码中有下面的内容,MY_DATA 结构会保存在.pdb符号文件中,并且可以被调试器显示出来:
typedef struct _MY_DATA {
. . .
} MY_DATA;
typedef MY_DATA *PMY_DATA;
另一方面,下面这样的代码就不足够,因为MY_DATA 和PMY_DATA 都是在初始的typedef中定义的,因此MY_DATA 本身并没有被任何typedef 定义使用到:
typedef struct _MY_DATA {
. . .
} MY_DATA, *PMY_DATA;
任何情况下,类型信息仅在完整符号文件中包含,而不在剥除了所有私有符号信息的符号文件中包含。更多信息,查看公有和私有符号。
公有和私有符号
当链接器构建一个全尺寸的.pdb或.dbg文件时,它包含两种截然不同的信息集合:私有符号数据和公有符号表。这些集合的不同点在于包含的条目和每条记录中的信息不一样。
私有符号数据包含以下内容:
- 函数
- 全局变量
- 局部变量
- 用户定义的结构、类和数据类型的信息
- 源代码文件名和文件生成的每条二进制指令对应的源码行号
公有符号表包含少一些的内容:
- 函数(除了用static声明的函数)
- 指定为extern的全局变量 (和其他在多个object 文件中可见的全局变量)
作为一般规则,公有符号表包含的恰好是那些需要跨源文件访问的内容。仅在一个对象文件(object file)中可见的项目——如static 函数、仅在一个文件中的全局变量和局部变量——都不包含在公有符号表中。
两种集合的数据在包含的每条项目的信息方面也有所不同。下面是私有符号数据中每条记录包含的典型数据:
- 条目的名字
- 条目在虚拟内存中的地址
- 每个函数的帧指针省略记录(FPO)
- 每个变量、结构和函数的数据类型
- 每个函数的参数类型和名字
- 每个局部变量的作用域
- 源码中每一行对应的符号
另一方面,公有符号表仅为每条记录包含以下内容:
- 条目的名字。
- 条目在它的模块的虚拟内存空间中的地址。对于函数来说,是入口地址。
- 每个函数的帧指针省略记录(FPO) 。
换句话说,公有符号数据在两方面是私有符号数据的一个子集:它包含的条目要少一些,并且每条记录包含的内容也要少一些。例如,公有符号不包含任何局部变量的信息。每个局部变量的地址、数据类型和作用域都仅包含在私有符号中。另一方面,函数在私有和公有符号中都包含,但是私有符号包含函数名、地址、FPO记录、输入参数名字和类型、以及返回值类型;而公有符号仅包含函数名、地址和FPO记录。
私有符号数据和公有符号表之间还有一个差异。公有符号表中的很多条目使用前缀或后缀来修饰。这些修饰名是由C、C++编译器和MASM汇编器添加的。典型的前缀由一系列下划线或字符串__imp_ (表示导入函数)组成。典型的后缀包含一个或多个at符号(@),后跟地址或其他指示字符串。链接器使用这些修饰符来消除歧义,因为不同模块中的函数名或全局变量名可能会重复。对于公有符号表是私有符号的子集的普通规则来说,这些修饰符是一个例外。
完整符号文件和省略的符号文件
完整符号文件同时包含私有符号和公有符号表。这种文件有时用私有符号文件来称呼,但是该名称有些令人误解,因为这种文件同时包含私有和公有符号。
省略的符号文件是仅包含公有符号表的小一些的文件——或者,某些情况下只是公有符号表的一个子集。这种文件有时称为公有符号文件。
创建完整和省略的符号文件
如果使用Visual Studio来构建二进制文件,可以创建完整的或省略的符号文件。当构建二进制文件的"调试版"时,Visual Studio一般会创建完整符号文件。当构建"发行版"时,Visual Studio一般不会创建符号文件,但是可以通过设置一些选项来生成完整的或省略的符号文件。
如果使用Build实用工具来构建二进制文件,会创建完整符号文件。
使用BinPlace 工具,可以从完整符号文件生成省略的符号文件。当使用最常用的BinPlace 选项(-a -x -s -n)时,省略的符号文件会放在-s开关之后指定的目录内,完整符号文件放在 -n 开关指定的目录内。当BinPlace 剪切符号文件时,省略符号文件和完整版本的文件会生成同样的签名和其他标识信息。这使得在调试时可以使用任一个版本。
使用PDBCopy 工具,可以通过移除完整符号文件中的私有符号数据来生成省略的符号文件。PDBCopy也可以移除公有符号表中的指定子集。详细信息,查看PDBCopy。
使用SymChk 工具,可以查看一个符号文件中是否包含私有符号。详细信息,查看SymChk。
在调试器中查看公有和私有符号
可以使用WinDbg、KD或CDB来查看符号。如果某个调试器可以访问一个完整符号文件,它可以同时拥有私有符号和公有符号表中的数据。私有符号要更加详细,而公有符号包含符号修饰符。
访问私有符号时,总是使用私有符号数据,因为这些符号在公有符号表中并没有包含。这些符号是不会添加修饰符的。
访问公有符号时,调试器的行为由特定的符号选项决定:
- SYMOPT_UNDNAME选项打开时,显示公有符号的名字时,不会包含修饰符。此外,在搜索符号时也跳过修饰名。关闭该选项时,显示公有符号时会显示修饰名,并且在搜索时也会包含。私有符号在任何情况下都不会包含修饰符。该选项在所有调试器中默认被打开。
- SYMOPT_PUBLICS_ONLY选项打开时,私有符号数据会被忽略,只使用公有符号表。所有调试器默认关闭该选项。
- SYMOPT_NO_PUBLICS选项打开时,公有符号表被忽略, 搜索符号信息时只使用私有符号数据。所有调试器中都默认关闭该选项。
- SYMOPT_AUTO_PUBLICS选项打开时(并且SYMOPT_PUBLICS_ONLY和SYMOPT_NO_PUBLICS都关闭) ,第一次符号搜索针对私有符号。如果需要的符号找到,则结束搜索。如果没有,则继续搜索共有符号表。由于公有符号一般包含的是私有符号的子集,所以通常公有符号表中的结果被跳过。
- 当SYMOPT_PUBLICS_ONLY、 SYMOPT_NO_PUBLICS和SYMOPT_AUTO_PUBLICS 选项都被关闭时,每次需要符号时都会同时搜索私有符号数据和公有符号表。但是,如果两处都找到了匹配的项,则使用私有符号中的那个。因此,这时的行为和SYMOPT_AUTO_PUBLICS 打开时一样,除了SYMOPT_AUTO_PUBLICS 可能会让搜索速度略微加快。
这里有一个三次使用x (Examine Symbols)命令的示例。第一次使用默认的符号选项,所以信息是从私有符号数据中来的。注意数组typingString的信息中包含地址、大小和数据类型。第二次,使用.symopt+ 4000命令,使得调试器忽略私有符号数据。当x命令再次运行时,使用公有符号;这次没有typingString 的大小和数据类型信息了。最后,使用.symopt- 2命令,使得调试器包含修饰符。使用x命令时,会显示包含修饰符的名字_typingString。
0:000> x /t /d *!*typingstring*
00434420 char [128] TimeTest!typingString = char [128] ""
0:000> .symopt+ 4000
0:000> x /t /d *!*typingstring*
00434420 <NoType> TimeTest!typingString = <no type information>
0:000> .symopt- 2
0:000> x /t /d *!*typingstring*
00434420 <NoType> TimeTest!_typingString = <no type information>
使用DBH工具查看公有和私有符号
另一种查看符号的方法是使用DBH工具。DBH使用和调试器一样的符号选项。 和调试器一样,DBH默认关闭 SYMOPT_PUBLICS_ONLY 和SYMOPT_NO_PUBLICS,并打开SYMOPT_UNDNAME 和 SYMOPT_AUTO_PUBLICS。这些默认设置可以通过命令行选项或DBH命令覆盖。
这里有三次使用DBH 命令addr 414fe0的一个例子。第一次,使用默认符号选项,所以信息来自私有符号数据。注意函数fgets 的信息包含地址、大小和数据类型。第二次使用了命令symopt +4000,使得DBH忽略私有符号数据。再次运行addr 414fe0时,fgets函数的信息中没有包含大小和类型。最后使用了symopt -2 ,使得DBH包含修饰符。最后一次执行addr 414fe0,显示的是包含修饰符的函数名_fgets。
pid:4308 mod:TimeTest[400000]: addr 414fe0
fgets
name : fgets
addr : 414fe0
size : 113
flags : 0
type : 7e
modbase : 400000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagFunction (5)
index : 7d
pid:4308 mod:TimeTest[400000]: symopt +4000
Symbol Options: 0x10c13
Symbol Options: 0x14c13
pid:4308 mod:TimeTest[400000]: addr 414fe0
fgets
name : fgets
addr : 414fe0
size : 0
flags : 0
type : 0
modbase : 400000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagPublicSymbol (a)
index : 7f
pid:4308 mod:TimeTest[400000]: symopt -2
Symbol Options: 0x14c13
Symbol Options: 0x14c11
pid:4308 mod:TimeTest[400000]: addr 414fe0
_fgets
name : _fgets
addr : 414fe0
size : 0
flags : 0
type : 0
modbase : 400000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagPublicSymbol (a)
index : 7f
安装Windows符号文件
在调试Windows内核或运行在Windows上的驱动或应用程序时,需要访问适合的符号文件。
如果在调试器运行时能访问internet,可能想适用Microsoft的公有符号存储。可以通过简单的使用一次.symfix (Set Symbol Store Path)命令来连接。完整的详细信息,查看使用符号服务器和符号存储。
如果想手动安装符号,至关重要的是注意这个基本规则: 主控机上的符号文件必须和 目标机上安装的Windows系统版本匹配。如果要在Windows 2000主控机上对Windows XP目标进行内核调试,需要在Windows 2000系统上安装Windows XP符号文件。如果要在同一台机器上对目标程序进行用户模式调试,需要安装该机器上使用的Windows系统匹配的符号文件。如果在分析内存转储文件,在调试计算机上安装的符号文件要和生成dump文件的操作系统版本匹配,而不是和进行调试会话的机器上的操作系统匹配。
注意 如果想使用主控机来调试数个不同的目标机,可能需要不止一个Windows版本的符号文件。这时要注意把每类符号安装到不同的目录中。
如果在一台连接到网络的Windows计算机上进行调试,把各种不同版本的符号安装到一台网络服务器上是很有用的。Microsoft调试器可以使用网络路径(\\server\share\dir)作为符号目录路径。这避免了在网络上的每台机器上安装符号。
安装在已崩溃的目标机上的符号文件,对主控机上的调试器是无用的。
安装Windows XP或之后系统的符号文件
- 主控机的磁盘驱动器上需要至少1000 MB的空闲空间。
- 在浏览器中打开Windows Symbols 站点。
- 跟随链接来下载合适的符号包。
从网页上安装Windows 2000的符号文件
- 主控机的磁盘驱动器上需要至少1000 MB的空闲空间。
- 在浏览器中打开Windows Symbols 站点。
- 跟随链接来下载合适的符号包。
从支持CD上安装Windows 2000符号文件
- 主控机的磁盘驱动器上需要至少500 MB空闲空间。
- 插入Windows 2000用户支持诊断CD(Windows 2000 Customer Support Diagnostics CD)。
- 在安装符号(Install Symbols)上点击。
- 选择安装零售版符号(free build)或安装调试版符号(checked build)。符号必须和被调试的操作系统匹配。
- 输入符号安装的路径,或者使用默认路径。默认路径是%windir%\symbols。
从Windows CD上安装Windows NT 4.0的符号文件
- 主控机的磁盘驱动器上需要至少450MB空闲空间。
- 从命令提示符上进入Windows NT 4.0 CD-ROM的\support\debug folder 目录。
-
运行 ExpndSym 命令。ExpndSym 需要两个命令行参数, Source_drive 和 Target_folder。 Source_drive 是CD-ROM驱动器。Target_folder 是要安装符号文件的路径。
使用该工具的典型例子如下:
expandsym d: c:\winnt
该例子中,符号会被安装到c:\winnt\symbols (不是in c:\winnt 本身)。
符号文件安装顺序
如果要在单个目录树中保存符号, 符号文件的安装顺序应该和操作系统文件的安装顺序一样:
按正确顺序安装符号文件
- 安装操作系统符号文件。
- 安装正确的Service Pack 包(如果有的话)。
- 安装在Service Pack 安装之后安装的任何热补丁(Hot Fixes)的符号文件(如果有的话)。
但是,好的安装应该把每个Service Pack 和热补丁的文件安装到不同的目录树下,并且将所有这些目录都加入符号搜索路径。调试器会自己寻找合适的符号。(由于符号文件有日期和时间戳,所以调试器知道哪个才是最新的。)查看符号路径获取详细信息。
安装用户模式符号
如果要调试用户模式程序,同样需要安装该程序的符号文件。
在拥有程序的符号但是没有Windows符号时也可以进行调试。但是,结果会有更多限制。仍然可以单步执行程序代码,但是任何调试器需要分析内核的操作(例如获得调用堆栈)的操作都可能失败。