2023年1月

符号用来直接操作被调试程序的助记符(tokens)。例如,可以通过命令bp main来在main函数上设置断点,或者使用dd MyInt L1命令来显示整数变量MyInt 的值。很多情况下,符号可以作为调试器命令的参数。可以支持大多数数字参数,以及一些文本参数。除了常规的符号语法之外,也有一些适用于各种单独情况的语法规则。

常规符号语法规则

符号名由一个或多个字符组成,但是总是以字母、下划线(_)、问号(?)或美元符号($)开头。

符号名可以用模块名来进行限制。使用一个感叹号(!)来分隔模块名和符号(例如,mymodule!main)。没有使用模块名的时候,符号也可以用感叹号作为前缀。即使对于局部变量,使用不带模块名的感叹号也尤其有用,它可以向调试器命令指明某个参数是一个名字而不是16进制数字。例如,如果没有加上感叹号前缀或者-n选项的话,变量fade会被命令dt (Display Type)当作一个地址来读取。但是,可以通过加上美元符号( $ )和感叹号( ! )当作前缀来指明一个符号是局部变量,如$!lime

符号名是完全不区分大小写的。这意味着如果程序中同时存在myIntMyInt ,不能被调试器正确识别;不管怎么写,访问它们的命令都有可能访问到另外一个。

数值表达式中的符号语法

调试器可以识别两种不同的表达式:Microsoft宏汇编(MASM)表达式和C++表达式。由于和符号相关,这两种语法形式有以下区别:

  • 在MASM表达式中,每个符号都被解释成地址。根据该符号引用的内容不同,它可以是全局变量、局部变量、函数、段、模块的地址或者其他任何可识别的标志。
  • 在C++ 表达式中,符号根据它的类型来解释。根据引用内容的不同,可能被解释为整数、数据结构、函数指针或任何其他数据类型。不符合C++数据类型的符号(例如未更改的模块名)都会产生语法错误。

关于如何使用每一种语法的说明,查看表达式求值。

如果使用MSAM表达式语法,任何可以被解释为16进制数或寄存器的符号(例如,BadFeed, ebX)都必须用感叹号作为前缀。这能确保调试器将它识别为符号。

ss (Set Symbol Suffix)命令可以用来设置符号后缀。这使得调试器在找不到符号的情况下自动在符号名后添加"A" 或者"W"。

很多Win32函数存在ASCII和Unicode版本。这些函数的名字后被分别添加了"A" 或"W"。使用符号后缀可以帮助调试器搜索这些符号。

后缀匹配默认没有激活。

文本表达式中的符号语法

符号可以用于一些命令的文本参数 — 例如bm (Set Breakpoint)x (Examine Symbols)

这些文本参数支持很多通配符和说明。查看字符串通配符语法获取详细信息。除标准字符串通配符之外,用来指定符号的文本表达式也可以使用一个下划线作为前缀。当对这样的符号进行匹配时,调试器把它当作任意数量的下划线,包括零。

匹配文本表达式中的符号时,不会使用符号后缀。

符号文件的类型和它们的加载状态可以通过lm (List Loaded Modules)命令、 !lmi扩展命令或 WinDbg的Debug | Modules菜单命令获取。

它们都可以显示已加载的模块和它们的符号的信息。

下面这些缩写是这些命令产生的输出中使用的:

缩写含义
deferred 模块已经加载,但是调试器还没有尝试加载它的符号。符号将在需要的时候被加载。查看延迟符号加载获取详细信息。
# 符号文件和可执行文件的时间戳或者校验和有一些不匹配。
T 时间戳丢失、不能访问或者等于0。
C 校验和丢失、不可访问或者等于0。
DIA 符号文件通过调试接口访问(Debug Interface Access (DIA))被加载。
Export 没有找到实际的符号文件,所以符号信息是从二进制文件的导出表中获得的。
M 符号文件和可执行文件得时间戳或校验和有些不匹配。但是,因为符号选项的原因符号文件仍然被加载了。
PERF 该二进制文件包含性能优化后的代码。标准的地址计算方法可能产生不正确的结果。
Stripped 调试信息已经从映像文件中剥离出来了。
PDB 符号是.pdb格式的。
COFF 符号是通用对象文件格式(common object file format (COFF))的。

符号问题会以各种方式表现出来。可能堆栈回溯显示错误信息,或者不能显示堆栈中的函数名。也可能调试器命令不能识别模块、函数、变量、结构或数据类型的名字。

如果猜测没有正确加载符号,可以通过几个步骤来确认问题。

首先,使用lm (List Loaded Modules)命令来显示已加载模块和符号信息的列表。该命令最有用的形式如下:

