Summary of my Windbg command

 

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 errorthen :

!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可能通过模式一次下多个断点,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是要检查的类型和长度,Wwrite,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的值,否则显示ebxecx的值。

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 SymbolsPrivate PDBSymbols支持。KPKp相似,只是KP将参数换行显示了。Kv用于显示FPO和调用约定,KD,用于显示StackDump,在跟踪栈时比较有用。这些指令区分大小。

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用于显示ASCIIDU用于显示UNICODEBYBBYD,显示binaryBytebinaryDWORD,补充一个DV,用于查看本地变量用的

这些指令区分大小。d,da(ascii),dd(dword), db(byte),du(unicode),dc(char). //d开头显示内存地址上的值 

dt 变量名//显示变量的资料及结构

d esp//显示ESP寄存器指向的内存,defaultbyte

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//反汇编整个函数

////////////////////////////

15.    pa (Step to Address)

             pa命令执行程序直到到达指定地址,每一步都会被显示出来。

例如,下面的命令单步执行目标的代码直到到达当前函数的返回地址。

0:000> pa @$ra 

下面的命令演示了pakb命令命令一起用来显示堆栈回溯:

0:000> pa 70b5d2f1 "kb"

//new

16.   !analyze –v //analyze the break,显示dump文件信息,显示分析的详细信息

调试一个当机的目标计算机或应用程序,第一步是使用 !analyze 扩展命令。

该扩展执行大量的自动分析。分析结果在调试器命令窗口中显示。

若要数据的全冗长模式显示,你应该使用 -v 选项。

用户模式 !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_NAMEMODULE_NAMEIMAGE_NAMEDBG_FLR_IMAGE_TIMESTAMP字段表示这个指令相应的符号、模块、映像名字和映像时间戳。

STACK_COMMAND:  .ecxr ; kb
  
    

STACK_COMMAND字段表示用来获取STACK_TEXT的命令。你可以使用这个指令重复显示这个栈跟踪,或者改变它以获得有关的栈信息。

BUCKET_ID:  80000003_MyApp!FatalErrorBox+55
  
    

BUCKET_ID字段表示当前故障所属的特定故障类别。这个类别帮助调试器确定在分析输出中所显示的其他信息。

Followup: dbg
  
    
---------
  
    

关于FOLLOWUP_NAMEFollowup字段的信息,请看"Followup字段和triage.ini文件" The Followup Field and the triage.ini File

还可能出现其他一些字段:

  • 如果控制被转移给一个无效地址,那么FAULTING_IP字段将会包含有这个无效地址。不是FOLLOWUP_IP字段,而是FAILED_INSTRUCTION_ADDRESS字段将显示该地址上的反汇编码,虽然反汇编码可能是无意义的。在这种情形下,SYMBOL_NAMEMODULE_NAMEIMAGE_NAMEDBG_FLR_IMAGE_TIMESTAMP字段将指出这条指令的调用者
  • 如果处理器失败,你可能会看到SINGLE_BIT_ERRORTWO_BIT_ERRORPOSSIBLE_INVALID_CONTROL_TRANSFER字段。
  • 如果内存崩溃看起来已经发生,CHKIMG_EXTENSION字段将说明应该使用 !chkimg 扩展命令来调查。

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.

Syntax

!error Value [Flags]

Comments

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 functionunknown)。第二列是符号地址。第三列是符号大小,以字节为单位。第四列是模块名和符号名。某些情况下,输出后面跟了一个等号 (=)和符号的数据类型。符号的来源 (公有的或实际的符号信息)也会显示出来。

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进制形式。因此,上例的最后一行中,数据类型是42unsigned short整数的数组。数组大小是42*4 = 168,而16816进制是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

下面的命令对从0x3F80x3FB 端口上的所有串口访问设置断点。该断点在对这些端口进行任何读写操作时都会触发。

kd> ba i4 3f8

   bpbubm 命令设置新断点,但是它们有不同的特点:

  • bp (Set Breakpoint)命令在指定地址的断点位置上设置断点。如果设置断点时调试器还不能将地址表达式计算为断点位置,bp 断点被自动转换为bu 断点。使用bp 命令来设置在模块被卸载之后就不会再被激活的断点。
  • bu (Set Unresolved Breakpoint)命令设置 延迟的未定断点bu 设置在命令中指定的符号引用的断点位置上(不是一个地址上),并且当所引用的模块能够确定时激活。
  • bm (Set Symbol Breakpoint) 命令在能够匹配指定模板的符号上设置断点。该命令可以设置多于一个的断点。默认情况下,当模板被匹配之后,bm 断点和bu 断点相同。即bm 断点是针对符号引用设置的延迟断点。但是bm /d 命令会创建一个或多个bp断点。每个断点都被设置在能够匹配的位置的地址上,并且不会跟随模块状态改变。

        使用bp命令时,断点位置始终被转换成地址。如果bp 断点设置的代码被移动了,该断点仍然保持在相同位置并且可能指向不同的代码或者非法位置。

