Debugging Tools for Windows

公有和私有符号

当链接器构建一个全尺寸的.pdb.dbg文件时,它包含两种截然不同的信息集合:私有符号数据公有符号表。这些集合的不同点在于包含的条目和每条记录中的信息不一样。

私有符号数据包含以下内容:

公有符号表包含少一些的内容:

作为一般规则,公有符号表包含的恰好是那些需要跨源文件访问的内容。仅在一个对象文件(object file)中可见的项目 — 如static 函数、仅在一个文件中的全局变量和局部变量——都不包含在公有符号表中。

两种集合的数据在包含的每条项目的信息方面也有所不同。下面是私有符号数据中每条记录包含的典型数据:

另一方面,公有符号表仅为每条记录包含以下内容:

换句话说,公有符号数据在两方面是私有符号数据的一个子集:它包含的条目要少一些,并且每条记录包含的内容也要少一些。例如,公有符号不包含任何局部变量的信息。每个局部变量的地址、数据类型和作用域都仅包含在私有符号中。另一方面,函数在私有和公有符号中都包含,但是私有符号包含函数名、地址、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来查看符号。如果某个调试器可以访问一个完整符号文件,它可以同时拥有私有符号和公有符号表中的数据。私有符号要更加详细,而公有符号包含符号修饰符。

访问私有符号时,总是使用私有符号数据,因为这些符号在公有符号表中并没有包含。这些符号是不会添加修饰符的。

访问公有符号时,调试器的行为由特定的符号选项决定:

这里有一个三次使用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_ONLYSYMOPT_NO_PUBLICS,并打开SYMOPT_UNDNAMESYMOPT_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 

Build machine: CAPEBUILD