0:000> lml 

如果使用WinDbg,Debug | Modules菜单命令也可以看到这些信息。

对这些输出中的注释或缩写要特别注意。详细说明查看符号状态缩写。

如果没有看到是当的符号文件,首先应该做的是检查符号路径:

0:000> .sympath
Current Symbol Path is: d:\MyInstallation\i386\symbols\retail

如果符号路径错误,则修正它。如果正在使用内核调试器,要确认本地%WINDIR%不在符号路径中。

然后使用.reload (Reload Module)命令重新加载符号:

0:000> .reload ModuleName 

如果符号路径正确,可以激活详细模式(noisy mode)来查看dbghelp加载了什么符号文件。然后重新加载模块。查看设置符号选项来获得激活详细模式的方法。

这是一个"详细"重加载Microsoft Windows符号的例子:

kd> !sym noisy
kd> .reload nt
 1: Kernel Version 2081 MP Checked
 2: Kernel base = 0x80400000 PsLoadedModuleList = 0x80506fa0
 3: DBGHELP: FindExecutableImageEx-> Looking for D:\MyInstallation\i386\ntkrnlmp.exe...mismatched timestamp
 4: DBGHELP: No image file available for ntkrnlmp.exe
 5: DBGHELP: FindDebugInfoFileEx-> Looking for
 6: d:\MyInstallation\i386\symbols\retail\symbols\exe\ntkrnlmp.dbg... no file
 7: DBGHELP: FindDebugInfoFileEx-> Looking for
 8: d:\MyInstallation\i386\symbols\retail\symbols\exe\ntkrnlmp.pdb... no file
 9: DBGHELP: FindDebugInfoFileEx-> Looking for d:\MyInstallation\i386\symbols\retail\exe\ntkrnlmp.dbg... OK
10: DBGHELP: LocatePDB-> Looking for d:\MyInstallation\i386\symbols\retail\exe\ntkrnlmp.pdb... OK
11: *** WARNING: symbols checksum and timestamp is wrong 0x0036a4ea 0x00361a83 for ntkrnlmp.exe

符号处理器首先查找和即将加载的模块匹配的映像(第3和第4行)。映像本身并不总是需要的,但是如果存在一个不正确的,符号处理器常常会失败。这几行表明调试器找到了一个映像D:\MyInstallation\i386\ntkrnlmp.exe ,但是它的时间戳并不匹配。由于时间戳不匹配,所以继续进行搜索。然后,调试器查找匹配加载的映像的.dbg.pdb文件。从第6到第10行表明,虽然符号加载了,但是和映像的时间戳并不匹配(即符号是错误的)。

如果符号搜索遇到灾难性的错误,会看到这样的信息:

ImgHlpFindDebugInfo(00000000, module.dll, c:\MyDir;c:\SomeDir, 0823345, 0) failed

这可以由文件系统错误、网络错误和错误的.dbg文件造成。

诊断符号加载错误

在详细模式时,调试器不能加载某个符号文件时会打印出错误码。.dbg文件相关的错误码在winerror.h 中列出。.pdb
的错误代码由另一个源获得,并且通常以英文文本的形式输出出来。

一些常见的winerror.h 中的.dbg 文件错误代码如下:

0xB
ERROR_BAD_FORMAT
0x3
ERROR_PATH_NOT_FOUND
0x35
ERROR_BAD_NETPATH

有可能不能加载符号文件的原因是网络错误。如果从网络上另一台机器上加载符号并看到ERROR_BAD_FORMAT 或ERROR_BAD_NETPATH
错误,可以尝试将符号文件复制到本地计算机,并将它的路径加入符号路径。然后重新加载符号。

验证搜索路径和符号

假设符号路径中有"c:\MyDir;c:\SomeDir"Let "。应该从何处查找调试信息呢?

假设二进制文件和Windows发行版一样已经剥离了调试信息,首先会在以下位置查找.dbg文件:

c:\MyDir\symbols\exe\ntoskrnl.dbg
c:\SomeDir\symbols\exe\ntoskrnl.dbg
c:\MyDir\exe\ntoskrnl.dbg
c:\SomeDir\exe\ntoskrnl.dbg
c:\MyDir\ntoskrnl.dbg
c:\SomeDir\ntoskrnl.dbg
current-working-directory\ntoskrnl.dbg

然后,在以下位置查找.pdb文件:

c:\MyDir\symbols\exe\ntoskrnl.pdb
c:\MyDir\exe\ntoskrnl.pdb
c:\MyDir\ntoskrnl.pdb
c:\SomeDir\symbols\exe\ntoskrnl.pdb
c:\SomeDir\exe\ntoskrnl.pdb
c:\SomeDir\ntoskrnl.pdb
current-working-directory\ntoskrnl.pdb

注意,在搜索.dbg文件时,调试器交替的搜索MyDirSomeDir 目录,但是对.pdb的搜索不是这样。

Windows XP和之后版本的Windows不使用.dbg符号文件。

版本不匹配

在一台经常升级的机器上最常出现的调试问题是来自不同版本的不匹配的符号。该问题的三个普遍原因是:指向错误的版本,使用私有版本的二进制文件但是没有适合的符号,在多处理器机器上使用单处理器硬件抽象层(HAL)和内核符号。前两个问题只需要使用和二进制文件匹配的符号;第三个问题可以通过将hal*.dbg
ntkrnlmp.dbg 重命名为hal.dbg ntoskrnl.dbg来解决。

要知道目标机上安装的Windows版本,可以使用vertarget (Show Target Computer Version)命令:

kd> vertarget 
Windows XP Kernel Version 2505 UP Free x86 compatible
Built by: 2505.main.010626-1514
Kernel base = 0x804d0000 PsLoadedModuleList = 0x80548748
Debug session time: Mon Jul 02 14:41:11 2001
System Uptime: 0 days 0:04:53 

测试符号

测试符号要困难一些。它涉及到验证调试器的堆栈跟踪和查看调试输出是否正确。这里有一个例子:

kd> u videoprt!videoportfindadapter2
Loading symbols for 0xf2860000     videoprt.sys ->   videoprt.sys

VIDEOPRT!VideoPortFindAdapter2:
f2856f42 55               push    ebp
f2856f43 8bec             mov     ebp,esp
f2856f45 81ecb8010000     sub     esp,0x1b8
f2856f4b 8b4518           mov     eax,[ebp+0x18]
f2856f4e 53               push    ebx
f2856f4f 8365f400         and     dword ptr [ebp-0xc],0x
f2856f53 8065ff00         and     byte ptr [ebp-0x1],0x0
f2856f57 56               push    esi

u命令反汇编videoprt.sys 中的videoportfindadapter
字符串。调试器上的符号是正确的,因为常用的类似pushmov这样的堆栈指令被显示出来。大多数函数都以对基指针(ebp)或堆栈指针(esp)的加、减或压栈操作开头。

当符号工作不正常时一般都容易发现。Glintmp.sys在本例子中没有符号,因为Glintmp边上没有显示出函数名:

kd> kb
Loading symbols for 0xf28d0000     videoprt.sys ->   videoprt.sys
Loading symbols for 0xf9cdd000      glintmp.sys ->   glintmp.sys
*** ERROR: Symbols could not be loaded for glintmp.sys
ChildEBP RetAddr  Args to Child
f29bf1b0 8045b5fa 00000001 0000a100 00000030 ntoskrnl!RtlpBreakWithStatusInstruction
f29bf1b0 8044904e 00000001 0000a100 00000030 ntoskrnl!KeUpdateSystemTime+0x13e
f29bf234 f28d1955 f9b7d000 ffafb2dc f9b7d000 ntoskrnl!READ_REGISTER_ULONG+0x6
f29bf248 f9cde411 f9b7d000 f29bf2b0 f9ba0060 VIDEOPRT!VideoPortReadRegisterUlong+0x27
00000002 00000000 00000000 00000000 00000000 glintMP+0x1411 [No function listed.] 

这个堆栈跟踪中加载了错误版本的符号。注意前两个函数调用中没有列出函数名。堆栈回溯看起来像win32k.sys 绘制矩形的错误一样:

1: kd> 
1: kd> kb                      [Local        9:50 AM]
Loading symbols for 0xf22b0000       agpcpq.sys ->   agpcpq.sys
*** WARNING: symbols checksum is wrong 0x0000735a 0x00000000 for agpcpq.sys
*** ERROR: Symbols could not be loaded for agpcpq.sys
Loading symbols for 0xa0000000       win32k.sys ->   win32k.sys
*** WARNING: symbols checksum is wrong 0x00191a41 0x001995a9 for win32k.sys
ChildEBP RetAddr  Args to Child
be682b18 f22b372b 82707128 f21c1ffc 826a70f8 agpCPQ+0x125b [No function listed.]
be682b4c a0140dd4 826a72f0 e11410a8 a0139605 agpCPQ+0x372b [No function listed.]
be682b80 a00f5646 e1145100 e1cee560 e1cee560 win32k!vPatCpyRect1_6x6+0x20b
00000001 00000000 00000000 00000000 00000000 win32k!RemoteRedrawRectangle+0x32 

