windbg调试和断点学习总结2

WinDbg 设置断点



在windbg中,断点设置的地址形式有好多种,可以是以下几种:
1.虚拟地址:即给出直接地址,如 12345678
2.函数偏移量:如DriverEntry+5c.
3.源代码+行数 :`[[Module!]Filename][:LineNumber]`
4.对C++可以对模块中的某个类的方法设置断点:
 
设置断点语法:
         
1:无条件设置断点:   bp  Address
  例如:
    kd> bp 0040108c
    kd> bp main+5c
    kd> bp `source.c:31` 
    kd> bp MyClass::MyMethod 
    kd> bp MyClass__MyMethod 
    kd> bp @@( MyClass::MyMethod )
 
2:设置有条件断点
  
kd> bp Address "j (Condition) 'OptionalCommands'; 'gc' "kd> bp Address ".if (Condition) 


{OptionalCommands} .else {gc}"
这两种设断点语法是等价的。
 
bp,bu,bm 的区别:
bu:Set Unresolved Breakpoint


bp 和bu的区别:
 1:bp是立即生效,且马上被转化为内存中的某个地址,如果调式模块被改变,bp指向的地址不会变,


而bu只是和symbol文件相关联,模块改变的时候,指向的symbol的offset或者plus是不变的
 2: bp指定的断点在模块unload之后从bl列表中删除,而bu的断点是永远存在的。
 3:在windbg的可视源码或者可视的反汇编代码中所设的断点都是bu模块的断点
bm:Set Symbol Breakpoint
 使用bm设置断点支持正则表达式的模式匹配,所以可以使用他来设置多个断点
 如果正则表达式被匹配的话,他的效果将和bu设置的是一样的
 例如:
    0:000> bm dbgtest!*main*
 1: 00413530 @!"dbgtest!wmain"
 2: 00411810 @!"dbgtest!__tmainCRTStartup"
 3: 004117f0 @!"dbgtest!wmainCRTStartup"
 使用bp和bm /a的风险:
  当wndbg在设置软件断点在代码段的时候,windbg将程序指令替换为断点指令,但是当断点设置在数据


段的时候,将会将程序数据替换为断点指令,从而导致数据被修改,因此在设置断点在数据段的时候,


推荐使用 ba(ba (Break on Access).)指令
 
控制断点的方法
使用下面一些方法来控制或显示断点:
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设置的断点会立即绑定到某个地址。
bp和bu断点有以下三个主要的不同点:
bp 断点的位置总是被转换成地址。如果某个模块改变了,并且bp设置的地址位置改变,断点还是在原来


的位置。而bu断点仍然和使用的符号值关联(一般是符号加上偏移),它会一直跟踪符号的地址,即使这


个地址已经改变。
如果bp的断点地址在某个已加载模块中找到,并且该模块之后被卸载,则该断点会从断点列表中移除。


而bu断点经过反复的卸载和加载仍然存在。
用bp设置的断点不会保存到WinDbg 工作空间(workspaces)中,而使用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方法上设置断点,可以使用两种不同语法:
用MASM表达式语法,可以用双冒号或者双下划线来指定一个方法。
0:000> bp MyClass::MyMethod 
0:000> bp MyClass__MyMethod 
用C++表达式语法,必须用双冒号指定方法。
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上,则不能使用内核调试器在用户空间中设置断点



断点伪寄存器(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字节长。(包含在0x770000F4,0x770000F5, 和


0x770000F6三个地址的字节)如果要在该指令上使用bp、bu或 ba设置断点,则必须将地址指定为


0x770000F4 。
如果使用ba命令在0x770000F5 地址设置了断点,处理器将在该位置设置断点。但是 该断点永远不会被


触发,因为处理器认为0x770000F4 才是这条指令的实际地址。
如果使用bp 或bu命令在 0x770000F5 设置断点,调试器在这个位置会写入断点。但是由于调试器使用如


下方法设置断点,它可能造成目标运行错误:
调试器保存0x770000F5 地址的内容,并用一条断点指令写入该地址。
如果用调试器显示这段内存的内容,并不会显示被写入的断点指令,而是显示那里原来"应该是"的内容


。即调试器显示原来的内存,或者断点设置之后该内存被修改的内容。
如果使用BC命令删除断点,调试器将断点位置的内存恢复成原始值。
当在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中。关于这种情况的更多信息,查看从内核调试器控制用户模式调


试器。
@!""
@!"" 语法用于在MASM求值器中进行转义,使得符号解析支持任意文本。必须以@!"开始并以引号(")结束


。如果不使用该语法,则在MASM表达式的符号名中不能使用空格、大于小于号(<, >)和其他特殊字符。


模板和重载是符号中需要这种引号的主要原因。也可以使用如下的@!"" 语法来设置bu 命令。
0:000> bu @!"ExecutableName!std::pair,std::allocator > >::operator="
这个例子中,ExecutableName 是一个可执行文件的名字。
这种转义语法在C++中比C中更加有用(例如重载的操作符),因为C函数名中不会存在空格(或特殊字符)。


但是,该语法在托管代码中也同样重要,因为.NET Framwork中非常多的使用重载。
条件断点
可以设置仅在特定条件下被触发的断点。关于这类断点的更多信息,查看设置条件断点。
 
断点命令:
1.ba。break on acess. The ba command sets a data breakpoint.
2.bc.(Breakpoint Clear)
The bc command permanently removes previously set breakpoints from the system.
3.bd (Breakpoint Disable)
The bd command disables, but does not delete, previously set breakpoints.
Syntax
bd Breakpoints 
4.be (Breakpoint Enable)
The be command restores one or more breakpoints that were previously disabled.
Syntax
be Breakpoints 
5.bl (Breakpoint List)
The bl command lists information about existing breakpoints.
Syntax
bl [/L] [Breakpoints] 
========

在windbg中用bm加断点遇到的故事  



想在没有源码和pdb的情况下在可执行文件中增加断点,因为知道会有某个系统调用
而且这个系统调用就是我想加断点的地方,所以想利用微软提供的系统pdb来实现。


先下载微软的pdb,都没有问题。


attach上以后用bm加断点


0:003> bm *SysAllocStringLen*
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\fix.tmp\XML 


Interface Tester\XML Interface Tester.exe - 
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\fix.tmp\XML 


Interface Tester\WizLogger.dll - 
*** ERROR: Module load completed but symbols could not be loaded for C:\Windows


\system32\odbcint.dll
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows


\system32\PGPhk.dll - 
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows


\SYSTEM32\SYSFER.DLL - 
*** ERROR: Module load completed but symbols could not be loaded for C:\Windows\WinSxS


\x86_microsoft.vc80.mfcloc_1fc8b3b9a1e18e3b_8.0.50727.6195_none_03ce2c72205943d3\MFC80ENU.D


LL
  1: 73255efa @!"MFC80!SysAllocStringLen"
*** ERROR: Module load completed but symbols could not be loaded for C:\Windows


\system32\PGPmapih.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\Windows


\system32\AMINIT32.DLL
0:003> bl
 1 e 73255efa     0001 (0001)  0:**** MFC80!FtpRemoveDirectoryA
但是奇怪的事情发生了
搜索时找到的断点似乎正确,红色部分。
但是用bl再次查看断点的时候,老母鸡变鸭子了。
函数名字根本不对,绿色的部分。


用lm查看发现大量的pdb没有加载,而这些pdb就包括我想加断点的那个函数所在的pdb -- oleaut32
0:003> lm
start    end        module name
00400000 00431000   XML_Interface_Tester   (deferred)             
10000000 10012000   WizLogger   (deferred)             
50180000 5020c000   ODBC32     (deferred)             
504b0000 504e8000   odbcint    (deferred)             
65f50000 65f5d000   PGPhk      (deferred)             
6ef90000 6efff000   SYSFER     (deferred)             
724c0000 72544000   COMCTL32   (deferred)             
72ef0000 72efe000   MFC80ENU   (deferred)             
73190000 7329f000   MFC80      (private pdb symbols)  c:\symcache\MFC80.i386.pdb


\922F97184FA4489BBA5DF664B9B3CB07e\MFC80.i386.pdb
732a0000 7333b000   MSVCR80    (deferred)             
740a0000 740b3000   dwmapi     (deferred)             
74170000 741b0000   uxtheme    (deferred)             
741b0000 7434e000   comctl32_741b0000   (deferred)             
75030000 75038000   Secur32    (deferred)             
75060000 7507a000   SSPICLI    (deferred)             
750d0000 750dc000   CRYPTBASE   (deferred)             
75120000 7512e000   PGPmapih   (deferred)             
75190000 751a5000   AMINIT32   (deferred)             
75220000 7522c000   MSASN1     (deferred)             
752b0000 752fa000   KERNELBASE   (deferred)             
75390000 754ae000   CRYPT32    (deferred)             
754b0000 754cf000   IMM32      (deferred)             
754d0000 75505000   WS2_32     (deferred)             
75510000 76159000   SHELL32    (deferred)             
76190000 76230000   ADVAPI32   (deferred)             
763c0000 7646c000   msvcrt     (deferred)             
76660000 767bc000   ole32      (deferred)             
767c0000 76894000   kernel32   (deferred)             
76c10000 76cd9000   USER32     (deferred)             
76ce0000 76d81000   RPCRT4     (deferred)             
76d90000 76e2d000   USP10      (deferred)             
76ec0000 76f8c000   MSCTF      (deferred)             
76f90000 7701f000   OLEAUT32   (deferred)             
77020000 7715c000   ntdll      (pdb symbols)          c:\symcache\ntdll.pdb


\EC61266D53004BD6826A130DADFC5C732\ntdll.pdb
77160000 7716a000   LPK        (deferred)             
77170000 771be000   GDI32      (deferred)             
771c0000 771c6000   NSI        (deferred)             
771d0000 771e9000   sechost    (deferred)             
771f0000 77247000   SHLWAPI    (deferred)             


用reload命令强制加载所有的断点 f=全部
0:003> .reload /f
Reloading current modules
.*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\fix.tmp\XML 


Interface Tester\XML Interface Tester.exe - 
.*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\fix.tmp\XML 


Interface Tester\WizLogger.dll - 
..*** ERROR: Module load completed but symbols could not be loaded for C:\Windows


\system32\odbcint.dll
.*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows


\system32\PGPhk.dll - 
.*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows


\SYSTEM32\SYSFER.DLL - 
..*** ERROR: Module load completed but symbols could not be loaded for C:\Windows\WinSxS


\x86_microsoft.vc80.mfcloc_1fc8b3b9a1e18e3b_8.0.50727.6195_none_03ce2c72205943d3\MFC80ENU.D


LL
.........*** ERROR: Module load completed but symbols could not be loaded for C:\Windows


\system32\PGPmapih.dll
.*** ERROR: Module load completed but symbols could not be loaded for C:\Windows


\system32\AMINIT32.DLL
.....................
0:003> lm
start    end        module name
00400000 00431000   XML_Interface_Tester   (export symbols)       C:\fix.tmp\XML Interface 


Tester\XML Interface Tester.exe
10000000 10012000   WizLogger   (export symbols)       C:\fix.tmp\XML Interface Tester


\WizLogger.dll
50180000 5020c000   ODBC32     (pdb symbols)          c:\symcache\ODBC32.pdb


\0B85774251EE4F9D9A5054C74CA31E9D2\ODBC32.pdb
504b0000 504e8000   odbcint    (no symbols)           
65f50000 65f5d000   PGPhk      (export symbols)       C:\Windows\system32\PGPhk.dll
6ef90000 6efff000   SYSFER     (export symbols)       C:\Windows\SYSTEM32\SYSFER.DLL
724c0000 72544000   COMCTL32   (pdb symbols)          c:\symcache\comctl32v582.pdb


\57AB323CAC1E4056AFD759B4858103362\comctl32v582.pdb
72ef0000 72efe000   MFC80ENU   (no symbols)           
73190000 7329f000   MFC80      (private pdb symbols)  c:\symcache\MFC80.i386.pdb


\922F97184FA4489BBA5DF664B9B3CB07e\MFC80.i386.pdb
732a0000 7333b000   MSVCR80    (private pdb symbols)  c:\symcache\msvcr80.i386.pdb


\54C9E2F351544D1CB39517DC4B299EA81\msvcr80.i386.pdb
740a0000 740b3000   dwmapi     (pdb symbols)          c:\symcache\dwmapi.pdb


\D8D91B3F339A4FDC960FC7121D146DF42\dwmapi.pdb
74170000 741b0000   uxtheme    (pdb symbols)          c:\symcache\UxTheme.pdb


\5BECAB35E7714835A6BF3DADD891BB3A2\UxTheme.pdb
741b0000 7434e000   comctl32_741b0000   (pdb symbols)          c:\symcache\comctl32.pdb


\720C79A887E44232A1DC9ADAD3FC92DA2\comctl32.pdb
75030000 75038000   Secur32    (pdb symbols)          c:\symcache\secur32.pdb


\3249C80E3DDE4A7AB930F649D75CBAE32\secur32.pdb
75060000 7507a000   SSPICLI    (pdb symbols)          c:\symcache\sspicli.pdb


\4523AF509FEC4ECC99E6EB36CB692B382\sspicli.pdb
750d0000 750dc000   CRYPTBASE   (pdb symbols)          c:\symcache\cryptbase.pdb


\E62FEAE559EE4CD995614215B01AC2102\cryptbase.pdb
75120000 7512e000   PGPmapih   (no symbols)           
75190000 751a5000   AMINIT32   (no symbols)           
75220000 7522c000   MSASN1     (pdb symbols)          c:\symcache\msasn1.pdb


\7CC4D3288D43465B9217D924C0B79BDB2\msasn1.pdb
752b0000 752fa000   KERNELBASE   (pdb symbols)          c:\symcache\kernelbase.pdb


\BD568BB3D59243EEB8D8E8D5FC7366182\kernelbase.pdb
75390000 754ae000   CRYPT32    (pdb symbols)          c:\symcache\crypt32.pdb


\977EBFA0F06D40889C37967F478D25B22\crypt32.pdb
754b0000 754cf000   IMM32      (pdb symbols)          c:\symcache\imm32.pdb


\105C90B10D924E02AE71D8ECCE77CDC62\imm32.pdb
754d0000 75505000   WS2_32     (pdb symbols)          c:\symcache\ws2_32.pdb


\05B47564705B4BB0BFD23EEDD39091922\ws2_32.pdb
75510000 76159000   SHELL32    (pdb symbols)          c:\symcache\shell32.pdb


\FEFB494EDBAF46739E3D8D995410E95A2\shell32.pdb
76190000 76230000   ADVAPI32   (pdb symbols)          c:\symcache\advapi32.pdb


\8215E3385BE64C70AD230B20F032B9402\advapi32.pdb
763c0000 7646c000   msvcrt     (pdb symbols)          c:\symcache\msvcrt.pdb


