Summary of my Windbg command
这是我以前总结的一些Windbg command, 也共享出来吧,呵呵,2008就快结束了,2009就快开始了,祝大家2009好运J
A core set of my commands would be as below.
1. g//F5 Run(g) F7运行到光标处 F9设断点 p(step)//F10 单步run t//F11单步进入函数run ctrl+break 暂停
2. .sympath C:\MyCodesSymbols; SRV*C:\MyLocalSymbols*http://msdl.microsoft.com/download/symbols // set symbol file path
.srcpath \\... // set source file path
.exepath \\... // Set image file path
.reload /f //reload Symbols
3. F5(run) for get error,then :
!gle //see what the last error is using !GLE (Get Last Error) This dumps out the last error from the TEB.
bp kernel32!SetLastError// Lets set a breakpoint on last error to examine what is going on in the function calling it.
0:002> kv // Get the call stack
ChildEBP RetAddr
0:002> kv
ChildEBP RetAddr Args to Child
00d9fcc4 004116cb 00000057 00d9fe74 00000000 kernel32!SetLastError (FPO: [Non-Fpo]) //0x57 Invalid parameter error, Why?
00d9fd9c 00411657 00000000 00d9ff58 00000000 eatcpu!checkSomething+0x4b (FPO: [Non-Fpo]) (CONV: cdecl) [c:\source\eatcpu\eatcpu\eatcpu.cpp @ 57]
00d9fe74 004115a8 00000000 00000000 00000000 eatcpu!trySomething+0x27 (FPO: [Non-Fpo]) (CONV: cdecl) [c:\source\eatcpu\eatcpu\eatcpu.cpp @ 45]
00d9ff58 62bb4601 0017ff34 4f9f12e9 00000000 eatcpu!myThreadFunction+0x38 (FPO: [Non-Fpo]) (CONV: cdecl) [c:\source\eatcpu\eatcpu\eatcpu.cpp @ 35]
00d9ff94 62bb459c 00b937a8 00d9ffac 768019f1 MSVCR80D!_beginthread+0x221 (FPO: [Non-Fpo])
00d9ffa0 768019f1 00b937a8 00d9ffec 77a2d109 MSVCR80D!_beginthread+0x1bc (FPO: [Non-Fpo])
00d9ffac 77a2d109 00b93c48 00d926a6 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
00d9ffec 00000000 62bb4520 00b93c48 00000000 ntdll!_RtlUserThreadStart+0x23 (FPO: [Non-Fpo]) (CONV: stdcall) [d:\vistartm\base\ntos\rtl\rtlexec.c @ 2695]
0:002> !error 00000057 // double check, using !error, this will decode the error into a human readable string.
Error code: (Win32) 0x57 (87) - The parameter is incorrect.
4. Vertarget //检查进程概况
5. lmf //列出当前进程中加载的所有模块的详细信息,比lm更详细
6. x - //查找符号的二进制地址Examine symbols. This is very, very useful as it shows you the set of symbols loaded for particular modules. A good example here would be;
x USER32!* (show me all the symbols loaded from the USER32 module)
x USER32!Create* (show me all the symbols loaded from the USER32 module that begin with "Create")
x program !getcharbuffer //find函数二进制入口,找到后用bp下断点
x msvcrt!printf //查找printf入口地址,为77db27c2,then bp 77db27c2!
X ntdll!GlobalCouter//查找变量GlobalCouter的地址,为87db27c2,不是值,用d 87db27c2读值就OK了!
7. bp (Address) - set a break point.
bp KERNEL32!CreateFileA //对CreateFileA函数下断点,可以用x KERNEL32!CreateFile* to find it
bp program !MyClass::* //对类每个函数下断点
bp program !MyClass::Add //对类Add函数下断点
bm值一提,在符号表合法的情况下(符号表中包含私有符号的时候),bm可能通过模式一次下多个断点,bm mydriver!FastIo*指定可以将所有与FastIo*模式相匹配的函数下断点,如FastIoRead,FastIoWrite等。但是bm需要full or export symbols支持,Microsoft的提供的符号表不是都支持的,通常我们自己编译的程序的符号表(Windbg显示为private pdb symbols)默认是支持的。
Ba w4 testapp!g_Buffer//设定访问断点,testapp有一全局变量,符号是testapp!g_Buffer,程序在修改这个变量就会停下来!w4是要检查的类型和长度,W是write,4是长 度为4字节,作用就是监视一块内存地址区域,起点是testapp!g_Buffer所在地址,长度为4,当有代码对该位置发生写操作就停下来!
sxe, sxd, sxi, sxn. // Exception断点The commands beginning with "sx" set the behaviour for what the debugger should do when an exception occurs.
sxe 0xc0000005 which is saying "I want to break into the debugger if there's an access violation".
Sxe av //当access violation发生就停止
Sxn ld//当DLL Load,调试器就只是在屏幕上输出
Sxd eh//当C++ exception发生,调试器什么都不做
Bp exceptionapp!foo3 “k;.echo ‘break’;g”//在exceptionapp!foo3设断点,每次断下来,就用K显示callstack,然后用.echo输出break,最后g
bp USER32!GetMessageW "r $t1=poi(esp+4);r $t2=poi(@$t1+4); j(@$t2 = 0x102 ) 'du @$t1+8 L2;gc';'gc'"
这个条件断点,截取WM_CHAR消息,并将字符(包括中文)显示出来。
条件断点的最简形式:bp Address "j (Condition) 'OptionalCommands'; 'gc' "
Address是指令的地址,Condition是一个条件表达式,如果@eax=1,'OptionalCommands'是在断点被击中并且表达式成立时要执行的指令;gc指定是从一个条件断点返回,是不可少的一部分。
J.// j命令根据给定表达式的值,条件执行指定命令中的一个。
下面的命令当MySymbol 等于0时显示eax的值,否则显示ebx和ecx的值。
0:000> j (MySymbol=0) 'r eax'; 'r ebx; r ecx'
使用j命令来创建条件断点。
0:000> bp `mysource.cpp:143` "j (poi(MyVar)>0n20) ''; 'gc' "
Ba w4 exceptionapp!i ”j (poi(exceptionapp!i)<0n40 ‘.printf \”exceptionapp!i value is:5d\”,’’条件断点P46
Bl(list) - list all breakpoints. Each breakpoint listed has a number in the list which you need for...
bc (number), be (number), bd (number) - these respectively clear, enable and disable breakpoints from the list.
8. g - "GO!". That is, continue running.
9. 栈指令K[b|p|P|v]
KB显示三个参数,Kp显示所有的参数,但需要Full Symbols或Private PDBSymbols支持。KP与Kp相似,只是KP将参数换行显示了。Kv用于显示FPO和调用约定,KD,用于显示Stack的Dump,在跟踪栈时比较有用。这些指令区分大小。
K,Kb(byte), kp, kd(dword) //K开头显示当前线程的call stack ,后面是是显示的形式
kb 200 (200 is the maximum depth of stack trace that I'm looking for)
10. 数据查看指令d{b|c|d|D|f|p|q}分别是显示:byte&ASCII, double-word&ASCII,double-word,double-precision,float,pointer-sized,quad-word数据;
DA用于显示ASCII,DU用于显示UNICODE;BYB,BYD,显示binary和Byte及binary和DWORD,补充一个DV,用于查看本地变量用的
这些指令区分大小。d,da(ascii),dd(dword), db(byte),du(unicode),dc(char). //d开头显示内存地址上的值
dt 变量名//显示变量的资料及结构
d esp//显示ESP寄存器指向的内存,default是byte
dd 054efc14 //以dword形式显示054efc14地址的值
11. lm - lists the modules loaded by the program and what kind of symbols are loaded for the modules
lm v (verbose mode)
lm v mUSER* (verbose mode, matc any modules that begin with USER*)
12. .cls – clear the screen. You’ll be needing this one!
13. ~ //切换目标线程
~0s //把当前线程切换到0号线程,也就是主线程,切换就变为0:000
~* kb 200 which will show you the stack frames for all the threads in the process.
14. 反汇编指令U,UF //反汇编,把指定地址上的代码翻译成汇编代码
0:000> u 7739d023
USER32!NtUserWaitMessage
7739d023 mov eax,0x124a
0:000> uf USER32!NtUserWaitMessage//反汇编整个函数
////////////////////////////
pa命令执行程序直到到达指定地址,每一步都会被显示出来。
例如,下面的命令单步执行目标的代码直到到达当前函数的返回地址。
0:000> pa @$ra
下面的命令演示了pa和kb命令命令一起用来显示堆栈回溯:
0:000> pa 70b5d2f1 "kb"
//new
16. !analyze –v //analyze the break,显示dump文件信息,显示分析的详细信息
调试一个当机的目标计算机或应用程序,第一步是使用 !analyze 扩展命令。
该扩展执行大量的自动分析。分析结果在调试器命令窗口中显示。
若要数据的全冗长模式显示,你应该使用 -v 选项。
本例中,调试器被附加到一个已遭遇异常的用户模式应用程序。
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
Debugger SolutionDb Connection::Open failed 80004005
如果计算机有连接英特网,调试器尝试访问一个由微软维护的当机解决方案数据库。这里它显示了一个错误信息,指出或是你的机器不能够访问英特网或是网站关闭。
FAULTING_IP:
ntdll!PropertyLengthAsVariant+73
77f97704 cc int 3
FAULTING_IP字段表示出现故障时的指令指针。
EXCEPTION_RECORD: ffffffff -- (.exr ffffffffffffffff)
ExceptionAddress: 77f97704 (ntdll!PropertyLengthAsVariant+0x00000073)
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 3
Parameter[0]: 00000000
Parameter[1]: 00010101
Parameter[2]: ffffffff
EXCEPTION_RECORD字段表示这次当机的异常记录。也可以使用 .exr (显示异常记录) 命令查看该信息。
BUGCHECK_STR: 80000003
BUGCHECK_STR字段表示异常代码。这个名称使用不当 - 隐错检查bug check这个术语实际上表示是一个内核模式当机。在用户模式调试中将显示异常代码 - 这里是0x80000003。
DEFAULT_BUCKET_ID: APPLICATION_FAULT
DEFAULT_BUCKET_ID字段表示故障所属类别,这里显示是一般的故障。
PROCESS_NAME: MyApp.exe
PROCESS_NAME字段说明引发异常的进程名字。
LAST_CONTROL_TRANSFER: from 01050963 to 77f97704
LAST_CONTROL_TRANSFER字段表示在栈中最后的调用。这里,在地址0x01050963处的代码调用在0x77F97704处的一个函数。你可以使用ln (列出最近的符号)命令确定这些地址在什么模块和函数中。
STACK_TEXT:
0006b9dc 01050963 00000000 0006ba04 000603fd ntdll!PropertyLengthAsVariant+0x73
0006b9f0 010509af 00000002 0006ba04 77e1a449 MyApp!FatalErrorBox+0x55 [D:\source_files\MyApp\util.c @ 541]
0006da04 01029f4e 01069850 0000034f 01069828 MyApp!ShowAssert+0x47 [D:\source_files\MyApp\util.c @ 579]
0006db6c 010590c3 000e01ea 0006fee4 0006feec MyApp!SelectColor+0x103 [D:\source_files\MyApp\colors.c @ 849]
0006fe04 77e11d0a 000e01ea 00000111 0000413c MyApp!MainWndProc+0x1322 [D:\source_files\MyApp\MyApp.c @ 1031]
0006fe24 77e11bc8 01057da1 000e01ea 00000111 USER32!UserCallWinProc+0x18
0006feb0 77e172b4 0006fee4 00000001 010518bf USER32!DispatchMessageWorker+0x2d0
0006febc 010518bf 0006fee4 00000000 01057c5d USER32!DispatchMessageA+0xb
0006fec8 01057c5d 0006fee4 77f82b95 77f83920 MyApp!ProcessQCQPMessage+0x3b [D:\source_files\MyApp\util.c @ 2212]
0006ff70 01062cbf 00000001 00683ed8 00682b88 MyApp!main+0x1e6 [D:\source_files\MyApp\MyApp.c @ 263]
0006ffc0 77e9ca90 77f82b95 77f83920 7ffdf000 MyApp!mainCRTStartup+0xff [D:\source_files\MyApp\crtexe.c @ 338]
0006fff0 00000000 01062bc0 00000000 000000c8 KERNEL32!BaseProcessStart+0x3d
STACK_TEXT字段表示出错组件的一个栈跟踪(回溯)。
FOLLOWUP_IP:
MyApp!FatalErrorBox+55
01050963 5e pop esi
FOLLOWUP_NAME: dbg
SYMBOL_NAME: MyApp!FatalErrorBox+55
MODULE_NAME: MyApp
IMAGE_NAME: MyApp.exe
DEBUG_FLR_IMAGE_TIMESTAMP: 383490a9
当 !analyze 确定某指令可能引起错误的时候,就在FOLLOWUP_IP字段中显示它。 SYMBOL_NAME、MODULE_NAME、IMAGE_NAME和DBG_FLR_IMAGE_TIMESTAMP字段表示这个指令相应的符号、模块、映像名字和映像时间戳。
STACK_COMMAND: .ecxr ; kb
STACK_COMMAND字段表示用来获取STACK_TEXT的命令。你可以使用这个指令重复显示这个栈跟踪,或者改变它以获得有关的栈信息。
BUCKET_ID: 80000003_MyApp!FatalErrorBox+55
BUCKET_ID字段表示当前故障所属的特定故障类别。这个类别帮助调试器确定在分析输出中所显示的其他信息。
Followup: dbg
---------
关于FOLLOWUP_NAME和Followup字段的信息,请看"Followup字段和triage.ini文件" The Followup Field and the triage.ini File。
还可能出现其他一些字段:
17. vertarget;lm//用;可以将2个命令连起来运行
18. .ecxr (Display Exception Context Record)//The .ecxr command displays the context record that is associated with the current exception.
Eg:0:006> .ecxr;.frame 1;dt this
Local var @ 0x4cdfb94 Type CClassCache::CLSvrClassEntry::CFinishObject*
0x04cdfbb4
+0x000 __VFN_table : 0x765ebff0
+0x004 _dwScmReg : 0xffffffff
+0x008 _hWndDdeServer : (null)
+0x00c _pUnk : 0x0fe40c10 IUnknown
+0x010 _clsid : _GUID {e987a3ce-bf82-11d2-9d64-8cd2f1f51737}
In triage.ini, that iid maps it:
{e987a3ce-bf82-11d2-9d64-8cd2f1f51737}=XPVPROPS.DLL
Comments
The .ecxr command locates the current exception's context information and displays the important registers for the specified context record.
[ Note: Even though this command gives you a full register display, only a small number of the registers you see are really associated with this context record. (These include esp, ebp, and eip.) The rest haven't changed -- they are still the actual register values from the program counter's current location. ]
This command also instructs the debugger to use the context record that is associated with the current exception as the register context. After you run .ecxr, the debugger can access the most important registers and the stack trace for this thread. This register context persists until you enable the target to execute, change the current process or thread,[ is this true? ] or use another register context command (.cxr or .ecxr).
19. !error//The !error extension decodes and displays information about an error value.
!error Value [Flags]
The following example shows you how to use !error.
0:000> !error 2
Error code: (Win32) 0x2 (2) - The system cannot find the file specified.
0:000> !error 2 1
Error code: (NTSTATUS) 0x2 - STATUS_WAIT_2
20. Q; windbg -server tcp: port=12385 notepad.exe?
Take my machine for example.
I run windbg in my dev machine “Bravepcnew”, if I want let L319X86SPP to control my machine.
哦,我的开发机用COM联了测试机
然后319通过我的开发机来调试我的测试机
First in the command line of bravepcnew’s windbg
kd> .server tcport=5000
Server started. Client can connect with any of these command lines
0: <debugger> -remote tcport=5000,Server=BRAVEPCNEW
And then, in the remote machine, just run
C:\Debugger\kd -remote tcport=5000,Server=BRAVEPCNEW
Bruce详解:
1. X命令显示指定模块(Module)的公有符号中匹配指定模板(Symbol)的项。例如,下面的命令查找MyModule 中所有包含字符串"spin"的符号。
0:000> x mymodule!*spin*
下面的命令在MyModule 中快速定位"DownloadMinor" 和"DownloadMajor"符号。
0:000> x mymodule!downloadm??or
也可以使用下面的命令显示MyModule 中所有符号。
0:000> x mymodule!*
前一个命令也会强制调试器重新加载MyModule 的符号信息。如果要重新加载该模块得符号但是不显示那么多信息,可以使用下面的命令。
0:000> x mymodule!*start*
包含"start"的符号较少。因此,上面的命令会显示一些表明命令运行的输出,但是避免了像x mymodule!*一样很长的输出。
显示中包含每个符号的起始地址和完整的符号名。如果符号是一个函数名,还会包含参数类型的列表。如果符号是全局变量,则还会显示它的当前值。
这是另一个x命令的特殊用法。用来显示当前上下文所有局部变量的地址和名字。
0:000> x *
注意 大多数情况下如果没有加载私有符号,则不能访问局部变量。关于这种情况的更多信息,查看dbgerr005: Private Symbols Required。要显示局部变量的值,使用dv (Display Local Variables)命令。
下面的示例说明附加的x选项。使用/v选 项时,输出的第一列显示符号类型 (local、 global、 parameter、 function或unknown)。第二列是符号地址。第三列是符号大小,以字节为单位。第四列是模块名和符号名。某些情况下,输出后面跟了一个等号 (=)和符号的数据类型。符号的来源 (公有的或实际的符号信息)也会显示出来。
kd> x /v nt!CmType*
global 806c9e68 0 nt!CmTypeName = struct _UNICODE_STRING []
global 806c9e68 150 nt!CmTypeName = struct _UNICODE_STRING [42]
global 806c9e68 0 nt!CmTypeName = struct _UNICODE_STRING []
global 805bd7b0 0 nt!CmTypeString = unsigned short *[]
global 805bd7b0 a8 nt!CmTypeString = unsigned short *[42]
上例中,是以16进制给出大小,而数据类型是以10进制形式。因此,上例的最后一行中,数据类型是42个unsigned short整数的数组。数组大小是42*4 = 168,而168的16进制是0xA8。
可以使用/s Size选项来显示大小的字节数为指定值的符号。例如,可以限制上面的命令只显示相应对象的大小为0xA8的符号。
kd> x /v /s a8 nt!CmType*
global 805bd7b0 a8 nt!CmTypeString = unsigned short *[42]
/t选项使得调试器显示每个符号数据类型的信息。注意对于很多符号,该信息没有/t选项也会显示出来。使用/t时,这些符号的符号信息会被显示两次。
0:001> x prymes!__n*
00427d84 myModule!__nullstring = 0x00425de8 "(null)"
0042a3c0 myModule!_nstream = 512
Type information missing error for _nh_malloc
004021c1 myModule!MyStructInstance = struct MyStruct
00427d14 myModule!_NLG_Destination = <no type information>
0:001> x /t prymes!__n*
00427d84 char * myModule!__nullstring = 0x00425de8 "(null)"
0042a3c0 int myModule!_nstream = 512
Type information missing error for _nh_malloc
004021c1 struct MyStruct myModule!MyStructInstance = struct MyStruct
00427d14 <NoType> myModule!_NLG_Destination = <no type information>
下面的例子说命令过滤模块notepat.exe中的函数时对/f开关的使用。
0:000> x /f /v notepad!*main*
prv func 00000001`00003340 249 notepad!WinMain (struct HINSTANCE__ *, struct HINSTANCE__ *, char *, int)
prv func 00000001`0000a7b0 1c notepad!WinMainCRTStartup$filt$0 (void)
prv func 00000001`0000a540 268 notepad!WinMainCRTStartup (void)
2. 调试器使用ID 号来在之后的bc (Breakpoint Clear)、bd (Breakpoint Disable)和be (Breakpoint Enable)命令中引用该断点。使用bl (Breakpoint List) 命令来查看关联到当前设置的所有断点上的ID号。
ba 命令支持由调试寄存器提供的一些功能。可以在特定内存位置被读、写或执行时中断下来。
断点仅在给定地址给定长度的内存被访问时起效。如果被访问的内存仅是和要监控的内存部分重叠,断点不会被触发。
虽然所有断点类型都需要大小,但是执行断点仅在该地址是指令的第一个字节时就可以触发。
在内核模式下调试多处理器系统时,使用bp (Set Breakpoint) 或ba命令设置的断点会应用到所有处理器。例如,当前处理器是3并且输入了ba e1 MemoryAddress 来在MemoryAddress 设置一个断点,任何处理器(不止是处理器3)执行到该地址时都会产生断点陷阱。
不能使用ba 命令来为用户模式进程设置初始断点。
不能对相同地址创建多个仅CommandString 值不同的断点。但是,可以在单个地址创建多个不同条件的断点(例如/p、 /t、/c和/C的值不同)。
内核模式调试时,目标机的用户模式和内核模式数据断点有区别。用户模式数据断点不能作用于内核执行或内存访问。根据用户模式代码是否使用了调试寄存器状态和是否有用户模式调试器附加上去,内核模式数据断点可以作用于用户模式的执行或内存访问。
要将当前进程已存在的数据断点应用到另一个寄存器上下文中,可以使用.apply_dbp (Apply Data Breakpoint to Context)命令。
下面的例子说明ba命令的使用。下例对变量myVar 上的4字节读访问设置断点。
0:000> ba r4 myVar
下面的命令对从0x3F8到0x3FB 端口上的所有串口访问设置断点。该断点在对这些端口进行任何读写操作时都会触发。
kd> ba i4 3f8
bp、bu和bm 命令设置新断点,但是它们有不同的特点:
使用bp命令时,断点位置始终被转换成地址。如果bp 断点设置的代码被移动了,该断点仍然保持在相同位置并且可能指向不同的代码或者非法位置。
相反的,bu 断点始终和命令指定的符号化的断点位置关联(一般是符号加上一个可选的偏移)。这种关联在符号的值改变或者包含该位置的模块加载或卸载之后仍然保持。
使用bp 设置的断点会保持到使用bc (Breakpoint Clear)命令或WinDbg的Breakpoints 对话框移除为止。但是,因为这些断点指向的是一个地址,bp 断点在包含所引用的位置的模块卸载之后就不再有效了。
bu 设置的断点会保存在WinDbg工作空间中,但是bp 设置的断点不会保存。
当使用鼠标在WinDbg的反汇编窗口或源码窗口中设置断点时,调试器创建bu断点。
bm 在想使用包含通配符的符号模板来设置断点时很有用。bm SymbolPattern 语法和使用x SymbolPattern然后对搜索结果使用bu 是一样的。例如,要在Myprogram 模块中所有以字符串"mem"开头的符号上设置断点,可以使用如下命令。
0:000> bm myprogram!mem*
4: 0040d070 MyProgram!memcpy
5: 0040c560 MyProgram!memmove
6: 00408960 MyProgram!memset
由于bm命令设置软断点(不是处理器断点),它会自动避开数据位置,以避免破坏数据。
但是,当使用bp 和bm /a时 要小心。这些命令可以在数据段中设置软断点。调试器在代码上设置软断点时,会将处理器指令替换为中断指令。但是当调试器在数据段设置软断点时,会把数据替 换为中断指令。这种中断指令会破坏数据。只有在只当作代码进行执行的数据上设置软件断点才是安全的。要在数据段设置断点,使用ba (Break on Access)命令。该命令可以设置数据断点而不是软件断点。
要在例如C++公有类这样的任意文本上设置断点,或者在operator new 函数上设置断点,需要将表达式括在园括号中。例如,使用bp (??MyPublic) 或bp (operator new)。
要在MASM表达式类型的任意文本上设置断点,使用bu @!"text"。要使用C++语法文本设置断点,对C++兼容的符号使用bu @@c++(text)。
Bp、bu和bm命令通过将处理器命令替换为中断指令来设置软断点。要调试只读代码或不能改变的代码,使用ba e 命令,e用于设置执行访问。
如果单个逻辑代码行跨越了多个物理行,断点设置在语句或调用的最后一个物理行上。如果调试器在要求的位置不能设置断点,则会将断点放到下一个可用的位置上。
如果指定了Thread,断点设置在指定线程上。例如,~*bp在所有线程上设置断点, ~#bp在产生当前异常的线程上设置,而 ~123bp 在线程123上设置。~bp 和~.bp 命令都在当前线程设置断点。
在内核模是下调试多处理器系统时,使用bp 或ba (Break on Access) 设置的断点会应用到所有处理器。例如,如果当前处理器是3,并且输入bp MemoryAddress来在 MemoryAddress上设置了断点。任何执行到该地址的处理器(不止是处理器3)都会产生断点陷阱。
下面的例子说明了如何使用不bp命令。这个命令在MyTest 函数后面12字节位置处设置断点。该断点忽略前6次对指定代码的执行,但是第7次执行该代码时会中断下来。
0:000> bp MyTest+0xb 7
下面的命令在RtlRaiseException 上设置断点,显示eax寄存器并显示符号MyVar 的值,然后继续。
kd> bp ntdll!RtlRaiseException "r eax; dt MyVar; g"
3. 使用k、kb、kp、kP或kv 命令时,堆栈回溯按照表格形式显示。如果启用了行号,源码模块和行号也会显示出来。
堆栈回溯中包含堆栈帧的基指针、返回地址和函数名。
如果使用kp 或 kP 命令,堆栈回溯中每个函数的所有参数都会显示出来。参数列表包含每个参数的数据类型、名字和值。
该命令的执行可能很慢。例如,当MyFunction1 调用MyFunction2时,调试器必须获得MyFunction1 的完整符号信息来显示传递过去的参数。该命令不能完整显示在公有符号中未暴露的Microsoft Windows内部例程。
如果使用kb 或 kv命令,则显示传递给每个函数的前三个参数。如果使用kv,FPO数据也会显示出来。
在x86处理器上,kv命令也会显示调用约定的信息。
在基于Itanium的处理器上,kv命令也会使得非易失性寄存器(nonvolatile registers)显示出来。该信息使得可以回溯寄存器堆栈。
使用kv命令时,FPO信息按如下格式添加到行末。
FPO文本
|
含义
|
FPO: [non-Fpo]
|
帧中没有FPO数据。
|
FPO: [N1,N2,N3]
|
N1是参数的总数。 |
FPO: [N1,N2] TrapFrame @ Address
|
N1 是参数总数。 |
FPO: TaskGate Segment:0
|
Segment 是任务门的段选择子。
|
FPO: [EBP 0xBase]
|
Base 是帧的基指针。
|
kd命令显示原始堆栈数据。每个DWORD值都显示在单独的行上。这些行中的符号信息也和关联的符号显示在一起。这种格式比其他k*包含更详细的列表。kd命令和使用dds (Display Memory)命令并将堆栈地址作为参数一样。
如果想从不是当前堆栈位置开始堆栈回溯,可以使用BasePtr参数来指定基指针的值。在x86处理器上指定基指针需要指定BasePtr、StackPtr和InstructionPtr。这些参数需要和堆栈回溯对应的ebp、esp、eip值相等。如果指定了BasePtr 而省略StackPtr 和InstructionPtr,当存在FPO帧时可能获得错误的结果。
如果在某个函数开头使用k命令(在函数内部的预处理执行之前),会得到错误的结果。调试器使用帧寄存器来计算回溯,而该寄存器在预处理执行之前都不会被正确设置。
在用户模式下,堆栈回溯基于当前线程的栈。关于线程的更多信息,查看控制进程和线程。
在内核模式下,堆栈跟踪基于当前寄存器上下文。可以设置寄存器上下文来匹配指定线程、上下文记录或陷阱帧。
d*命令显示给定范围内存的内容。Dd、dD、 dw和dW命令的第二个字母和dyb 、dyd的第三个字母是大小写敏感的。
命令
|
显示
|
d
|
这种显示的格式和最近一次d*命令的格式相同。如果之前没有使用过d*命令,d 和db 的效果相同。 |
da
|
ASCII 字符。 |
db
|
字节值和ASCII字符。 |
dc
|
双字值(4字节)和ASCII字符。 |
dd
|
双字值(4字节) |
dD
|
双精度浮点数(8字节) |
df
|
单精度浮点数(4字节) |
dp
|
指针大小的值。该命令根据目标机的处理器是32位还是64位的,分别等于dd 或dq。 |
dq
|
四字值(Quad-word values) (8 bytes)。 |
du
|
Unicode字符 。 |
dw
|
WORD值(2字节)。 |
dW
|
WORD值(2字节)和ASCII字符。 |
dyb
|
二进制值和字节的值。 |
dyd
|
二进制值和双字值(4字节)。 |
如果尝试显示一个非法地址,它的内容会显示为问号(?)。
关于内存操作和内存相关的命令的概述,查看读写内存。
5. dt 命令的输出总是用10进制显示有符号数,16进制显示无符号数。
dt的所有参数都可以使用符号值,也可以使用字符串通配符。查看字符串通配符语法获得详细信息。
dt 使用到的类型信息包含所有以typedef 创建的类型名、所有Windows定义的类型。例如,unsigned long 和char 不是合法的类型名,但是ULONG 和CHAR 合法。查看Microsoft Windows SDK获得Windows类型名的完整列表。
代码中所有用typedef定义的类型都可用,只要它们在程序中被实际用到了。但是,在头文件中被定义但是从来没有实际用到过的类型不会包含在.pdb 符号文件中,也不能被调试器访问。
任何NAME或Field之前都可以有-y 和-n选项。-y选项使得可以指定类型或结构名的开始部分。例如,dt -y ALLEN 将显示类型ALLENTOWN 的数据。但是,不能使用dt -y A 来显示ALLENTOWN,而因该使用dt -ny A,因为A是一个合法的16进制值,如果没有-n的话会被当作地址。
如果NAME指定了一个结构,它的所有字段都会显示出来(例如,dt myStruct)。如果只需要一个特定的字段,可以使用dt myStruct myField。这样会显示C中称为myStruct.myField的成员。但是,注意命令dt myStruct myField1 myField2显示myStruct.myField1和myStruct.myField2,而不是显示myStruct.myField1.myField2。
如果结构名或字段后跟一个下标,则表示一个数组的单个实例。例如,dt myStruct myFieldArray[3] 将显示要查询的数组中第四个成员。但是如果是类型名后跟下标,则指定整个数组。例如,dt CHAR[8] myPtr 将显示一个8字符的字符串。不管当前基数是什么,下表总是10进制数,使用0x前缀会产生错误。
由于该命令使用.pdb 文件中的类型信息,所以可以自由的调试任何CPU平台。
如果要显示unicode字符串,需要首先使用.enable_unicode (Enable Unicode Display)命令。可以通过.enable_long_status (Enable Long Integer Display)命令来控制长整形的显示。
下面的例子中,dt显示一个全局变量:
0:000> dt mt1
+0x000 a : 10
+0x004 b : 98 'b'
+0x006 c : 0xdd
+0x008 d : 0xabcd
+0x00c gn : [6] 0x1
+0x024 ex : 0x0
下例中,dt显示数组字段gn:
0:000> dt mt1 -a gn
+0x00c gn :
[00] 0x1
[01] 0x2
[02] 0x3
[03] 0x4
[04] 0x5
[05] 0x6
下面的命令显示变量的一些子字段:
0:000> dt mcl1 m_t1 dpo
+0x010 dpo : DEEP_ONE
+0x070 m_t1 : MYTYPE1
这个命令显示m_t1子字段。因为点号自动产生前缀匹配,所以也会显示所有以"m_t1"开头的子字段:
0:000> dt mcl1 m_t1.
+0x070 m_t1 :
+0x000 a : 0
+0x004 b : 0 ''
+0x006 c : 0x0
+0x008 d : 0x0
+0x00c gn : [6] 0x0
+0x024 ex : 0x0
可以重复任意深度。例如命令dt mcl1 a..c. 会显示深度为4的所有字段,并且第一个字段名以a开头,第三个字段名以c开头。
这里是如何显示子字段的一个更详细的示例。首先显示Ldr域:
0:000> dt nt!_PEB Ldr 7ffdf000
+0x00c Ldr : 0x00191ea0
然后展开该指针类型的字段:
0:000> dt nt!_PEB Ldr Ldr. 7ffdf000
+0x00c Ldr : 0x00191ea0
+0x000 Length : 0x28
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x191ee0 - 0x192848 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x191ee8 - 0x192850 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x191f58 - 0x192858 ]
+0x024 EntryInProgress : (null)
现在显示CriticalSectionTimeout字段:
0:000> dt nt!_PEB CriticalSectionTimeout 7ffdf000
+0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
在第一级深度展开CriticalSectionTimeout结构的子字段:
0:000> dt nt!_PEB CriticalSectionTimeout. 7ffdf000
+0x070 CriticalSectionTimeout : 0xffffe86d`079b8000
+0x000 LowPart : 0x79b8000
+0x004 HighPart : -6035
+0x000 u : __unnamed
+0x000 QuadPart : -25920000000000
现在展开CriticalSectionTimeout 结构的第二级子字段:
0:000> dt nt!_PEB CriticalSectionTimeout.. 7ffdf000
+0x070 CriticalSectionTimeout : 0xffffe86d`079b8000
+0x000 LowPart : 0x79b8000
+0x004 HighPart : -6035
+0x000 u :
+0x000 LowPart : 0x79b8000
+0x004 HighPart : -6035
+0x000 QuadPart : -25920000000000
下面的命令显示位于0x0100297C 的一个MYTYPE1 的数据类型的示例:
0:000> dt 0x0100297c MYTYPE1
+0x000 a : 22
+0x004 b : 43 '+'
+0x006 c : 0x0
+0x008 d : 0x0
+0x00c gn : [6] 0x0
+0x024 ex : 0x0
下面的命令显示地址0x01002BE0处的10个ULONG的数组:
0:000> dt -ca10 ULONG 01002be0
[0] 0x1001098
[1] 0x1
[2] 0xdead
[3] 0x7d0
[4] 0x1
[5] 0xcd
[6] 0x0
[7] 0x0
[8] 0x0
[9] 0x0
这个命令在另一个地址继续使用上面的显示。注意不需要再重新输入"ULONG":
0:000> dt -ca4 . 01002d00
Using sym ULONG
[0] 0x12
[1] 0x4ac
[2] 0xbadfeed
[3] 0x2
这是一些类型显示的示例。下面的命令显示thismodule中所有以"MY"开头的类型和全局变量。具有地址前缀的是实际的实例,而没有地址的是类型定义:
0:000> dt thismodule!MY*
010029b8 thismodule!myglobal1
01002990 thismodule!myglobal2
thismodule!MYCLASS1
thismodule!MYCLASS2
thismodule!MYCLASS3
thismodule!MYTYPE3::u
thismodule!MYTYPE1
thismodule!MYTYPE3
thismodule!MYTYPE3
thismodule!MYFLAGS
当进行类型显示时,-v 选项可以用来显示每个项目的大小。-s size 选项用来仅枚举指定大小的项。同样,有地址前缀的表示实例,没有地址的表示是类型定义:
0:001> dt -s 2 -v thismodule!*
Enumerating symbols matching thismodule!*, Size = 0x2
Address Size Symbol
002 thismodule!wchar_t
002 thismodule!WORD
002 thismodule!USHORT
002 thismodule!SHORT
002 thismodule!u_short
002 thismodule!WCHAR
00427a34 002 thismodule!numberOfShips
00427a32 002 thismodule!numberOfPlanes
00427a30 002 thismodule!totalNumberOfItems
这是一个使用-b选项的示例。结构被展开并且结构中的OwnerThreads 数组也被展开,但是并不跟踪Flink 和Blink链表指针:
kd> dt nt!_ERESOURCE -b 0x8154f040
+0x000 SystemResourcesList : [ 0x815bb388 - 0x816cd478 ]
+0x000 Flink : 0x815bb388
+0x004 Blink : 0x816cd478
+0x008 OwnerTable : (null)
+0x00c ActiveCount : 1
+0x00e Flag : 8
+0x010 SharedWaiters : (null)
+0x014 ExclusiveWaiters : (null)
+0x018 OwnerThreads :
[00]
+0x000 OwnerThread : 0
+0x004 OwnerCount : 0
+0x004 TableSize : 0
[01]
+0x000 OwnerThread : 0x8167f563
+0x004 OwnerCount : 1
+0x004 TableSize : 1
+0x028 ContentionCount : 0
+0x02c NumberOfSharedWaiters : 0
+0x02e NumberOfExclusiveWaiters : 0
+0x030 Address : (null)
+0x030 CreatorBackTraceIndex : 0
+0x034 SpinLock : 0
这是一个内核模式的dt示例。下面的命令处理结果类似!process 0 0:
kd> dt nt!_EPROCESS -l ActiveProcessLinks.Flink -y Ima -yoi Uni 814856f0
ActiveProcessLinks.Flink at 0x814856f0
---------------------------------------------
UniqueProcessId : 0x00000008
ImageFileName : [16] "System"
ActiveProcessLinks.Flink at 0x8138a030
---------------------------------------------
UniqueProcessId : 0x00000084
ImageFileName : [16] "smss.exe"
ActiveProcessLinks.Flink at 0x81372368
---------------------------------------------
UniqueProcessId : 0x000000a0
ImageFileName : [16] "csrss.exe"
ActiveProcessLinks.Flink at 0x81369930
---------------------------------------------
UniqueProcessId : 0x000000b4
ImageFileName : [16] "winlogon.exe"
....
如果要对列表中的每一项执行命令,使用!list扩展。
最后,dt -h 命令可以显示dt语法的简短帮助文本。
在详细模式下,每个变量的地址也会显示出来。(这也可以通过x (Examine Symbols) 命令实现)
数据结构和陌生的数据类型不会完整显示,而只显示他们的类型名。要显示整个结构或结构中的特定成员,使用dt (Display Type) 命令。
6. 读写内存
三个调试器都可以直接读写内存。这些内存可以用地址或变量名来引用。
使用虚拟地址访问内存
可以使用各种命令访问内存或内存区域。
下面的这些命令可以以各种格式读写内存。这些格式包括16进制字节、字(字、双字和4字)、整数(short, long, quad integers 和unsigned integers)、实数(10字节、16字节、32字节和64字节实数)、以及ASCII字符。
d* (Display Memory) 命令显示指定内存或范围的内容。
e* (Enter Values) 命令在指定内存地址写入数据。
(仅WinDbg) 内存窗口(Memory window) 可以显示或修改指定的内存区域的内容。
使用下面一些命令来处理更特殊的数据类型:
dt (Display Type) 命令查找多种数据类型并显示被调试程序创建的数据结构。该命令功能非常多并且有很多变化和选项。
ds, dS (Display String) 命令显示STRING 、STRING 或UNICODE_STRING 数据结构。
dl (Display Linked List) 命令跟踪和显示一个链表。
d*s (Display Words and Symbols) 命令查找可能包含符号信息的双字或四字,并显示数据和符号信息。
!address扩展命令显示指定地址的内存地属性。
使用如下一些命令来操作内存块:
m (Move Memory)命令将一个内存区域的内容移动到另一个。
f (Fill Memory) 命令用一个模板写入内存区域,并重复直到区域被填满。
c (Compare Memory) 命令比较两个内存区域的内容。
s (Search Memory) 在内存区域搜索指定的模板或搜索内存区域中的ASCII或Unicode字符。
.holdmem (Hold and Compare Memory) 命令将一个内存区域和另外一个比较。
大多数情况下,这些命令都按照当前的基数来解释参数。因此,当前基数不是16时,要在16进制数前加上0x 。但是,这些命令的显示输出不管当前基数是什么,一般是16进制格式。(关于输出的更多信息,查看各个命令的主题。) 内存窗口(Memory window)使用10进制显示整数和实数,用16进制显示其他格式的数据。
使用n (Set Number Base) 命令改变当前基数。用? (Evaluate Expression) 或 .formats (Show Number Formats) 命令来快速的将数字从一种基数转变为另一种。
进行用户模式调试时,虚拟地址的意义由当前进程决定。进行内核模式调试时,虚拟地址的意义可以由调试器控制。更多信息,查看进程上下文。
使用物理地址访问内存
用!db、!dc、!dd、!dp、!du和!dw 扩展命令读取物理内存的内容。
使用!eb 和!ed 扩展命令写入物理内存。
fp (Fill Physical Memory) 命令在物理内存范围内写入一个模板,并重复直到内存块被填充满。
内核模式下使用WinDbg时,也可以直接使用Memory窗口读写物理内存。
在物理内存中搜索一块数据或一个范围内的数据,使用!search 扩展命令。
同样,关于物理地址的更多信息,查看将虚拟地址转换为物理地址。
访问全局变量
全局变量的名字保存在应用程序编译时创建的符号文件中。调试器将全局变量名转换成虚拟地址。因此,任何接受地址作为参数的命令也可以适用变量名作为参数。
因此,可以使用本主题前面提到的所有命令来读写全局变量。
另外,可以使用? (Evaluate Expression) 命令来显示和任何符号关联的地址。
下面例子中,假设需要查看一个32位整数MyCounter 全局变量的值。假设当前的基数为10。
可以用下面的方法得到它的地址并显示它。
0:000> ? MyCounter
Evaluate expression: 1244892 = 0012fedc
0:000> dd 0x0012fedc L1
0012fedc 00000052
第一个命令的输出表明MyCounter 的地址为0x0012FEDC。然后可以使用 D* 命令来显示该地址的一个双字。 (也可以使用1244892,这个地址的10进制值。但是,大多数C程序员会选择使用0x0012FEDC。) 第二条命令表明MyCounter 的值为0x52 (10进制82)。
可以用下面一条命令实现上面这些步骤。
0:000> dd MyCounter L1
0012fedc 00000052
要将MyCounter 修改为10进制的83,使用下面的命令。
0:000> ed MyCounter 83
这个例子使用10进制输入,因为这样对于整数来说要自然一些。但是,D* 命令的输出仍然是16进制格式。
0:000> dd MyCounter L1 0012fedc 00000053
访问局部变量
局部变量和全局变量类似,也在符号文件中保存了信息。调试器同样会将它们的名字转换为地址。它们可以使用和全局变量一样的方式来读写。
也可以使用下面这些方法来显示、修改和使用局部变量:
dv (Display Local Variables) 命令显示所有局部变量的名字和值。
(仅WinDbg) The 局部窗口(Locals window)显示所有局部变量的名字和值。可以使用该窗口来修改这些变量的值。
!for_each_local 扩展命令可以对每个局部变量执行一条命令。
但是,局部变量和全局变量有一个主要的不同。当程序运行时,局部变量的意义由程序计数器的位置决定,因为这些变量的作用范围仅在定义它们的函数内部。
调试器根据局部上下文来解释局部变量。默认情况下这个上下文和程序计数器位置匹配。但是调试器可以改变上下文。关于局部上下文的更多信息,查看局部上下文。
当局部上下文被改变时,局部窗口会立即更新以显示新的局部变量集合。DV 命令也显示新的变量。所有这些新的变量都能够被上述的内存命令正确解释,之后就可以读写这些变量了。
调试优化后的代码时,有些局部变量可能被合并(collapsed?)、使用寄存器替代或可能只是临时存放到堆栈中。如果要在调试中使用源码文件或局部变量,最好不要优化代码。
通过监视窗口控制变量
在WinDbg中,也可以使用监视窗口(Watch window)来显示、修改全局和局部变量。
监视窗口可以显示任何需要的变量列表。可以包含全局变量和任何函数的局部变量。任何时候,监视窗口都显示这些变量中和当前函数作用范围匹配的那部分变量。同样可以通过监视窗口修改变量的值。
和局部窗口不同,监视窗口不会受到局部上下文的影响。只有当前的程序计数器作用范围内定义的变量能够被显示和修改。
关于该窗口的更多信息,查看Watch 窗口。
断点位于可执行代码中,它使得操作系统停止程序执行并中断到调试器。 然后,就可以分析目标和执行调试器命令。
可以通过指定虚拟地址、模块和函数偏移或源码文件和行号来设置断点(当在源码模式中时)。如果在某个例程上设置断点并且没有使用偏移,则当运行到这个例程上时就会触发断点。
还有下面一些类型的断点:
如果在用户模式下调试多于一个进程,每个进程都有它自己的断点集合。要查看或修改某个进程的断点,必须将该进程设置为当前进程。关于当前进程的更多信息,查看控制进程和线程。
使用下面一些方法来控制或显示断点:
每个断点都有一个关联的10进制数字称为断点ID 。该数字在各种命令中用于指定断点。
如果一个断点是设置在某个还未加载的函数名上,则称为延迟、虚拟或未定断点。 (这些术语可交替使用。) 未定断点没有被关联到任何具体被加载的模块上。每当一个新的模块被加载时,会检查该函数名。如果这个函数出现,调试器计算虚拟断点的实际位置并启用它。
使用bu设置的断点自动被认为是未定断点。如果断点在一个已加载模块中,则会启用并正常生效。但是,如果模块之后被卸载并重新加载,这个断点不会消失。而使用bp设置的断点会立即绑定到某个地址。
bp和bu断点有以下三个主要的不同点:
当在WinDbg 反汇编窗口或源码窗口中使用鼠标设置断点时,调试器创建的是bu断点。
当调试器启动一个新的目标程序时,初始断点在主映像和所有静态加载的DLL被加载、DLL初始化例程被调用之前自动触发。
调试器附加到一个已存在的用户模式程序时,初始断点立即触发。
-g 命令行选项使得WinDbg或CDB跳过初始断点。在这时可以自动执行命令。更多信息,查看控制异常和事件。
如果想启动新调试目标并在实际的程序即将开始执行的时候中断下来,就不要使用-g选项。应该让初始断点被触发。当调试器激活之后,在main或winmai函数上设置断点并使用g (Go) 命令。之后所有初始化过程都会运行并且程序在main函数即将执行时停止。
关于内核模式的自动断点的更多信息,查看崩溃和重起目标机。
断点支持几种地址语法,包括虚拟地址、函数偏移和源码行号。例如,可以使用下面的方法之一来设置断点:
0:000> bp 0040108c
0:000> bp main+5c
0:000> bp `source.c:31`
关于这些语法的更多信息,查看数值表达式语法, 源码行语法,以及各个命令的主题。
在内核模式下,最多可以使用32个断点。在用户模式下,可以使用任意数量的断点。
数据断点的数量由目标处理器架构决定。
如果要在MyClass类的MyMethod方法上设置断点,可以使用两种不同语法:
0:000> bp MyClass::MyMethod
0:000> bp MyClass__MyMethod
0:000> bp @@( MyClass::MyMethod )
如果要使用更复杂一些的断点命令,应该使用MASM表达式语法。表达式语法的更多信息,查看表达式求值。
每个用户模式应用程序在虚拟内存0x00000000 到0x7FFFFFFF 的地址称为用户空间。
当WinDbg或CDB在小于0x80000000的地址上下断时,断点是在单个进程的指定的用户空间的地址设置。用户模式调试时,当前进程决定了虚拟地址的意义。更多信息,查看控制进程和线程。
在内核模式,可以使用bp、 bu、 ba 命令和Breakpoints 对话框在用户空间设置断点。必须首先使用.process /i (或在一些内核空间的函数上的指定进程的断点)来将目标切换成当前进程上下文,并使用该进程上下文来指定拥有目标地址空间的用户模式进程。
用户模式的断点总是和设置该断点时进程上下文为激活状态的进程关联起来。如果有用户模式调试器在调试该进程,而还有一个内核模式调试器在调试进程运行的机器,即使断点由内核调试器设置,它中断时也是进入用户模式调试器。这时可以从内核模式调试器中断系统,或使用.breakin (Break to the Kernel Debugger) 命令来将控制权交给内核调试器。
注意 如果目标机运行在Microsoft Windows NT 4.0上,则不能使用内核调试器在用户空间中设置断点。
如果在某个表达式中想引用某个断点的地址,可以使用一个$bpNumber 语法的伪寄存器,Number是断点ID。关于该语法的更多信息,查看伪寄存器语法。
当使用内存地址或符号加偏移的方式设置断点时,一定不能将断点设置到一条指令的中间。
例如,有下面一段汇编代码。
770000f1 5e pop esi
770000f2 5b pop ebx
770000f3 c9 leave
770000f4 c21000 ret 0x10
770000f7 837ddc00 cmp dword ptr [ebp-0x24],0x0
前三条指令只有1个字节长。但是第四条指令有3字节长。(包含在0x770000F4,0x770000F5, 和0x770000F6三个地址的字节)如果要在该指令上使用bp、bu或 ba设置断点,则必须将地址指定为0x770000F4 。
如果使用ba命令在0x770000F5 地址设置了断点,处理器将在该位置设置断点。但是 该断点永远不会被触发,因为处理器认为0x770000F4 才是这条指令的实际地址。
如果使用bp 或bu命令在 0x770000F5 设置断点,调试器在这个位置会写入断点。但是由于调试器使用如下方法设置断点,它可能造成目标运行错误:
当在0x770000F5设置断点时,调试器保存它的值并写入断点指令。但是当程序运行时到达0x770000F4 时,会将它视为一条多字节指令的第一个字节。处理器将0x770000F4、0x770000F5可能还有后面的一些字节当作一条指令。这会产生各种非正 常的行为。
因此,当使用bp、bu或ba 命令设置断点时,要确定断点在合适的地址上。如果使用WinDbg图形界面来添加断点就不用在意这样的情况,因为它会自动选择正确的地址。
可以在断点中包含一条命令用于在断点触发时自动执行。
也可以包含一条用于执行的命令字符串。但是,其中任何恢复程序执行的命令(例如g和t)都会终止命令列表的执行。
例如下面的命令在MyFunction+0x47中断,写入一个dump文件并恢复执行。
0:000> bu MyFunction+0x47 ".dump c:\mydump.dmp; g"
注意 如果正在从内核调试器控制用户模式调试器,不要在命令字符串中使用g (Go) 。串口的速度可能跟不上该命令,并且不能再中断到CDB中。关于这种情况的更多信息,查看从内核调试器控制用户模式调试器。
@!"<chars>" 语法用于在MASM求值器中进行转义,使得符号解析支持任意文本。必须以@!"开始并以引号(")结束。如果不使用该语法,则在MASM表达式的符号名中不能使用空格、大于小于号(<, >)和其他特殊字符。模板和重载是符号中需要这种引号的主要原因。也可以使用如下的@!"<chars>" 语法来设置bu 命令。
0:000> bu @!"ExecutableName!std::pair<unsigned int,std::basic_string<unsigned short,std::char_traits<unsigned short>,std::allocator<unsigned short> > >::operator="
这个例子中,ExecutableName 是一个可执行文件的名字。
这种转义语法在C++中比C中更加有用(例如重载的操作符),因为C函数名中不会存在空格(或特殊字符)。但是,该语法在托管代码中也同样重要,因为.NET Framwork中非常多的使用重载。
断点指令 + “j(Excecute If-Else) 和 gc (Go from Conditional Breakpoint)”
形如:bp Address "j (Condition) 'OptionalCommands'; 'gc' "
这里仅简单说明该如何写后面的条件语句
1. 非结构体变量:
在代码中,MyVar是整数变量。默认的debug配置采用MASM语法,因此MyVar被当作指针看待,在做条件判断时,需要使用poi解引用。如果 debug配置采用C++语法,MyVar会被解析为整数变量,可直接用于条件判断。条件为真时对应的语句为空,则当条件满足时,会断在此处。gc表示从 断点处继续运行。
2. 结构体变量
判断结构体变量中的某个成员变量时,采用C++语法解析表达式:@@c++(...)。因默认配置是masm语法,故对于结构体成员都用此种方法解析。
3. 寄存器
1
2
3
式<1>:当eax的值为0xa3时,触发该断点。
在masm表达式中,寄存器是做符号扩展的,即0xc0004321实际被当作是0xffffffff`c0004321,即便实际显示时是 0xc00004321。这种符号扩展仅存在于kernel mode中。因此式<2>在kernel mode会失败。最好的改法则是按照式<3>的方式做条件比较,该方式可以同时用于kernel mode和user mode。