NetRoc's Blog

N-Tech

 

WinDbg 文档翻译----36

cc682/NetRoc

http://netroc682.spaces.live.com/

符号无效或丢失

符号无效或丢失是最常见的调试器问题的原因。遇到很多问题时,需要找出是否有符号问题。

某些情况下,解决方案就是获得正确的符号文件。另一些情况下,只需要对调试器进行简单的配置,以使得它能正确识别已有的符号文件。但是如果不能获得正确的符号文件,只能带着这些问题使用有限制的功能对目标进行调试。

先使用公有符号然后又切换成使用私有符号的情况并不经常需要,但是当存在不同的导出符号拥有相同地址时,可能存在问题。这种情况下,可以设置两个断点:一个在公有符号上,第二个在私有符号上。

本节包含:

验证符号

使用页面换出的文件头读取符号

不使用符号调试用户模式进程

验证符号

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

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

首先,使用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*.dbgntkrnlmp.dbg 重命名为hal.dbgntoskrnl.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)

列举所有模块并且显示这些模块的符号的加载状态。

!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)或堆栈指针(espsp)的加、减或压栈操作开头。可以尝试对堆栈中的一些函数(从0偏移开始)进行反汇编(U <funct>)来验证符号。

网络和端口问题

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

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

问题和误解

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

A: 不一定。这个问题最可能的原因是使用了错误的符号。使用本节中上面的步骤来检查是否加载了错误的符号。一些东西工作正常不能假定拥有了正确的符号。例如,在没有正确的符号是很可能可以成功的执行dd nt!ntbuildnumberu 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 的符号是错误的。

使用页面换出的文件头读取符号

内核调试器必须读取每个已加载模块的映像头部来确定匹配该模块的符号。

如果模块的头部被换页到磁盘上,调试器就不能加载该模块的符号。如果这发生在被调试进程的核心模块上,就会成为严重问题。

下面是用来解决该问题的步骤。

获得换出的文件头的符号

  1. 制作一个内核本身的拷贝。将它放到网络共享上可能是最方便的。
  2. 将该共享的根目录加入符号路径。查看符号路径获得修改符号路径的方法。
  3. 使用.reload (Reload Module) 命令。
  4. 使用!sym noisy扩展命令来查看详细输出。如果这样,就可以哪些符号是从目标机上的模块映像加载,哪些是从内核模块的拷贝加载的。

这种技术需要小心的使用,因为调试器没有办法验证这个拷贝文件是否和原始文件真正匹配。所以目标机和网络共享上的Windows版本匹配是至关重要的。

该技术仅用于内核调试。用户模式调试时,系统会负责将所有需要的头换页进内存(除非包含页面文件的磁盘被卸载 (dismounted)或者不能访问)。

这是使用该技术的一个例子:

kd> .reload
Connected to Windows XP 2268 x86 compatible target, ptr64 FALSE
Loading Kernel Symbols
..........Unable to read image header for dmload.sys at fe0be000 - NTSTATUS 0xC0000001
..........Unable to read image header for dmboot.sys at fda93000 - NTSTATUS 0xC0000001
.....................................Unable to read image header for fdc.sys at fdfc2000 - NTSTATUS 0xC0000001
...Unable to read image header for flpydisk.sys at fde4a000 - NTSTATUS 0xC0000001
.Unable to read image header for Fs_Rec.SYS at fe0c8000 - NTSTATUS 0xC0000001
.Unable to read image header for Null.SYS at fe2c4000 - NTSTATUS 0xC0000001
...................Unable to read image header for win32k.sys at a0000000 - NTSTATUS 0xC0000001
..Unable to read image header for dxg.sys at a0194000 - NTSTATUS 0xC0000001
.......Unable to read image header for ati2draa.dll at a01a4000 - NTSTATUS 0xC0000001
..Unable to read image header for ParVdm.SYS at fe116000 - NTSTATUS 0xC0000001
.......
Loading unloaded module list
..............
Loading User Symbols
Unable to retrieve the PEB address. This is usually caused
by being in the wrong process context or by paging