\F107690F432145FAA54510BF68F4DB732\msvcrt.pdb
76660000 767bc000   ole32      (pdb symbols)          c:\symcache\ole32.pdb


\71F977C67D9A486DACD23F0BE30AD8802\ole32.pdb
767c0000 76894000   kernel32   (pdb symbols)          c:\symcache\kernel32.pdb


\76F7267453AB403CB21544B4D990DD1D2\kernel32.pdb
76c10000 76cd9000   USER32     (pdb symbols)          c:\symcache\user32.pdb


\C1D1D6EB9354465389912A697CCB2D502\user32.pdb
76ce0000 76d81000   RPCRT4     (pdb symbols)          c:\symcache\rpcrt4.pdb


\20EE55884BAB426589CA1892F8B9003C2\rpcrt4.pdb
76d90000 76e2d000   USP10      (pdb symbols)          c:\symcache\usp10.pdb


\0F136332ED524622ACD511B27629058A1\usp10.pdb
76ec0000 76f8c000   MSCTF      (pdb symbols)          c:\symcache\msctf.pdb


\173DAEF86B2548DBA6134EB74C4D2F232\msctf.pdb
76f90000 7701f000   OLEAUT32   (pdb symbols)          c:\symcache\oleaut32.pdb


\9D6BC892D2F344E68615FFA93E2B18122\oleaut32.pdb
77020000 7715c000   ntdll      (pdb symbols)          c:\symcache\ntdll.pdb


\EC61266D53004BD6826A130DADFC5C732\ntdll.pdb
77160000 7716a000   LPK        (pdb symbols)          c:\symcache\lpk.pdb


\B99319FE4427418F9EB5432B9F6A13412\lpk.pdb
77170000 771be000   GDI32      (pdb symbols)          c:\symcache\gdi32.pdb


\AF16A95F2D39406DA1912EE039CBF0652\gdi32.pdb
771c0000 771c6000   NSI        (pdb symbols)          c:\symcache\nsi.pdb


\D15A81679FAE4A7392344B6FD26867942\nsi.pdb
771d0000 771e9000   sechost    (pdb symbols)          c:\symcache\sechost.pdb


\7AF14D02D41E4CD6942745FE0E6372B11\sechost.pdb
771f0000 77247000   SHLWAPI    (pdb symbols)          c:\symcache\shlwapi.pdb


\372BB4590B784DBDA605154B826C29EB2\shlwapi.pdb


再用lm查看pdb已经全部加载了
再用bm加断点,这次用了/a=全部断点
0:003> bm  /a *SysAllocStringLen*
breakpoint 1 redefined
  1: 73255efa @!"MFC80!SysAllocStringLen"
  2: 7327c348 @!"MFC80!_imp__SysAllocStringLen"
  3: 73256ba1 @!"MFC80!_imp_load__SysAllocStringLen"
  4: 742fc180 @!"comctl32_741b0000!_imp__SysAllocStringLen"
  5: 741f206e @!"comctl32_741b0000!_imp_load__SysAllocStringLen"
  6: 758d9318 @!"SHELL32!_imp__SysAllocStringLen"
  7: 758bfe20 @!"SHELL32!_imp_load__SysAllocStringLen"
  8: 767a60a4 @!"ole32!_imp__SysAllocStringLen"
  9: 7678bffa @!"ole32!_imp_load__SysAllocStringLen"
 10: 76f24d5a @!"MSCTF!SysAllocStringLen"
 11: 76fb060a @!"OLEAUT32!ErrSysAllocStringLen"
 12: 76f945d2 @!"OLEAUT32!SysAllocStringLen"
 13: 77242214 @!"SHLWAPI!_imp__SysAllocStringLen"
 14: 7723ee50 @!"SHLWAPI!_imp_load__SysAllocStringLen"
0:003> bl
 1 e 73255efa     0001 (0001)  0:**** MFC80!FtpRemoveDirectoryA
 2 e 7327c348     0001 (0001)  0:**** MFC80!_imp__SysAllocStringLen
 3 e 73256ba1     0001 (0001)  0:**** MFC80!_imp_load_SysAllocStringLen
 4 e 742fc180     0001 (0001)  0:**** comctl32_741b0000!_imp__SysAllocStringLen
 5 e 741f206e     0001 (0001)  0:**** comctl32_741b0000!_imp_load__SysAllocStringLen
 6 e 758d9318     0001 (0001)  0:**** SHELL32!_imp__SysAllocStringLen
 7 e 758bfe20     0001 (0001)  0:**** SHELL32!_imp_load__SysAllocStringLen
 8 e 767a60a4     0001 (0001)  0:**** ole32!_imp__SysAllocStringLen
 9 e 7678bffa     0001 (0001)  0:**** ole32!_imp_load__SysAllocStringLen
10 e 76f24d5a     0001 (0001)  0:**** MSCTF!SysAllocStringLen
11 e 76fb060a     0001 (0001)  0:**** OLEAUT32!ErrSysAllocStringLen
12 e 76f945d2     0001 (0001)  0:**** OLEAUT32!SysAllocStringLen
13 e 77242214     0001 (0001)  0:**** SHLWAPI!_imp__SysAllocStringLen
14 e 7723ee50     0001 (0001)  0:**** SHLWAPI!_imp_load__SysAllocStringLen
注意到这次会有很多,也包括第一的那个,但是也终于有我想要的了


然后我又查了查,发现断点是否加上和reload 那个操作无关只要bm的时候带/a就能加上断点了
而且断点加上以后再查lm,那些pdb也都加载了
所以不需要自己加载pdb,这是可以自动完成的。
========

Windbg调试命令详解

http://www.yiiyee.cn/Blog/windbg/


1. 概述


用户成功安装微软Windows调试工具集后,能够在安装目录下发现四个调试器程序,分别是:cdb.exe、


ntsd.exe、kd.exe和Windbg.exe。其中cdb.exe和ntsd.exe只能调试用户程序,Kd.exe主要用于内核调试


,有时候也用于用户态调试,上述三者的一个共同特点是,都只有控制台界面,以命令行形式工作。


Windbg.exe在用户态、内核态下都能够发挥调试功能,尤其重要的是,它不再是命令行格式而是采用了


可视化的用户界面。所以绝大部分情况下,我们在谈及Windows调试工具的时候,都直接指向Windbg,而


不大谈及前三者。


Windbg在用户态和内核态下,都支持两种调试模式,即“实时调试模式(Living)”和“事后调试模式


(Postmortem)”。所谓实时模式,是被调试的目标对象(Target)当前正在运行当中,调试器可以实


时分析、修改被调试目标的状态,如寄存器、内存、变量,调试exe可执行程序或双击双机实时调试都属


于这种模式;所谓事后模式,是被调试的目标对象(Target)已经结束了,现在只是事后对它保留的快


照进行分析,这个快照称为转储文件(Dump文件)。


Windbg另一个重大优点,还在于它支持源码级的调试,就像VC自带的调试器一样。


虽然提供了用户界面,但Windbg归根结底还是需要用户一个个地输入命令来指挥其行动。这就是他的


Command窗口。


每个调试命令都各有使用范围,有些命令只能用于内核调试,有些命令只能用于用户调试,有些命令只


能用于活动调试。但用户也不必记得这许多,一旦在某个环境下,使用了不被支持的命令,都会显


示“No export XXX found”的字样。就拿!process命令来说吧,它显示进程信息,但只能用于内核调试


中,如果在用户调试中使用,就是下面的情景:


0:001> !process
No export process found
1.1 寻求帮助


我们首先来看如何在使用过程中获取有用的帮助。Windbg中的调试命令,分为三种:基本命令,元命令


和扩展命令。基本命令和元命令是调试器自带的,元命令总是以“.”开头,而扩展命令是外部加入的,


总是以感叹号“!”开头。各种调试命令成千上万,我们首先要想办法把它们都列举出来,并取得使用方


法。


基本命令最少了,大概40个左右。列举所有的基本命令,使用如下命令:


?
元命令有一百多个,使用下面命令列举所有元命令:


.help  [/D]
如使用“/D”参数,命令列表将以DML格式显示。DML是一种类似于HTML的标识语言,下面会讲到。下图


以DML格式显示所以有字母a开头的元命令:


dotcommand


最后讲扩展命令。所谓扩展命令,顾名思义是可以“扩展”的。扩展命令从动态连接库中暴露出来,一


般以DLL文件名来代表一类扩展命令集,首先我们要搜索出系统中有多少个这样的DLL文件,使用下面命


令:


.chain  [/D]
此命令能够给出一个扩展命令集的链表。和.help命令一样,也可以使用/D参数以DML格式显示。如下所


示:


0:001> .chain
Extension DLL search Path:
    C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\WINXP;
Extension DLL chain:
    dbghelp: image 6.2.9200.20512, API 6.2.6, built Fri Sep 07 13:45:49 2012
        [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\dbghelp.dll]
    ext: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:11:33 2012
        [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\winext\ext.dll]
    exts: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:15:20 2012
        [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\WINXP\exts.dll]
    uext: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:15:09 2012
        [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\winext\uext.dll]
    ntsdexts: image 6.2.9200.16384, API 1.0.0, built Thu Jul 26 10:16:01 2012
        [path: C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\WINXP\ntsdexts.dll]
            最上面两行显示了扩展模块的搜索路径。接下来共列出了六个Windbg自带的扩展模块:


wdfkd、dbghellp、ext、exts、uext和ntsdexts。可以查看到这些扩展模块的版本信息、镜像文件路径


。如何列出某个扩展库中所包含的扩展命令列表呢?绝大部分扩展模块可使用如下命令:


!模块名.help
此外,扩展命令模块是可“扩展”的。如果读者从第三方处获取,或自己编写了一个扩展调试模块,则


可通过.load/.unload命令动态加载/卸载。


1.2 DML语言


DML(Debugger Markup Language调试器标记语言)像HTML一样,可从一处链接到另一处。不同处在于,


DML的链接内容需要用户点击后才会动态生成。一般用来以精简方式显示大量信息和扩展功能。


DML有很多实用的功能,如果使用者一时不知道从何下手,最好就是输入.dml_start命令,开始DML之旅





dml


DML链接以更加可视化的方式,引导用户查看调试信息,使得调试工具的使用相比纯指令格式而言,更为


友好。DML如同是对原指令的一层轻微的包装一样,让生硬的指令更加温和了。所以建议读者总是把DML


默认开启。


.prefer_dml  1
开始DML。


.prefer_dml  0
关闭DML。


一旦开启DML后,像k等支持DML的调试命令,将默认以DML格式显示输出内容。


DML还能以一种很特殊的方式为函数画流程图。它主要的原理是使用反汇编,类似于uf,但在逻辑分支处


,它会停止反汇编并显示分支让用户选择。另外,它能显示汇编代码对应的行号,这一点真的非常好。


如果稍加精进,他就能画出非常漂亮的流程图了。他的一个特点是反汇编的顺序是从后往前推。只要细


想一想,就会觉得很有道理。如果正推的话,分支太多;而反推则分支顺序在用户的参与下(即用户进


行分支选择),是固定了的。


.dml_flow  FindAllInfFilesA  FindAllInfFilesA+30
这是一个非常简单、实用的例子,对Kernel32库中的FindAllInfFilesA接口函数进行反汇编,效果类似


uf命令却更强大。


1.3 基本信息


本节讲解和调试器软件本身相关的命令,比如:查看软件版本、启动参数,以及最基本的软件设置命令


。首先看版本命令:


version
此命令显示操作系统的版本信息以及Windbg本身的版本信息,Windbg的配置和操作系统密切相关,所以


将操作系统的版本信息一并显示出来是很有必要的。在内核环境与用户环境下运行此命令,会得到不同


的输出。下图为内核环境下输出结果:


0:001> version
Windows 7 Version 7601 (Service Pack 1) MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
kernel32.dll version: 6.1.7601.18015 (win7sp1_gdr.121129-1432)
Machine Name:
Debug session time: Thu Aug 22 10:11:04.000 2013 (UTC + 8:00)
System Uptime: 14 days 17:26:44.613
Process Uptime: 14 days 17:14:25.000
  Kernel time: 0 days 0:09:02.000
  User time: 0 days 0:42:36.000
Full memory user mini dump: C:\Users\mozhang\AppData\Local\Temp\dwm.DMP


Microsoft (R) Windows Debugger Version 6.2.9200.16384 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
除了Windbg版本信息,上面的输出中还包括目标系统信息。如果纯粹是为了查看目标系统的版本信息,


可使用下面的vertarget命令:


vertarget
Windbg支持对多个调试系统中的多个调试目标同时进行调试。上面我们通过version或vertarget命令列


出了当前调试系统的版本信息,还可以查看当前目标系统的状态:


||
如Windbg中同时打开多个调试对象,“||”命令将列出对象列表。笔者为了演示此种情况,先在Windbg


中开启Local Debug环境,然后两次调用.opendump命令打开两个DUMP文件,这样就同时拥有了三个被调


试的目标对象。下图显示了这个情况:


multi-target


            上图中的活动对象是0号对象(可从数字0前面的小数点看出)。调试器需要在多个调试目


标之间进行切换的话,使用“s”参数。如要切换到1号目标可使用下面的命令:


||  1  s
最后一个命令用来查看系统时间。这包括系统当前时间,以及系统正常运行持续时间;用户模式下还会


显示当前进程的持续时间。命令格式如下:


.time
0:001> .time
Debug session time: Thu Aug 22 10:11:04.000 2013 (UTC + 8:00)
System Uptime: 14 days 17:26:44.613 // 系统运行时间
Process Uptime: 14 days 17:14:25.000// 当前进程运行时间
  Kernel time: 0 days 0:09:02.000
  User time: 0 days 0:42:36.000
1.4 基本设置


首先看一个清屏命令:


.cls
当命令窗口中的内容太乱的时候,这个命令帮你快刀斩乱麻。


下面看一个设置默认数字进制的命令:


n  [8|10|16]
软件默认是16进制,但有时候我们也需要把默认进制改成八进制或十进制的。下面尝试在八进制下面求


数字11的值,如下:


0:001> n 8
base is 8
0:001> ? 11
Evaluate expression: 9 = 00000000`00000009
最后再来说一个处理器模式指令。关于处理器模式很值得一说,很重要。处理器模式的设置,反映了


Windbg软件的强大。举例来说,主机为32位的系统,却可以同时调试X86、IA64、X64的目标系统——前


提是先将主机的处理器模式设置正确了。可用处理器模式值有:x86、adm64、ia64、ebc。


.effmach  x86
命令.effmach表示Effective Machine Type,即有效的机器类型。此命令将当前的处理器模式设置为x86


模式。


1.5 格式化显示


将一个整数以各种格式显示,包括:16进制、10进制、8进制、二进制、字符串、日期、浮点数等。是不


是很方便?这个命令是:


.formats  整数
下面以0x123abc为例:


0:001> .formats 0x123abc
Evaluate expression:
  Hex:     00000000`00123abc
  Decimal: 1194684
  Octal:   0000000000000004435274
  Binary:  00000000 00000000 00000000 00000000 00000000 00010010 00111010 10111100
  Chars:   ......:.
  Time:    Thu Jan 15 03:51:24 1970
  Float:   low 1.67411e-039 high 0
  Double:  5.90252e-318
