Debugging Tools for Windows |
符号问题会以各种方式表现出来。可能堆栈回溯显示错误信息,或者不能显示堆栈中的函数名。也可能调试器命令不能识别模块、函数、变量、结构或数据类型的名字。
如果猜测没有正确加载符号,可以通过几个步骤来确认问题。
首先,使用lm (List Loaded Modules)命令来显示已加载模块和符号信息的列表。该命令最有用的形式如下:
如果使用WinDbg,Debug | Modules菜单命令也可以看到这些信息。
对这些输出中的注释或缩写要特别注意。详细说明查看符号状态缩写。
如果没有看到是当的符号文件,首先应该做的是检查符号路径:
Current Symbol Path is: d:\MyInstallation\i386\symbols\retail
如果符号路径错误,则修正它。如果正在使用内核调试器,要确认本地%WINDIR%不在符号路径中。
然后使用.reload (Reload Module)命令重新加载符号:
如果符号路径正确,可以激活详细模式(noisy mode)来查看dbghelp加载了什么符号文件。然后重新加载模块。查看设置符号选项来获得激活详细模式的方法。
这是一个"详细"重加载Microsoft Windows符号的例子:
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行表明,虽然符号加载了,但是和映像的时间戳并不匹配(即符号是错误的)。
如果符号搜索遇到灾难性的错误,会看到这样的信息:
这可以由文件系统错误、网络错误和错误的.dbg文件造成。
在详细模式时,调试器不能加载某个符号文件时会打印出错误码。.dbg文件相关的错误码在winerror.h 中列出。.pdb 的错误代码由另一个源获得,并且通常以英文文本的形式输出出来。
一些常见的winerror.h 中的.dbg 文件错误代码如下:
有可能不能加载符号文件的原因是网络错误。如果从网络上另一台机器上加载符号并看到ERROR_BAD_FORMAT 或ERROR_BAD_NETPATH 错误,可以尝试将符号文件复制到本地计算机,并将它的路径加入符号路径。然后重新加载符号。
假设符号路径中有"c:\MyDir;c:\SomeDir"Let "。应该从何处查找调试信息呢?
假设二进制文件和Windows发行版一样已经剥离了调试信息,首先会在以下位置查找.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\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)命令:
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
测试符号要困难一些。它涉及到验证调试器的堆栈跟踪和查看调试输出是否正确。这里有一个例子:
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边上没有显示出函数名:
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> 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 的矩形绘制错误了:
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
下面的命令和扩展命令可能对查找符号问题很有帮助:
如果内核符号是正确的,但是没有获得完整调用堆栈,下面的命令可能会很有用:
另一个验证符号的有用技术是对代码进行反汇编。大多数函数以对基指针(ebp)或堆栈指针(esp 或sp)的加、减或压栈操作开头。可以尝试对堆栈中的一些函数(从0偏移开始)进行反汇编(U Function)来验证符号。
当连接到调试器时可能出现符号文件的问题。在遇到这些问题时需要在头脑中保留这些东西:
Q: 我成功加载了符号,但是调用堆栈看起来是错的。调试器坏了吗?
A: 不一定。这个问题最可能的原因是使用了错误的符号。使用本节中上面的步骤来检查是否加载了错误的符号。一些东西工作正常不能假定拥有了正确的符号。例如,在没有正确的符号是很可能可以成功的执行dd nt!ntbuildnumber 或u nt!KeInitializeProcess。使用上面描述的步骤来验证符号的正确性。
Q: 调试器可以使用不正确的符号工作吗?
A: 可以说是也可以说不是。常常可以摆脱符号没有严格匹配的问题。例如,前一个Windows版本的符号常常在某些情况下也可以使用,但是它什么时候又用,什么时候没用是不确定的。
Q: 我在内核调试器中中断下来,并且想查看用户模式进程中的符号。可以这样做吗?
A: 通常可以。对这种情况的支持不多,因为内核调试器并没有保留足够信息用来跟踪每个进程的模块加载,但是有合理的替代方法。要加载用户模式模块的符号,可以执行.reload –user命令。他可以加载当前上下文的用户模式模块。
Q: 下面这条消息是什么意思?
A: 这意味着ntkrnlmp.exe 的符号是错误的。