相反的,bu 断点始终和命令指定的符号化的断点位置关联(一般是符号加上一个可选的偏移)。这种关联在符号的值改变或者包含该位置的模块加载或卸载之后仍然保持。

使用bp 设置的断点会保持到使用bc (Breakpoint Clear)命令或WinDbgBreakpoints 对话框移除为止。但是,因为这些断点指向的是一个地址,bp 断点在包含所引用的位置的模块卸载之后就不再有效了。

bu 设置的断点会保存在WinDbg工作空间中,但是bp 设置的断点不会保存。

当使用鼠标在WinDbg的反汇编窗口或源码窗口中设置断点时,调试器创建bu断点。

bm 在想使用包含通配符的符号模板来设置断点时很有用。bm SymbolPattern 语法和使用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)

Bpbubm命令通过将处理器命令替换为中断指令来设置软断点。要调试只读代码或不能改变的代码,使用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.       使用kkbkpkPkv 命令时,堆栈回溯按照表格形式显示。如果启用了行号,源码模块和行号也会显示出来。

堆栈回溯中包含堆栈帧的基指针、返回地址和函数名。

如果使用kp kP 命令,堆栈回溯中每个函数的所有参数都会显示出来。参数列表包含每个参数的数据类型、名字和值。

该命令的执行可能很慢。例如,当MyFunction1 调用MyFunction2时,调试器必须获得MyFunction1 的完整符号信息来显示传递过去的参数。该命令不能完整显示在公有符号中未暴露的Microsoft Windows内部例程。

如果使用kb kv命令,则显示传递给每个函数的前三个参数。如果使用kvFPO数据也会显示出来。

x86处理器上,kv命令也会显示调用约定的信息。

在基于Itanium的处理器上,kv命令也会使得非易失性寄存器(nonvolatile registers)显示出来。该信息使得可以回溯寄存器堆栈。

使用kv命令时,FPO信息按如下格式添加到行末。

FPO文本

含义

FPO: [non-Fpo]

帧中没有FPO数据。

FPO: [N1,N2,N3]

N1是参数的总数。

N2
是局部变量的DWORD值个数。

N3
是保存了的寄存器的个数。

FPO: [N1,N2] TrapFrame @ Address

N1 是参数总数。

N2
是局部变量的DWORD值个数。

Address
是陷阱帧的地址。

FPO: TaskGate Segment:0

Segment 是任务门的段选择子。

FPO: [EBP 0xBase]

Base 是帧的基指针。

 kd命令显示原始堆栈数据。每个DWORD值都显示在单独的行上。这些行中的符号信息也和关联的符号显示在一起。这种格式比其他k*包含更详细的列表。kd命令和使用dds (Display Memory)命令并将堆栈地址作为参数一样。

如果想从不是当前堆栈位置开始堆栈回溯,可以使用BasePtr参数来指定基指针的值。在x86处理器上指定基指针需要指定BasePtrStackPtrInstructionPtr。这些参数需要和堆栈回溯对应的ebpespeip值相等。如果指定了BasePtr 而省略StackPtr InstructionPtr,当存在FPO帧时可能获得错误的结果。

如果在某个函数开头使用k命令(在函数内部的预处理执行之前),会得到错误的结果。调试器使用帧寄存器来计算回溯,而该寄存器在预处理执行之前都不会被正确设置。

在用户模式下,堆栈回溯基于当前线程的栈。关于线程的更多信息,查看控制进程和线程。

在内核模式下,堆栈跟踪基于当前寄存器上下文。可以设置寄存器上下文来匹配指定线程、上下文记录或陷阱帧。

4.       d, da, db, dc, dd, dD, df, dp, dq, du, dw, dW, dyb, dyd (Display Memory)

d*命令显示给定范围内存的内容。DddD dwdW命令的第二个字母和dyb dyd的第三个字母是大小写敏感的。

命令

显示

d

这种显示的格式和最近一次d*命令的格式相同。如果之前没有使用过d*命令,d db 的效果相同。

注意d重复前一个以d开头的命令。包括ddaddpddudpadppdpu dqadqpdquddsdpsdqsdsdSdgdldtdv,以及本页中的显示命令。如果在d之后的参数不适当,可能会产生错误。

da

ASCII 字符。