1.6 开始调试


现在领大家进入调试阶段。首先看看如何让调试器附载到一个已运行的进程中去?比如IE软件在运行过


程中发生了崩溃,打开Windbg后如何调试呢?第一步就是把Windbg附载到发生崩溃的IE进程上。使用如


下命令格式:


.attach  PID
或者通过Windbg的启动参数进行挂载:


Windbg –p PID
上面两个命令中,PID指定了进程ID。如果觉得指定PID不方便,也可以通过进程名进行挂载:


Windbg -pn 进程名
比如挂载到记事本就可以这样:


windbg –pn notepad.exe


上面的命令是把调试器挂载到已经存在的进程上,另外调试器可以创建新进程并对它进行调试,这二者


使用了不同的侵入方法。使用下面的命令:


.create 程序启动命令行
或者Windbg启动参数


Windbg 程序启动命令行
比如创建并调试一个记事本子进程,可用.create notepad或者windbg notepad命令。也可以打开Windbg


后,在File菜单中选择“Open Executable…”启动Notepad子进程,但这个选项只能被执行一次(之后


会灰掉)。


使用上述命令可将调试器连续附载到多个进程,也就是说,能够同时调试多个进程,这一点看上去很神


奇哦。下例中,调试器先创建子程序IOCTL.exe,然后又调用.attach命令附加到记事本进程,使用命令


“|”列出所有被调试进程。


读者可能会奇怪,多个进程同时调试怎么兼顾呢?只要有一个切换指令就可以了,这样就能够切换到任


意的进程(令其为当前进程)并对之进行调试。比如上图显示1号进程为当前进程(注意1前面的小点)


,如何将当前进程切换到0号进程呢?可以使用进程列表命令“|”轻松切换,比如:


|  0  s
此命令把当前调试环境切换到0号IOCTL.exe进程。另外需注意的是,多个用户进程调试目标都处于同一


个调试会话中,使用“||”命令会看到,它们属于同一个 “Live user mode”调试会话。


下面看dump文件调试,使用命令:


.opendump  文件名
此命令打开一个dump文件,并建立一个DUMP调试会话。如何手动创建一个dump文件呢?比如在调试过程


中,遇到了无法解决的问题,希望获得异地帮助,则把当前调试环境保存到Dump文件中发送给能提供帮


助的人,不失为一种好办法。


.dump  文件名
Dump文件一般以.dmp为后缀,系统生成的Dump文件都默认以.dmp为后缀的,但使用.dump命令时,使用者


可以设置任意后缀,甚至无后缀。下例中,首先为当前进程生成一个dump文件保存到a.txt中(即后缀名


为.txt),然后将之打开并分析:


0:001> .dump a.txt
Creating a.txt - mini user dump
Dump successfully written
0:001> .opendump a.txt
Loading Dump File [C:\Program Files (x86)\Windows Kits\8.0\Debuggers\a.txt]
User Mini Dump File: Only registers, stack and portions of memory are available


Opened 'a.txt' // 打开成功
上文讲到进程挂载命令,当需要解除挂载时,可使用解挂命令,如下:


.detach
此命令结束当前调试会话, Windbg解除和被调试进程之间的调试关系(不管是通过挂载,还是通过创建


方式建立的调试关系),解挂后,被调试进程能够独立运行;如果当前的调试会话是一个Dump文件,此


命令直接结束对dump文件的调试,即结束调试会话。


如果需要彻底结束调试,下面的命令更有用:


q | qq | qd
q是Quit的缩写。结束当前调试会话,并返回到最简单的工作空间,甚至把命令行界面也关闭掉。q和qq


两个命令将结束(close)被调试的进程,qd不会关闭调试进程,而是进行解挂操作。


双机调试的时候,如果你感觉调试已经陷入僵局,比如目标机Hang住了动都动不了,此时通过主机让目


标机强制宕机或重启,不失为一个好主意。


.crash
.reboot
crash命令能引发一个系统蓝屏,并生成dump文件;而.reboot使系统重启,不产生dump文件。


2. 符号与源码


符号与源码是调试过程中的重要因素,它们使得枯燥生硬的调试内容更容易地调试人员读懂。在可能的


情况下,应该尽量地为模块加载符号和源码。大部分情况下源码难以得到,但符号却总能以符号文件的


形式易于得到。


什么是符号文件呢?编译器和链接器在创建二进制镜像文件(诸如exe、dll、sys)时,伴生的后缀名为


.dbg、.sym或.pdb的包含镜像文件编译、链接过程中生成的符号信息的文件称为符号文件。具体来说,


符号信息包括如下内容:


全局变量(类型、名称、地址);
局部变量(类型、名称、地址);
函数(名称、原型、地址);
变量、结构体类型定义;
源文件路径以及每个符号对应于源文件中的行号,这是进行源码级别调试的基础。


有这么多的信息包含在符号文件中,使得符号文件通常要比二进制文件(PE格式文件)本身要大很多。


调试过程中,符号之重要性不言而喻。只有正确设置了符号路径,使得调试器能够将调试目标、符号文


件以及源码文件一一对应起来,才能够最好地发挥调试器的强大功用。


symbols


符号信息隶属于指定的模块,所以只有调试器需要用到某个模块时,他的符号信息才有被加载和分析的


必要。所以我们在讲符号内容之前,先讲和模块相关的命令。


2.1 模块列表


每个可执行程序都是由若干个模块构成,有些模块静态加载,有些模块以动态方式进行加载。所以对于


有些模块,可能在A时刻运行时被加载,而在B时刻运行时,自始至终都未被加载。调试过程中,调试器


根据模块的加载情况加载符号。有几个命令可以用来列举模块列表,分别是:lm、!dlls、.reload /l、


!imgreloc。下面分别来看。


lm [选项] [a Address] [m Pattern | M Pattern]
lm是list loaded modules的缩写,他还有一个DML版本:


lmD [选项] [a Address] [m Pattern | M Pattern]
使用/v选项能列出模块的详细信息,包括:模块名、模块地址、模块大小、镜像名、时间戳、以及对应


的符号文件信息(包括类型、路径、类型、编译器、符号加载状态)。


如使用参数a,后面跟地址(address),则只有指定地址所在的模块能够被列出;


如使用参数m,后面跟一个表示模块名的字符串通配符,如lm  m  *o*将显示所有名称中包含字母o的模


块,下图所示:


||0:0:001> lm m *o*
start             end                 module name
f3380000 f3512000   dwmcore    (private pdb symbols) 
f92d0000 f9327000   d3d10_1core   (deferred)             
fa890000 fa9f1000   WindowsCodecs   (deferred)             
faa50000 fac44000   comctl32   (deferred)             
fbf70000 fbf7c000   version    (deferred)             
fce20000 fce2f000   profapi    (deferred)             
fd970000 fdb73000   ole32      (deferred)             
fee60000 fee7f000   sechost    (deferred)
下面介绍另一个命令:


!dlls [选项] [LoaderEntryAddress]
            首先看他的可选参数:


            -i/-l/-m:排序方式,分别按照初始化顺序、加载顺序、内存起始地址顺序排列。


-a:列出镜像文件PE结构的文件头、Section头等详细信息,是分析PE结构的好帮手(更好的帮手是利用


自如PEView或Stud_PE等UI工具)。


            -c:指定函数所在的模块。这个选项非常实用,比如我想知道NtCreateFile函数是哪个模


块暴露出来的接口,如下:


0:000> !dlls -c ntcreatefile
Dump dll containing 0x7c92d0ae:
0x00251f48: C:\WINDOWS\system32\ntdll.dll
      Base   0x7c920000  EntryPoint  0x7c932c48  Size        0x00096000
      Flags  0x00085004  LoadCount   0x0000ffff  TlsIndex    0x00000000
             LDRP_IMAGE_DLL
             LDRP_LOAD_IN_PROGRESS
             LDRP_ENTRY_PROCESSED
             LDRP_PROCESS_ATTACH_CALLED
除了lm和!dlls外,下文将讲到的.reload命令在加入 /l选项后,也能列举模块,其命令格式如下:


.reload /l
最后再来看一个!imgreloc命令,它也能够列出模块列表并显示各模块地址。但其主要作用尚不在此,它


用来判断各个模块是否处于preferred地址范围。所谓Preferred地址是这么一回事:二进制文件在编译


的时候,编译器都会为其设置一个理想地址(Preferred Address),这样二进制文件被加载时,系统会


尽可能将他映射到这个理想地址。当然,所谓“理想”往往是会受到“现实”的挑战的,当存在地址竞


争的时候,需要适当调整二进制文件的加载地址,选择另一个合适的地方加载之。!imgreloc命令就是用


来查看这种情况的,命令如下:


!imgreloc [模块地址]
命令!imgReloc是Image Relocate的缩写,字面已能够反映其含义:镜像文件重定位信息。下面是一个例


子。


 imgreloc


      上例中,大部分系统模块(上图下部方框所示)其地址由于事先经过统筹分配,所以一般都能被


加载到preferred地址处。只有少数模块(如最上面的Normaliz模块)由于地址冲突而受到了调整。


2.2 模块信息


      上一节我们了解了如何枚举模块列表,这一节我们研究针对单个模块,如何获取详细信息。有多


个命令可以查看指定模块的详细模块信息,这包括:lm、!dh、lmi等,下面来一一介绍。


首先看lm,这个命令上面我们已经介绍过,现在利用它来获取指定模块信息。其命令格式如下:


lm v a 模块地址
这里使用了v选项,以显示详细(verbose)信息;并使用a参数以指定模块地址。通过此命令显示的信息


,和我们在explorer资源管理器中通过鼠标右键查看一个文件的属性所看到的信息差不多。请看下面的


清单:


0:000> lm v a 00400000
start    end        module name
00400000 00752000   UsbKitApp C (private pdb symbols)  C:\Trunk\CY001\UsbKitApp\Debug


\UsbKitApp.pdb
    Loaded symbol image file: UsbKitApp.exe
    Image path: UsbKitApp.exe
    Image name: UsbKitApp.exe
    Timestamp:        Tue Mar 16 22:07:02 2010 (4B9F9086)
    CheckSum:         00000000
    ImageSize:        00352000
    File version:     1.0.0.1
    Product version:  1.0.0.1
    File flags:       1 (Mask 3F) Debug
    File OS:          4 Unknown Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0804.03a8
    CompanyName:      TODO: <公司名>
    ProductName:      TODO: <产品名>
    InternalName:     UsbKitApp.exe
    OriginalFilename: UsbKitApp.exe
    ProductVersion:   1.0.0.1
    FileVersion:      1.0.0.1
    FileDescription:  TODO: <文件说明>
    LegalCopyright:   TODO: (C) <公司名>。保留所有权利。
    下面看!lmi命令,此命令通过指定模块地址查找模块并获取其信息,其命令格式如下


!lmi 模块地址
此命令侧重获取对调试器有用的信息,请看下面的列表:


0:000> !lmi 0x400000
Loaded Module Info: [0x400000]
         Module: UsbKitApp
   Base Address: 00400000
     Image Name: UsbKitApp.exe
   Machine Type: 332 (I386)
     Time Stamp: 4b9f9086 Tue Mar 16 22:07:02 2010
           Size: 352000
       CheckSum: 0
Characteristics: 103
Debug Data Dirs: Type  Size     VA  Pointer
CODEVIEW  - GUID: {5DB12DF1-71CA-43F7-AD85-0977FB3629A4}
               Age: 3, Pdb: C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb
     Image Type: FILE     - Image read successfully from debugger.
                 UsbKitApp.exe
    Symbol Type: PDB      - Symbols loaded successfully from image header.
                 C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb
       Compiler: Resource - front end [0.0 bld 0] - back end [9.0 bld 21022]
    Load Report: private symbols & lines, not source indexed
                 C:\Trunk\CY001\UsbKitApp\Debug\UsbKitApp.pdb
 如果还要查看更详细、丰富的模块信息,可以使用!dh命令,命令格式如下:


!dh [标志] 模块地址
dh是display header的缩写,直译就是“显示文件头”的意思,它能够显示非常详细的PE头信息。下图


截取了输出信息中的开头部分,其它详细内容,需要读者熟悉微软的PE结构才能看懂:


!dh


模块相关的知识点讲完了,下面讲符号有关命令。和符号相关的知识点包括:符号路径、符号服务器、


符号缓存、符号加载以及符号的使用等。


2.3 符号路径


什么是符号路径呢?就是调试器寻找符号文件的方向,它可以是本地文件夹路径、可访问的UNC路径、或


者是符号服务器路径。什么是符号服务器呢?如果调试过程中,需要涉及到成千上万个符号文件,以及


同一个符号文件存在不同平台下的不同符号文件版本的时候,那么一一手动设置符号路径肯定是不现实


的,于是引入符号服务器的概念。符号服务器有一套命名规则,使得调试软件能够正确找到需要的符号


文件。一般来说,符号服务器比较大,都是共用的,放在远程主机上。为了降低网络访问的成本,又引


入了符号缓存的概念,即将从服务器上下载到的符号文件,保存在本地缓存中,以后调试器需要符号文


件的时候,先从缓存中寻找,找不到的时候再到服务器上下载。下面分几部分一一来看。


设置符号路径:


设置符号路径的语法如下:


.sympath  [+]  [路径]
如果不加入任何参数执行.sympath命令,将显示当前的路径设置:


.sympath
如要覆盖原来的路径设置,使用新路径即可:


.sympath  <新路径>
要在原有路径的基础上添加一个新路径,可使用:


.sympath+  <新增路径>
要注意的是,使用.sympath改变或新增符号路径后,符号文件并不会自动更新,应再执行.reload命令以


更新之。


这里要谈一谈延迟加载的知识点。延迟加载使得模块的符号表,只在第一次真正使用的时候才被加载。


这加快了程序启动,不用在一开始耗费大量时间加载全部的符号文件。


使用.symopt +4和.symopt -4来开启或关闭延迟加载设置。


在已经启动了延迟加载的情况下,如想临时改变策略,立刻将指定模块的符号加载到调试器中,可以使


用ld或者.reload /f命令。


符号服务器与符号缓存:


设置符号服务器的基本语法是:


SRV*[符号缓存]*服务器地址
语法由SRV引导,符号缓存和服务器地址的前面各有一个星号引导。符号缓存一般也叫做下游符号库。如


某公司有一台专门的符号服务器,地址为\\symsrv\\symbols,则他们公司的所有开发人员都应该在他们


的调试器中使用类似下面的命令:


.sympath+ srv*c:\symbols*\\symsrv\symbols
此外,我们总是应该把微软的公用符号库加入到我们的符号路径中:


.sympath+ srv*<缓存地址>*http://msdl.microsoft.com/download/symbols
这是一台微软对外公开的服务器,使用http地址访问,不是所有人都能牢记这个网址,所以最好的办法


就是使用.symfix命令,语法如下:


.symfix [+] [符号缓存地址]
这个命令等价于上面的.sympath命令,而不用输入长长的http地址。


0:000> .symfix c:\windows\symbols


0:000> .sympath
Symbol search path is: SRV*c:\windows\symbols*http://msdl.microsoft.com/download/symbols
符号选项:


命令格式如下:


显示当前设置:.symopt
增加选项:.symopt+  Flags
删除选项:.symopt-  Flags
第一个命令没有任何参数,显示当前设置。后面两个,第二个命令含有“+”代表添加一个选项,第三个


命令含有“-”代表去除一个选项。


001> .symopt
Symbol options are 0x30337:
  0x00000001 - SYMOPT_CASE_INSENSITIVE
  0x00000002 - SYMOPT_UNDNAME
  0x00000004 - SYMOPT_DEFERRED_LOADS
  0x00000010 - SYMOPT_LOAD_LINES
  0x00000020 - SYMOPT_OMAP_FIND_NEAREST
  0x00000100 - SYMOPT_NO_UNQUALIFIED_LOADS
  0x00000200 - SYMOPT_FAIL_CRITICAL_ERRORS
  0x00010000 - SYMOPT_AUTO_PUBLICS
  0x00020000 - SYMOPT_NO_IMAGE_SEARCH
可用的符号选项请见下表:





可读名称


描述


0×1


SYMOPT_CASE_INSENSITIVE


符号名称不区分大小写


0×2


SYMOPT_UNDNAME


符号名称未修饰


0×4


SYMOPT_DEFERRED_LOADS


延迟加载


0×8


SYMOPT_NO_CPP


关闭C++转换,C++中的::符号将以__显示


0×10


SYMOPT_LOAD_LINES


从源文件中加载行号


0×20


SYMOPT_OMAP_FIND_


NEAREST


如果由于编译器优化导致找不到对应的符号,就以最近的一个符号代替之


0×40


SYMOPT_LOAD_ANYTHING


使得符号匹配的时候,匹配原则较松散,不那么严格。


0×80


SYMOPT_IGNORE_CVREC


忽略镜像文件头中的CV记录


0×100


SYMOPT_NO_UNQUALIFIED_


LOADS


只在已加载模块中搜索符号,如果搜索符号失败,不会自动加载新模块。


0×200


SYMOPT_FAIL_CRITICAL_


ERRORS


不显示文件访问错误对话框。


0×400


SYMOPT_EXACT_SYMBOLS


进行最严格的符号文件检查,只要有微小的差异,符号文件都不会被加载。


0×800


SYMOPT_ALLOW_ABSOLUTE_


SYMBOLS


允许从内存的一个绝对地址处读取符号信息。


0×1000


SYMOPT_IGNORE_NT_


SYMPATH


忽视在环境变量中设置的符号路径,也忽视被调试进程的执行路径。也就是说,当搜索符号文件的时候


,不会从这些路径中搜索。


0×2000


SYMOPT_INCLUDE_32BIT_MODULES


让运行在安腾系统上的调试器,也枚举32位模块。


0×4000


SYMOPT_PUBLICS_ONLY


仅搜索符号文件的公共(PUBLIC)符号表,忽略私有符号表。


0×8000


SYMOPT_NO_PUBLICS


不搜索符号文件的公共(PUBLIC)符号表


0×10000


SYMOPT_AUTO_PUBLICS


先搜索pdb文件的私有符号表,如果在其中找到对应的符号,就不再搜索公共(PUBLIC)符号表,这可以


加快搜索速度。


0×20000


SYMOPT_NO_IMAGE_SEARCH


不搜索镜像拷贝


0×40000


SYMOPT_SECURE


安全模式,让调试器尽量不影响到主机。


0×80000


SYMOPT_NO_PROMPTS


不显示符号代理服务器的认证对话框,将导致某些时候无法访问符号服务器


0×80000000


SYMOPT_DEBUG


显示符号搜索的详细过程和信息


表8-1 符号选项


2.4 符号加载


本节分下面几个子题目分别讲解。


立刻加载:


其命令格式如下:


ld 模块名 [/f 符号文件名]
加载指定模块的符号。调试器默认采用延迟模式加载符号,也就是直到符号被使用的时候,才将符号文


件加载到调试器中并进行解析。ld使得延迟模式被打破,让指定模块的符号文件立刻加载到调试器中。


此指令可为模块的符号文件设置自定义的匹配名称,比如:


ld  123  /f  abc
这样一来,abc.pdb将成为123.exe的符号文件。正常情况下,这是不可能的,只能是abc.pdb对应


abc.exe。


重新加载:


如果对自己正在使用的符号文件感到疑惑,比如源代码和行号明显不匹配,最好的做法就是重新加载一


下符号文件。此命令语法如下:


.reload /f /v [模块名]
.reload命令的作用是删除指定或所有已加载的符号文件,默认情况下,调试器不会立刻根据符号路径重


新搜索并加载新的符号文件,而是推迟到调试器下一次使用到此文件时。


使用/f参数(force),将迫使调试器立刻搜索并重新加载新的符号文件。


其它参数解释如下:


/v:将搜索过程中的详细信息都显示出来。
/i:不检查pdb文件的版本信息;
/l:只显示模块信息,内核模式下,和“lm n t”命令类似,但显示内容比后者更多,因为包含了用户


模块信息;
/n:仅重载内核符号,不重载用户符号;
/o:强制覆盖符号库中的符号文件,即使版本相同;
/d:用户层模式下使用Windbg时的默认选项,重载调试器模块列表中的所有模块;
/s:内核模式下使用Windbg时的默认选项,重载系统模块列表中的所有模块,另外,如果调试器在用户


模式下运行,要加载内核模块,也必须使用/s选项,否则调试器将只会在调试器模块列表中搜索而导致


找不到内核模块;
/u:卸载指定模块。如发现当前符号版本不对,使用/u开关先卸载之再重新加载。
符号验证:


上面讲到.reload的时候,我们说过,符号文件会出现不匹配的情况。这是很有可能的,程序员在后期测


试的时候可能会将工程多次编译,为了维护多个版本而使得自己也被搞混。可以使用下面的命令验证一


个模块的符号文件:


!chksym <模块名> [符号名]
加载选项:!sym


有两类符号加载选项。第一类是Noisy/Quiet,Noisy选项将打印符号加载的详细信息,Quiet选项则忽略


这些信息。第二类是Prompts/Prompts off,即是否允许执行提示(Prompts)对话框。


一般都是在调用.reload 命令之前,执行加载选项命令,以见立竿见影之效。


所谓Noisy是吵闹的意思,调试器在搜索、加载符号的时候,会显示更多与搜索有关的信息。而安静模式


下,则不会显示这些信息。不管吵闹与否,都不会影响到最终的搜索、加载结果。


当从网络上下载符号文件的时候,可能会碰到网络服务器要求客户进行安全认证的情况,如果开启


Prompts选项,则弹出认证对话框,让用户输入认证信息;否则,不弹出对话框,并且不会下载符号文件





不加任何参数的情况下,显示当前加载选项设置,下面的清单表明当前的设置为Quite及Prompts模式:


lkd> !sym
!sym <noisy/quiet - prompts/prompts off> - quiet mode - symbol prompts on
2.5 符号搜索


符号搜索包括全局搜索和就近搜索两种。


全局搜索:


命令“x”被用来进行符号的全局搜索,你可以把它直接就理解为search。格式如下:


x  [参数]  [模块!符号]
如果什么参数都没有的话,它将列出当前调试环境下的所有局部变量,前提是要在有局部变量存在的情


况下,显示局部变量的另一个命令是dv,后文也会讲到。


x  kernel32!a*,
上面命令搜索并打印出kernel32模块中所有a开头的符号。x命令支持DML,使用/D选项即以DML格式显示





x-command


如果你不知道ntcreatefile这个函数是在哪个模块中定义的,可以试着使用下面的命令:


x  *!*NtCreateFile* (注:亦请参照!dlls –c命令)
同名函数在多个系统模块中并定义,这可能出乎你的意料,但却给你带来真正的知识。


此外,x命令有多个可选参数。建议总是带上/t和/v,可显示更多符号、类型信息。


/f:将只显示函数符号;并且会显示函数的详细定义。
/d:显示更多的变量类型相关信息。 
就近搜索


如果知道了符号的大概地址,但不能确定确切的符号名称,该怎么处理呢?就近查找命令ln能发挥作用


,ln是List Nearest的缩写。它的作用是:(根据给定的地址)列出附近一定范围内的所有符号。下表


中,指定地址0x7c8179f0的前后各有一个符号被找到:


0:000> ln 7c8179f0
(7c8179c3)   kernel32!NlsServerInitialize+0x29   |  (7c8179fe)   kernel32!AllocTables
2.6 源码命令


如果含有源码信息,可使得调试过程能够以源码模式逐行进行。和源码相关的命令包括下面几个: 


源码路径:


和符号路径类似,要设置源码路径,使用如下语法格式:


.srcpath[+] [路径1;路径2]
不含任何参数的情况下,显示当前设置的源码路径。


下面命令将覆盖原设置,设置新的源码搜索路径:


.srcpath <路径信息>
使用“+”可以将新的路径添加到原设置中,而不会把原设置覆盖掉:


.srcpath+ <路径信息> 
源码选项


这里列出的源码选项有三个,下面分别来讲。第一个是源码的Noisy选项,语法如下:


.srcnoisy [1|0]
此命令乃source noisy缩写,可以理解成“嘈杂的源码”,类似于符号设置中也存在的noisy选项。他的


三种运用如下所示:


状态:.srcnoisy
开启:.srcnoisy 1
关闭:.srcnoisy 0
开始“吵闹的源码”选项后,在源码加载、卸载,甚至单步的时候,都会显示丰富的源码信息。下图显


示了一个含有Noisy信息的单步命令:


 sourcenoisy


第二个命令是行号选项,即在符号文件加载过程中,是否将行号也一并加载进来。因为Windbg支持源码


级调试,所以它在Windbg中是默认开启(Enable)的,我们一般也不应该去禁止他。语法如下:


.lines [-d|-e|-t]
参数d是disable的意思;e是enable的意思;t表示切换的意思,即自动在disable和enable两者之间切换





最后看第三个命令,是代码行选项,包括行号和内容。语法如下:


打开:l+ [选项]
关闭:l- [选项]
命令l是line的缩写,和上面的.lines命令不同的是,.lines是加载时选项,l是调试时选项。我建议读


者总是调用“l+*”指令,打开所有的行选项,效果会很不错。这样在单步调试的时候,每一步的代码和


行号都会显示出来。显得很醒目!


            值得注意的是,进入源码模式和进入汇编模式的命令分别为:


源码模式:l+t
汇编模式:l-t
运行这两个命令和在Windbg的Debug菜单下点击source mode选项其效果是一样的。


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
则诸如NtCreateFile\NtCreateMailslotFile\NtCreateNamedPipeFile等函数都将被设置断点。


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号断点。
5. 内存命令


这一节里面,我们学习如何查看内存信息。内存是存储数据、代码的地方,通过内存查看命令可以分析


很多问题。相关命令可以分为:内存查看命令和内存统计命令。内存统计命令用来分析内存的使用状况





5.1 查看内存


            有非常丰富的内存查看命令,它们被统一为d*格式,如下所示:


d[类型]  [地址范围]
            d代表Display,类型包括:字符、字符串、双字等。具体来说,d*命令共有这几种:d、 


da、db、dc、dd、dD、df、dp、dq、du、dw、dW、dyb、dyd、ds、dS。解释如下:


内存类型 


基本类型:


dw = 双字节WORD格式;
dd = 4字节DWORD格式 ;
dq = 8字节格式;
df = 4字节单精度浮点数格式;
dD =8字节双精度浮点数格式;
dp = 指针大小格式,32位系统下4字节,64位系统下为8字节。
基本字符串:


da = ASCII字符串格式;
du = UNICODE字符串格式;
db =字节 + ASCII字符串;
dW = 双字节WORD + ASCII字符串;
dc = 4字节DWORD + ASCII字符串。
高级字符串:


ds = ANSI_STRING类型字符串格式;
dS = UNICODE_STRING类型字符串格式。
二进制 + 基本类型:


byb = 二进制 + 字节;
byd = 二进制 + DWORD值
如果读者对此感觉不明白,特别是组合模式究竟是何种情形?看下例应能清楚。下例将同一个ASCII字符


串,分别以ASCII字符串、Unicode字符串以及组合模式等共五种方式显示:


read-mem


此系列命令还有一些可加利用的开关选项,介绍如下:


/c 列数:指定列数。默认情况下,列数 等于16除以列长,如dd命令的默认列数即为4列(=16/4)。例





dd  /c  8
此命令每列显示8个DWORD数,即32字节内容。


/p:此选项用来显示物理内存信息,只能用于内核模式中。不使用此命令时,都将显示虚拟内存信息。


如:


d  /p  [地址范围]
L 长度: 默认情况下,d命令只显示固定长度的内存,一般为128或64字节。L可指定长度,如下面的命令


将显示地址0×80000000开始处的0×100个字节内容:


db  0×80000000  L100 
数组形式内存


难能可贵的是,d*命令还能够以数组形式显示一段内存信息,包括:dda, ddp、 ddu、dds、dpa、dpp、


dpu、dps、dqa、dqp、dqu、dqs。


何谓“以数组形式显示”呢?这一组命令能够将指定地址处的内容,作为一系列指针,进而显示指针所


指处内容。听上去有点拗口吧,读者这样想会清楚些:前一组命令显示address值,本节这一组命令显示


*address值。


程序代码中如有类似“char *array[10]”的数组变量,可使用这些命令显示数组内容。下面会有例子。


这一系列命令实则由第一组命令演化而来,可分为三组:


4字节DWORD为单位的dd*系列数组指令;
指针长度为单位的dp*系列数组指令;
8字节为单位的dq*系列数组指令。
查看链表内存


            最后,d*命令的另一个变体是以链表形式显示内存内容。命令如下:


dl  开始地址
默认情况下,以正向从头到尾遍历链表;也可反向(由尾向头)遍历,指定b开关选项:


dl  b  尾地址
应注意的是,命令dl是Display List的缩写,这里的链表不能是用户自定义的链表,而专指符合


LIST_ENTRY或SINGLE_LIST_ENTRY格式的链表。


5.2 内存信息


系统的内核空间很大的,想知道这么广大的内存空间里面都有些什么东西吗?想要知道一个内存地址,


到底是被一个内核栈使用着,亦或被堆管理器使用着吗?我们这一节就领大家看看内存的地理概况。首


先看Address命令:


!address  [地址]
            显示进程或系统的内存状态、信息,!address是最好的工具。不加任何参数,在用户模式


下此命令将以内存块为单位,列出从地址0开始到0×80000000(略小于)的全部地址空间信息;内核模