这是正确的调用堆栈。真正的问题是AGP440.sys 造成的。一般出现在调用堆栈中的第一项就是错误出现的地方。注意这里没有win32k.sys 的矩形绘制错误了:

1: kd> kb                      [Local        9:49 AM]
ChildEBP RetAddr  Args to Child
be682b18 f22b372b 82707128 f21c1ffc 826a70f8 agpCPQ!AgpReleaseMemory+0x88
be682b30 f20a385c 82703638 e183ec68 00000000 agpCPQ!AgpInterfaceReleaseMemory+0x8b
be682b4c a0140dd4 826a72f0 e11410a8 a0139605 VIDEOPRT!AgpReleasePhysical+0x44
be682b58 a0139605 e1cee560 e11410a8 a00e5f0a win32k!OsAGPFree+0x14
be682b64 a00e5f0a e1cee560 e11410a8 e1cee560 win32k!AGPFree+0xd
be682b80 a00f5646 e1145100 e1cee560 e1cee560 win32k!HeapVidMemFini+0x49
be682b9c a00f5c20 e1cee008 e1cee008 be682c0c win32k!vDdDisableDriver+0x3a
be682bac a00da510 e1cee008 00000000 be682c0c win32k!vDdDisableDirectDraw+0x2d
be682bc4 a00da787 00000000 e1843df8 e1843de8 win32k!PDEVOBJ__vDisableSurface+0x27
be682bec a00d59fb 00000000 e1843de8 00000000 win32k!PDEVOBJ__vUnreferencePdev+0x204
be682c04 a00d7421 e1cee008 82566a98 00000001 win32k!DrvDestroyMDEV+0x30
be682ce0 a00a9e7f e1843e10 e184a008 00000000 win32k!DrvChangeDisplaySettings+0x8b3
be682d20 a008b543 00000000 00000000 00000000 win32k!xxxUserChangeDisplaySettings+0x106
be682d48 8045d119 00000000 00000000 00000000 win32k!NtUserChangeDisplaySettings+0x48
be682d48 77e63660 00000000 00000000 00000000 ntkrnlmp!KiSystemService+0xc9 

有用的命令和扩展命令

下面的命令和扩展命令可能对查找符号问题很有帮助:

lm (List Loaded Modules)
列举所有模块并且显示这些模块的符号的加载状态。
!dhimage-header-base
显示以image-header-base 开头的已加载映像的头信息。
.reload /n
重新加载所有内核符号。
.reload [image-name]
(仅CDB 或WinDbg) 重新加载名为image-name 的映像的符号。如果没有指定image-name,则重新为所有映像加载符号。
(在修改符号路径之后需要重新加载符号。)
!sym noisy
打开符号加载的详细模式。这可以用来获得模块加载的信息。查看设置符号选项获得详细说明。
.sympath [new-symbol-path]
设置新的符号路径,或显示当前符号路径。查看符号路径获得详细信息。

如果内核符号是正确的,但是没有获得完整调用堆栈,下面的命令可能会很有用:

X *!
这会列出当前加载了符号的模块。在内核符号正确的时候很有用。
.reload /user
尝试重新加载所有用户模式符号。这在进行内核调试,某个进程正在运行的时候加载了符号,并且之后另一个进程中触发了断点时需要。这种情况下,从新进程而来的用户模式符号在执行该命令之前都不会被加载起来。
X wdmaud!*start*
这会列出wdmaud 模块中包含"start"字符串的符号。这样有一个好处是可以强制重新加载wdmaud
中的所有符号,但是只显示包含"start"的。(这意味着列表更短,但是由于很可能一些符号包含了"start",还是会发生一些验证。)

另一个验证符号的有用技术是对代码进行反汇编。大多数函数以对基指针(ebp)或堆栈指针(esp
sp)的加、减或压栈操作开头。可以尝试对堆栈中的一些函数(从0偏移开始)进行反汇编(Function)来验证符号。

网络和端口问题

当连接到调试器时可能出现符号文件的问题。在遇到这些问题时需要在头脑中保留这些东西:

  • 确认调试电缆连接到测试系统的哪个COM端口上。
  • 检查测试系统的boot.ini 设置。查看/debug开关并检查波特率和COM端口设置。
  • 当通过网络访问符号文件时,调试时可能遇到网络问题。
  • 相同名字的.dll .sys文件 (例如 − mga64.sys mga64.dll)
    如果他们没有分布在不同的符号目录树中,会搞乱调试器。
  • 内核调试时用私有符号文件替换版本符号文件并不总是好的。检查符号路径几次并对表现不正常的符号使用.reloadFileName!dlls命令有时候很有用。