每行最多48个字符。显示一直继续直到遇到第一个null字节或者到达range 值指定的所有字符都已经显示。所有不可打印字符,如回车和换行都被显示为点号(.)

db

字节值和ASCII字符。

每个显示行都包含该行第一个字节的地址,后面跟16进制字节值。这些字节值后面会紧跟它们对应的ASCII值。第8和第916进制值之间会用连字号(-)分隔。所有不可打印字符,如回车和换行都被显示为点号(.)

默认的显示个数为128字节。

dc

双字值(4字节)ASCII字符。

每个显示行都会显示行中第一个数据的地址,并且每行最多显示816进制值以及它们对应的ASCII字符。

默认的显示数量为32DWORD(128字节)

dd

双字值(4字节)
默认的显示数量为32DWORD(128字节)

dD

双精度浮点数(8字节)

默认的显示数量是15个数字(120字节)

df

单精度浮点数(4字节)

默认的显示数量是16个数字(64字节)

dp

指针大小的值。该命令根据目标机的处理器是32位还是64位的,分别等于dd dq

默认显示数量为32DWORD或者16个四字(quad-word) (128 字节)

dq

四字值(Quad-word values) (8 bytes)

默认显示数量为16个四字 (128 字节)

du

Unicode字符

每行最多显示48个字符。显示一直继续直到遇到第一个null字节或者到达range 值指定的所有字符都已经显示。所有不可打印字符,如回车和换行都被显示为点号(.)

dw

WORD(2字节)

每个显示行都会显示行中第一个数据的地址,并且每行最多显示816进制值。

默认显示数量为64WORD(128字节)

dW

WORD(2字节)ASCII字符。

每个显示行都会显示行中第一个数据的地址,并且每行最多显示816进制值。

默认显示数量为64WORD(128字节)

dyb

二进制值和字节的值。

默认显示数量为32字节。

dyd

二进制值和双字值(4字节)

默认显示数量为8DWORD(32字节)

 如果尝试显示一个非法地址,它的内容会显示为问号(?)

关于内存操作和内存相关的命令的概述,查看读写内存。

5.       dt 命令的输出总是用10进制显示有符号数,16进制显示无符号数。

dt的所有参数都可以使用符号值,也可以使用字符串通配符。查看字符串通配符语法获得详细信息。

dt 使用到的类型信息包含所有以typedef 创建的类型名、所有Windows定义的类型。例如,unsigned long char 不是合法的类型名,但是ULONG CHAR 合法。查看Microsoft Windows SDK获得Windows类型名的完整列表。

代码中所有用typedef定义的类型都可用,只要它们在程序中被实际用到了。但是,在头文件中被定义但是从来没有实际用到过的类型不会包含在.pdb 符号文件中,也不能被调试器访问。

任何NAMEField之前都可以有-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.myField1myStruct.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处的10ULONG的数组:

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)
在内存区域搜索指定的模板或搜索内存区域中的ASCIIUnicode字符。
.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 窗口。

7.       使用断点

断点位于可执行代码中,它使得操作系统停止程序执行并中断到调试器。 然后,就可以分析目标和执行调试器命令。

可以通过指定虚拟地址、模块和函数偏移或源码文件和行号来设置断点(当在源码模式中时)。如果在某个例程上设置断点并且没有使用偏移,则当运行到这个例程上时就会触发断点。

还有下面一些类型的断点:

  • 和特定线程关联的断点。
  • 在被触发前可以被挑过指定次数的断点。
  • 触发时自动执行特定命令的断点。
  • 设置在非执行内存上的断点,并等待该内存被读写时中断。

如果在用户模式下调试多于一个进程,每个进程都有它自己的断点集合。要查看或修改某个进程的断点,必须将该进程设置为当前进程。关于当前进程的更多信息,查看控制进程和线程

控制断点的方法