式下,将列出从地址0×80000000开始到0xFFFFFFFF(略小于)的全部地址空间信息;如指定地址值,则


将显示此地址所在内存块的内存信息(此命令在Vista以后系统中,不能在内核模式下正常使用,此Bug


应会在Windbg的以后版本中被修正)。下面分别截取了用户与内核空间中的内存信息片段:


0:009> !address                            


        BaseAddress      EndAddress+1        RegionSize     Type       State                


 Protect             Usage
-------------------------------------------------------------------------------------------


-----------------------------
+        0`00000000        0`00010000        0`00010000             MEM_FREE    


PAGE_NOACCESS                      Free       
+        0`00010000        0`00020000        0`00010000 MEM_MAPPED  MEM_COMMIT  


PAGE_READWRITE                     Heap64     [ID: 1; Handle: 0000000000010000; Type: 


Segment]
+        0`00020000        0`00021000        0`00001000 MEM_PRIVATE MEM_COMMIT  


PAGE_READWRITE                       
+        0`00021000        0`00030000        0`0000f000             MEM_FREE    


PAGE_NOACCESS                      Free       
+        0`00030000        0`00031000        0`00001000 MEM_PRIVATE MEM_COMMIT  


PAGE_READWRITE                       
// 省略很多...
            上图截取了两段用户区内存,第一段从0开始,长度0×10000,状态为释放(FREE),表明


这一段地址空间不可用;第二段从0×10000开始,长度0×1000,属于私有内存,状态为已提交,保护模


式为可读写,此内存块被用于环境块。


下面给大家解释一下内存块中的几个值:


内存类型:即Type值,共有四种:第一种是什么都不是,即尚未被使用的;第二种是MEM_IMAGE,即地址


映射于一个可执行镜像文件片段,如DLL文件;第三种是MEM_ MAPPED,即地址映射于不可执行的镜像文


件片段,如页文件;第四种是MEM_PRIVATE,即私有有内存,这里的私有是针对进程而言的,私有内存无


法在多个进程间共享;


保护模式:即Protect值,上例中见识了两种保护模式,NOACCESS和READWRITE。从字面即很容易理解其


意思,前者是不能做任何访问的,因为空闲内存是无效内存;后者则可读可写,但不能执行,说明是保


存数据的地方。所有可用的保护包括:PAGE_NOACCESS(不可访问),PAGE_READONLY(只读),


PAGE_READWRITE(读写),PAGE_EXECUTE(可执行), PAGE_EXECUTE_READ(执行并可读),


PAGE_EXECUTE_READWRITE(执行并可读写),PAGE_WRITECOPY(写时拷贝),PAGE_EXECUTE_WRITECOPY


(执行,并写时拷贝), PAGE_GUARD(保护)。


内存状态:即State值,共三种:MEM_FREE,即空闲内存;MEM_RESERVED,即保留内存,保留内存尚不能


被实际使用,但其地址空间已被预留,尚需一个提交动作。最后是MEM_COMMIT,即内存已被提交,正在


被使用。


内存用途:即Usage值,有这样一些值和用途。RegionUsageIsVAD:表示此地址区域已被分配;


RegionUsageFree:代表此地址区域已被释放,既没有保留也没有被提交,将来可以申请使用;


RegionUsageImage:代表此地址区域被映射到二进制文件的镜像;Region UsageStack:代表此地址区域


用于线程栈;RegionUsageTeb:代表此地址区域用于保存目标进程的所有线程的TEB结构;


RegionUsageHeap:代表此地址区域用于堆内存;RegionUsage Pdb:代表此地址区域用于保存目标进程


的PEB结构;RegionUsageProcessParameters:代表此内存块用于保存目标进程的启动参数;


RegionUsageEnviromentBlock:代表此地址区域用于保存目标进程的环境块。


用户环境下可使用下面的命令显示内存统计信息,包括内存用途、内存类型、内存状态。


!address  -summary
0:009> !address  -summary


--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy 


%ofTotal
Free                                    101        0`7a5ba000 (   1.912 Gb)           


95.60%
Image                                   294        0`022b8000 (  34.719 Mb)  38.49%    


1.70%
                                 7        0`0113a000 (  17.227 Mb)  19.10%    0.84%
Stack32                                  51        0`01100000 (  17.000 Mb)  18.84%    


0.83%
Heap32                                   26        0`006e0000 (   6.875 Mb)   7.62%    


0.34%
MappedFile                               12        0`0069e000 (   6.617 Mb)   7.34%    


0.32%
Stack64                                  51        0`00440000 (   4.250 Mb)   4.71%    


0.21%
Other                                     8        0`001c1000 (   1.754 Mb)   1.94%    


0.09%
Heap64                                    9        0`00190000 (   1.563 Mb)   1.73%    


0.08%
TEB64                                    17        0`00022000 ( 136.000 kb)   0.15%    


0.01%
TEB32                                    17        0`00011000 (  68.000 kb)   0.07%    


0.00%
PEB64                                     1        0`00001000 (   4.000 kb)   0.00%    


0.00%
PEB32                                     1        0`00001000 (   4.000 kb)   0.00%    


0.00%


--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy 


%ofTotal
MEM_PRIVATE                             181        0`02f11000 (  47.066 Mb)  52.17%    


2.30%
MEM_IMAGE                               295        0`022b9000 (  34.723 Mb)  38.49%    


1.70%
MEM_MAPPED                               18        0`0086c000 (   8.422 Mb)   9.34%    


0.41%


--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy 


%ofTotal
MEM_FREE                                101        0`7a5ba000 (   1.912 Gb)           


95.60%
MEM_RESERVE                              94        0`02f5d000 (  47.363 Mb)  52.50%    


2.31%
MEM_COMMIT                              400        0`02ad9000 (  42.848 Mb)  47.50%    


2.09%


--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy 


%ofTotal
PAGE_EXECUTE_READ                        56        0`01414000 (  20.078 Mb)  22.26%    


0.98%
PAGE_READONLY                           129        0`0117a000 (  17.477 Mb)  19.37%    


0.85%
PAGE_READWRITE                          153        0`004b5000 (   4.707 Mb)   5.22%    


0.23%
PAGE_WRITECOPY                           26        0`0004c000 ( 304.000 kb)   0.33%    


0.01%
PAGE_READWRITE|PAGE_GUARD                34        0`00048000 ( 288.000 kb)   0.31%    


0.01%
PAGE_EXECUTE_READWRITE                    2        0`00002000 (   8.000 kb)   0.01%    


0.00%


--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
Free                                      0`030b0000        0`6cf40000 (   1.702 Gb)
Image                                     0`75d71000        0`00879000 (   8.473 Mb)
                                 0`7f0e0000        0`00f00000 (  15.000 Mb)
Stack32                                   0`00cd0000        0`000fd000 (1012.000 kb)
Heap32                                    0`02f13000        0`0019d000 (   1.613 Mb)
MappedFile                                0`01a90000        0`002cf000 (   2.809 Mb)
Stack64                                   0`00160000        0`00039000 ( 228.000 kb)
Other                                     0`006b0000        0`00181000 (   1.504 Mb)
Heap64                                    0`02b90000        0`000bf000 ( 764.000 kb)
TEB64                                     0`7ef76000        0`00002000 (   8.000 kb)
TEB32                                     0`7ef78000        0`00001000 (   4.000 kb)
PEB64                                     0`7efdf000        0`00001000 (   4.000 kb)
PEB32                                     0`7efde000        0`00001000 (   4.000 kb)
上图分别以内存使用、内存类型、内存状态显示用户空间内存统计信息。


和!address命令类似的,用户模式下还有下面两个命令可用:


!vprot  [地址]
!vadump  [-v]
命令!vprot显示指定内存块的信息,侧重于内存保护信息;命令!vadump显示整个内存空间信息,dump者


倾泻也,开启-v选项将显示详细(Verbose)信息。


上面讲过,用户环境下使用“!address  –summary”可显示用户空间的内存统计信息;现在再看两个内


核命令,在内核环境下显示内存的统计信息:


!memusage
此命令从物理内存角度显示内存统计信息。无数个页表信息将被打印出来,可以说是“最内存”的信息


。此命令会查看所有的页帧,所以运行时会非常地耗时。


!vm
此命令从虚拟内存的角度显示内存统计信息,不仅能从全局角度显示虚拟内存的使用情况,还能以进程


为单位显示内存使用情况。


5.3 其他命令


内核模式下,查看文件缓存信息,命令格式如下:


!filecache
            此命令在用户内核模式下,显示文件缓存和页表状态。每一行信息表示一个虚拟地址控制


块 (VACB)。虚拟地址控制块可能对应着一个命名文件,也可能对应着一个元数据块。如果对应着一个命


名文件,则此文件名称将被显示,否则显示元数据名称。


实验:查看文件缓存


很多软件都使用文件缓存的方式保存数据,比如Office Word。直接查看WORD文档,由于其
内部格式不透明,故而不便分析。但如果使用WORD打开一个txt文本文档,它就会以文本文档
的方式来处理之,并且依旧使用文件缓存的方式。


读者用WORD打开一个TXT文档(比如:测试.txt)。
运行内核调试器并执行!filecache命令,在打印信息中查找“测试.txt”。
用户模式下查看堆信息,命令格式如下:


!heap
下面的清单显示了某个进程中共有4个堆:


0:004> !heap -a
Index   Address  Name      Debugging options enabled
  1:   00150000
    Segment at 00150000 to 00250000 (00031000 bytes committed)


  2:   00250000
    Segment at 00250000 to 00260000 (00006000 bytes committed)


  3:   00260000
    Segment at 00260000 to 00270000 (00003000 bytes committed)


  4:   00390000
    Segment at 00390000 to 003a0000 (00008000 bytes committed)
    Segment at 01370000 to 01470000 (0007b000 bytes committed)
            堆资源是属于进程的,每个进程都会创建若干个堆,如C运行时堆、进程默认堆等。以第一


个堆为例,地址范围是[0x150000,0x250000],已经有0×31000个字节被申请提交。
========

我常用的Windbg命令

用Windbg也有些时间了, 很喜欢这个强大的调试器, 不仅调试驱动爽, 调试应用程序也很爽. 命令有点


多, 但经常用到的也是少数, 将自己经常用到的留存在这里, 以后用的时候, 难得去翻.


1 下断点: bp, bm, ba, bl, bd, be


bp 普通下断点, 可以下在符号上, 也可下在地址上


bm 可以一次下很多断点, 支持通配符匹配, 例如, bm ga*, 能够下在所有ga开头的函数上.


ba 下在内存上, 当需要检视某块内存的读写或者执行时, 用这个命令. 格式一般如下: ba r4 


8504160c, 从8504160c的内存, 当有读时, break


bl和bd, be就不用说了


br 从新分配断点id br 13 8


// 下条件断点. article_name: Setting a Conditional Breakpoint


bp `sufilter.c:143` ".if (poi(MyVar)>5) {.echo MyVar Too Big} .else {.echo MyVar 


Acceptable; gc} "


2 ln


查看某个地址附近的情况, 例如, 需要了解某段地址附近有哪些代码, 可用这个命令.


3 x


查看module在内存中的地址.


x *! 查看所有模块在内存中的地址范围


x sufilter! 查看sufilter所有的代码符号(全局变量, 函数)的加载地址


x sufilter!sufilterreadwritemt


4 !analyze -v


分析dump信息, 包括被断下来的蓝屏


5 .crash


强制目标机器crash, 这对有时候需要带走dump信息应该好使


要dump目标机器的信息, 使用.dump命令. .dump c:\xxxxx.dmp


分析, kd(windbg) -z xxxxx.dmp. .dump /f dump full信息. 默认dump为minidump


6 .reboot


强制目标机器重新启动.


7 设置Windbg启动参数, 通常将其设置在快捷方式里


"C:\Program Files\Debugging Tools for Windows (x86)\windbg.exe" -b -v -i D:\PROJECTS


\Founder\fastrest\FastDisk\objchk_w2k_x86\i386 -y D:\PROJECTS\Founder\fastrest\FastDisk


\objchk_w2k_x86\i386 -srcpath D:\PROJECTS\Founder\fastrest\FastDisk -k


-b - 参数, 1 debugger启动后, 立即断下目标机器. 2 机器重新启动后, 内核一旦初始化, 就立即被断


下来.


-k - 参数, 用来指示连接参数. 参见windbg帮助.windbg ... -k com:port=ComPort,baud=BaudRate


-v - Enables verbose output from debugger.


-i - 指示ImagePath


-y - 指示symbolpath


-z - DumpFile. 分析dump文件时用.


-srcpath - 指示源文件path


8 查看堆栈 - k, 一般用kb, 其他参数参见帮组


childebp, retadd, argument func_addr


childebp - 该函数的堆栈, ebp起始地址, 局部变量, 参数, 都是通过ebp来访问的. 所以, 了解ebp的


地址是非常重要的


retadd - 返回地址, 可以通过返回地址ln retadd, 知道是那个函数call进来的.


argument - 不用说了. 进参是ebp+8开始的


dd ebp时, 看到的第一个dword是保存的父调用函数的ebp, 第二个dword是返回地址, 然后就是参数了.


dd ebp-x, 是本函数堆栈, 编译时, 如果有cod生成, 可以看到local variable在堆栈中的位置, ebp-4, 


ebp-8, ebp-c等等.


kv 显示Displays frame pointer omission (FPO) information. 这个Frame pointer, 我暂时的理解为


堆栈或者Trap的框架指针.


当要用.trap命令查看trap frame时,先运行kv命令. 会看到诸如:a815e874 8052c409 badb0d00 


e3051000 00000000 nt!KiTrap0E+0x238 (FPO: [0,0] TrapFrame @ a815e874)的样式. 然后, 运


行.trap a815e874. 查看trap frame


9 r 显示寄存器


r eip=a9876045 修改寄存器的值.


10 !chkimg


查看在debug过程中, 手动修改过哪些地方.


!chkimg -v -f 查看module是否在调试过程中被修改过. -v - 详细信息 -f - 修复.


11 lm


显示加载的module


lmvm module 就是lm带v和m选项. 列出module的详情. m用来指定通配符. 如lmvm s*


12 t


t有很多子命令, 运行到分支tb, 运行到下一个call, tc, 运行到下一个return, tt等等


13 u


u也有很多子命令. 其中, ur(realmode bios)和ux(x86 bios)反汇编bios相关的代码.


14 dt


查看类型, dt -b 显示全部类型,包括子类型.


显示数组.dt -a array


显示子域. 如struct _st {int v1; int v2[10];} 要显示b的内容. dt (_st)st_var -a v2


15 刚开始使用时, 经常让我莫名奇妙的问题:


关于源代码下不了断点, 有可能有如下原因:


源代码不是最新的, 源代码的版本与符号文件的版本不一致.


注意, 经常用.reload来重新加载Symbol文件. 这是用windbg很容易犯的一个错误, 莫名奇妙的不知道怎


