调试时符号文件问题排查思路
符号问题会以各种方式表现出来。可能堆栈回溯显示错误信息,或者不能显示堆栈中的函数名。也可能调试器命令不能识别模块、函数、变量、结构或数据类型的名字。
如果猜测没有正确加载符号,可以通过几个步骤来确认问题。
首先,使用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文件时,调试器交替的搜索MyDir 和SomeDir 目录,但是对.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 字符串。调试器上的符号是正确的,因为常用的类似push和mov这样的堆栈指令被显示出来。大多数函数都以对基指针(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)
- 列举所有模块并且显示这些模块的符号的加载状态。
- !dh image-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偏移开始)进行反汇编(U Function)来验证符号。
网络和端口问题
当连接到调试器时可能出现符号文件的问题。在遇到这些问题时需要在头脑中保留这些东西:
- 确认调试电缆连接到测试系统的哪个COM端口上。
- 检查测试系统的boot.ini 设置。查看/debug 开关并检查波特率和COM端口设置。
- 当通过网络访问符号文件时,调试时可能遇到网络问题。
- 相同名字的.dll 和.sys文件 (例如 − mga64.sys 和mga64.dll) 如果他们没有分布在不同的符号目录树中,会搞乱调试器。
- 内核调试时用私有符号文件替换版本符号文件并不总是好的。检查符号路径几次并对表现不正常的符号使用.reload FileName。!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 的符号是错误的。