zoukankan      html  css  js  c++  java
  • Windbg调试命令详解(3)

    3 进程与线程

    既可以显示进程和线程列表,又可以显示指定进程或线程的详细信息。调试命令可以提供比taskmgr更详尽的进程资料,在调试过程中不可或缺。

    3.1 进程命令

    进程命令包括这些内容:显示进程列表、进程环境块、设置进程环境。

    进程列表

    多个命令可显示进程列表,但一般只能在特定情况下使用,它们是:|、.tlist、!process和!dml_proc。

    竖线命令显示当前被调试进程列表的状态信息,这个命令在本章开头已作过介绍,命令格式如下:

    • |  [进程号]

    请注意这里的定语:被调试进程列表。大多数情况下调试器中只有一个被调试进程,但可以通过.attach或者.create命令同时挂载或创建多个调试对象。当同时对多个进程调试时,进程号是从0开始的整数。下图中显示了两个被调试的进程。

    attach-process

    如何在多个进程间进行切换呢?使用s参数即可,这一点前文已然讲过。

    • .tlist [选项] [模块名]

                .tlist命令显示当前系统中的进程列表,他是目前唯一可在用户模式下显示系统当前进程列表的命令。它有两个可选项:-v显示进程详细信息,-c只显示当前进程信息。

                内核模式下同样可以使用.tlist,但更好的命令是!process。!process在内核模式下显示进程列表,和指定进程的详细信息,也能显示进程中的线程和调用栈内容。典型格式如下:

    • !process: 显示调试器当前运行进程信息
    • !process 0 0: 显示进程列表
    • !process PID: PID是进程ID,根据进程ID显示此进程详细信息。

    dml-process

    此外,还有一个DML版本的进程列表命令,如下:
    • !dml_proc [进程号|进程地址]

    此命令可以看成“|”和“!process”命令的DML合并版本,可在用户与内核模式下使用。显示的进程信息偏重于线程和调用栈。用户模式下此命令和“|”一样,只能显示被调试进程的信息。右图是内核模式下使用此命令的效果:

     

    进程信息

    进程环境块(Process Enviroment Block)是内核结构体,使用!peb命令参看其信息,但也可以用dt命令查看完整的结构体定义。格式如下:

    • !peb  [地址]

    如果未设置PEB地址,则默认为当前进程。内核模式下可通过!process命令获取PEB结构体地址;用户模式下只能显示当前进程的PEB信息,故而一般不带参数。

    • dt  nt!_peb  地址

    此命令显示系统nt模块中所定义的内核结构体PEB详细内容。使用之前必须先熟悉结构体定义。

    进程切换

    进程环境的切换,将伴随着与进程相关的寄存器、堆栈的切换。在不同进程环境中进行的调试结果有天壤之别。上文在讲“|”命令的时候,讲过用户环境下多进程间如何互相切换,使用命令:

    • |  [进程号]  s

    那么内核模式下,情况又不同了。内核模式下的进程切换,不同于用户模式下的被调试进程间切换,而是系统存在的多进程间切换。内核环境下,以进程地址作为参数,调用如下命令以进行进程环境切换:

    • .process [进程地址]

    如果不使用任何参数,.process命令将显示当前进程地址。所谓进程地址,即ERPCESS结构体地址。

    或以页目录地址为参数,调用下面命令切换用户地址空间:

    • .context [页目录地址]

    如果不使用任何参数,.context命令将显示当前页目录地址。页目录地址就是!process命令中显示的DirBase值。

    进程切换后,为了检测是否正确切换,可再用!peb命令检查当前进程的环境信息。

    3.2 线程命令

    命令“~”能够进行线程相关的操作。不带任何参数的情况下,它列出当前调试进程的线程。下图是计算器进程某时刻的线程列表:

    ||0:0:001> ~
    #  0  Id: f78.374 Suspend: 0 Teb: 000007ff`fffdc000 Unfrozen
    .  1  Id: f78.fb0 Suspend: 0 Teb: 000007ff`fffda000 Unfrozen
       2  Id: f78.a4c Suspend: 0 Teb: 000007ff`fffd8000 Unfrozen
       3  Id: f78.22c8 Suspend: 0 Teb: 000007ff`fff9a000 Unfrozen
       4  Id: f78.2658 Suspend: 0 Teb: 000007ff`fffd4000 Unfrozen
       5  Id: f78.cbc Suspend: 0 Teb: 000007ff`fff96000 Unfrozen
       6  Id: f78.21ec Suspend: 0 Teb: 000007ff`fffd6000 Unfrozen

    使用此命令可进行的线程操作包括:线程切换、线程环境、线程时间等。

    线程冰冻

    参数f与u分别代表freeze和unfress,前者是指冻住指定线程,后者将被冰冻线程解冻。

    • ~2f

    表示把2号线程冻住,在解冻之前,不再分发CPU时间给它。

    若要让指定线程重新运行,需使用参数u:

    • ~2u

    针对这两个命令,下面有一个小实验:

    运行Windbg,并选择调试记事本程序(Notepad.exe)。程序起来后,CTL+BREAK中断程序运行,输入命令: 
    
    ~0f 
    
    再输入g命令让记事本继续运行。 
    
    此时尝试用鼠标定位到notepad软件,发现软件界面无法被定位、移动、最大小化,甚至“清空桌面”操作也无济于事。这是因为0号线程为notepad的主线程,被冻住后整个软件都失去响应。 
    
    更严重的是,“清空桌面”操作(Win + D)也会失效,应是Notepad拒绝响应的缘故。
    
    线程挂起 
    
    参数n和m分别代表increase和resume,前者增加一个线程挂起计数,后者减少一个线程挂起计数。如果两次增加线程挂起计数(即达到2),则必须两次resume才能让线程恢复到运行状态。

    把上面实验中的~0f命令改变成~0n,也能达到相似的效果。

    线程切换

    查看指定线程的信息,用下面的命令:

    • 线程号

    线程号是由调试器软件内部维护的线程ID值,是一个从0开始的整数,和线程ID不是一回事。

    线程信息中包括有线程环境块地址,可通过!teb命令查看环境块信息:

    • !teb  [teb地址]

    如要在多线程间作切换,需使用~命令的s参数:

    • ~  线程号 s

    由于线程号在外部是没有太大意义的,所以另一个线程切换命令是以线程ID来标识一个线程的。这个命令比较奇怪,以双波浪线打头,格式如下:

    • ~~[线程ID]  s

    注意这个命令中的[]并非可选符,而是命令的一部分。例如命令:~~[11a0] s,它将当前线程切换到线程ID为0x11a0的线程。线程ID是系统维护的系统唯一的ID值。

    thread-info

    下图是关于线程切换的:

    0:014> ~
       0  Id: 4f0.4f4 Suspend: 1 Teb: 00000000`7efdb000 Unfrozen
       1  Id: 4f0.5e0 Suspend: 1 Teb: 00000000`7ef9d000 Unfrozen
       2  Id: 4f0.604 Suspend: 1 Teb: 00000000`7ef9a000 Unfrozen
       3  Id: 4f0.61c Suspend: 1 Teb: 00000000`7ef97000 Unfrozen
       4  Id: 4f0.600 Suspend: 1 Teb: 00000000`7ef94000 Unfrozen
       5  Id: 4f0.610 Suspend: 1 Teb: 00000000`7ef91000 Unfrozen
       6  Id: 4f0.608 Suspend: 1 Teb: 00000000`7ef8e000 Unfrozen
       7  Id: 4f0.60c Suspend: 1 Teb: 00000000`7ef8b000 Unfrozen
       8  Id: 4f0.614 Suspend: 1 Teb: 00000000`7ef88000 Unfrozen
       9  Id: 4f0.5fc Suspend: 1 Teb: 00000000`7ef85000 Unfrozen
      10  Id: 4f0.5f4 Suspend: 1 Teb: 00000000`7ef82000 Unfrozen
      11  Id: 4f0.5f8 Suspend: 1 Teb: 00000000`7ef7f000 Unfrozen
      12  Id: 4f0.f58 Suspend: 1 Teb: 00000000`7ef76000 Unfrozen
      13  Id: 4f0.28e8 Suspend: 1 Teb: 00000000`7efd5000 Unfrozen
    . 14  Id: 4f0.1b80 Suspend: 1 Teb: 00000000`7ef79000 Unfrozen
    # 15  Id: 4f0.1f80 Suspend: 1 Teb: 00000000`7efd8000 Unfrozen
    
    0:016> ~~[5fc]s
    wow64cpu!CpupSyscallStub+0x9:
    00000000`74412e09 c3              ret
    
    0:009> ~
       0  Id: 4f0.4f4 Suspend: 1 Teb: 00000000`7efdb000 Unfrozen
       1  Id: 4f0.5e0 Suspend: 1 Teb: 00000000`7ef9d000 Unfrozen
       2  Id: 4f0.604 Suspend: 1 Teb: 00000000`7ef9a000 Unfrozen
       3  Id: 4f0.61c Suspend: 1 Teb: 00000000`7ef97000 Unfrozen
       4  Id: 4f0.600 Suspend: 1 Teb: 00000000`7ef94000 Unfrozen
       5  Id: 4f0.610 Suspend: 1 Teb: 00000000`7ef91000 Unfrozen
       6  Id: 4f0.608 Suspend: 1 Teb: 00000000`7ef8e000 Unfrozen
       7  Id: 4f0.60c Suspend: 1 Teb: 00000000`7ef8b000 Unfrozen
       8  Id: 4f0.614 Suspend: 1 Teb: 00000000`7ef88000 Unfrozen
    .  9  Id: 4f0.5fc Suspend: 1 Teb: 00000000`7ef85000 Unfrozen
      10  Id: 4f0.5f4 Suspend: 1 Teb: 00000000`7ef82000 Unfrozen
      11  Id: 4f0.5f8 Suspend: 1 Teb: 00000000`7ef7f000 Unfrozen
      12  Id: 4f0.f58 Suspend: 1 Teb: 00000000`7ef76000 Unfrozen
      13  Id: 4f0.28e8 Suspend: 1 Teb: 00000000`7efd5000 Unfrozen
      14  Id: 4f0.1b80 Suspend: 1 Teb: 00000000`7ef79000 Unfrozen
      15  Id: 4f0.17cc Suspend: 1 Teb: 00000000`7efd8000 Unfrozen
    # 16  Id: 4f0.1538 Suspend: 1 Teb: 00000000`7ef7c000 Unfrozen

    第一个命令“~”运行时,当前线程是14号线程,请注意3号线程前面有一小点;运行第二个命令,将当前线程切换为9号线程;为了检验结果,再次运行“~”命令,此时注意到小点移到9号线程前,表明9号线程为当前线程。 

    线程遍历

    仍然是~命令。它除了能够作为线程列表命令外,还可用来对线程进行遍历,并执行指定命令。只需借助通配符“*”即可。如:

    • ~*k

    显示所有线程栈信息(此命令意指:对所有线程执行k指令)。下图中,当前进程共包含两个线程,显示了这两个线程各自的栈信息:

    0:009> ~*k
    
       0  Id: 4f0.4f4 Suspend: 1 Teb: 00000000`7efdb000 Unfrozen
    Child-SP          RetAddr           Call Site
    0019e648 74412bf1 wow64cpu!CpupSyscallStub+0x9
    0019e650 7448d07e wow64cpu!Thunk0ArgReloadState+0x23
    0019e710 7448c549 wow64!RunCpuSimulation+0xa
    0019e760 77084956 wow64!Wow64LdrpInitialize+0x429
    0019ecb0 77081a17 ntdll!LdrpInitializeProcess+0x17e4
    0019f1a0 7706c32e ntdll! ?? ::FNODOBFM::`string'+0x29220
    0019f210 00000000 ntdll!LdrInitializeThunk+0xe
    
       1  Id: 4f0.5e0 Suspend: 1 Teb: 00000000`7ef9d000 Unfrozen
    Child-SP          RetAddr           Call Site
    00abebc8 74412bf1 wow64cpu!CpupSyscallStub+0x9
    00abebd0 7448d07e wow64cpu!Thunk0ArgReloadState+0x23
    00abec90 7448c549 wow64!RunCpuSimulation+0xa
    00abece0 770be707 wow64!Wow64LdrpInitialize+0x429
    00abf230 7706c32e ntdll! ?? ::FNODOBFM::`string'+0x29364
    00abf2a0 00000000 ntdll!LdrInitializeThunk+0xe

    其他有用的遍历指令包括:

    • ~*r

    显示线程寄存器信息。

    • ~*e

    上面的e是execute(执行)的缩写,后可跟一个或多个Windbg命令。它遍历线程并对每个线程执行指定命令,如:

    • ~*e  k;r

    此命令意为:在所用线程环境中(~*),分别执行(e)栈指令(k)和寄存器指令(r)。

    线程时间

    在软件调试的时候,若发现某线程占用执行时间过多,就需要当心是否有问题。线程执行时间的多少,其实就是占用CPU执行工作的时间多少。某线程占用越多,此长彼消,则系统中其它线程占用CPU的时间就越少。

    线程的时间信息包括三个方面:自创建之初到现在的总消耗时间、用户模式执行时间、内核模式执行时间。需注意的是,消耗时间一定会远远大于用户时间+内核时间,多出来的是大量空闲时间(为Idle进程占用)。使用下面的命令查看线程时间:

    • .ttime
    • !runaway 7

    在!runaway命令中加入标志值7,将显示线程的全部三种时间值。

    这两个命令的区别之处是,.ttime只能显示当前线程的时间信息,!runaway能显示当前进程的所有线程时间。下图是这两个命令的使用情况:

    0:009> .ttime
    Created: Wed Aug  7 16:47:29.011 2013 (UTC + 8:00)
    Kernel:  0 days 0:00:00.031
    User:    0 days 0:00:00.046
    0:009> !runaway 7
     User Mode Time
      Thread       Time
       2:604       0 days 0:00:51.729
       4:600       0 days 0:00:47.159
       0:4f4       0 days 0:00:00.031
       3:61c       0 days 0:00:00.000
       1:5e0       0 days 0:00:00.000
     Kernel Mode Time
      Thread       Time
       4:600       0 days 0:13:45.073
       2:604       0 days 0:08:44.100
       3:61c       0 days 0:00:00.000
       1:5e0       0 days 0:00:00.000
       0:4f4       0 days 0:00:00.000
     Elapsed Time
      Thread       Time
       0:4f4       16 days 0:14:02.254
       4:600       16 days 0:14:02.207
       3:61c       16 days 0:14:02.207
       2:604       16 days 0:14:02.207
       1:5e0       16 days 0:14:02.207

                对上二图进行对比,能看出两个命令之间的功能差异。

    3.3 异常与事件

    在调试器语境中,事件是一个基本概念,Windbg是事件驱动的。Windows操作系统的调试子系统,是“事件”的发生源。调试器的所有操作,都是因事件而动,因事件被处理而中继。Windows定义了9类调试事件,异常是其中一类(ID为1)。所以异常和事件,这二者是前者包含于后者的关系。

    系统对各种异常和调试事件进行了分类,执行sx命令可以列出针对当前调试目标的异常或非异常事件的处理。下面是一个片段:

    0:009> sx
      ct - Create thread - ignore
      et - Exit thread - ignore
     cpr - Create process - ignore
     epr - Exit process - break
      ld - Load module - break
           (only break for livekd.exe)
      ud - Unload module - ignore
     ser - System error - ignore
     ibp - Initial breakpoint - break
     iml - Initial module load - ignore
     out - Debuggee output - output
    
      av - Access violation - break - not handled
    asrt - Assertion failure - break - not handled
     aph - Application hang - break - not handled
     bpe - Break instruction exception - break
    bpec - Break instruction exception continue - handled
      eh - C++ EH exception - second-chance break - not handled
     clr - CLR exception - second-chance break - not handled
    clrn - CLR notification exception - second-chance break - handled
     cce - Control-Break exception - break
      cc - Control-Break exception continue - handled
     cce - Control-C exception - break
      cc - Control-C exception continue - handled
      dm - Data misaligned - break - not handled
    dbce - Debugger command exception - ignore - handled
      gp - Guard page violation - break - not handled
      ii - Illegal instruction - second-chance break - not handled
      ip - In-page I/O error - break - not handled
      dz - Integer divide-by-zero - break - not handled
     iov - Integer overflow - break - not handled
      ch - Invalid handle - break
      hc - Invalid handle continue - not handled
     lsq - Invalid lock sequence - break - not handled
     isc - Invalid system call - break - not handled
      3c - Port disconnected - second-chance break - not handled
     svh - Service hang - break - not handled
     sse - Single step exception - break
    ssec - Single step exception continue - handled
     sbo - Security check failure or stack buffer overrun - break - not handled
     sov - Stack overflow - break - not handled
      vs - Verifier stop - break - not handled
    vcpp - Visual C++ exception - ignore - handled
     wkd - Wake debugger - break - not handled
     rto - Windows Runtime Originate Error - second-chance break - not handled
     rtt - Windows Runtime Transform Error - second-chance break - not handled
     wob - WOW64 breakpoint - break - handled
     wos - WOW64 single step exception - break - handled
    
       * - Other exception - second-chance break - not handled

    可以看到这几个调试事件,当发生进程退出(Exit Process)和初始化断点(Initial breakpoint)事件的时候,调试器应当被中断(Break)。模块加载(Load Modual)以及有调试输出(Debuggen Output)时,需要输出相关信息;其他的都被忽略掉,不做处理(Ignore)。     我们分析一下前两个事件。使用调试器调试记事本进程时,不管是用.attach挂载方式还是.create创建方式,在调试器正式侵入记事本进程前,都会有一个中断(Initial breakpoint异常);调试开始后运行一段时间,在外面将记事本关闭,又会发生一个中断(Exit Process异常)。

    可以通过Debug|Event Filters…打开事件设置对话框。这个对话框中列出了全部调试事件,用户可分别对它们进行设置。

    sx

                这个对话框列出了对于当前调试会话可用的全部调试事件。针对每个调试事件,可设置其属性。右列Execution和Continue两组单选键,分别表示事件的中断属性中继属性。右列Argument按钮可设置调试事件执行参数(上图中Load Module事件有一个Kernel32.dll参数,即当Kernel32.dll模块被加载时,调试器将被中断),Commands按钮可设置事件两轮机会发生时的执行命令。

    更细致的内容,本章无力铺陈,请读者参阅《Windows 高级调试》(Mario & Daniel 2009 机械工业出版社)第三章,及《软件调试》(张银奎 2008 电子工业出版社)第9、30章相关内容。 

    sxr:

          此命令将当前所有对调试事件的设置,恢复到调试器的默认设置。最后一个字母r表示Reset。

    sx{e|d|n|i}:

          这4个命令分别代表了图8-38中Execution组(中断属性)中的四个按钮,即Enable、Disable、Output、Ignore。Enable是开启中断,Disable是禁止事件中断(但对于异常,只禁止第一轮机会,第二轮机会到来时仍会中断到调试器),Output是禁止中断但会输出相关信息,Ignore表示完全忽略这个事件(对于异常,Output和Ignore两选项使得两轮机会都不会中断到调试器)。

    sx{e|d|n|i} -h:

          上述命令如果带上-h选项,就不是设置中断属性,而是设置中继属性了。对应了图8-38中的Continue组。其中sxe –h表示Handled,se{d|n|i} –h都表示Not Handled。

          下面继续介绍异常、事件相关的其他调试命令:

    .lastevent:

    显示最近发生的一个调试事件,往往是导致中断发生的那个。下图显示的是一个很典型的初始化断点引发的中断事件。

    .exr:

          此命令显示一个异常记录的详细内容,传入一个异常记录地址:

    • .exr 记录地址

    如果仅仅为了显示最近的一条异常记录,可以用-1代替异常记录地址:

    • .exr -1

    由于异常是事件的一种,所以使用.exr -1命令得到的异常,可能和使用.lastevent命令获取的事件(其实是异常),是同一个。但二者显示的信息各有侧重点。请对照图8-39看下面,同样的初始化断点异常,使用.exr命令时所显示的信息:

    0:009> .lastevent
    Last event: 4f0.1538: Break instruction exception - code 80000003 (first chance)
      debugger time: Fri Aug 23 16:58:02.995 2013 (UTC + 8:00)

    还有一个类似的命令:!cppexr,他分析并显示一个C++异常信息。

    .bugcheck:

    此命令不带参数。在内核环境下,显示当前bug check的详细信息;可用于活动调试或者crash dump调试环境中。用户环境不可用。见下图:

    0:009> .exr -1
    ExceptionAddress: 0000000077090530 (ntdll!DbgBreakPoint)
       ExceptionCode: 80000003 (Break instruction exception)
      ExceptionFlags: 00000000
    NumberParameters: 1
       Parameter[0]: 0000000000000000 

    !analyze:  

    此命令分析当前最近的异常事件(如果在进行dump分析,则是bug check),并显示分析结果。这个异常事件,就是上面.lastevent命令对应的事件。

    • -v:显示异常的详细信息,这个选项在调试错误的时候,最有用。
    • -f:f是force的缩写。强制将任何事件都当作异常来分析,即使仅仅是普通的断点事件。将因此多输出一些内容。
    • -hang:这个选项很有用,对于遇到死锁的情况,它会分析原因。在内核环境中,它分析内核锁和DPC栈;在用户环境中,它分析线程的调用栈。用户环境中,调试器只会对当前线程进行分析,所以一定要将线程环境切换到最可能引起问题的那个线程中去,才有帮助。这个参数非常有用,当真的遇到死锁时,它可以救命(另一个分析死锁的有效命令是!locks)。
    •  -show bug-check-代码 [参数]:在内核环境下,显示指定的bug check的详细信息。

    !error:

    此命令和VC里面内置的errlook工具类似(请有兴趣的读者使用作者编写的免费软件e-look,它比errlook功能更好且易于使用)。用来根据错误码,查看对应的可读错误信息。微软系统中常用的全局错误码有两套,一套是Win32错误码,通过函数GetLastError()获得的值;另一套是NTSTATUS值。!error命令对这二者都能支持。区别的方法,若错误码后面无参数1,则为win32错误码;否则就是NTSTATUS错误码。

                比如,获取错误码为2的Win32错误信息,可用:!error 2

                获取错误码为2的NTSTATUS错误信息,可用:!error 2 1 

    !gle:

    此命令是Get Last Error的缩写。它调用Win32接口函数GetLastError()取得线程的错误值,并打印分析结果。如果带有-all选项,则针对当前进程的所有线程(内核环境下为所有用户线程)执行GetLastError()操作;否则仅针对当前线程。

    gh/gn:这两个命令是g命令的扩展。

                gh是go with Exception handled的缩写,意思是:把异常标识为已处理而并继续执行程序;注意这里面的措辞,仅仅把异常“标识为”已处理,而并非真的被处理了。gh的作用在于,当遇到某个可以忽略的非致命异常时,将它先放过一边,而继续执行程序。

                而gn是go with Exception not handled的缩写,意思是,对异常不进行任何处理,而继续执行程序。这时候,程序自己的异常处理模块将有机会处理异常。

    3.4 局部变量

    有两个命令可以打印当前的局部变量列表:x 和dv。x命令前文已经讲过。dv是Display local Variable的缩写。下面是对一段简单的Win32控制台代码获取其局部变量的情况:

    local-variable

                上图无法体现dv比x命令在显示局部变量上的高明之处。dv命令有几个开关选项,介绍如下:

    •             /v:显示虚拟地址(virtual);
    •             /i:显示变量详细信息(information),包括局部变量、全局变量、形参、函数变量等。
    •             /t:显示变量类型(type),如int、char等等。
    •             /f:可指定进行分析的函数,需指定函数名。

             命令中选项/f wmain是指针对wmain函数(即_tmain)分析其局部变量。看第一个变量argc,“prv param”对应/i开关选项;“@ebp+0×08”对应/v开关选项;“int”对应/t开关选项。

    3.5 显示类型

                利用dt命令可以查看结构体的类型定义。命令dt是Display Type的缩写。当我们要查看一些内核结构体的定义时,dt命令是最直接有效的手段。

    4. 断点

    4.1 软件断点

    软件断点的本质是代码改写,即:将INT 3(代码为0xCC)指令替换到断点所在指令处(第一个字节),并保存被替换掉的代码(即一个字节内容)。等执行到断点处时,调试器将因断点而中断,并将被替换的一字节内容恢复到原内存中。其原理和代码补丁是一样的。

    源码或汇编模式下,最简单的断点设置方式,是定位到正确的代码处,并按下F9键。此外还有三种设置软件断点的指令,分别讲解如下:

    bp:

    命令bp是BreakPoint的缩写。其指令格式如下:

    bp[ID] [Options] [Address [Passes]] ["CommandString"]

    参数Address表示需设置断点的地址,缺省情况下使用当前指令指针(EIP)的地址。ID是断点号,一般不手动设置,由调试器内部编号。Passes是一个整数值,即第几次经过断点所在代码时,调试器才需要中断执行,默认为1,即每次都中断。CommandString用来设置一组命令,当断点发生的时候,就执行这一组命令,比如可以把它设置为“k”,这样断点处就会输出当前的调用栈。

    Options是一组可选开关项,有下面几种:

    /1:即阿拉伯数字1。这个选项表明这个被设置的断点只会在第一次有效,此后断点信息即被删除。

    /p:这个开关项后跟一个EPCOESS结构体指针,只能用在内核调试环境下。内核调试环境下,如果要把断点设置到用户程序地址(即用户空间地址),需要使用这个开关,因为用户地址是进程相关的。

    /t:这个开关项后跟一个ETHREAD结构体指针,只能用在内核调试环境下。此开关项与/p起到类似的作用,只不过前者定位到进程,后者更进一步定位到线程。

    /c与/C:c或者C代表CallStack(调用栈)。这两个开关项和调用栈深度有关,都后跟一个整数值。前者表示调用栈深度如果小于这个整数值,断点才会引发中断,后者表示调用栈深度如果大于这个整数值,断点才会引发中断。

    bu:

    此命令格式与bp类似,u代表了Unresolved。使用此命令设置的断点虽登记到调试器,但它具体对应到哪处代码或指令,尚未确定。

    比如某EXE程序使用动态加载的方式加载DLL(使用函数LoadLibrary()),那么当DLL尚未加载时,就可用bu指令设置DLL中的代码断点,等到DLL加载时,调试器再正式落实此断点。

    bm:

    此命令用来批量设置代码断点,它带有一个通配符字符串,凡是符合通配符格式的地址都将被设置断点,如:

    • bm /a ntdll!NtCreate*File

    则诸如NtCreateFileNtCreateMailslotFileNtCreateNamedPipeFile等函数都将被设置断点。

    4.2 硬件断点

    硬件断点的原理和软件断点完全不同,硬件断点是通过CPU的断点寄存器来实现的,亦即依靠硬件方式实现。由于CPU的调试寄存器数量是有限的,所以能设置的硬件断点数量也是有限的。设置硬件断点的命令是ba,a代表了Address。指令格式如下:

    ba[ID] Access Size [Options] [Address [Passes]] ["CommandString"]

    参数ID、Options、Passes及CommandString,含义与前文bp指令相同,此处不述。

    参数Address是内存地址,有别于前文的指令地址,内存地址既可以是指令地址,也可以是数据地址。缺省为当前指令寄存器地址(EIP)。参数Size表示地址长度,x86系统可选值为1、2、4,X64系统可选值为1、2、4、8。需要注意的是,Address地址必须对齐到Size,即Address值必须是Size的整数倍。参数Access是内存访问类型,有下面几种:

    e:作为指令执行;r:读,或者写;w:写;i:执行IN/OUT操作。     比如:

    • ba r4 @ebp-0×08

    地址@ebp-8一定是一个局部变量地址,所以当CPU对这个局部变量执行读写操作时,将引发硬件中断。

    4.3 其他操作

                其他的断点操作包括:显示断点列表、禁止或恢复断点、删除断点等。

    • bl:列出所有断点
    • bd:禁止断点,d代表Disable。如bd 1,禁止断点1。断点被禁止后将不起作用,但亦未删除。
    • be:恢复断点,e代表Enable。恢复被禁止的断点。如be 1恢复1号断点。
    • bc:清除断点,如:bc 1,清除断点1;bc *,清除全部断点。
    • br:序号管理,r代表ReNumber,即重新排序。如:br 2 0,将2号断点重设为0号断点。
  • 相关阅读:
    Activator.CreateInstance 反射实例化对象
    MVC Form提交
    Redis 下载
    List<T> 序列化与反序列化
    快速反射DataTable
    数据库特性
    javascript判断文件大小
    MD5
    HttpHelper
    cacheHelper
  • 原文地址:https://www.cnblogs.com/james1207/p/3358183.html
Copyright © 2011-2022 走看看