么搞的, 就发现不对劲了.


源代码的某段被宏注释后, 可能下不了断点.


16 值得看的帮助主题


//


Debugging a Stack Overflow


// 下条件断点


Setting a Conditional Breakpoint


// 实时下载更新Symbols


Microsoft Public Symbols


========

摘录自《WinDbg用法详解》  

 
1、工作空间是以累积的形式打开的。


2、删除工作空间更快的方法是使用“Regedit”,在键目录


“\\Registry\\CurrentUser\\Software\\Microsoft\\WinDbg”中将Workspace全部删掉。


3、可以通过导入注册表或者Open Workspace in File打开.WEW文件来使用默认的Theme(主题)——经


过特殊定制的工作空间。


4、WinDbg命令分为三类:标准命令、元命令和扩展命令。


(1)标准命令在命令行输入“?”可以显示出标准命令的列表和介绍。


(2)元命令在命令行输入“.help”可以显示出元命令的列表和介绍。


(3)扩展命令在命令行输入“.chain”可以显示出当前加载的所有扩展模块,使


用“.unload”和“.unloadall”可以卸载指定的或所有扩展模块。


(4)标准命令和元命令是存储在程序内部文件的,而扩展命令是实现在DLL里的,需指定加载。通过


WinDbg的SDK用户可以自己编写自己的扩展模块和扩展命令。


5、对于用户态目标,命令提示符的完整形式是:


[||system_index:]process_index:thread_index>


对于本地内核态调试,为:


[||system_index:][processor_index:]ldk


6、使用“||<system_index> s”可切换当前系统,


使用“|<process_index> s”可切换当前进程,


使用“~<thread_index> s”可切换当前线程。(只能在一个系统的范围内切换线程)


7、可以通过“$$...;”或者“*......”来为命令添加注释。(但该命令之前必须存在“;”)


8、别名的定义和使用:


(1)“r $.u<0~9>=... ; *固定别名定义”


(2)“as 别名名称 别名实体 ; *用户别名定义”


(3).echo 别名名称; *查看某个别名定义


(4)如果用户别名名称和命令的其它部分是连续的,则必须使 用“${用户别名}”,如下:


dd 用户别名 +8 = dd ${用户别名}+8


(5)使用al 列出所有用户别名定义,ad删除用户别名定义。 (ad * 删除全部)


9、命令“@$伪寄存器名”用来使用伪寄存器,同样用


“r $.t<0~9>”来定义用户定义伪寄存器。


10、循环和条件执行:


(1)使用z命令,例如执行:


“r ecx=2”


“r ecx=ecx-1;r ecx; z(ecx);r ecx = ecx+1”


得到:


ecx=00000001


redo [1] r ecx=ecx-1;r ecx; z(ecx);r ecx = ecx+1


ecx=00000000


[1]代表循环次数,z命令测试"()"内的条件,当满足的时候执行之前的命令,不满足则跳到后面的命令


继续执行。


(2)使用“!for_each_XXXX”表示对每个“XXXX”执行操作。例如以下命令表示对每个帧栈打印每个局


部变量:


“!for_each_frame !for_each_local dt @#Local”


(3)使用“j<条件判断表达式>['Command1'];['Command2']”


表示条件成立则执行Command1,否则执行Command2,单引号表示可以在其内部执行一组命令。


(4)使用元命令中的


“.if (XXXX) .elseif {XXXX }.else {XXXX}”


更为复杂点的例子:


“bp 'my.cpp:122' "j(poi(myValue))>5;'.echo My value too big;'My value acceptable;gc' "”


其中“bp 'my.cpp:122'”表示在my.cpp文件的第122行插入断点,后跟“XXXX”表示在撞击断点时执行


的命令;poi()表示取该地址的值,类似C语言的“*”;“gc”表示继续执行。


11、进程、线程限定符:“~.”“~.”分别表示当前进程,又如


  “~1 r;~2 r;~3 k *查看线程1、2的寄存器和线程3的堆栈”


“~0e r; k *查看线程0的寄存器和堆栈”(等同于"~0 r;~0 k")


“~* r *查看所有线程的寄存器”


12、使用“.logfile”“.logopen”“logclose”三个元命令来显示、打开、关闭日志文件。


13、双机内核连接:目标机为VISTA之前的OS可以使用COM端口(稳定,兼容性好),1394(火线,速度


快、兼容性不好),VISTA之后的OS则可以使用USB2.0(速度快,对控制器有特殊要求[可以查看主板手册


])


可以使用k开关启动命令参数来建立内核调试连接。如下:


"windbg -k com:port=Com1, baud:115200"


14、分离调试目标:


用户态程序:让目标继续运行(脱离调试关系)。


内核态:与Stop Debugging效果一样。


抛弃调试目标:让目标不再运行(挂起,保留调试关系),若想再附加一个进程,则需在调试器启动命


令行中打开pe开关,


比如:“WinDbg -pe -p 2227”(表示这是重新附加的进程,否则会报告错误)


杀死当前被调试进程:“.kill”(内部调用TerminateProcess API)


15、若调试器僵死,使用pe开关开启一个新的进程,再终止僵死的程序。


16、当在进行内核调试的时候,需要切换进程上下文来实现观察另一个进程的用户空间,可以使用如下


方法:


(1)“.process < implicit process address>; ”


“!procee 0 0 ; *list basic information of processes in system”


(2)“.context; *display base address of page directory”(as displayed in CR3 register in 


X86)


17、系统将寄存器保存到内存中当发生异常或者线程切换时,我们查看寄存器的值的时候线程往往是挂


起的,所以修改寄存器的值其实也就是修改内存中的值。


18、使用“.thread <implicit thread address>”设置寄存器上下文所针对的线程。“.thread”(或


“.cxr”)恢复成原来的线程地址。(当调试用户态的转储文件时可以使用“.ecxr”将转储文件中保存


的异常上下文设置成当前寄存器上下文)


19、使用“!process <adreess of EPROCESS structure in current process> f”列出当前进程的所有


线程。


20、局部上下文:当前所使用的参数和变量所基于的函数。使用“.frame”可以列出当前的局部上下文


,“.frame <thread number>”切换局部上下文,使用“.kn”查看当前帧栈(为单个过程分配的栈)列


表,“.dv”列出当前局部上下文使用的参数和变量。


21、VC编译器缺省将默认类型符号放在VCx0.pdb文件中,WinDbg没有很好的处理该文件,所以会显示很


多的 NO Type information,解决办法是将符号设置为C7 Compatable(Settings->C++->General->Debug 


Info)。


22、使用“symsrv*ServerDLL*[Downsream Store*]ServerPath”来设置搜索符号文件的路径。其中


ServerDLL是符号服务器DLL的位置,Downstream Store是下游符号库的位置,ServerPath是符号服务器


的URL。


可以使用简化模式:“SRV*[Downstream Store*]Server Path”(其中SRV 相当于 symsrv*symsrv.dll


[symsrv.dll是windbg自带的符号服务器DLL,用户也可自己编写])。


23、使用“lm”显示加载的模块,“lm v”为每个模块提供更详细的信息,“!lm <ModuleName>”显示


一个详细的模块信息,“lmo”显示已经加载的模块,“lml”显示已经加载符号文件的模块,“lme”显


示有符号问题的模块,“lm m *<CharFilter>”显示以一个char过滤的模块(若是M,则为路径过滤)。


24、Windows定义了9类调试事件,异常事件是其中的一种,异常事件又包含很多个子类,其他事件则不


包含子类。常见的异常子类有:


(1)win32异常,这是windows操作系统所定义的异常,其中主要是CPU产生的异常,典型的有除零、非


法访问等,这类异常代码定义在 ntstatus.h中。


(2)VisualC++异常, 这是VisualC++编译器throw关键字所抛出的异常(throw关键字调用


RaiseExceptionAPI产生异常)。


(3)托管异常,这是.Net程序使用托管方法抛出的异常。


(4)其他异常,包括用户程序直接调用RaiseExceptionAPI抛出的异常,以及其它C++编译器抛出的异常


等。


25、对于每个异常,windows都会给两轮处理机会,对于每一轮机会,windows都先将异常交给调试器(


如果存在),然后再寻找异常处理器(VEH,SEH等)处理。每次处理后调试器都应该向系统返回一个处理


结果,说明它是否处理了这个异常。对于二轮机会,如果调试器不处理,则系统采取终极方法:若异常


发生在应用程序中,则立即终止应用程序;若异常发生在内核中,则导致BSOD。用户可自己定制调试器


的调试事件处理方式。


26、非默认状况下,可以使用“GH”和“GN”来返回与设置的不同状态,前者表示HANDLED,后者表示


NOT HANDLED。


27、若要分析程序的入口函数,在初始断点的时候对入口函数设置断点是个合适的时机。


28、创建一个新进程时, 很多早期的创建工作都是在父进程的环境下完成的。初始线程真正在新进程环


境下执行时从内核态的KiThreadStartup开始的。


29、当将windbg附加到一个进程的时候,windbg在目标进程创建一个新的线程来触发一个初始断点,这


个断点发生在新创建的线程上下文中。因此,这个线程并不是目标的本来线程,当我们恢复执行时,该


线程也会立即结束。


30、若远程线程创建后还没有执行断点指令就被挂起了,这时候WINDBG收不到断点事件,会提


示“Break-in sent, waiting 30seconds...”再等待30秒,WINDBG会人工合成一个异常事件(Wake 


Debugger)。


31、单步执行根据当前是否处于源代码模式(Source Mode)分为源代码级的单步和汇编指令级的单步,


选中Debug菜单的Source Code菜单项(或者使用命令“1+t”)进入源代码模式, 反选(或者使用命令


“1-t”)进入汇编模式。


32、单步越过的命令是通过在下一条指令处设置一个软件断点来实现的,而源代码级的单步执行是通过


多次汇编级的单步执行来实现的。(可查看相关的函数调用及参数说明来观察到)


33、“p|t [r][= StartAddress][Count]["Command"]”


其中,r的用处是禁止自动显示寄存器的内容,在使用[= StartAddress]命令参数时需要注意若指定的地


址在函数外部,由于跳过了调整栈的代码,会发生栈错误。Count表示单步执行几次,Command表示在每


次单步执行后需要执行的指令。


34、“pa|ta [r][= StartAddress]StopAddress”


单步执行到指定地址,显示每一次命令执行后的结果。例如:使用伪寄存器$ra(return address),命令


“par @$ra *其效果相当于gu(执行至上一层函数)”。


35、“pc|tc [r][= StartAddress][Count]”,其中c代表call,即从当前或者指定的地址执行指令到下


一个函数调用处(call命令)为止,count代表执行到第几个call指令处,缺省为1。


36、“pb|tb [r][= StartAddress][Count]”其中b代表branch,即从当前或者指定的地址执行指令到下


一个分支指令处为止,X86平台上该命令只能用于内核态调试。(与pc|tc这样反复单步执行的指令不同


,该命令是设置好msr(mode status register)寄存器和标准寄存器后恢复程序执行,因此更高效)


37、g命令语法:


“g[a][=StartAddress][BreakAddress...[;BreakCommands]]”


a代表将断点设置为硬件断点,不指定则为软件断点BreakAddress用来指定一个隐藏的断点地址,当执行


到该地址时windbg自动删除该断点(SourceCode窗口或AssembelCode窗口下Debug选项中的Run to 


Cursor便是用该命令实现的)。


38、在函数入口处,命令“wt”用来了解一个函数的执行路径和它调用了哪些函数,每个函数又有多少


条指令。(若不在函数入口处,则其执行效果相当于“p”)


39、单步执行和g指令导致的程序指针飞跃在一定条件下有比较大的用处:如我们不想执行某个函数或者


跳过导致异常的某段代码。但如果跳过了涉及栈操作的代码,会引发栈的不平衡,导致程序发生错误。


40、“bp|bu [ID][Option][Address[Passes]]["CommandString"]”,Option选项中有(/1 /c|C /p /t)


其中/1表示设置当击中该断点时自动将其从断点列表中删除(即设置为一次击中断点),/c|C分别指定


中断给用户的最大(小)函数深度。/p和/t只能用于内核调试中,后面分别跟一个EPROCESS和ETHREAD结


构,用来表示只有在指定的进程(线程)中才能访问该断点。


批断点设置指令“bm[Option][Passes]]["CommandString"]”


bu用来设置一个延迟的以后再求解的断点,用于对尚未加载模块中的代码设置断点,所以bu命令对调试


动态加载的模块的入口函数或者初始化代码特别有用。如:“bu Driver!DriverEntry”


41、bm命令在设置断点前需要确认匹配的符号对应的是代码不是数据,所以使用bm命令时要求目标模块


的调试符号有类型信息,能够判断出一个符号的类型。这通常需要所谓的私有符号文件,也就是调试版


本的符号文件。对于公用的符号文件,如我们输入“x ntdll!DbgPrint*”会显示WinDbg抱怨没有符号类


型信息,再输入“bm ntdll!DbgPrint*”时显示没有找到匹配的代码符号,类似解决办法有两个:


(1)开启/a开关告诉调试器无论是数据还是代码都要设置断点,在不确定所有符号均为代码时这样做会


导致错误。


(2)使用调试器告诉你的方法:用dll内部的export symbols,具体做法是将该符号文件路径清空(清


空路径后.reload一下),再输入“x ntdll!DbgPrint*”,目的是让调试器自动使用dll内部的export 


symbols,发现WinDbg不再抱怨没有符号类型信息(尽管还是缺参数信息)。当再次输入“bm ntdll!


DbgPrint*”发现批断点设置成功。


42、“ba[ID]AccessSize[Options][Address[Passes]]["CommandString"]”该命令设置硬件断点。硬件


断点就是通过CPU的硬件寄存器(x86中为dr0~dr7(不包含4、5),最多同时4个硬件断点,dr6为断点的


状态寄存器,dr7为断点的控制寄存器)设置的断点,硬件断点具有数量限制,但是可以使用软件断点不


具有的功能,比如监视数据访问和I/O访问等。(这点比较重要)


Options中指定了触发断点的条件,具体参数参考手册。另外,AccessSize参数用来指定访问的长度,对


于访问代码硬件断点,它的值应该为1。


43、当我们只关心特定条件的断定命中的时候,可以使用条件断点简化调试过程。其运作原理是当断点


发生时,让调试器检查一个条件,对于不满足条件的情况,立刻恢复目标执行,只有满足条件了才中断


给用户。


常见的编写条件断点的方式有两种:


(1)


“bp|bu|bm|ba Address "j(Condition)'OptionalCommands';'gc'"”


(2)


“bp|bu|bm|ba Address ".if(Condition){OptionalCommands};.else{gc}"”


例如:“bp dbgee!wmain"j(poi(argc) > 1)'dd argc l1; du poi(poi(argv+4))';'gc'"”


该命令对dbgee程序的wmain函数设置一个断点,只有当命令行的参数的个数大于1时,执行'dd argc l1; 


