WinDbg调试器
转自:http://www.cnblogs.com/dubingsky/archive/2009/07/21/1527843.html
你可以从微软网站上下载到的调试器:
· KD-内核调试器。你可以用它来调试蓝屏一类的系统问题。如果是开发设备驱动程序是少不了它的。
· CDB-命令行调试器。这是一个命令行程序
· NTSD-NT调试器。这是一个用户模式调试器,可以用来调试用户模式应用程序。它实际上是一个CDB的windows UI增强。
· WinDbg-用一个漂亮的UI包装了KD和NTSD。WinDbg即可以调试内核模式,也可以调试用户模式程序。
· VS, VS.net-使用同KD和NTSD相同的调试引擎,并且相比于同样用于调试目的的WinDbg,提供了功能更丰富的界面。
WinDbg实际上包装了NTSD和KD并且提供了一个更好用的用户界面。它也提供了命令行开关,比如最小化启动(-m),附加到一PID指定的进程(-p)以及自动打开崩溃文件(-z)。它支持三种类型的命令。
· Regular commands(比如: k) 用来调试进程
· Dot commands(比如:.sympath)用来控制调试器
· Extension commands(比如: !handle)-这些命令属于可以用来添加到WinDbg的自定义命令;它们用扩展DLL的输出函数来实现。
PDB文件
PDB文件, 是链接器生成程序数据库文件(Program database files)。私有的PDB文件包括私有以及公有符号,源代码行号,类型,局部以及全局变量。公有的PDB文件不包含类型,局部变量以及源代码行号信息。
配置WinDbg
运行WinDbg->菜单->File->Symbol File Path->按照下面的方法设置_NT_SYMBOL_PATH变量:
在弹出的框中输入“C:\MyCodesSymbols; SRV*C:\MyLocalSymbols*http://msdl.microsoft.com/download/symbols”(按照这样设置,WinDbg将先从本地文件夹C:\MyCodesSymbols中查找Symbol,如果找不到,则自动从MS的Symbol Server上下载Symbols)。另一种做法是从这个Symbol下载地址中http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx,下载相应操作系统所需要的完整的Symbol安装包,并进行安装,例如我将其安装在D:\WINDOWS\Symbols,在该框中输入“D:\WINDOWS\Symbols”。(这里要注意下载的Symbols的版本一定要正确,在我的Win2003+Sp1上,我曾经以为安装Win2003+Sp2的Symbols可能会牛×点,但结果证明我错了,用WinDbg打开可执行文件时,提示“PDB symbol for mscorwks.dll not loaded;Defaulted to export symbols for ntdll.dll”的错误,我有重新装上Win2003+Sp1的Symbols, 现在一切运行正常^_^)
set _NT_SYMBOL_PATH=srv*C:\MySymbols*http://msdl.microsoft.com/download/symbols
WINDBG命令
调试前的必备工作
在开始调试前首先要做的工作是设置好符号(Symbols)路径。没有符号,你看到的调用堆栈基本上毫无意义。Microsoft的操作系统符号文件(PDB)是对外公开的。另外请注意在编译你自己的程序选择生成PDB文件的选项。如果设置好符号路径后,调用堆栈看起来还是不对。可以使用lm, !sym noisy, !reload 等命令来验证符号路径是否正确。
Windbg也支持源码级的调试。在开始源码调试前,你需要用.srcpath设置源代码路径。如果你是在生成所执行代码的机器上进行调试,符号文件中的源码路径会指向正确的位置,所以不需要设置源代码路径。如果所执行代码是在另一台机器上生成的,你可以将所用的源码拷贝(保持原有的目录结构)的一个可以访问的文件夹(可以是网络路径)并将源代码路径设为该文件夹的路径。注意如果是远程调试,你需要使用.lsrcpath来设置源码路径。
2)启动Debugger
Windbg可以用于如下三种调试:
(1)远程调试:你可以从机器A上调试在机器B上执行的程序。具体步骤如下:
在机器B上启动一个调试窗口(Debug Session)。你可以直接在Windbg下运行一个程序或者将Windbg附加(Attach)到一个进程。
在机器B的Windbg命令窗口上启动一个远程调试接口(remote):
.server npipe:pipe=PIPE_NAME
PIPE_NAME是该接口的名字。
在机器A上运行:
windbg –remote npipe:server=SERVER_NAME,pipe=PIPE_NAME
SERVER_NAME是机器B的名字。
Dump文件调试:如果在你的客户的机器上出现问题,你可能不能使用远程调试来解决问题。你可以要求你的用户将Windbg附加到出现问题的进程上,然后在命令窗口中输入:
.dump /ma File Name
创建一个Dump文件。在得到Dump文件后,使用如下的命令来打开它:
windbg –z DUMP_FILE_NAME
(2)本地进程调试:你可以在Windbg下直接运行一个程序:
Windbg “path to executable” arguments
也可以将Windbg附加到一个正在运行的程序:
Windbg –p “process id”
Windbg –pn “process name”
注意有一种非侵入(Noninvasive)模式可以用来检查一个进程的状态并不进程的执行。当然在这种模式下无法控制被调试程序的执行。这种模式也可以用于查看一个已经在Debugger控制下运行的进程。具体命令如下:
Windbg –pv –p “process id”
Windbg –pv –pn “process name”
(3)调试多个进程和线程
如果你想控制一个进程以及它的子进程的执行,在Windbg的命令行上加上-o选项。Windbg中还有一个新的命令.childdbg 可以用来控制子进程的调试。如果你同时调试几个进程,可以使用 | 命令来显示并切换到不同的进程。
在同一个进程中可能有多个线程。~命令可以用来显示和切换线程。
.hh keyword 如何得到帮助
静态命令:
显示调用堆栈:在连接到一个调试窗口后,首先要知道的就是程序当前的执行情况k* 命令显示当前线程的堆栈。~*kb会显示所有线程的调用堆栈。如果堆栈太长,Windbg只会显示堆栈的一部分。.kframes可以用来设置缺省显示框架数。
显示局部变量:接下来要做通常是用dv显示局部变量的信息。CTRL+ALT+V可以切换到更详细的显示模式。关于dv要注意的是在优化过的代码中dv的输出极有可能是不准确的。这时后你能做的就是阅读汇编代码来发现你感兴趣的值是否存储在寄存器中或堆栈上。有时后当前的框架(Frame)上可能找不到你想知道的数据。如果该数据是作为参数传到当前的方法中的,可以读一读上一个或几个框架的汇编代码,有可能该数据还在堆栈的某个地址上。静态变量是储存在固定地址中的,所以找出静态变量的值较为容易。.Frame(或者在调用堆栈窗口中双击)可以用来切换当前的框架。注意dv命令显示的是当前框架的内容。你也可在watch窗口中观察局部变量的值。
显示类和链表: dt可以显示数据结构。比如dt PEB 会显示操作系统进程结构。在后面跟上一个进程结构的地址会显示该结构的详细信息:dt PEB 7ffdf000。
Dl命令可以显示一些特定的链表结构。
显示当前线程的错误值:!gle会显示当前线程的上一个错误值和状态值。!error命令可以解码HRESULT。
搜索或修改内存:使用s 命令来搜索字节,字或双字,QWORD或字符串。使用e命令来修改内存。
计算表达式:?命令可以用来进行计算。关于表达式的格式请参照帮助文档。使用n命令来切换输入数字的进制。
显示当前线程,进程和模块信息:!teb显示当前线程的环境信息。最常见的用途是查看当前线程堆栈的起始地址,然后在堆栈中搜索值。!peb显示当前进程的环境信息,比如执行文件的路径等等。lm显示进程中加载的模块信息。
显示寄存器的值:r命令可以显示和修改寄存器的值。如果要在表达式中使用寄存器的值,在寄存器名前加@符号(比如@eax)。
显示最相近的符号:ln Address。如果你有一个C++对象的指针,可以用来ln来查看该对象类型。
查找符号:x命令可以用来查找全局变量的地址或过程的地址。x命令支持匹配符号。x kernel32!*显示Kernel32.dll中的所有可见变量,数据结构和过程。
查看lock:!locks显示各线程的锁资源使用情况。对调试死锁很有用。
查看handle:!handle显示句柄信息。如果一段代码导致句柄泄漏,你只需要在代码执行前后使用!handle命令并比较两次输出的区别。有一个命令!htrace对调试与句柄有关的Bug非常有用。在开始调试前输入:
!htrace –enable
然后在调试过程中使用!htrace handle_value 来显示所有与该句柄有关的调用堆栈。
显示汇编代码:u。
程序执行控制命令:
设置代码断点:bp/bu/bm 可以用来设置代码断点。你可以指定断点被跳过的次数。假设一段代码KERNEL32!SetLastError在运行很多次后会出错,你可以设置如下断点:
bp KERNEL32!SetLastError 0x100.
在出错后使用bl 来显示断点信息(注意粗体显示的值):
0 e 77e7a3b0 004f (0100) 0:*** KERNEL32!SetLastError
重新启动调试(.restart命令)并设置如下的断点:
bp Kernel32!SetLastError 0x100-0x4f
Debugger会停在出错前最后一次调用该过程的地方。
你可以指定断点被激活时Debugger应当执行的命令串。在该命令串中使用J命令可以用来设置条件断点:
bp `mysource.cpp:143` "j (poi(MyVar)”0n20) ''; 'g' "
上面的断点只在MyVar的值大于32时被激活(g命令
条件断点的用途极为广泛。你可以指定一个断点只在特殊的情况下被激活,比如传入的参数满足一定的条件,调用者是某个特殊的过程,某个全局变量被设为特殊的值等等。
设置内存断点:ba可以用来设置内存断点。调试过程中一个常见的问题是跟踪某些数据的变化。如下的断点:
ba w4 0x40000000 "kb; g"
可以打印出所有修改0x40000000的调用堆栈。
控制程序执行:p, pa,t, ta等命令可以用来控制程序的执行。
控制异常和事件处理:Debugger的缺省设置是跳过首次异常(first chance expcetion),在二次异常(second chance exception)时中断程序的执行。sx命令显示Debugger的设置。sxe和sxd可以改变Debugger的设置。
sxe clr
可以控制Debugger在托管异常发生时中断程序的执行。常用的Debugger事件有:
av 访问异常
eh C++异常
clr 托管异常
ld 模块加载
-c 选项可以用来指定在事件发生时执行的调试命令。
堆栈显示指令kb , kp, kP , kv
反汇编指令 u,uf
跟踪指令 T,TA,TB,TC
执行相关指令 P,PA,PC
跟踪查看指令 WT
----------------------------------------------------------------------------
堆栈显示指令 k [b|p|P|v] 在内核调试的时候,k命令用来显示内核栈的内容 先说说内核栈用来干嘛的 看了些资料个人理解是这样的 比如我们的代码运行时,肯定会有函数函数然后还会调用函数 但是系统如何记录是哪个父函数调用了这个子函数,在子函数调用之前整个状态又是怎样的,其实系统是利用了堆栈记录的 栈这个东西好阿 先进后出 最近调用的函数记录在最顶层 函数执行完后就从栈内弹出之前记录的参数,如果调用函数 一样的把函数压进栈内就好了 这样一来 一旦子函数执行完,从栈内弹出的第一个函数肯定是该子函数的老爹 我们可以看上层堆栈的状态等等 功能大家慢慢去体会吧我也没用过 呵呵 不好说什么 下面说些细节的东西 b 显示传给函数的前三个参数 p 显示传给函数的全部参数 P( 大写) 跟上面那个一样 只不过是显示形式不同而已 V 外加显示一些额外的信息 ---------------------------------------------------------------------------- u [f] 反汇编指令,嘿嘿 超级有用的指令哟虽然说内核很多东西很复杂 认识偶尔小小反下也是可以的 u 反汇编当前寄存器指向的代码 uf 函数名(比如nt!ZwCreateFile) 反汇编指定的函数 ---------------------------------------------------------------------------- t [r] 单步跟踪 r 打开指显示寄存器的详细信息,状态的开关(下面指令一样有效,在用1次就会关闭哦~) ta 地址 让程序执行到指定地址 tb 让程序运行到分支语句时停止 tc 让程序运行到下一个函数调用停止 ---------------------------------------------------------------------------- p [r] 单步执行一跳指令 r 打开指显示寄存器的详细信息,状态的开关(下面指令一样有效,在用1次就会关闭哦~) pa 让程序执行到指定地址 pc 让程序执行到函数调用就停止 ---------------------------------------------------------------------------- wt 在想查看指定函数的信息而又不想单步通过该函数时很有用。可以到函数的起始地址并执行 wt 命令。(摘自翻译文档) 这个感觉用处不是很大.不细细研究了 ---------------------------------------------------------------------------- Ps: 很多人不清楚到底p指令和t指令有什么区别 其实很简单 p指令执行到函数时把这个当做一个指令来执行也就是说不会进入函数执行,但是t指令会进入到函数里面执行 就这么简单~~呵呵
远程调试
使用WinDbg进行远程调试是很容易的,而且有很多种可行的方法。在下文中,’调试服务器’指的是运行在你所要调试的远程机器上的调试器。’调试客户端’指的是控制当前会话的调试器。
· 使用调试器:你需要CDB, NTSD或者WinDbg已经安装在远程机器上。WinDbg客户端可以连接到CDB, NTSD或者WinDbg中的任何一个作为服务器,反之亦然。在客户端和服务器直接可以选择TCP或者命名管道作为通讯协议。
o 在服务器端的启动过程:
§ WinDbg –server npipe:pipe=pipename(注:可以允许多个客户端连接)
§ 从WinDbg内部: .server npipe:pipe=pipename(注,连接单个客户端)
你可以用多种协议开启不同的服务会话。并且可用密码来保护一个会话。
o 从客户端连接:
§ WinDbg -remote npipe:server=Server, pipe=PipeName[,password=Password]
§ 从WinDbg内部: File->Connect to Remote Session: for connection string, enter npipe:server=Server, pipe=PipeName [,password=Password]
· 使用Remote.exe: Remote.exe使用命名管道作为通讯的方式。如果你使用的是一个命令行接口的程序,比如KD,CDB或者NTSD。你可以使用remote.exe来远程调试。注意:使用@q(不是q)来退出客户端,不用关掉服务端。
o 要启动一个服务端:
§ Remote.exe /s “cdp –p <pid>” test1
o 从客户端连接:
§ Remote.exe /c <machinename> test1
上面的test1是我们所选择的命名管道的名字。
服务端会显示那个客户端从那个服务器连接以及执行过的命令。你可以使用‘qq’命令来退出服务端;或者使用File->Exit来退出客户端。另外,如果要进行远程调试,你必须属于远程机器的”Debugger User”组并且服务器必须允许远程连接。
即时调试
在WinDbg的文档的”Enabling Postmorten Debugging”部分对此有很详细的讨论。简而言之,你可以把WinDbg设置成默认的即时调试器,命令就是:Windbg –I。这个命令实际上是把注册表中 HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug的键值设置成WinDbg。如果要把WinDbg设置成为默认的托管调试器,你需要显示设置如下的注册表键值:
HKLM\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSetting 设置成 2
HKLM\Software\Microsoft\.NETFramework\DbgManagedDebugger 设置成Windbg.(注意其中的启动参数设置)
通过JIT的设置,当一个应用程序在不是调试的状态下抛出了未处理的异常之时,WinDbg就会被启动。
64位调试
所有这些调试器均支持在AMD64和IA64上的64位调试环境。
托管应用程序的调试
WinDbg 6.3以后的版本支持在Widbey(VS2005和.net 2.0的内部开发代号) .net CLR托管调试。在文档中针对托管调试有很好的讨论。需要注意的是,对于托管程序来说,没有刚才所说的PDB(译注:托管代码实际上也是有PDB的,但是这个PDB实际上记录了C#代码和IL代码的对应关系以及相关的一些信息)的概念,因为所有的程序都是编译成为ILASM。调试器通过CLR来查询所需的附加信息。
有几点需要注意:
你只能在托段函数的代码被执行过至少一次之后才能设置断点。只有这样它才能被编译成汇编代码。记住以下的几点:
· 关于函数的地址的复杂化以及对应的断点设置:
o CLR有可能丢弃已经编译好的代码,所以函数的入口地址有可能改变。
o 同样的代码有可能被多次编译,如果多个应用程序域没有共享这段代码的话。如果你设置了一个断点,它就会被设置在当前线程(译注:CLR的逻辑线程)所在的应用程序域内。
o 泛型的特殊实例可能导致同一个函数有不同的地址。.
· 数据存储布局的复杂化以及对应的数据检查:
CLR可能会在运行的时候任意改变数据的存储布局,所以一个结构体成员的偏移量可能会被改变掉. (译注:实际上是在一个类型被加载的时候决定的数据布局,之后是不会改变的。)
一个类型的信息是在第一次使用的时候被加载,所以你可能不能够查看一个数据成员如果它还没有被使用过.
· 调试器命令的复杂化
o 当跟踪托管代码的时候,你会需要穿越大段的CLR自己的代码比如JIT编译器的代码,原因可能是你第一次进入一个函数,或者是你在托管和非托管代码之间进行切换。
调试Windows服务
使用WinDbg,你可以像调试其它应用程序那样调试Windows服务程序。即可以通过附加进程的方法启动Windows服务,也可以把WinDbg当作一个即时调试器,并且在代码中调用DbgBreakPoint 或者 DebugBreak,或者在x86机器上加入一条int 3汇编指令。
调试异常
一个调试器会得到两次的异常通知-第一次在应用程序有机会处理异常之前(‘first chance exception’);如果应用程序没有处理这个异常,这时候调试器就会有机会来处理异常(‘second-chance exception’)。如果调试器没有处理二次机会的异常,应用程序就会退出。
.lastevent或者,!analyze –v命令会给你显示异常的记录以及异常抛出所在函数的堆栈跟踪信息。
你也可以使用 .exr, .cxr以及 .ecxr命令来显示异常和上下文记录。同时需要注意的是,你也可以改变first-chance的处理选项。对应的命令就是: sxe, sxd, sxn和sxi。
虚拟机调试驱动
1)
将 WinDbg 发送一个快捷方式,并修改在快捷方式上右键=>"属性"
将"目标"中的 WinDbg 文件名后添加 "-k com:port=\\.\pipe\com_1,baud=115200,pipe" , 如下:
"C:\Program Files\Debugging Tools for Windows\windbg.exe" -k com:port=\\.\pipe\com_1,baud=115200,pipe
2)
打开虚拟机中的 c:\boot.ini 文件(之前去掉"只读"属性),复制一行
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /fastdetect
即添加了一个启动选项,并修改为:
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Windows Server 2003, Enterprise[Debug]" /fastdetect /debug /debugport=com1 /baudrate=115200
即添加了调试选项,调试端口以及串口的速率.
保存.
3)
关闭虚拟机里的目标windows系统(必须,否则在"Settings..."里的"Add..."将为灰色,不可选状态),
选择目标windows系统的"Settings..."选项,在"Hardware"选项中,点击下面的"Add..."按钮.
选择"Serial Port"点击"Next",再选择"Output to named pipe","Next",
这一向导中,前两项不修改,最后一项修改为"The other end is an application.",
如果这里存在"高级"选项,则在其中选择"Yield CPU on poll"[注:有些虚拟机在这里并没有"高级"选项,则在"Finish"后,选择"Serial Port",再勾选右下角的"Yield CPU on poll"],
"Finish".
"OK",完成"Virtual Maching Setting".
4)
打开虚拟机中的目标Windows系统,选择"Windows Server 2003, Enterprise[Debug]"后,立即打开之前创建的WinDbg快捷方式(若先打开WinDbg,则会报找不到'com:port=....'等信息).
5)
连接上虚拟机中的目标windows系统后,立即发送一个WinDbg中的"Debug"=>"Break"来中断它.
6)
设置"Symbol"路径,"Source"路径和"ImagePath"路径(若有多个,则用";"号隔开).
如:
"File"=>"Symbol" 里设置:"G:\虚拟机共享文件\Win2003Symbols;G:\虚拟机共享文件\GiveIO",勾选"Reload","OK". (第一个是Windows的符号[这里要注意,符号应该是虚拟机内Windows系统对应的],第二个是要调试的驱动的符号)
"File"=>"Source" 里设置:"G:\虚拟机共享文件\GiveIO", "OK". (要调试的驱动的源码我放在了此目录中)
"File"=>"Image File Path" 里设置: "G:\虚拟机共享文件\GiveIO", "Reload", "OK". (我把要调试的驱动程序放在了此目录中)
"File"=>"Open Source File", 打开驱动源文件.
7)
在要调试的驱动程序上下断点(这里我调试的驱动模块名为"GiveIO",后面是在"DriverEntry"入口点下断点[bu设置的是延迟断点]):
bu GiveIO!DriverEntry
按"F5"(或在命令行中用"g"命令),即可让虚拟机中的目标Windows顺利启动.
8)
在虚拟机中运行要调试的驱动程序,即可运行到断点处.
9)
一些说明:
在已运行的Windows目标系统中调试指定驱动:
首先应使WinDbg产生一个断点,使用WinDbg中的"Debug"=>"Break",此时目标Windows系统就会中断.
此时使用:
bu GiveIO!DriverEntry
命令,在指定驱动的模块名的入口点下断点(GiveIO为此处的驱动模块名).
运行驱动后,即会在驱动的DriverEntry处断下来,此时就可以进行跟踪调试了.
bl命令可以查看已下的断点
bc清除断点,如果后面跟"*",则清除所有断点.