注意很多映像没有可访问的头。检查某个文件的符号(本例中为fs_rec.sys):

kd> x fs_rec!*
*** ERROR: Module load completed but symbols could not be loaded for fs_rec.sys

头部明显被换出了。所以需要将合适的映像加入到符号路径中:

kd> .sympath+ \\myserver\myshare\symbols\x86fre\symbols
Symbol search path is: symsrv*symsrv.dll*c:\localcache*http://msdl.microsoft.com/download/symbols;\\myserver\myshare\symbols\x86fre\symbols

kd> .reload
Connected to Windows XP 2268 x86 compatible target, ptr64 FALSE
Loading Kernel Symbols
..........Unable to read image header for dmload.sys at fe0be000 - NTSTATUS 0xC0000001
..........Unable to read image header for dmboot.sys at fda93000 - NTSTATUS 0xC0000001
.....................................Unable to read image header for fdc.sys at fdfc2000 - NTSTATUS 0xC0000001
...Unable to read image header for flpydisk.sys at fde4a000 - NTSTATUS 0xC0000001
.Unable to read image header for Fs_Rec.SYS at fe0c8000 - NTSTATUS 0xC0000001
.Unable to read image header for Null.SYS at fe2c4000 - NTSTATUS 0xC0000001
...................Unable to read image header for win32k.sys at a0000000 - NTSTATUS 0xC0000001
..Unable to read image header for dxg.sys at a0194000 - NTSTATUS 0xC0000001
.......Unable to read image header for ati2draa.dll at a01a4000 - NTSTATUS 0xC0000001
..Unable to read image header for ParVdm.SYS at fe116000 - NTSTATUS 0xC0000001
.......
Loading unloaded module list
..............
Loading User Symbols
Unable to retrieve the PEB address. This is usually caused
by being in the wrong process context or by paging

会出现同样的警告,但是符号本身已经可用了:

kd> x fs_Rec!*
fe0c8358  Fs_Rec!_imp___allmul
fe0c8310  Fs_Rec!_imp__IoCreateDevice
fe0c835c  Fs_Rec!_imp___allshr
........
fe0c8360  Fs_Rec!ntoskrnl_NULL_THUNK_DATA
fe0c832c  Fs_Rec!_imp__KeSetEvent
fe0c9570  Fs_Rec!_NULL_IMPORT_DESCRIPTOR

不使用符号调试用户模式进程

在启动调试器调试用户模式错误之前获得符号文件是很重要的。但是有时候调试器在没有符号的情况下启动起来。如果问题很容易重现,可以复制符号再来一次。但是如果问题难以重现,还是可以搜集一些错误的信息:

  1. 要计算出地址的意义,需要有一台和出问题的机器一样的计算机。他需要有相同的平台 (x86、Intel Itanium或x64)并且加载相同版本的Windows。
  2. 配置好计算机后,将用户模式符号和二进制文件复制到用于调试的新机器上。
  3. 在没有符号的机器上启动CDB或WinDbg。
  4. 如果不知道无符号及其上哪个程序出错了,可以使用| (Process Status) 命令。如果这样不能知道名字,在无符号机器上中断到KD,并执行!process 0 0,查找CDB命令中给出的进程ID。
  5. 设置好了两个调试器之后——一个有符号但是没有遇到错误,另一个遇到错误但是没有符号——在无符号机器上执行k (Display Stack Backtrace)命令。
  6. 在有符号机器上,对每个无符号机器上调用堆栈显示出来的地址执行u (Unassemble) 命令。这样就可以得到无符号机器上的错误的调用堆栈。

通过查看调用堆栈,可以找到调用相关的模块和函数。

posted on 2008-05-28 13:27 NetRoc 阅读(1955) 评论(2)  编辑 收藏 引用

评论

# re: WinDbg 文档翻译----36 2008-05-28 16:18 盛大推广员

收藏下  回复  更多评论   

# re: WinDbg 文档翻译----36 2008-08-22 20:55 cobala

提个小意见,能否按照章节的标题作为你译文的标题,你知道磁带机进步到磁盘机的特性之一就是随机存取。  回复  更多评论   

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