使用下面一些方法来控制或显示断点:

  • bl (Breakpoint List)命令列出当前存在的断点和他们的状态。
  • bp (Set Breakpoint) 命令设置新断点。
  • bu (Set Unresolved Breakpoint) 命令设置新断点。使用bu设置的断点和bp设置的断点特点不同,详细信息查看后面的内容。
  • bm (Set Symbol Breakpoint) 在匹配指定格式的符号上设置断点。
  • ba (Break on Access)命令设置数据断点。这种断点在指定内存被访问时触发。(可以在写入、读取、执行或发生内核I/O时触发,但不是所有处理器都支持所有的内存访问断点。更多信息,查看ba (Break on Access))
  • bc (Breakpoint Clear) 命令移除一个或多个断点。
  • bd (Breakpoint Disable) 命令暂时禁用一个或多个断点。
  • be (Breakpoint Enable) 命令重新启用一个或多个断点。
  • br (Breakpoint Renumber)命令修改一个已存在的断点的ID
  • (WinDbg) 反汇编窗口(Disassembly window) 源码窗口(Source windows) 会将设置了断点的行高亮。已启用的断点为红色,禁用的断点为黄色,如果当前程序计数器(EIP)位置是断点位置则显示为紫色。
  • (WinDbg) Edit | Breakpoints 命令或ALT+F9快捷键打开Breakpoints对话框。该对话框会列出所有断点,所以可以用它来禁用、启用、删除已存在的断点或设置新断点。
  • (WinDbg) 如果光标在反汇编窗口或源码窗口中,可以按下F9或点击工具栏上的Insert or remove 按钮 ( ) 来在光标所在行上设置断点。如果在当前窗口不是反汇编窗口或源码窗口时按下快捷键或点击上述按钮,则和使用Edit | Breakpoints具有相同效果。

每个断点都有一个关联的10进制数字称为断点ID 。该数字在各种命令中用于指定断点。

未定断点:BU vs. BP

如果一个断点是设置在某个还未加载的函数名上,则称为延迟虚拟未定断点。 (这些术语可交替使用。) 未定断点没有被关联到任何具体被加载的模块上。每当一个新的模块被加载时,会检查该函数名。如果这个函数出现,调试器计算虚拟断点的实际位置并启用它。

使用bu设置的断点自动被认为是未定断点。如果断点在一个已加载模块中,则会启用并正常生效。但是,如果模块之后被卸载并重新加载,这个断点不会消失。而使用bp设置的断点会立即绑定到某个地址。

bpbu断点有以下三个主要的不同点:

  • bp 断点的位置总是被转换成地址。如果某个模块改变了,并且bp设置的地址位置改变,断点还是在原来的位置。而bu断点仍然和使用的符号值关联(一般是符号加上偏移),它会一直跟踪符号的地址,即使这个地址已经改变。
  • 如果bp的断点地址在某个已加载模块中找到,并且该模块之后被卸载,则该断点会从断点列表中移除。而bu断点经过反复的卸载和加载仍然存在。
  • bp设置的断点不会保存到WinDbg 工作空间(workspaces)中,而使用bu设置的断点会保存。

当在WinDbg 反汇编窗口源码窗口中使用鼠标设置断点时,调试器创建的是bu断点。

初始断点

当调试器启动一个新的目标程序时,初始断点在主映像和所有静态加载的DLL被加载、DLL初始化例程被调用之前自动触发。

调试器附加到一个已存在的用户模式程序时,初始断点立即触发。

-g 命令行选项使得WinDbgCDB跳过初始断点。在这时可以自动执行命令。更多信息,查看控制异常和事件

如果想启动新调试目标并在实际的程序即将开始执行的时候中断下来,就不要使用-g选项。应该让初始断点被触发。当调试器激活之后,在mainwinmai函数上设置断点并使用g (Go) 命令。之后所有初始化过程都会运行并且程序在main函数即将执行时停止。

关于内核模式的自动断点的更多信息,查看崩溃和重起目标机

断点中的地址

断点支持几种地址语法,包括虚拟地址、函数偏移和源码行号。例如,可以使用下面的方法之一来设置断点:

0:000> bp 0040108c
0:000> 
bp main+5c
0:000> 
bp `source.c:31`

关于这些语法的更多信息,查看数值表达式语法 源码行语法,以及各个命令的主题。

断点的数量

在内核模式下,最多可以使用32个断点。在用户模式下,可以使用任意数量的断点。

数据断点的数量由目标处理器架构决定。

方法的断点

如果要在MyClass类的MyMethod方法上设置断点,可以使用两种不同语法:

  • MASM表达式语法,可以用双冒号或者双下划线来指定一个方法。

0:000> bp MyClass::MyMethod 
0:000> 
bp MyClass__MyMethod 

  • C++表达式语法,必须用双冒号指定方法。

0:000> bp @@( MyClass::MyMethod ) 

如果要使用更复杂一些的断点命令,应该使用MASM表达式语法。表达式语法的更多信息,查看表达式求值

用户空间和系统空间

每个用户模式应用程序在虚拟内存0x00000000 0x7FFFFFFF 的地址称为用户空间

WinDbgCDB在小于0x80000000的地址上下断时,断点是在单个进程的指定的用户空间的地址设置。用户模式调试时,当前进程决定了虚拟地址的意义。更多信息,查看控制进程和线程

