你可以从微软网站上下载到的调试器:
· 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的输出函数来实现。
调试分类:
【快速入门】
1. windbg选择file -> open executable打开可执行文件,然后设置symbol file路径,假如你是在生成所执行代码的机器上进行调试,符号文件中的源码路径会指向正确的位置,所以不需要设置源代码路径。如果你打算microsoft的操作系统符号文件(pdb)是对外公开的。如果想设置window symbol,只需要:
SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols 或者 简单用 .symfix 命令自动将symbol path设置为微软的公开符号文件服务器地址
2. 设置断点
x MyApp!AlterConstBuffer
bp 00da1400
或者:bp MyApp!AlterConstBuffer
3. 运行
g
4. 得到Access Violation
(1a94.1cbc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00da6808 ebx=7efde000 ecx=0000002b edx=53ed23e8 esi=002cfb88 edi=002cfb7c
eip=00da1436 esp=002cfab0 ebp=002cfb7c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
MyApp!AlterConstBuffer+0x36:
00da1436 c60024 mov byte ptr [eax],24h ds:002b:00da6808=2b
观察错误出现在 mov byte ptr[eax], 24h
使用dc可以查看eax上的内存数据。即输入dc eax便可以快速查看,发现就是在修改字符+的时候出现了错误。
还可以看出计算机内部是big-endian, 低地址存放高字节。
5. 深入分析 !address eax
假如有操作系统的symbol file那么可以继续进行调试,即查看eax的相应内存页的属性。输入!address eax便可以查看了,发现这块区域的内存是只读的即是常量指针。所以使用mov命令会导致程序出错。
0:000> !address eax
Usage: Image
Allocation Base: 01230000
Base Address: 01246000
End Address: 01248000
Region Size: 00002000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
More info: lmv m MyApp
More info: !lmi MyApp
More info: ln 0x1246808
6. 输入u加上地址可以查看反汇编指令,使用!address加上某个函数的地址也可以查看内存信息。
0:000> u MyApp!AlterConstBuffer
MyApp!AlterConstBuffer [c:\windbgfirst\windbgfirst\windbgfirst.cpp @ 12]:
01241400 55 push ebp
01241401 8bec mov ebp,esp
01241403 81ecc0000000 sub esp,0C0h
01241409 53 push ebx
0124140a 56 push esi
0124140b 57 push edi
0124140c 8dbd40ffffff lea edi,[ebp-0C0h]
01241412 b930000000 mov ecx,30h
附:Windbg其他命令
Windbg为了节省程序员在调试中输入过长的命令,一般都采用类似unix命令的风格,大多数都采取简写(扩展命令除外)。例如k是callstack的简写(c的发音是k),bp是breakpoint的简写,bu是break unresolved的意思,bm是break matches,等等。
如何得到帮助
在命令(command)窗口中输入.hh 命会调出帮助文件令。
.hh keyword
会显示关于keyword的具体命令。
启动debugger
windbg可以用于如下三种调试:
在机器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 /ma file name
创建一个dump文件。在得到dump文件后,使用如下的命令来打开它:
windbg –z dump_file_name
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”
调试多个进程和线程
假如你想控制一个进程以及它的子进程的执行,在windbg的命令行上加上-o选项。windbg中还有一个新的命令.childdbg 可以用来控制子进程的调试。假如你同时调试几个进程,可以使用 | 命令来显示并切换到不同的进程。
在同一个进程中可能有多个线程。~命令可以用来显示和切换线程。 ~2s切换到第二个线程
调试前的必备工作
在开始调试前首先要做的工作是设置好符号(symbols)路径。没有符号,你看到的调用堆栈基本上毫无意义。microsoft的操作系统符号文件(pdb)是对外公开的。另外请注重在编译你自己的程序选择生成pdb文件的选项。假如设置好符号路径后,调用堆栈看起来还是不对。可以使用lm, !sym noisy, !reload 等命令来验证符号路径是否正确。
windbg也支持源码级的调试。在开始源码调试前,你需要用.srcpath设置源代码路径。假如你是在生成所执行代码的机器上进行调试,符号文件中的源码路径会指向正确的位置,所以不需要设置源代码路径。假如所执行代码是在另一台机器上生成的,你可以将所用的源码拷贝(保持原有的目录结构)的一个可以访问的文件夹(可以是网络路径)并将源代码路径设为该文件夹的路径。注重假如是远程调试,你需要使用.lsrcpath来设置源码路径。
.sympath显示当前symbols的路径
.sympath+ C:\Windows\symbols\dll 叠加新的路径
.reload重新加载
静态命令:
显示调用堆栈:在连接到一个调试窗口后,首先要知道的就是程序当前的执行情况k* 命令显示当前线程的堆栈。~*kb会显示所有线程的调用堆栈。假如堆栈太长,windbg只会显示堆栈的一部分。.kframes可以用来设置缺省显示框架数。
显示局部变量:接下来要做通常是用dv显示局部变量的信息。ctrl+alt+v可以切换到更具体的显示模式。关于dv要注重的是在优化过的代码中dv的输出极有可能是不正确的。这时后你能做的就是阅读汇编代码来发现你感爱好的值是否存储在寄存器中或堆栈上。有时后当前的框架(frame)上可能找不到你想知道的数据。假如该数据是作为参数传到当前的方法中的,可以读一读上一个或几个框架的汇编代码,有可能该数据还在堆栈的某个地址上。静态变量是储存在固定地址中的,所以找出静态变量的值较为轻易。.frame(或者在调用堆栈窗口中双击)可以用来切换当前的框架。注重dv命令显示的是当前框架的内容。你也可在watch窗口中观察局部变量的值。
显示类和链表: dt可以显示数据结构。比如dt peb 会显示操作系统进程结构。在后面跟上一个进程结构的地址会显示该结构的具体信息:dt peb 7ffdf000。
dl命令可以显示一些特定的链表结构。
显示当前线程的错误值:!gle会显示当前线程的上一个错误值和状态值。!error命令可以解码hresult。
!error 0x2
Error code: (Win32) 0x2 (2) - The system cannot find the file specified.
搜索或修改内存:使用s 命令来搜索字节,字或双字,qword或字符串。使用e命令来修改内存。
计算表达式:?命令可以用来进行计算。关于表达式的格式请参照帮助文档。使用n命令来切换输入数字的进制。
显示当前线程,进程和模块信息:!teb显示当前线程的环境信息。最常见的用途是查看当前线程堆栈的起始地址,然后在堆栈中搜索值。!peb显示当前进程的环境信息,比如执行文件的路径等等。lm显示进程中加载的模块信息。
lm vm msvcr90D 显示具体模块信息
win7_gdr是编译的branch信息
显示寄存器的值:r命令可以显示和修改寄存器的值。假如要在表达式中使用寄存器的值,在寄存器名前加@符号(比如@eax)。
显示最相近的符号:ln address。假如你有一个c++对象的指针,可以用来ln来查看该对象类型。
查找符号:x命令可以用来查找全局变量的地址或过程的地址。x命令支持匹配符号;
x kernel32!*显示kernel32.dll中的所有可见变量,数据结构和过程;
x /t /v notepad!* 显示notepad里面有哪些public的函数以及变量。
查看lock:!locks显示各线程的锁资源使用情况。对调试死锁很有用。
查看handle:!handle显示句柄信息。假如一段代码导致句柄泄漏,你只需要在代码执行前后使用!handle命令并比较两次输出的区别。有一个命令!htrace对调试与句柄有关的bug非常有用。在开始调试前输入:
!htrace –enable
然后在调试过程中使用!htrace handle_value 来显示所有与该句柄有关的调用堆栈。
显示汇编代码:u。
程序执行控制命令:
设置代码断点:bp/bu/bm 可以用来设置代码断点。
bp `myapp.cpp:21`在源代码myapp.cpp的第21行设置断点
你可以指定断点被跳过的次数。假设一段代码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 Access Size [地址]
Access 是访问的方式, 比如 e (执行), r (读/写), w (写) Size 是监控访问的位置的大小,以字节为单位。 值为 1、2或4,还可以是 8(64位机)。
比如要对内存0x0483DFE进行写操作的时候下断点,可以用命令 ba w4 0x0483DFE
如下的断点:
ba w4 0x40000000 "kb; g" 可以打印出所有修改0x40000000的调用堆栈。
bp 命令是在某个地址下断点, 可以 bp 0x7783FEB 也可以 bp MyApp!SomeFunction 。 对于后者,WinDBG 会自动找到MyApp!SomeFunction 对应的地址并设置断点。 但是使用bp的问题在于:1)当代码修改之后,函数地址改变,该断点仍然保持在相同位置,不一定继续有效; 2)WinDBG 不会把bp断点保存工作空间中 。 所以,我比较喜欢用bu 命令。
bu 命令是针对某个符号下断点。 比如 bu MyApp!SomeFunction 。 在代码被修改之后, 该断点可以随着函数地址改变而自动更新到最新位置。 而且bu 断点会保存在WinDbg工作空间中, 下次启动 Windbg 的时候该断点会自动设置上去。
另外,在模块没有被加载的时候,bp 断点会失败(因为函数地址不存在),而bu 断点则可以成功。 新版的WinDBG中 bp失败后会自动被转成bu 。
如果你程序中有:
#ifdef _UNICODE
# define _ttol _wtol
#else
# define _ttol atol
#endif
而宏是在编译期间就被编译器扩展,并不会被加到符号文件中去,因此如果你试图使用bp命令在_ttol入口设置断点的话,是会失败的。因此你可以使用类似下面的通配符来查找正确的函数名:
x MSVCR90D!*tol×
然后用bm *tol*给所有含有tol的函数都设置断点;
然后用bl查看断点列表,用bc 2-6 清除,用bd 2-6禁用 第二个到6个断点
控制程序执行: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 选项可以用来指定在事件发生时执行的调试命令。
注:本文大部分是网上资料的收集。