转载注明>> 【作者:张佩】【镜像:http://www.yiiyee.cn/Blog】
1. 概述
用户成功安装微软Windows调试工具集后,能够在安装目录下发现四个调试器程序,分别是:cdb.exe、ntsd.exe、kd.exe和Windbg.exe。其中cdb.exe和ntsd.exe只能调试用户程序,Kd.exe主要用于内核调试,有时候也用于用户态调试,上述三者的一个共同特点是,都只有控制台界面,以命令行形式工作。
Windbg.exe在用户态、内核态下都能够发挥调试功能,尤其重要的是,它不再是命令行格式而是采用了可视化的用户界面。所以绝大部分情况下,我们在谈及Windows调试工具的时候,都直接指向Windbg,而不大谈及前三者。
Windbg在用户态和内核态下,都支持两种调试模式,即“实时调试模式(Living)”和“事后调试模式(Postmortem)”。所谓实时模式,是被调试的目标对象(Target)当前正在运行当中,调试器可以实时分析、修改被调试目标的状态,如寄存器、内存、变量,调试exe可执行程序或双击实时调试都属于这种模式;所谓事后模式,是被调试的目标对象(Target)已经结束了,现在只是事后对它保留的快照进行分析,这个快照称为转储文件(Dump文件)。
Windbg另一个重大优点,还在于它支持源码级的调试,就像VC自带的调试器一样。
虽然提供了用户界面,但Windbg归根结底还是需要用户一个个地输入命令来指挥其行动。这就是他的Command窗口。
每个调试命令都各有使用范围,有些命令只能用于内核调试,有些命令只能用于用户调试,有些命令只能用于活动调试。但用户也不必记得这许多,一旦在某个环境下,使用了不被支持的命令,都会显示“No export XXX found”的字样。就拿!process命令来说吧,它显示进程信息,但只能用于内核调试中,如果在用户调试中使用,就是下面的情景:
0:001> !process No export process found
1.1 寻求帮助
我们首先来看如何在使用过程中获取有用的帮助。Windbg中的调试命令,分为三种:基本命令,元命令和扩展命令。基本命令和元命令是调试器自带的,元命令总是以“.”开头,而扩展命令是外部加入的,总是以感叹号“!”开头。各种调试命令成千上万,我们首先要想办法把它们都列举出来,并取得使用方法。
基本命令最少了,大概40个左右。列举所有的基本命令,使用如下命令:
- ?
元命令有一百多个,使用下面命令列举所有元命令:
- .help [/D]
如使用“/D”参数,命令列表将以DML格式显示。DML是一种类似于HTML的标识语言,下面会讲到。下图以DML格式显示所以有字母a开头的元命令:
最后讲扩展命令。所谓扩展命令,顾名思义是可以“扩展”的。扩展命令从动态连接库中暴露出来,一般以DLL文件名来代表一类扩展命令集,首先我们要搜索出系统中有多少个这样的DLL文件,使用下面命令:
- .chain [/D]
此命令能够给出一个扩展命令集的链表。和.help命令一样,也可以使用/D参数以DML格式显示。如下所示:
0:001> .chain Extension DLL search Path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\WINXP; Extension DLL chain: dbghelp: image 6.2.9200.20512, API 6.2.6, built Fri Sep 07 13:45:49 2012 [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\dbghelp.dll] ext: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:11:33 2012 [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\winext\ext.dll] exts: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:15:20 2012 [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\WINXP\exts.dll] uext: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:15:09 2012 [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\winext\uext.dll] ntsdexts: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:16:01 2012 [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\WINXP\ntsdexts.dll]
最上面两行显示了扩展模块的搜索路径。接下来共列出了六个Windbg自带的扩展模块:wdfkd、dbghellp、ext、exts、uext和ntsdexts。可以查看到这些扩展模块的版本信息、镜像文件路径。如何列出某个扩展库中所包含的扩展命令列表呢?绝大部分扩展模块可使用如下命令:
- !模块名.help
此外,扩展命令模块是可“扩展”的。如果读者从第三方处获取,或自己编写了一个扩展调试模块,则可通过.load/.unload命令动态加载/卸载。
1.2 DML语言
DML(Debugger Markup Language调试器标记语言)像HTML一样,可从一处链接到另一处。不同处在于,DML的链接内容需要用户点击后才会动态生成。一般用来以精简方式显示大量信息和扩展功能。
DML有很多实用的功能,如果使用者一时不知道从何下手,最好就是输入.dml_start命令,开始DML之旅。
DML链接以更加可视化的方式,引导用户查看调试信息,使得调试工具的使用相比纯指令格式而言,更为友好。DML如同是对原指令的一层轻微的包装一样,让生硬的指令更加温和了。所以建议读者总是把DML默认开启。
- .prefer_dml 1
开始DML。
- .prefer_dml 0
关闭DML。
一旦开启DML后,像k等支持DML的调试命令,将默认以DML格式显示输出内容。
DML还能以一种很特殊的方式为函数画流程图。它主要的原理是使用反汇编,类似于uf,但在逻辑分支处,它会停止反汇编并显示分支让用户选择。另外,它能显示汇编代码对应的行号,这一点真的非常好。如果稍加精进,他就能画出非常漂亮的流程图了。他的一个特点是反汇编的顺序是从后往前推。只要细想一想,就会觉得很有道理。如果正推的话,分支太多;而反推则分支顺序在用户的参与下(即用户进行分支选择),是固定了的。
- .dml_flow FindAllInfFilesA FindAllInfFilesA+30
这是一个非常简单、实用的例子,对Kernel32库中的FindAllInfFilesA接口函数进行反汇编,效果类似uf命令却更强大。
1.3 基本信息
本节讲解和调试器软件本身相关的命令,比如:查看软件版本、启动参数,以及最基本的软件设置命令。首先看版本命令:
- version
此命令显示操作系统的版本信息以及Windbg本身的版本信息,Windbg的配置和操作系统密切相关,所以将操作系统的版本信息一并显示出来是很有必要的。在内核环境与用户环境下运行此命令,会得到不同的输出。下图为内核环境下输出结果:
0:001> version Windows 7 Version 7601 (Service Pack 1) MP (8 procs) Free x64 Product: WinNt, suite: SingleUserTS kernel32.dll version: 6.1.7601.18015 (win7sp1_gdr.121129-1432) Machine Name: Debug session time: Thu Aug 22 10:11:04.000 2013 (UTC + 8:00) System Uptime: 14 days 17:26:44.613 Process Uptime: 14 days 17:14:25.000 Kernel time: 0 days 0:09:02.000 User time: 0 days 0:42:36.000 Full memory user mini dump: C:\Users\mozhang\AppData\Local\Temp\dwm.DMP Microsoft (R) Windows Debugger Version 6.2.9200.16384 AMD64 Copyright (c) Microsoft Corporation. All rights reserved.
除了Windbg版本信息,上面的输出中还包括目标系统信息。如果纯粹是为了查看目标系统的版本信息,可使用下面的vertarget命令:
- vertarget
Windbg支持对多个调试系统中的多个调试目标同时进行调试。上面我们通过version或vertarget命令列出了当前调试系统的版本信息,还可以查看当前目标系统的状态:
- ||
如Windbg中同时打开多个调试对象,“||”命令将列出对象列表。笔者为了演示此种情况,先在Windbg中开启Local Debug环境,然后两次调用.opendump命令打开两个DUMP文件,这样就同时拥有了三个被调试的目标对象。下图显示了这个情况:
上图中的活动对象是0号对象(可从数字0前面的小数点看出)。调试器需要在多个调试目标之间进行切换的话,使用“s”参数。如要切换到1号目标可使用下面的命令:
- || 1 s
最后一个命令用来查看系统时间。这包括系统当前时间,以及系统正常运行持续时间;用户模式下还会显示当前进程的持续时间。命令格式如下:
- .time
0:001> .time Debug session time: Thu Aug 22 10:11:04.000 2013 (UTC + 8:00) System Uptime: 14 days 17:26:44.613 // 系统运行时间 Process Uptime: 14 days 17:14:25.000// 当前进程运行时间 Kernel time: 0 days 0:09:02.000 User time: 0 days 0:42:36.000
1.4 基本设置
首先看一个清屏命令:
- .cls
当命令窗口中的内容太乱的时候,这个命令帮你快刀斩乱麻。
下面看一个设置默认数字进制的命令:
- n [8|10|16]
软件默认是16进制,但有时候我们也需要把默认进制改成八进制或十进制的。下面尝试在八进制下面求数字11的值,如下:
0:001> n 8 base is 8 0:001> ? 11 Evaluate expression: 9 = 00000000`00000009
最后再来说一个处理器模式指令。关于处理器模式很值得一说,很重要。处理器模式的设置,反映了Windbg软件的强大。举例来说,主机为32位的系统,却可以同时调试X86、IA64、X64的目标系统——前提是先将主机的处理器模式设置正确了。可用处理器模式值有:x86、adm64、ia64、ebc。
- .effmach x86
命令.effmach表示Effective Machine Type,即有效的机器类型。此命令将当前的处理器模式设置为x86模式。
1.5 格式化显示
将一个整数以各种格式显示,包括:16进制、10进制、8进制、二进制、字符串、日期、浮点数等。是不是很方便?这个命令是:
- .formats 整数
下面以0x123abc为例:
0:001> .formats 0x123abc Evaluate expression: Hex: 00000000`00123abc Decimal: 1194684 Octal: 0000000000000004435274 Binary: 00000000 00000000 00000000 00000000 00000000 00010010 00111010 10111100 Chars: ......:. Time: Thu Jan 15 03:51:24 1970 Float: low 1.67411e-039 high 0 Double: 5.90252e-318
1.6 开始调试
现在领大家进入调试阶段。首先看看如何让调试器附载到一个已运行的进程中去?比如IE软件在运行过程中发生了崩溃,打开Windbg后如何调试呢?第一步就是把Windbg附载到发生崩溃的IE进程上。使用如下命令格式:
- .attach PID
或者通过Windbg的启动参数进行挂载:
- Windbg –p PID
上面两个命令中,PID指定了进程ID。如果觉得指定PID不方便,也可以通过进程名进行挂载:
- Windbg -pn 进程名
比如挂载到记事本就可以这样:
windbg –pn notepad.exe
上面的命令是把调试器挂载到已经存在的进程上,另外调试器可以创建新进程并对它进行调试,这二者使用了不同的侵入方法。使用下面的命令:
- .create 程序启动命令行
或者Windbg启动参数
- Windbg 程序启动命令行
比如创建并调试一个记事本子进程,可用.create notepad或者windbg notepad命令。也可以打开Windbg后,在File菜单中选择“Open Executable…”启动Notepad子进程,但这个选项只能被执行一次(之后会灰掉)。
使用上述命令可将调试器连续附载到多个进程,也就是说,能够同时调试多个进程,这一点看上去很神奇哦。下例中,调试器先创建子程序IOCTL.exe,然后又调用.attach命令附加到记事本进程,使用命令“|”列出所有被调试进程。
读者可能会奇怪,多个进程同时调试怎么兼顾呢?只要有一个切换指令就可以了,这样就能够切换到任意的进程(令其为当前进程)并对之进行调试。比如上图显示1号进程为当前进程(注意1前面的小点),如何将当前进程切换到0号进程呢?可以使用进程列表命令“|”轻松切换,比如:
- | 0 s
此命令把当前调试环境切换到0号IOCTL.exe进程。另外需注意的是,多个用户进程调试目标都处于同一个调试会话中,使用“||”命令会看到,它们属于同一个 “Live user mode”调试会话。
下面看dump文件调试,使用命令:
- .opendump 文件名
此命令打开一个dump文件,并建立一个DUMP调试会话。如何手动创建一个dump文件呢?比如在调试过程中,遇到了无法解决的问题,希望获得异地帮助,则把当前调试环境保存到Dump文件中发送给能提供帮助的人,不失为一种好办法。
- .dump 文件名
Dump文件一般以.dmp为后缀,系统生成的Dump文件都默认以.dmp为后缀的,但使用.dump命令时,使用者可以设置任意后缀,甚至无后缀。下例中,首先为当前进程生成一个dump文件保存到a.txt中(即后缀名为.txt),然后将之打开并分析:
0:001> .dump a.txt Creating a.txt - mini user dump Dump successfully written 0:001> .opendump a.txt Loading Dump File [C:\Program Files (x86)\Windows Kits\8.0\Debuggers\a.txt] User Mini Dump File: Only registers, stack and portions of memory are available Opened 'a.txt' // 打开成功
上文讲到进程挂载命令,当需要解除挂载时,可使用解挂命令,如下:
- .detach
此命令结束当前调试会话, Windbg解除和被调试进程之间的调试关系(不管是通过挂载,还是通过创建方式建立的调试关系),解挂后,被调试进程能够独立运行;如果当前的调试会话是一个Dump文件,此命令直接结束对dump文件的调试,即结束调试会话。
如果需要彻底结束调试,下面的命令更有用:
- q | qq | qd
q是Quit的缩写。结束当前调试会话,并返回到最简单的工作空间,甚至把命令行界面也关闭掉。q和qq两个命令将结束(close)被调试的进程,qd不会关闭调试进程,而是进行解挂操作。
双机调试的时候,如果你感觉调试已经陷入僵局,比如目标机Hang住了动都动不了,此时通过主机让目标机强制宕机或重启,不失为一个好主意。
- .crash
- .reboot
crash命令能引发一个系统蓝屏,并生成dump文件;而.reboot使系统重启,不产生dump文件。