du poi(poi(argv+4))'并中断给用户,否则继续执行。(dd argc l1表示显示argc参数的值,du poi


(poi(argv+4)表示显示第一个命令行参数的字符串内容)


该命令等同于“bp dbgee!wmain".if(poi(argc) > 1){dd argc l1; du poi(poi(argv+4))};.else{gc}"





注:该命令使用了poi命令,是因为WinDbg缺省使用MASM语法的表达式评估器(expression evaluator)


,在MASM的语法中,argc代表一个地址,要取它的值必须使用poi操作符,poi表示从地址中取出指针长度


的数据(pointer-sized data),类似的还有by、wo、dwo、qwo分别表示从指定地址取1、2、4、8个字


节的数据。(关于MASM表达式的更多内容,参考WinDbg帮助文件的MASM Numbers and Operators)


可以使用该命令将MASM表达式评估器变为C++评估器:


“.expr /s c++”,此时以上的命令可以表述为:


“bp dbgee!wmain"j(argc> 1)'? argc l1; ?? argc[1]';'gc'"”(更符合C++语法风格,但当前的


WinDbg版本不一定支持该功能----可能存在bug),如果当前是MASM表达式评估器,可以使用@@前导符来


嵌入C++表达式,如:


“bp dbgee!wmain"j@@(argc> 1)'dd argc l1; du @(argc[1])';'gc'"”(该前导符后应接括号表示对括


号里的内容使用C++评估器)


44、断点命令的地址(Address参数)的设置有如下几种方法:


(1)模块名+函数符号+地址偏移,如:“bp dbgee!main+3”


(2)直接使用内存地址,如“bp 456321578”


(3)若使用的是完全的调试符号,调试符号内包含源文件的行信息,可以使用该形式设置地址:`


[[Module!]Filename][:LineNumber]`,注意整个表达式用``(不是'')包围起来,如:“bp `dbgee!


dbgee.cpp:14`”


(4)对于C++的类方法,可以使用__或者::连接类名称和类方法,如:“bp 


MyClass__MyMethod”或“bp MyClass::MyMethod”.


(“bp @@(MyClass::MyMethod)”)


注:若使用方法(1)或(2),要注意设置的地址必须是一个指令的起始位置。如果插入在指令的中间


位置,CPU会将中间字节替换为中断指令,当CPU执行到这个位置时,CPU会因为这里是一条多字节的指令


将原指令的前一部分和断点指令作为一个新的指令来解码,会引发严重的错误。


45、设置针对线程的断点:


(1)用户程序,在bp前加入线程编号,如“~0 bp dbgee!main+4”


(2)内核程序,使用上面介绍的/p /t 选项来指定进程和线程。


46、bl命令列出所有断点:第一列表示断点编号,第二列表示断点状态,e为enable,d为disable,e或d


后面可能跟u,表示尚未落实unresolved,第三列为断点地址,表示方法有多种,若针对硬件断点,地址


后有访问方式和访问长度,第四列表示还需多少次击中目标才中断给用户,括号内为总共需要击中目标


的次数(即设置断点参数时Passes的值),第五列表示进程和线程信息,其中****表示对所有线程均设


置该断点,第六列表示断点地址的符号表示。如果断点有相关的命令,则显示在第六列之后。当设置


了/c /C 选项时,断点信息下一行会显示Call stack shallower (deepper)than : XXXXXXXX。


可以使用bc(cancle), bd, be后跟断点编号分别删除、禁止、启用断点。例如“bd 0-2,4”“be *”


分别表示禁止0、1、2、4号断点,启用所有断点。


br(remark)改变断点编号,例如当删除3号断点后,“br 4,3”将4号断点变为3号断点。


47、使用k系列命令观察栈回溯,其中第一列的ChildEbp表示该行函数的ebp(帧指针)的值,调用函数位


置是通过寻找距离该行的RetAddress最近的符号获得。k系列命令后可跟L(注意大写)来隐藏在源文件


的位置。


48、kb命令可以只显示该函数的前三个参数,第一个是ebp+8,依次类推。注:说是函数的前三个参数,


其实是不准确的,这三个数只是存放在栈上的数,不一定是函数参数,如对于调用惯例为fastcall(函


数参数通过寄存器传递)的函数。这是我们可以使用kp命令让调试器根据符号文件的信息帮我们进行判


断,但此时只能是使用私有符号文件才可以这么做,没有符号文件的情况下,kp不显示任何参数,这就


是为什么通常使用kb命令的原因。


kv命令可以显示FPO(帧指针省略信息)和调用惯例。


kn命令可以显示帧栈的编号,若再加上f选项可以查看相邻栈帧的间距(上下相邻的ebp的差值),该数


值越大,说明该函数使用的栈空间越大。


49、查看栈帧空间参数和变量:


“dv /i /t /v(V)”i表示information,t表示type,即变量的类型,v表示virtual space address,即


内存地址,而V附加显示了该变量对于ebp的偏移地址。


命令“!for_each_local”用于枚举当前栈帧的所以变量,后面可跟附加命令。“!for_each_frame”用


于枚举当前线程的所有帧栈后面可跟附加命令,如附加“dv /i /t /V”查看所有帧栈的变量及参数信息


。使用“.frame <frameNumber>”可以切换当前局部上下文。


注:(1)dv命令只能对加载了私有符号文件的模块使用,若没有加载私有符号,可以使用如下方法查看


局部变量。一、直接使用内存观察窗口观察帧栈空间,利用栈帧分布的知识。二、通过使用汇编指令中


对局部变量的引用来观察该内存地址的内容,对于没有使用FPO的函数,局部变量一般都是在EBP XXXX的


地址上。


(2)对于VC7或更高的版本,局部变量是从EBP-CH开始的,EBP-4用来存放安全Cookie值,EBP-8存放安全


Cookie值的屏障字段(即0XCCCCCCCC)。


50、当EBP和ESP的值已经不可信时,此时无法使用k系列命令来查看栈回溯,应该通过配合使用“!teb”


(得到栈的内存位置)和“dds <AddressScope>”(显示和分析栈内存)来手动分析栈回溯(通过排除


不是函数的字符串行)。


51、查看内存内容:


“d{a|b|c|d|D|f|p|q|u|w|W}[Option][Range]”,


“dy{b|d}”(以二进制显示字节和双字)


“da|u”(分别显示单(宽)字符集的字符串)


显示范围有以下两种表示方法:


(1)起始地址加空格加终止地址


(2)起始地址加空格加L(或者l)和对象个数


(3)终止地址加空格加L(或者l)加负号加对象个数


注:


(1)对象个数为数据单位,直接执行d命令保持上次执行查询内存的命令。


(2)对于数据结构类型的字符串,可以通过命令“ds|S”分别显示STRING和UNICODE_STRING数据类型的


字符串内容。


52、WinDbg提供dt命令来显示符号类型信息(Dump symbolic Type information),有以下三种用法:


(1)“dt [ModuleName]!TypeName”,若省略模块名,则自动查找所有已加载的模块,如“dt ntdll!*


”显示ntdll里的所有类型信息。其中,-b开关指定递归的显示所有子类型的信息。-r加数字指定递归显


示的深度,如-r0表示不显示子类型信息。若不想显示全部字段,可以使用开关-ny附加字段过滤搜索信


息,如“dt _TEB -ny LastError”。


(2)第二种用法是在上一种用法之后加上内存地址,按照指定的内存地址的内容来显示具体类型的变量





(3)第三种用法是显示类型的实例,如全局变量、静态变量和函数。同样可以枚举函数符号,此时同x


命令的功能相似,如“dt dbgee!*wmain*”,若是指定的函数,则会显示该函数的参数取值和返回值类


型。


53、可以使用如下方法搜索内存内容:


(1)“s-[[Flags]]sa|su Range”用来搜索ASCII(UNICODE)字符串,可以用l加整数指定字符串的长


度。例如“s-[l5] sa (注意这里sa之前不能有空格)0X600000 0X800000”。


(2)“s-[[Flags]]v Range Object”,在指定内存地址范围内与指定对象相同类型的对象。


(3)“s [-[[Flags]]Type] Range Pattern”,Type决定了匹配搜索内容的方式,可以为b、w、d、q、


a、u,Pattern参数用来指定要搜索的内容,可以用空格分隔依次搜索的数值,如:


“s -w -0X400000 l2A000 41 64 76 44 62 67”,要搜索的内容也可以表示为ASCII码,如:“s -w 


0X400000 l2A000 'A' 'd' 'v' 'D' 'b' 'g'”


(其中l后面跟数字表示在起始地址之后多少范围内进行搜索)


54、修改内存:


(1)以字符串方式编辑:“e{a|u|za|zu} Address "String"”


其中,za|zu代表以0结尾的ASCII(UNICODE)字符串。


(2)以数值方式编辑:“e{b|w|d|D|f|p|q} Address Value”,其中,Value参数决定用户需要修改多少


数值。若不键入Value,则会进入交互式的修改内存界面。


55、扩展命令!d{b|c|d|p|q|u|w}和!e{b|d}显示和修改物理地址,!adress[Address]显示某个内存区域


的特征信息。只有在内核调试时才能使用该命令。


56、使用“dl LinkAddress”查看链表。还可以用dt或者!list命令查看。


57、使用.call元命令在当前进程中调用一个函数。Windbg在当前线程栈上模拟出函数调用的环境,将参


数、返回地址和寄存器等准备好,然后恢复目标执行,恢复目标执行后便执行要调用的函数。但要求必


须有私有调试符号文件支持。


57、可以编写命令程序作为WinDbg的输入,进行预调试。


58、多线程调试......1 !address eax查看对应内存页的属性
2 vertarget 显示当前进程的大致信息
3 !peb 显示process Environment Block
4 lmvm 可以查看任意一个dll的详细信息
例如:0:026 lmvm msvcrt (deferred)表示察看msvcrt.dll的信息,但是没有加载
symbol可以通过.reload命令来加载
5.reload /!sym 加载符号文件
6 lmf 列出当前进程中加载的所有dll文件和对应的路径
0:018> lmf
7 r 命令显示和修改寄存器上的值
r命令显示和修改寄存器上的值
0:018> r 显示寄存器的值
0:018> r eax=0 修改了寄存器,把eax的值修改为0x0
8 d命令显示esp寄存器指向的内存
如下
0:018>d esp
用dd命令直接指定054efc14地址
0:018>dd 054efc14 
注意:第二个d表示DWORD格式,此外还有db(byte),du(Unicode),dc(char)等等。
数据查看指令 d{a|b|c|d|D|f|p|q|u|w|W}
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,用于查看本地变量用的
9 e命令可以用来修改内存地址
跟d命令一样,e命令后面也可以跟类型后缀,比如ed命
令表示用DWORD的方式修改。下面的命令把054efc14地址上的值修改为11112222。
0:018>ed 054efc14 11112222
修改后可以用dd命令来查看内存。
0:018>dd 0543fc14 L4 L4参数指定内存区间的长度为4个DWORD,这样输出只有1行,
而不是8行了。
10s 命令用来搜索内存具体见help文档
11!runaway 可以显示每一个线程的cpu消耗
0:018> !runaway 结果如下:
0:83c 0 days 0:00:00.406
13:bd4 0 days 0:00:00.046
10:ac8 0 days 0:00:00.046
24:4f4 0 days 0:00:00.031
上面输出的第一列是线程的编号和线程ID,后一列对应的是该线程在用户态模式中的
总的繁忙时间。
在该命令加上f参数,还可以看到内核态的繁忙时间,当进程内存占用率比较高的时候
,通过该命令可以方便的找到对应的繁忙线程。
12 ~ 命令是用来切换目标线程
0:018> ~ 可以显示线程的信息
0:018> ~0s把当前的线程切换到0号线程,也就是主线程,切换后提示符会变为0:000.
13 ~* 命令列出当前进程中的所有线程的详细信息
14~*kb命令列出所有线程的堆栈
15 k 命令用来显示当前线程的堆栈,如下
0:018> k
跟d命令一样,k后面也可以跟很多后缀,比如kb kp,kn,kv,kl等,这些后缀控制了
显示的格式和信息。
栈指令k[b|p|P|v]
这四条指令显示的内容类似,但是每个指令都有特色,KB显示三个参数,Kp显示所有的参数,但需要


Full Symbols或Private PDBSymbols支持。KP与Kp相似,只是KP将参数换行显示了。Kv用于显示FPO和调


用约定,KD,用于显示Stack的Dump,在跟踪栈时比较有用。
这些指令区分大小。
16 u命令把指定地址上的代码翻译成汇编输出
0:018> u 7739d023 
USER32!NtUserWaitMessage:
7739d023 b84a120000 mov eax,0x124a
7739d028 ba0003fe7f mov edx,0x7ffe0300
7739d02d ff12 call dword ptr [edx]
7739d02f c3 ret
如果符号文件加载正确,可以用uf命令直接反汇编整个函数,比如uf USER32! NtUserWaitMessage
17 x 查找符号的二进制地址如下
0:018> x msvcr!printf
77bd27c2 msvcrt!printf = 
上面的命令找到了printf函数的入口地址在77bd27c2
0:001> x ntdll!GlobalCounter
7c99f72c ntdll!GlobalCounter = 
上面的命令表示ntdll!GlobalCounter这个变量保存的地址是7c99f72c。
注意:符号对应的是变量和变量所在的地址,不是变量的值,上面只是找到GlobalCounter这个变量的值


是7c99f72,要找到变量的值,需要用d命令读取内存地址来获取。
X命令还支持通配符,比如x ntdll !*命令列出ntdll模块中的所有的符号,以及对应的二进制地址。
18 dds 打印内存地址上的二进制值
同时自动搜索二进制值对应的符号。
比如要看看当前**中保存了那些函数地址,就可以检查ebp指向的内存
0:018>dds ebp
0013ed98 0013ee24
0013ed9c 75ecb30f BROWSEUI!BrowserProtectedThreadProc+0x44
0013eda0 00163820
0013eda4 0013ee50
0013eda8 00163820
0013edac 00000000
0013edb0 0013ee10
0013edb4 75ece83a BROWSEUI!__delayLoadHelper2+0x23a
0013edb8 00000005
0013edbc 0013edcc
0013edc0 0013ee50
0013edc4 00163820
0013edc8 00000000
0013edcc 00000024
0013edd0 75f36d2c BROWSEUI!_DELAY_IMPORT_DESCRIPTOR_SHELL32
0013edd4 75f3a184 BROWSEUI!_imp__SHGetInstanceExplorer
0013edd8 75f36e80 BROWSEUI!_sz_SHELL32
0013eddc 00000001
0013ede0 75f3726a BROWSEUI!urlmon_NULL_THUNK_DATA_DLN+0x116
0013ede4 7c8d0000 SHELL32!_imp__RegCloseKey (SHELL32+0x0)
0013ede8 7c925b34 SHELL32!SHGetInstanceExplorer
这里dds命令从ebp指向的内存地址0013ed98开始打印,第一列是内存地址的值,第二列是地址上对应的