问题和误解

Q:我成功加载了符号,但是调用堆栈看起来是错的。调试器坏了吗?

A:
不一定。这个问题最可能的原因是使用了错误的符号。使用本节中上面的步骤来检查是否加载了错误的符号。一些东西工作正常不能假定拥有了正确的符号。例如,在没有正确的符号是很可能可以成功的执行dd
nt!ntbuildnumber
u nt!KeInitializeProcess。使用上面描述的步骤来验证符号的正确性。

Q: 调试器可以使用不正确的符号工作吗?

A:
可以说是也可以说不是。常常可以摆脱符号没有严格匹配的问题。例如,前一个Windows版本的符号常常在某些情况下也可以使用,但是它什么时候又用,什么时候没用是不确定的。

Q:我在内核调试器中中断下来,并且想查看用户模式进程中的符号。可以这样做吗?

A:
通常可以。对这种情况的支持不多,因为内核调试器并没有保留足够信息用来跟踪每个进程的模块加载,但是有合理的替代方法。要加载用户模式模块的符号,可以执行.reload
–user
命令。他可以加载当前上下文的用户模式模块。

Q:下面这条消息是什么意思?

*** WARNING: symbols checksum and timestamp is wrong 0x0036d6bf 0x0036ab55 for ntkrnlmp.exe

A:这意味着ntkrnlmp.exe 的符号是错误的。

!lmi

!lmi扩展显示某个模块的详细信息。

语法

!lmi Module 

参数

Module
用名字或者基地址指定一个已加载的模块。

DLL

Windows 2000 Dbghelp.dll
Windows XP和之后 Dbghelp.dll

注释

可以使用lm (List Loaded Modules)命令获得模块基地址。

!lmi扩展分析模块头,并格式化显示它包含的摘要信息。如果模块头被页换出了,则显示一条错误信息。要查看更多头信息,使用!dh扩展命令。

该命令会显示很多具有不同标题的字段。某些标题有特殊的意义:

  • Image Name字段显示可执行文件名,包括扩展名。一般来说,用户模式下会包含完整路径,而内核模式下不会。
  • Module字段显示模块名。它通常是没有扩展名的文件名。但是有些情况下模块名和文件名会有显著的不同。详细信息,查看可执行映像路径。
  • Symbol Type字段显示调试器会尝试用来加载该模块的符号的信息。各种状态值得说明,查看符号状态缩写。如果符号已经加载,则后面会跟符号的文件名。
  • Base Address显示模块的首地址。Size显示模块大小。 因此,如果Base Address是"faab4000" 并且Size是"2000",则模块是在0xFAAB4000 到0xFAAB5FFF范围内。

下面是一个例子:

0:000> lm 
start    end        module name
00400000 0042d000   Prymes     C (pdb symbols)              Prymes.pdb
77e80000 77f35000   KERNEL32     (export symbols)           C:\WINNT\system32\KERNEL32.dll
77f80000 77ffb000   ntdll        (export symbols)           ntdll.dll

0:000> !lmi 00400000
Loaded Module Info: [00400000] 
         Module: Prymes
   Base Address: 00400000
     Image Name: Prymes.exe
   Machine Type: 332 (I386)
     Time Stamp: 3c76c346 Fri Feb 22 14:16:38 2002
           Size: 2d000
       CheckSum: 0
Characteristics: 230e stripped 
Debug Data Dirs: Type Size     VA  Pointer
                 MISC  110,     0,   77a00 [Data not mapped]
    Symbol Type: EXPORT   - PDB not found
    Load Report: export symbols

例子中的Characteristics行显示的缩写的说明,查看符号状态缩写。

.wtitle (Set Window Title)

.wtitle 命令设置WinDbg主窗口或者NTSD、CDB、 KD窗口的标题

语法

.wtitle Title 

参数

Title
窗口要使用的标题。

环境

该命令不能用于脚本文件。

模式 用户模式、内核模式
目标 活动目标、崩溃转储
平台 所有

注释

对于CDB、NTSD或KD如果没有使用 .wtitle 命令,窗口标题是启动调试器使用的命令行。

对于WinDbg,如果没有使用.wtitle,主窗口标题包含目标的名字。如果激活了调试服务器,也会显示连接字符串。如果激活了多个调试服务器,则只显示最近的一个。

使用.wtitle后, Title会替代所有信息。即使之后又启动了调试服务器,Title也不会改变。

WinDbg版本号总是会显示在窗口标题栏上,不管是否使用了该命令。