在内核模式,可以使用bp bu ba 命令和Breakpoints 对话框在用户空间设置断点。必须首先使用.process /i (或在一些内核空间的函数上的指定进程的断点)来将目标切换成当前进程上下文,并使用该进程上下文来指定拥有目标地址空间的用户模式进程。

用户模式的断点总是和设置该断点时进程上下文为激活状态的进程关联起来。如果有用户模式调试器在调试该进程,而还有一个内核模式调试器在调试进程运行的机器,即使断点由内核调试器设置,它中断时也是进入用户模式调试器。这时可以从内核模式调试器中断系统,或使用.breakin (Break to the Kernel Debugger) 命令来将控制权交给内核调试器。

注意  如果目标机运行在Microsoft Windows NT 4.0上,则不能使用内核调试器在用户空间中设置断点。

断点伪寄存器(Pseudo-Registers)

如果在某个表达式中想引用某个断点的地址,可以使用一个$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字节长。(包含在0x770000F40x770000F5 0x770000F6三个地址的字节)如果要在该指令上使用bpbu ba设置断点,则必须将地址指定为0x770000F4

如果使用ba命令在0x770000F5 地址设置了断点,处理器将在该位置设置断点。但是 该断点永远不会被触发,因为处理器认为0x770000F4 才是这条指令的实际地址。

如果使用bp bu命令在 0x770000F5 设置断点,调试器在这个位置会写入断点。但是由于调试器使用如下方法设置断点,它可能造成目标运行错误:

  1. 调试器保存0x770000F5 地址的内容,并用一条断点指令写入该地址。
  2. 如果用调试器显示这段内存的内容,并不会显示被写入的断点指令,而是显示那里原来"应该是"的内容。即调试器显示原来的内存,或者断点设置之后该内存被修改的内容。
  3. 如果使用BC命令删除断点,调试器将断点位置的内存恢复成原始值。

当在0x770000F5设置断点时,调试器保存它的值并写入断点指令。但是当程序运行时到达0x770000F4 时,会将它视为一条多字节指令的第一个字节。处理器将0x770000F40x770000F5可能还有后面的一些字节当作一条指令。这会产生各种非正 常的行为。

因此,当使用bpbuba 命令设置断点时,要确定断点在合适的地址上。如果使用WinDbg图形界面来添加断点就不用在意这样的情况,因为它会自动选择正确的地址。

断点命令

可以在断点中包含一条命令用于在断点触发时自动执行。

也可以包含一条用于执行的命令字符串。但是,其中任何恢复程序执行的命令(例如gt)都会终止命令列表的执行。

例如下面的命令在MyFunction+0x47中断,写入一个dump文件并恢复执行。

0:000> bu MyFunction+0x47 ".dump c:\mydump.dmp; g" 

注意  如果正在从内核调试器控制用户模式调试器,不要在命令字符串中使用g (Go) 。串口的速度可能跟不上该命令,并且不能再中断到CDB中。关于这种情况的更多信息,查看从内核调试器控制用户模式调试器

@!"<chars>"

@!"<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中非常多的使用重载。

8.       条件断点

断点指令 + “j(Excecute If-Else) gc (Go from Conditional Breakpoint)”
形如:bp Address "j (Condition) 'OptionalCommands'; 'gc' "

这里仅简单说明该如何写后面的条件语句

1.
非结构体变量:       

        "j (poi(MyVar)>5) '';'gc'"

   在代码中,MyVar是整数变量。默认的debug配置采用MASM语法,因此MyVar被当作指针看待,在做条件判断时,需要使用poi解引用。如果 debug配置采用C++语法,MyVar会被解析为整数变量,可直接用于条件判断。条件为真时对应的语句为空,则当条件满足时,会断在此处。gc表示从 断点处继续运行。

2.
结构体变

        "j (@@c++(MyStruct->field)>5) '';'gc'"

   判断结构体变量中的某个成员变量时,采用C++语法解析表达式:@@c++(...)。因默认配置是masm语法,故对于结构体成员都用此种方法解析。

3.
寄存

1         "j @eax = 0xa3 ''; 'gc'"                                     <1>
2
        "j @eax = 0xc0004321 '';'gc'"                            <2>
3
        "j (@eax & 0x0`ffffffff) = 0x0`c0004321 '';'gc'"     <3>
  
<1>:当eax的值为0xa3时,触发该断点。
  
masm表达式中,寄存器是做符号扩展的,即0xc0004321实际被当作是0xffffffff`c0004321,即便实际显示时是 0xc00004321。这种符号扩展仅存在于kernel mode中。因此式<2>kernel mode会失败。最好的改法则是按照式<3>的方式做条件比较,该方式可以同时用于kernel modeuser mode

你可能感兴趣的:(command)