二进制数据,第三列是二进制对应的符号。上面的命令自动找到了75ecb390f对应的符号是BROWSEUI!


BrowserProtectedThreadProc +0x44.
Com interface 和c++ vtable里面的成员函数都是顺序排列的。所以,dds命令可以方便的找到虚函数表


中的具体的函数地址,比如用下面的命令可以找到OpaqueDatinfo类型中虚函数的实际函数地址。
首先通过x命令找到OpaqueDataInfo虚函数地址
0:000> x ole32!OpaqueDataInfo::vftable’
7768265c ole32!OpaqueDataInfo::`vftable'' = 
77682680 ole32!OpaqueDataInfo::`vftable'' = 
接下来dds命令可以打印出虚函数表中的函数名字
0:000> dds 7768265c 
19 .frame 命令在栈中切换以便检查局部变量
要查看局部变量的需要如下:
1查看线程的callstack
0:018>knl
00 0012f7a0 7c821c94 ntdll!KiFastSystemCallRet
01 0012f7a4 7c836066 ntdll!NtRequestWaitReplyPort+0xc
02 0012f7c4 77eaaba3 ntdll!CsrClientCallServer+0x8c
03 0012f8bc 77eaacb8 kernel32!ReadConsoleInternal+0x1b8
04 0012f944 77e41990 kernel32!ReadConsoleA+0x3b
第一列的号称为Frame num,通过.frame命令就可以切换到对应的函数中检查局部变量,比如我们检查


kernel32!ReadConsoleA,这个函数的frame num是4,于是,我们如下
2 iframe切换到指定行号的函数中
0:018> .frame 4
3然后调用x显示当前frame的局部变量,比如这个函数中有两个局部变量pcls和rawptr
0:018> x
0012fced pcls = 0x0039ba80
0012fcd8 rawptr = 0x0039ba80
20 dt 格式化显示资料
Dt命令格式化显示变量的资料和结构
0:000> dt pcls
Local var @ 0x12fce4 Type MyCls*
0x0039ba80 
+0x000 str : 0x00416648 'abcd'
+0x004 inobj : inner
上面的命令打印出pcls的类型是MyCls指针,指向的地址是0x0039ba80,其中的两个class成员的偏移分


别在+0和+4,对应的值在第2列显示。加上-b -r参数可以显示inner class和数组的信息:
0:000> dt pcls -b -r
Local var @ 0x12fce4 Type MyCls*
0x0039ba80 
+0x000 str : 0x00416648 'abcd'
+0x004 inobj : innner
+0x000 arr : 'abcd'
[00] 97 ''a''
[01] 98 ''b''
[02] 99 ''c''
[03] 100 ''d''
[04] 0 ''''
[05] 0 ''''
[06] 0 ''''
[07] 0 ''''
[08] 0 ''''
[09] 0 ''''
对于任意的地址,也可以手动指定符号类型来格式化显示。比如把0x0039ba80地址上的数据用MyCls类型


来显示:
0:000> dt 0x0039ba80 MyCls
+0x000 str : 0x00416648 'abcd'
+0x004 inobj : innner
21bp设定调试断点
比如可以这样写:0:018>bp notepad!WinMain 在notepade的winmain函数处下断点
断点的位置可以用符号来表示,如上,也可以直接用地址以及windbg的Pseudo_Register(虚拟寄存器)


。比如,我们用$exentry表示进程的入口,那么可以用bp @$exentry在进程的入口设置断点,如果


notepade的winmain的入口地址为01006420,那么断点也可以这么写
Bp 01006420
bp mysource.cpp:143` 'j (poi(MyVar)”0n20) ''''; ''g'' '
意思就是:当myvar的值等于0x20时,g命令继续执行
下面一个设置条件断点
0:001> bp exceptioninject!foo3 “k; .echo ‘breaks’ ; g”
在exceptioninject!foo3上设置断点后,每次断下来后,先用k显示callstack,然后用.echo命令输出简


单的字符串‘breaks’,最后g命令继续执行。
下面看一个更复杂的设置条件断点的例子:
ba w4 execptioninject!i ”j(poi(exceptioninject!i)<0n40) ‘.printf//”exceptioninject!i 


value is :%d//”,poi(exceptioninject!i); g’ ; ‘.echo stop!’ ”
首先ba w4 exceptioninject!i 表示在修改exceptioninject!i这个全局变量的时候,停下来,
j(judge)命令的作用就是对后面的表达式作条件判断如果为true,执行第一个单引号里面的命令,否则


执行第2个单引号里面的命令。
条件表达式是(poi(exceptioninject!i)<0n40),在windbg中excepioninject!i符号表示符号所在的内


存地址,而不是符号的数值,相当于c语言的&操作符的作用,poi命令就是取这个地址上的值,相当于c


语言的*操作符。所以这个条件判断的意思就是判断exceptioninject!i的值,是否小于十进制的40。如


果为真,就执行第一个单引号,‘.printf//”exceptioninject!i value is :%d//”,poi


(exceptioninject!i); g’,如果为假,就执行第二个单引号‘.echo stop!’
第一个单引号里有三个命令,.printf .echo 和g。这里的printf和c语言的printf函数语法一样,不过


由于这个printf命令本身是在ba命令的双引号里面,所以需要用//来转义print中的引号。第一个引号的


作用是:打印出当前exceptioninject!i的值,.echo命令换行 g命令继续执行
第二个引号的作用就是显示stop,由于后面没有g命令,所以windbg会停下。
22 bm 使用模式匹配设置断点
这个功能需要符号表的支持,bm可以通过模式一次设置多个断点,比如
bm mydriver!FastIO* 可以将所有与FastIO*模式匹配的函数下设置断点,比如FastIoRead ,


FastIoWriter等函数都会被设置上断点。需要注意的是,bm命令需要full or export symbols支持。
23 ba 对内存访问设置断点 break on access
就是对于内存访问设置断点,对于在多核处理或者多核处理器调试的时候很有用,对于调试多线程也很


有用,比如说,我们可以对一个全局变量设置断点,
ba mydriver!gMonitoreedDevices , 如果你认为这个变量的值被莫名的修改了,相信通过ba设置的断点


,你可以很快找到是谁修改的。
也可以这样
ba w4 0x4000000 'kb;g' 当0x4000000地址有写操作时,进入断点 。w表示类型为写 4表示长度为4个字



24 bl 列出所有的断点 break list
25 bc 清除断点 break clear
26 be 开启断点 break enable
27 bd禁用断点 break disable
以上提到的断点指令通过和j指令很容易形成条件断点,比如
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指定是从一个条件断点返回,是不可少的一部分。
28跟踪指令T,TA,TB,TC,WT,P,PA,PC
T指令单步执行,在源码调试状态下,可指源码的一行,根据不同的选项也可以为一行ASM指令;
TA单步跟踪到指定地址,如果没有参数将运行到断点处。
TB执行到分支指令,分支指令包括calls, returns, jumps, counted loops, and while loops
TC执行到Call指令
WT Trace and Watch Data,一条强大指令,对执行流程做Profile,执行一下看看结果吧
P,PA,PC相信不用多做解释,大家也都明白了
29源代码操作指令.,lsf,lsc,ls,l,lsp
.指令打一个源文件,可以打开一个全路径的文件,也可以通过函数地址来打开并定位到源文件中函数的


位置,如. –a myapp!main,. j://mydriver//mydriver.c
lsf指定一个源文件为当前源文件,使用lsc可显示当前指定的源文件ls可显示源文件的代码。Lsf可以使


用全路径,如果源路径已经设置,也可以直接指定源文件名称。如lsf mydriver.c,lsf 


j://mydriver//mydriver.c
lsc显示当前源文件
ls显示当前源文件的代码,如ls 200显示第200行
l 用于设置源文件选项
lsp 设置源文件行在调试时显示范围比如,
显示当前行的前50,后50,lsp 100
但通常使用Windbg时,可以直接用Ctrl+O来打开并查看源文件
30 查询符号
kd> x nt!KeServiceDescriptorTable* 
8046e100 nt!KeServiceDescriptorTableShadow = 
8046e0c0 nt!KeServiceDescriptorTable = 
kd> ln 8046e100 
(8046e100) nt!KeServiceDescriptorTableShadow | (8046e140) nt!MmSectionExtendResource 
Exact matches: 
nt!KeServiceDescriptorTableShadow = 
31!gle 查看LastError值
32指定进制的形式0x/0n/0t/y 分别表示 16/10/8/2进制
? 0x12345678+0n10 
Evaluate expression: 305419906 = 12345682 
33!sym noice/quiet symbol prompts开关
34.srcpath 设置源代码的路径
35dv查看本地变量
36!teb 显示当前线程的执行块(execution block)
37!peb 显示当前进程的执行块(execution block)
38ln[Address] 显示当前地址上的对象类型
39!locks 显示死锁
40!handle可以获取整个进程或者某一个handle的详细信息
首先运行以下!handle,可以看到当前进程的每个一个handle的类型,以及统计信息
0:002>!handle
Handle 4
Type key
Handle c
Type keyEvent
…….
然后找到一个key,查看详细信息
0:001>!handle 4 f
就会列出这个handle的详细信息。
41!htrace命令检查操作句柄的历史记录
!htrace命令可以打印出指定的handle的最近几次调用堆栈
0:001>!htrace 384
42!cs列出CriticalSection的详细信息
43!threadpool能看到完成端口,线城池工作线程和timer回调占线程池的情况
44.time 可以看到进程跑了多长时间
45 !dso 查看当前线程中有哪些对象,分析泄露时用到
46.dump保存进程的dump文件
Dump文件是进程的内存镜像,
可当在调试器中打开dump文件时,使用上面的命令检查,看到的结果跟用调试检查进程看到的一样
.dump /ma c://testdump.dmp
这个命令把当前进程的镜像保存为c://testdump.dmp,其中/ms参数表示dump的文件应该包含进程的完整


信息。
在windbg中,通过file—open---open Crash dump菜单打开dump文件进行分析。打开文件后,运行调试


命令看到的信息和状态就是dump文件保存时进程的状态。通过dump文件能够方便的保存发生问题时进程


的状态,方便事后分析。
========

Windbg设置条件断点



条件断点(condition breakpoint)的是指在上面3种基本断点停下来后,执行一些自定义的判断。
  在基本断点命令后加上自定义调试命令,可以让调试器在断点触发停下来后,执行调试器命令。每个


命令之间用分号分割。
语法格式如:
0:000&gt; bp Address "j (Condition) 'OptionalCommands'; 'gc' "
0:000&gt; bp Address ".if (Condition) {OptionalCommands} .else {gc}"
这两条是等价的.
当然
.if
{
}
.else
{
}
更好理解.
0:000&gt; bp `mysource.cpp:143` "j (poi(MyVar)&gt;0n20) ''; 'gc' " 
0:000&gt; bp `mysource.cpp:143` ".if (poi(MyVar)&gt;0n20) {} .else {gc}"
若MyVar大于20则不stop,
否则stop下来进行调试.
MyVar符号表示符号所在的内存地址,而不是符号的数值,相当于C语言中的 &操作符的作用。Windbg命


令poi的作用是取这个地址上的值,相当于C语言中的*操作符.因此这里取得MyVar的值.
伪寄存器,帮助保存调试的中间信息
考虑这样的情况,如果要记录某一个函数被执行了多少次,应该怎么做?简单的做法就是修改代码,在


对应的函数入口做记录。可是,如果要记录的函数是系统API呢?
设置寄存器   条件断点
当eax内的值为0xa3时断点Sop. 没问题,Hah.
0:000&gt; bp mydriver!myFunction "j @eax = 0xa3  '';'gc'" 
0:000&gt; bp mydriver!myFunction ".if @eax = 0xa3  {} .else {gc}"
但以下就不一定了,当eax中人值为0xc0004321时,
不一定会断下来.
为什么呢?
原因是内核态时,MASM会对EAX中的值进行符号扩展.
那么0xc0004321  会变成0xFFFFFFFFc0004321 
这样当然断不下来啦。
0:000&gt; bp mydriver!myFunction "j @eax = 0xc0004321  '';'gc'" 
0:000&gt; bp mydriver!myFunction ".if @eax = 0xc0004321  {} .else {gc}"
如何处理呢?看看下面就知道了.
0:000&gt; bp mydriver!myFunction "j (@eax & 0x0`ffffffff) = 0x0`c0004321  '';'gc'" 
0:000&gt; bp mydriver!myFunction ".if (@eax & 0x0`ffffffff) = 0x0`c0004321  {} .else {gc}"
爽吧,高位清0!
下面的命令可以统计VirtualAllocEx被执行了多少次:
bp /1 /c @$csp @$ra;g
bp kernel32!VirtualAllocEx "r $t0=@$t0+1;.printf \"function executes: %d times \",@


$t0;.echo;g"
这里用到的$t0就是Windbg提供的伪寄存器。可以用来存储中间信息。这里用它来存储函数执行的次数。


r命令可以用来查看,修改寄存器(CPU寄存器和Windbg的伪寄存器都有效)的值。随便挑一个繁忙的进


程,用这个命令设定断点后观察:
0:009&gt; bp kernel32!VirtualAllocEx "r $t0=@$t0+1;.printf 
\"function executes: %d times \",@$t0;.echo;g"
0:009&gt; g
function executes: 1 times
function executes: 2 times 
function executes: 3 times 
function executes: 4 times

========
相关推荐
<p class="course_target sub-content">通过本课程学习,可以快学习长为VC++调试高手 ,快速提升软件开发效率,事半功倍</p> <p class="sub-title">课程简介:</p> <p><span style="font-size: 24px;"><strong> </strong></span></p> <p><span style="font-size: 24px;"><strong>在C++开发程序的过程中,您是否经常遇到过这些问题?</strong></span></p> <p><span style="font-size: 20px;"><strong> </strong></span></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*程序运行结果不正确,但是很难找到原因</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*多线程死锁,但是也很难找到问题所在</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*程序运行时间长了,内存居高不下</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*在自己的机器上运行正常,在测试的机器上就出问题</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*调试版运行正常,但是发布出去就运行不正常</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*偶尔还需要修复Linux程序的bug,但是却没有Linux环境</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;">*也偶尔遇到程序崩溃了,却没有保留现场,无从下手解决问题</span></strong></p> <p><strong><span style="font-size: 20px; color: #4f81bd; text-decoration: none;"> </span></strong></p> <p> </p> <p><span style="font-size: 20px; color: #00b050;"><strong>没有关系,通过本课程的学习,这些问题都会迎刃而解!</strong><strong>一路绿灯,从此告别加班与熬夜!</strong></span><span style="font-size: 20px;"><strong><br /></strong></span></p> <p><span style="font-size: 20px; color: #00b050;"><strong><img title="1616244269571548.png" src="https://s2.51cto.com/images/20210320/1616244269571548.png" alt="图片.png" /></strong></span></p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页