windbg - thawk/wiki GitHub Wiki
- 1. 基本概念
- 2. windbg在64位系统载入32位程序的dump时看不到堆栈信息
- 3. 表达式
- 4. 分析
- 5. 符号表相关
- 6. 注释
- 7. 调用栈相关
- 8. 处理线程
- 9. 查看变量和内存的值
- 10. 内存相关
- 11. 断点
-
12. 调试命令
- 12.1. .restart
- 12.2. g (Go)
- 12.3. gc (Go from Conditional Breakpoint)
- 12.4. gh (Go with Exception Handled)
- 12.5. gn (Go with Exception Not Handled)
- 12.6. gu (Go Up)
- 12.7. p (Step)
- 12.8. pa (Step to Address)
- 12.9. pc (Step to Next Call)
- 12.10. pct (Step to Next Call or Return)
- 12.11. ph (Step to Next Branching Instruction)
- 12.12. pt (Step to Next Return)
- 12.13. t (Trace)
- 12.14. ta (Trace to Address)
- 12.15. tb (Trace to Next Branch)
- 12.16. tc (Trace to Next Call)
- 12.17. tct (Trace to Next Call or Return)
- 12.18. th (Trace to Next Branching Instruction)
- 12.19. tt (Trace to Next Return)
- 12.20. wt (Trace and Watch Data)
- 13. 常用技巧
- 14. 异常分析技巧
- 15. 配置
- 16. 脚本
- 17. FAQ
- 18. 参考文章
在内核模式调试时,同时有很多进程、线程有时也有很多用户会话在运行。所以,例如"虚拟地址 0x80002000"或" eax 寄存器"这样的短语都是不明确的。必须指定这些短语所在的上下文,才可以被理解。
调试器在调试时有5种不同的上下文可以设置:
-
会话上下文(session context指定默认的用户会话。 (该上下文仅在Microsoft Windows XP和之后的Windows中具有。这些系统允许同时有多个登陆会话共存。)
-
进程上下文(process context) 用于决定调试器如何解释虚拟地址。
-
用户模式地址上下文(user-mode address context)几乎从来不会直接设置。该上下文在改变进程上下文时自动设置。
-
寄存器上下文(register context)决定调试器如何 解释寄存器,也用于控制堆栈跟踪的结果。该上下文也称为线程上下文,尽管这个术语不是完全准确。显式上下文( explicit context)也是一种寄存器上下文。如果指定了显式上下文,则它会取代当前的寄存器上下文。
-
局部上下文(local context)决定调试器如何解释局部变量。该上下文也称为作用域。
在Windows XP和之后版本的Windows中,同时可以运行多个登陆会话。每个登陆会话都有它自己的进程。
!session扩展命令显示所有登陆会话或改变当前会话上下文。
当!sprocess和!spoolused 扩展命令输入的会话号为"-2"时,会用到会话上下文。
会话上下文改变时,进程上下文会自动切换到该会话的活动进程。
每个进程都有自己的页目录,用于记录虚拟地址如何映射到物理地址的信息。当一个进程中的任何线程执行时,Windows操作系统使用页目录来解释虚拟地址。
用户模式调试时,当前进程决定了进程上下文。调试器命令、扩展命令和调试信息窗口都会使用当前进程的页目录来解释虚拟地址。
在内核模式调试时,使用.process (Set Process Context) 命令来设置进程上下文。该命令用来选择解释虚拟地址时使用哪个进程的页目录。设置了进程上下文滞后,任何用到地址的命令中都使用该上下文。也可以在该地址中设置断点。通过使用.process 和/i 选项来指定入侵式调试,在内核调试器里面也可以设置用户空间的断点。
也可以在内核空间函数上使用指定进程的断点来在内核调试器中设置用户模式断点。设置这种断点并等待切换到适合的上下文。
用户模式地址上下文是进程上下文的一部分。一般来说不用直接设置用户模式地址上下文。如果设置了进程上下文,用户模式地址上下文会自动改变为使用该进程关联的页表。但是,在基于Itanium的处理器上,一个进程可能拥有多于一个的页目录。这种情况下,可以使用 .context (Set User-Mode Address Context) 命令来改变用户模式地址上下文。
在内核模式调试时设置了进程上下文之后,它会一直保持到使用另一个.process命令改变为止。用户模式地址上下文也会保持到使用.process 或.context 命令改变为止。这些上下文在目标机运行时都不会改变,也不会因为改变寄存器上下文和局部上下文而受影响。
每个线程都有它自己的寄存器值。当线程执行时这些值保存在CPU寄存器中;当其他线程运行时,则保存在内存中。
用户模式调试时,当前线程一般决定了寄存器上下文。所有调试器命令、扩展命令和调试信息窗口中对寄存器的引用都根据当前线程的寄存器来解释。
使用下面的方法之一,在用户模式调试时可以将寄存器上下文改变为当前线程之外的值:
内核模式调试时,可以使用包括下面一些命令在内的多种命令来控制寄存器上下文:
这些命令不改变CPU寄存器的值,调试器是从内存位置中获得指定的寄存器上下文。实际上,调试器只能找到已保存的寄存器值。(其他值是动态设置的并且不会保存下来。重建堆栈跟踪使用已保存的值就足够了。)
寄存器上下文设置之后,任何使用寄存器值的命令都会使用新的寄存器上下文,例如k (Display Stack Backtrace)和r (Registers)。
但是,当调试多处理器的计算机时,一些命令可以指定处理器。 (关于这些命令的更多信息,查看多多处理器语法。)如果为一条命令指定了处理器,该命令使用指定的处理器上的激活线程的寄存器上下文,而不是当前寄存器上下文,即使指定的处理器就是活动处理器。
同样,如果寄存器上下文和当前处理器模式设置不匹配,这些命令会产生错误或无意义的输出。要避免这种输出错误,在将处理器模式设置为和寄存器上下文匹配之前,这些依赖寄存器状态的命令都会失败。使用.effmach (Effective Machine) 命令改变处理器模式。
改变寄存器上下文也会改变局部上下文。所以,寄存器上下文会影响局部变量的显示。
如果任何程序发生执行、单步或跟踪的事件,寄存器上下文会立即重设为和程序计数器的位置匹配。在用户模式下,当前进程或线程改变时,寄存器上下文也会重设。
寄存器上下文也作用于堆栈跟踪,因为堆栈跟踪是从堆栈寄存器(基于x86处理器上的esp或基于Itanium处理器上的sp)的指向的位置开始的。如果寄存器上下文设置为非法或不可访问的值,就不能进行堆栈跟踪。
可以使用.apply_dbp (Apply Data Breakpoint to Context)命令在指定的寄存器上下文中设置数据断点。
程序运行时,局部变量的意义由程序计数器来决定,因为这些变量的作用域仅在定义它们的函数内部。
进行用户模式或内核模式调试时,调试器使用当前函数的作用域(堆栈上的当前帧)作为局部上下文。使用.frame (Set Local Context)命令或者在Calls window中双击需要的帧来改变局部上下文。
在用户模式调试时,局部上下文总是当前线程的堆栈回溯中的一帧。在内核模式调试时,局部上下文总是当前寄存器上下文的线程的堆栈回溯中的一帧。
同一时刻只能使用一个堆栈帧作为局部上下文。位于其他帧中的局部变量不能被访问。
下面这些事件发生时,局部上下文会被重置:
任何程序执行、单步或跟踪的动作 任何命令中使用线程限定符(~) 对寄存器上下文的任何改变
!for_each_frame扩展命令可以对堆栈中的每一个帧使用一条单独的命令。该命令为每一个帧改变局部上下文,执行命令,然后将局部上下文设置为原始值。
!wow64exts.sw
- !sw
-
[!wow64exts.sw] 在多个mode:x86、x64上进行循环切换 注:如果win32程序在x64的mode下,会看到地址是64位的
- !k
-
[!wow64exts.k] 打印32位、64位堆栈
- !k 5
-
[!wow64exts.k 5] 打印32位、64位堆栈,栈帧个数为5
- !info
-
[!wow64exts.info] 输出wow64相关的PEB、TEB和TLS基本信息
- !r
-
[!wow64exts.r] 输出处理器当前上下文信息
- !r dumpTest!main
-
[!wow64exts.r dumpTest!main] 输出main函数地址的上下文信息
显示保存在指定地址的上下文信息。也可以设置寄存器上下文
.cxr [Options] [Address] Options 可以是下面这些选项的任意组合: /f Size 强制上下文的大小的字节数等于Size。这在当上下文和实际的目标不匹配时有用 — 例如,当通过WOW64调试在x64目标上使用x86上下文时。如果指定了非法的或矛盾的大小,会显示 "Unable to convert context to canonical form" 错误。 /w 将当前上下文写入内存,并显示写入的内存的地址。 Address 系统上下文记录的地址(指向CONTEXT结构)。省略该地址则不会显示任何上下文记录的信息,但是会重置寄存器上下文。
除了使用ld和.reload命令直接加载符号文件,某些使用符号的命令也可以触发调试器来加载符号,如:栈回溯命令(k*)和反汇编命令(u)等。
值得说明的是,windbg缺省使用的是懒惰式符号加载策略,当它收到模块加载事件时,它通常是不会加载符号的,符号状态显示为deferred(延迟加载)。
- !sym noisy
-
激活详细符号加载(noisy symbol loading)显示
- !sym quiet
-
禁止详细符号加载显示
- ld *
-
为所有模块加载符号
- ld kernel32
-
加载kernel32.dll的符号
- .reload
-
为所有已加载模块载入符号信息
- .reload /i
-
重新加载不匹配符号的模块【dmp文件没有对应的pdb时使用】
- .reload /i TGame.exe
-
重新加载不匹配符号的TGame.exe
- .reload /f /v
-
f:强制立即模式(不允许延迟载入) v:详细模式
- .reload /f @"c:\windows\System32\verifier.dll"
-
为指定模块加载符号信息
- .reload /f TGame.exe
-
为TGame.exe加载符号信息
- .reload /u TGame.exe
-
卸载TGame.exe及其载符号信息
- .chain
-
显示已经加载进来的模块
- x *!
-
列出所有模块对应的符号信息
- lm
-
列出所有模块(加载和未加载)对应的符号信息
- lmv
-
列出所有模块(加载和未加载)对应的符号信息
- lmvm ntdll
-
查看ntdll.dll的详细信息(注意exe、dll等都不要带后缀名)
- x ConsoleTest!*
-
列出ConsoleTest模块中的所有符号
- x ConsoleTest!add*
-
列出ConsoleTest模块中的所有add开头的符号
- x /t /v ConsoleTest!*
-
带数据类型、符号类型和大小信息,列出ConsoleTest模块中的所有符号
- x kernel32!LoadLib
-
列出kernel32模块中所有含LoadLib字样的符号
DUMPBIN /PDBPATH:VERBOSE filename.exe
!lmi ntdll
!chksym ntdll_777a0000
!itoldyouso ntdll_777a0000
{可执行文件名}.pdb{PDB_SIG}{AGE}{可执行文件名}.pdb
如mdgw文件:
0:016> !chksym mdgw *** ERROR: Module load completed but symbols could not be loaded for mdgw.exe mdgw.exe Timestamp: 568B39BE SizeOfImage: 5B4000 pdb: d:\jenkins\jobs\stsv5_cs_trunk_release\workspace\bin\marketdata\mdgw\msvc-11.0\release\debug-symbols-on\link-static\threading-multi\mdgw.pdb pdb sig: 6B6C2994-DBBD-4C6F-8C94-D26EB40B7523 age: C
应存放在symbols目录下的 mdgw.pdb\6B6C2994DBBD4C6F8C94D26EB40B7523c\mdgw.pdb
x命令显示所有上下文中匹配指定模板的符号。
-
Options
指定符号搜索选项。可以使用下面这些选项的一个或者多个:
-
/t
如果知道数据类型的话,显示每个符号的数据类型。
-
/v
显示每个符号的符号类型(局部、全局、参数或未知) 。该选项还显示每个符号的大小。函数符号的大小是该函数在内存中的大小。其他符号的大小是该符号对应的数据类型的大小。该大小总是以字节为单位并且以16进制形式显示。
-
/s Size
仅显示以字节为单位的大小等于Size的符号。函数符号的大小是该函数在内存中的大小。其他符号的大小是该符号对应的数据类型的大小。未知大小的符号总是被显示出来。Size不能为0。
-
/q
以引用的形式显示符号名。
-
/p
显示函数名和它的参数时省略左括号前的空格。这种类型的输出使得将x的显示复制到其他地方时更容易。
-
/f
显示函数的数据大小。
-
/d
显示数据的数据大小。
-
/a
按照地址以升序的形式排序输出。
-
/A
按照地址以降序的形式排序输出。
-
/n
按照名字以升序的形式排序输出。
-
/N
按照名字以降序的形式排序输出。
-
/z
按照大小以升序的形式排序输出。
-
/Z
按照大小以降序的形式排序输出。
-
-
Module
指定要搜索的模块。该模块可以是.exe、 .dll、.sys 文件。 Module 可以包含各种通配符和修饰符。该语法的更多信息,查看字符串通配符语法。
-
Symbol
指定符号必须包含的模板。Symbol可以包含各种通配符和修饰符。该语法的更多信息,查看字符串通配符语法。
由于该模板用来匹配符号,这种匹配是不区分大小写的,并且开头的下划线(_) 相当于任意多个下划线。在Symbol中可以加入空格,所以可以不通过通配符来指定包含空格的符号名 (例如 "operator new" 或"Template<A, B>")。
x *!
x /D /f ConsoleApplication1!*
x命令显示指定模块(Module)的公有符号中匹配指定模板(Symbol)的项。例如,下面的命令查找MyModule 中所有包含字符串"spin"的符号。
0:000> x mymodule!spin ---
下面的命令在MyModule 中快速定位"DownloadMinor" 和"DownloadMajor"符号。
0:000> x mymodule!downloadm??or ---
也可以使用下面的命令显示MyModule 中所有符号。
0:000> x mymodule!* ---
前一个命令也会强制调试器重新加载MyModule 的符号信息。如果要重新加载该模块得符号但是不显示那么多信息,可以使用下面的命令。
0:000> x mymodule!start ---
包含"start"的符号较少。因此,上面的命令会显示一些表明命令运行的输出,但是避免了像x mymodule!*一样很长的输出。
显示中包含每个符号的起始地址和完整的符号名。如果符号是一个函数名,还会包含参数类型的列表。如果符号是全局变量,则还会显示它的当前值。
这是另一个x命令的特殊用法。用来显示当前上下文所有局部变量的地址和名字。
0:000> x * ---
Note
|
大多数情况下如果没有加载私有符号,则不能访问局部变量。关于这种情况的更多信息,查看dbgerr005: Private Symbols Required。要显示局部变量的值,使用dv (Display Local Variables)命令。 |
下面的示例说明附加的x选项。使用/v选项时,输出的第一列显示符号类型 (local、 global、 parameter、 function或unknown)。第二列是符号地址。第三列是符号大小,以字节为单位。第四列是模块名和符号名。某些情况下,输出后面跟了一个等号 (=)和符号的数据类型。符号的来源 (公有的或完整的符号信息)也会显示出来。
kd> x /v nt!CmType* global 806c9e68 0 nt!CmTypeName = struct _UNICODE_STRING [] global 806c9e68 150 nt!CmTypeName = struct _UNICODE_STRING [42] global 806c9e68 0 nt!CmTypeName = struct _UNICODE_STRING [] global 805bd7b0 0 nt!CmTypeString = unsigned short *[] global 805bd7b0 a8 nt!CmTypeString = unsigned short *[42] ---
上例中,是以16进制给出大小,而数据类型是以10进制形式。因此,上例的最后一行中,数据类型是42个unsigned short整数的数组。数组大小是42*4 = 168,而168的16进制是0xA8。
可以使用/s Size选项来显示大小的字节数为指定值的符号。例如,可以限制上面的命令只显示相应对象的大小为0xA8的符号。
kd> x /v /s a8 nt!CmType* global 805bd7b0 a8 nt!CmTypeString = unsigned short *[42] ---
/t选项使得调试器显示每个符号数据类型的信息。注意对于很多符号,该信息没有/t选项也会显示出来。使用/t时,这些符号的符号信息会被显示两次。
0:001> x prymes!n* 00427d84 myModule!nullstring = 0x00425de8 "(null)" 0042a3c0 myModule!_nstream = 512 Type information missing error for _nh_malloc 004021c1 myModule!MyStructInstance = struct MyStruct 00427d14 myModule!_NLG_Destination = <no type information> ---
0:001> x /t prymes!n* 00427d84 char * myModule!nullstring = 0x00425de8 "(null)" 0042a3c0 int myModule!_nstream = 512 Type information missing error for _nh_malloc 004021c1 struct MyStruct myModule!MyStructInstance = struct MyStruct 00427d14 <NoType> myModule!_NLG_Destination = <no type information> ---
下面的例子说命令过滤模块notepad.exe中的函数时对/f开关的使用。
0:000> x /f /v notepad!main prv func 00000001`00003340 249 notepad!WinMain (struct HINSTANCE *, struct HINSTANCE *, char *, int) prv func 00000001`0000a7b0 1c notepad!WinMainCRTStartup$filt$0 (void) prv func 00000001`0000a540 268 notepad!WinMainCRTStartup (void) ---
k*命令显示给定线程的调用堆栈,以及其他相关信息。
[~Thread] k[b|p|P|v] [n] [f] [L] [FrameCount] [~Thread] k[b|p|P|v] [n] [f] [L] = BasePtr [FrameCount] [~Thread] k[b|p|P|v] [n] [f] [L] = BasePtr StackPtr InstructionPtr [~Thread] kd [WordCount] Thread 指定要显示调用堆栈的线程。如果省略该参数,则显示当前线程的堆栈。线程语法的更多信息,查看线程语法。仅有用户模式可以指定线程。 Processor 指定要显示调用堆栈的处理器。处理器语法的更多信息,查看多处理器语法。仅在内核模式下可以指定处理器。 b 显示传递给堆栈回溯中的每个函数的前三个参数。 p 显示传递给堆栈回溯中的每个函数的所有参数。参数列表包含参数的数据类型、名字和值。p命令是区分大小写的。使用该参数需要完整符号信息。 P 和p参数一样,显示传递给堆栈回溯中的每个函数的所有参数。但是,使用P ,函数参数在第二行中显示,而不是作为数据的结尾在行末显示。 v 显示帧指针省略信息(FPO)。在x86处理器上,还显示调用约定(即__stcdall、__cdecl等等 — 译者注)的信息。 n 显示帧号码。 f 显示临近的帧之间的距离。该距离是在实际堆栈中分隔两个帧的字节数。 L 隐藏源码行信息。L区分大小写。 FrameCount 指定要显示的堆栈帧号码。如果没有使用n (Set Number Base)命令改变基数的话,需要使用16进制数。如果没有使用.kframes (Set Stack Length)命令改变的话,它的默认值是20(0x14)。 BasePtr 指定堆栈回溯的基指针。BasePtr 参数仅在命令后有等号(=)时可用。在x86处理器上,BasePtr 之后可以添加一个参数(被当作FrameCount 参数)或两个参数(被当作StackPtr 和InstructionPtr 参数)。 StackPtr (仅x86处理器) 指定用于堆栈回溯的堆栈指针。如果省略掉StackPtr 和InstructionPtr ,命令会使用esp寄存器中指定的堆栈指针和eip中指定的指令指针。 InstructionPtr (仅x86处理器) 指定用于堆栈回溯的指令指针。如果省略掉StackPtr 和InstructionPtr ,命令会使用esp寄存器中指定的堆栈指针和eip中指定的指令指针。 WordCount 指定堆栈中要转储的DWORD_PTR 值的个数。如果没有使用.kframes (Set Stack Length)命令修改的话,默认值为20 (0x14)。
dds、dps和dqs命令显示给定范围内存的内容。该内存被假定为符号表中的一连串地址。相应的符号也会被显示出来。
dds [Options] [Range] dqs [Options] [Range] dps [Options] [Range] Options 指定一个或多个显示选项。除了不能包含一个以上/p*选项之外,可以包含任何下面的这些选项: /c Width 指定要显示的列的数量。如果省略掉,默认的列数由显示类型决定。由于这些命令显示符号的方法,一般情况下最好使用只有一个数据列的默认值。 /p (仅内核模式) 使用物理内存地址进行显示。Range 指定的范围将被当作物理地址而不是虚拟内存。 /p[c] (仅内核模式) 除了读取高速缓冲存储器(cached memory)中的内存之外,和/p一样。必须包含c两边的中括号。 /p[uc] (仅内核模式) 除了读取非缓存的内存(uncached memory)之外和/p一样。必须包含uc两边的中括号。 /p[wc] (仅内核模式) 除了会读取写聚合内存(write-combined memory)之外,和/p一样。必须包含wc两边的中括号。 Range 指定要显示的内存区域。语法的详细信息,查看地址和地址区域语法。如果省略掉Range ,命令将会从上一条内存查看命令结束的位置开始。如果Range 省略掉并且之前没有执行过内存显示命令,则从当前指令指针位置开始显示。如果只是简单的给定一个地址,默认的范围长度为128字节。
0:050:x86> dps 07eedb00 07eedb54 07eedb00 07eedb18 07eedb04 777cdea3 ntdll_777a0000!RtlFreeHeap+0x105 07eedb08 0052d630 07eedb0c 0052d62c 07eedb10 0052d608 07eedb14 0052d600 07eedb18 07eedb2c 07eedb1c 763a14d1 kernel32!HeapFree+0x14 07eedb20 00460000 07eedb24 00000000 07eedb28 0052d608 07eedb2c 07eedb40 07eedb30 720bdcc2 MSVCR110!free+0x1a 07eedb34 00460000 07eedb38 00000000 07eedb3c 0052d608 07eedb40 07eedba8 07eedb44 01476aa5 mdgw!boost::log::v2s_mt_nt5::record_view::private_data::destroy+0x45 [d:\jenkins\jobs\stsv5_cs_trunk_release\workspace\lib\cppf\common\3rd\boost_1_59_0\libs\log\src\core.cpp @ 133] 07eedb48 0052d608 07eedb4c 0b8b1eb8 07eedb50 0b8b1eb8 07eedb54 0b8b1eb8
Note
|
在结果中,看到的函数名为调用者,其下方的几个参数是这个函数调用下一层时push到stack上的参数,并非调用这个函数的参数。 如上面的例子中,07eedb30处看到的free是free调用HeapFree的点,07eedb34-07eedb3c是HeapFree的三个参数。free本身的参数在07eedb48处。 |
指定使用哪个局部上下文(作用域)来解析局部变量,或者显示当前的局部上下文。
.frame [/r] [FrameNumber] .frame [/r] = BasePtr [FrameIncrement] .frame [/r] = BasePtr StackPtr InstructionPtr 参数 /r 显示指定的局部上下文的寄存器和其他信息。 FrameNumber 指定想使用的局部上下文的帧序号。如果该参数为0,则命令指定当前帧。如果省略该参数,则显示当前的局部上下文。 BasePtr 如果在命令名(.frame)后添加了一个等号(=),指定要用来确认堆栈帧的堆栈回溯基指针。在基于x86的处理器上,在BasePtr后可以添加一个(作为 FrameIncrement)或两个(作为InstructionPtr 和StackPtr)附加的参数。 FrameIncrement (仅x86处理器) 指定要越过基指针之后的帧的数目。例如,如果基指针0x0012FF00是第三帧的地址,命令.frame 12ff00 相当于 .frame 3,而.frame 12ff00 2 相当于 .frame 5。 StackPtr (仅x86处理器) 指定用来确认帧的堆栈回溯的堆栈指针。如果省略StackPtr 和InstructionPtr,调试器会使用esp寄存器中的堆栈指针和eip中的指令指针。 InstructionPtr (仅x86处理器) 指定用来确认帧的堆栈回溯的指令指针。如果省略StackPtr 和InstructionPtr,调试器会使用esp寄存器中的堆栈指针和eip中的指令指针。
显示指定线程或当前进程中的所有线程的信息。
0:001> ~ 该命令也显示所有线程。 0:001> ~* 下面的命令显示当前活动线程。 0:001> ~. 下面的命令显示原始的产生异常的线程(或调试器附加到进程时活动的线程)。 0:001> ~# 下面显示号码为2的线程。 0:001> ~2
(?) 命令可用于计算和显示一个表达式的值。
- NOTE
-
单个问号(?)显示命令帮助。? expression 命令计算给定表达式的值。
? Expression 参数 Expression 指定要计算的表达式。
双问号(??)命令根据C\++表达式规则来计算和显示表达式的值。
?? Expression 参数 Expression 指定要求值的pass:[C++]表达式。语法的更多信息,查看pass:[C++]数值和操作符。
dt命令显示局部变量、全局变量或数据类型的信息。它也可以仅显示数据类型。即结构和联合(union)的信息。
dt [-DisplayOpts] [-SearchOpts] [module!]Name [[-SearchOpts] Field] [Address] [-l List] dt [-DisplayOpts] Address [-l List] dt -h
[Processor] dt [-DisplayOpts] [-SearchOpts] [module!]Name [[-SearchOpts] Field] [Address] [-l List] dt [-DisplayOpts] Address [-l List] dt -h
Processor 指定运行包含需要的信息的进程的处理器。更多信息,查看多处理器语法。处理器只能在内核模式时指定。 DisplayOpts 指定下表中给定的一个或多个选项。这些选项以连字符开头。 选项 说明 -a[quantity] 在新行中显示每个数组成员和它的序号。共显示quantity 个成员。在a和quantity 间不能加入空格。如果-a后面没有跟数字,则数组中的所有成员都会显示出来。-a[quantity]开关必须紧跟在每个希望按这种方式显示的类型名或字段名后面。 -b 递归的显示块。如果被显示的结构包含子结构,它的所有层次也会被递归的展开并完整显示出来。指针仅在原始结构中显示出来,而不当作子结构展开。 -c 紧凑输出。如果可能的话将所有字段都显示到一行中。(当和-a开关一起使用时,每个数组元素都占据一行而不是被格式化为一个多行的块。) -d When used with a Name that is ended with an asterisk, display verbose output for all types that begin with Name. If Name does not end with an asterisk, display verbose output. -e 强制dt枚举类型。该选项仅在dt错误的将NAME当作一个实例而不是类型时使用。 -i 不对子类型进行缩进。 -o 省略掉结构字段的偏移值。 -p Address是一个物理地址,而不是虚拟地址。 -r[depth] 递归的转储子类型字段。如果给定了depth ,递归达到一定深度时就会停止。depth 必须是1到9之间的数字,并且在r和depth 之间不能包含空格。-r[depth] 必须紧靠在地址的前面现。 -s size 仅列举大小的字节数等于size 值的类型。-s 仅在枚举类型时有用。当指定了-s 时,-e 也被隐含的指定了。 -t 仅列举类型。 -v 详细输出。这会输出结构的总大小和字段数量这样的附加信息。当它和-y选项一起使用时,所有的符号都会被显示出来,即使他们没有任何关联的类型信息。 SearchOpts 指定下表中的一个或多个选项。这些选项以连字符开头。 选项 说明 -n 表明下个参数是一个名字。当下一个参数完全由16进制字符组成时使用,否则它会被当作一个地址。 -y 表明下一个参数是一个名字的开头,而不一定是整个名字。包含-y时,所有匹配项都会列出来,并在列表中第一个匹配项后面显示详细信息。如果没有包含-y,只有精确匹配的项会显示。 module 用于指定定义该结构的模块的可选参数。如果有一个局部变量或类型的名字和全局变量或类型的名字相同,则需要包含module 来表明它是全局变量。否则,dt命令会显示局部变量,即使和局部变量是不区分大小写的匹配而全局变量是区分大小写的匹配。 Name 指定类型或全局变量的名字。如果Name以星号(*),结尾,所有匹配项都会被列举出来。因此,dt A*会显示所有以"A"开头的数据类型、局部变量和静态变量,但是不会显示这些类型实际的实例。(如果同时使用了-v 显示选项,所有符号都会被显示出来 — 不只是具有关联的类型信息的。)也可以将NAME用一个点号(.)代替来说明想使用上一次用过的 NAME 值。如果NAME包含空格,需要用圆括号括起来。 Field 指定要显示的一个或多个字段。如果省略掉Field ,则显示所有字段。如果Field 后跟一个点号(.),则该字段的第一级子字段也会被显示出来。如果Field 后跟一系列的点号,则子字段显示的深度等于点号的个数。任何后面跟有点号的字段名都被当作一个前缀匹配,就像使用了-y搜索选项一样。如果Field后面跟一个星号(*),则它仅被当作字段的开头,而不是整个字段,并且所有匹配的字段都会被显示出来。 Address 指定要显示的结构的地址。如果省略掉Name,则必须包含Address 并且必须指定全局变量的地址。Address如果没有进行其它指示,则被当作一个虚拟地址。使用-p选项来指定物理地址。用"at" 号( @ )来指定寄存器(例如@eax)。 List 指定连接到一个链表的字段名。必须包含有Address 参数。
Note
|
|
0:000:x86> dt 0x093fa880 sscc::WanM::SslConnectionImpl m_recvBufferPtr m_recvBufferPtr. mdgw!sscc::WanM::SslConnectionImpl +0x3e4 m_recvBufferPtr : 0x087e4ac0 sscc::WanM::DoubleBufferQueue<sscc::WanM::Unit> +0x000 m_mutex : boost::mutex +0x008 m_capacity : 0x400 +0x00c m_produceBuffer : std::vector<sscc::WanM::Unit,std::allocator<sscc::WanM::Unit> > +0x018 m_consumeBuffer : std::vector<sscc::WanM::Unit,std::allocator<sscc::WanM::Unit> > +0x024 m_consumeIdx : 0
s [-[[Flags]Type]] Range Pattern s -[[Flags]]v Range Object s -[[Flags]]sa Range s -[[Flags]]su Range
s -d 0x00000000 L?0xffffffff 30c5bf9c
!heap 扩展显示堆使用信息,控制堆管理器中的断点,检测泄露的堆块,搜索堆块或者显示页堆(page heap)信息。
Note
|
Windows的Heap结构可以参见 Windows Heap相关内容。 |
- !heap -?
-
显示帮助
- !heap
-
列出Heap的地址
- !heap -h
-
列出Heap的所有Segment
- !heap -h [HeapAddr | Idx | 0]
-
列出指定Heap的所有Seqgment。0=所有Heap
- !heap -v [HeapAddr | Idx | 0]
-
验证指定Heap。0=所有Heap
- !heap -s [SummaryOptions] [HeapAddr | 0]
-
输出中包含指定Heap的摘要信息。0=所有Heap
-
SummaryOptions
可以是任意下面这些选项。SummaryOptions 不区分大小写。
- -v
-
验证所有数据块。
- -b BucketSize
-
指定存储单元(bucket)的大小。默认值为1024 bit。
- -d DumpBlockSize
-
指定存储单元(bucket)大小。
- -a
- -c
-
指示每个块的内容都应该显示出来。
-
- !heap -i [HeapAddr]
-
指定Heap的详细信息,可以用来decode _HEAP_ENTRY
- !heap -x [-v] Address
-
搜索包含指定地址的Heap。如果添加了-v ,命令会在当前进程的整个虚拟内存空间中搜索
- !heap -l
-
检查泄露的堆块
- -a
-
显示中包含指定堆的所有信息。这种情况下,大小会被四舍五入到堆的分配粒度。(运行!heap -a 选项相当于使用-h -f -m这三个选项,会需要较长时间。)
- -f
-
输出中包含指定堆的所有空闲列表项(free list entries)。
- -m
-
输出中包含指定堆的所有段条目(segment entries)。
- -t
-
使得输出重包含指定堆的标签信息(tag information)。
- -T
-
输出中包含指定堆的伪标签条目(pseudo-tag entries)。
- -g
-
输出中包括全局标签信息(global tag information)。全局标签和每个无标签的分配(untagged allocation)关联。
- -k
-
(仅x86目标) 输出中包含每个条目关联的堆栈回溯。
- !heap -b [{alloc | realloc | free} [Tag]] [BreakAddress | HeapAddr | Idx | 0]
-
在堆管理器中设置条件断点。-b 后可跟alloc、realloc或者 free,用于指定断点是否在分配、重新分配或者释放内存时激活。
如果用BreakAddress来指定块的地址,可以省略断点类型。如果指定堆地址或者堆序号,则必须包含类型和Tag参数。
- !heap -B [{alloc | realloc | free} [Tag]] [BreakAddress | HeapAddr | Idx | 0]
-
在堆管理器中移除一个条件断点。必须指定断点类型(alloc、 realloc或 free),并且必须和-b 选项使用的一样。
- !heap -flt s Size
-
限制显示出来的堆必须是指定的大小。
- !heap -flt r SizeMin SizeMax
-
限制显示出来的堆大小在指定范围内。
- !heap -stat
-
显示所有堆的使用统计。
- !heap -stat -h [HeapHandle | 0] [-grp GroupBy [MaxDisplay]]
-
显示句柄为HeapHandle的堆的使用统计。0=所有堆
-
-grp GroupBy
按照GroupBy 的指定重新排序显示。GroupBy 选项可以是下表中的值。
- A
-
根据分配大小显示使用统计。
- B
-
根据块数量显示使用统计。
- S
-
根据每次分配的总大小显示使用统计。
-
MaxDisplay
限制输出最多为MaxDisplay 行。
-
Note
|
The statistic includes AllocSize, #blocks, TotalMem for each AllocSize. |
- !heap -srch [Size] Pattern
-
显示包含指定模板的堆条目(heap entries)。
-
Pattern
要查找的内容。
-
Size
可以是下面选项中的任意一个。用于指定pattern 的大小。必须使用'-'。
- -b
-
pattern的大小是一个BYTE。
- -w
-
pattern的大小是一个WORD。
- -d
-
pattern的大小是一个DWORD。
- -q
-
pattern的大小是一个QWORD。
如果不指定上面任何一个,则会假定pattern的大小和机器的指针大小一致。
-
Note
|
使用任何!heap -p 扩展命令之前,目标进程中必须已经启用了页堆。 |
- !heap -p -?
-
显示页堆帮助(page heap help),包括堆块的图表。
- !heap -p
-
Summary for NtGlobalFlag, HeapHandle + NormalHeap list **
- !heap -p -h HeapHandle
-
显示句柄为HeapHandle的页堆的详细信息
- !heap -p -a UserAddr
-
显示块中包含Address的页堆。会包含该地址和完整的页堆块的关系的详细信息,如是否该地址是页堆的一部分、它在块中的偏移,以及这个块已经被分配还是空闲的。在可能时还会包含stack backtrace。使用该选项时,显示的大小是堆分配粒度的倍数。
- !heap -p -all
-
显示所有页堆的详细信息。
- !heap -t[c|s] [Traces]
-
显示大量使用堆的用户(heavy heap users)的纪录(collected traces)。Traces指定要显示的纪录数量,默认值为4。如果有比指定的数量更多的纪录,则显示前面的部分纪录。如果使用-t 或者-tc ,则纪录以使用记数(count usage)排序。如果使用-ts ,则纪录以大小排序。 (-tc 和-ts 选项仅在Windows XP中支持,-t选项在Windows XP和之前的版本中都支持。)
- !heap -fi [Traces]
-
显示最近的故障注入纪录(fault injection traces)。 Traces 指定要显示的熟练,默认值为4。
!heap [HeapOptions] [ValidationOptions] [Heap]
可以是下面这些选项中的一个。ValidationOptions 区分大小写。
- -D
-
禁止指定堆的调用时验证(validate-on-call)。
- -E
-
启用指定堆的调用时验证(validate-on-call)。
- -d
-
禁用指定堆的堆检查(heap checking)。
- -e
-
启用指定堆的堆检查(heap checking)。
- HEAP_ENTRY
-
Heaps store allocated blocks in contiguous Segments of memory, each allocated block starts with a 8-bytes header followed by the actual allocated data. The HEAP_ENTRY column is the address of the beginning of the header of the allocated block.
- Size
-
The heap manager handles blocks in multiple of 8 bytes. The column is the number of 8 bytes chunk allocated. In your sample, 0044 means that the block takes 0x220 bytes (0x44*8).
- Prev
-
Multiply per 8 to have the negative offset in bytes to the previous heap block.
- Flags
-
This is a bitmask that encodes the following information
0x01 - HEAP_ENTRY_BUSY 0x02 - HEAP_ENTRY_EXTRA_PRESENT 0x04 - HEAP_ENTRY_FILL_PATTERN 0x08 - HEAP_ENTRY_VIRTUAL_ALLOC 0x10 - HEAP_ENTRY_LAST_ENTRY
- UserPtr
-
This is the pointer returned to the application by the HeapAlloc (callbed by malloc/new) function. Since the header is always 8 bytes long, it is always HEAP_ENTRY +8.
- UserSize
-
This is the size passed the HeapAlloc function.
- state
-
This is a decoding of the Flags column, telling if the entry is busy, freed, last of its segment, …
Be aware that in Windows 7/2008 R2, heaps are by default using a front-end named LFH (Low fragmented heap) that uses the default heap manager to allocate chunks in which it dispatched user allocated data. For these heaps, UserPtr and UserSize will not point to real user data. The output of !heap -s displays which heaps are LFH enabled.
- internal
-
对应
HEAP_ENTRY_VIRTUAL_ALLOC
,
-
Select "Create user mode stack trace database" for your image in GFlags (gflags.exe /i +ust)
-
From WinDbg’s command line do a !heap -p -a , where is the address of your allocation.
is usually the address returned by HeapAlloc:
int AllocSyze = 0x100000; // == 1 MB BYTE* pUserAddr = (BYTE*) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, AllocSyze);
Often any address in the range [UserAddr....UserAddr+AlloSize] is also a valid parameter:
!heap -p -a [UserAddr....UserAddr+AlloSize]
-
While !heap -p -a will dump a call-stack, no source information will be included.
-
To get source information you must additionally enable page heap in step 1 (gflags.exe /i +ust +hpa)
-
Do a dt ntdll!_DPH_HEAP_BLOCK StackTrace , where is the DPH_HEAP_BLOCK address retrieved in step 3.
-
Do a dds ", where is the value retrieved in step 5.
-
Note that dds will dump the stack with source information included.
-
Select "Create user mode stack trace database" and "Enable page heap" for your image in GFlags (gflags.exe /i +ust +hpa)
-
From WinDbg’s command line do a !heap -p -h , where is the value returned by HeapCreate. You can do a !heap -stat or !heap -p to get all heap handles of your process.
-
Alternatively you can use !heap -p -all to get addresses of all _DPH_HEAP_ROOT’s of your process directly.
-
-
Do a dt ntdll!_DPH_HEAP_ROOT CreateStackTrace , where is the address of a _DPH_HEAP_ROOT retrieved in step 2
-
Do a dds , where is the value retrieved in step 3.
From WinDbg’s command line do a !address –summary.
If RegionUsageHeap or RegionUsagePageHeap are growing, then you might have a memory leak on the heap. Proceed with the following steps.
-
Enable "Create user mode stack trace database" for your image in GFlags (gflags.exe /i +ust)
-
From WinDbg’s command line do a !heap -stat, to get all active heap blocks and their handles.
-
Do a !heap -stat -h 0. This will list down handle specific allocation statistics for every AllocSize.
-
For every AllocSize the following is listed: AllocSize, #blocks, and TotalMem. Take the AllocSize with maximum TotalMem.
-
Do a !heap -flt s . =AllocSize that we determined in the previous step. This command will list down all blocks with that particular size.
-
Do a !heap -p -a to get the stack trace from where you have allocated that much bytes. Use the that you got in step 4.
-
To get source information you must additionally enable page heap in step 1 (gflags.exe /i +ust +hpa)
-
Do a dt ntdll!_DPH_HEAP_BLOCK StackTrace , where is the DPH_HEAP_BLOCK address retrieved in step 5.
-
Do a dds ", where is the value retrieved in step 7.
Note that dds will dump the stack with source information included.
- !peb
-
查看进程的PEB,其中ProcessHeap
- dt ntdll!_HEAP
-
Dump _HEAP structure.
- dt _heap 005b0000 -r1
-
查看堆信息
Note如果是64bit下的32bit dump,需要通过 lm m ntdll*
看一下32bit ntdll的模块名称,如ntdll_777a0000,再使用dt ntdll_777a0000!_heap 460000
来以32bit解释460000处的HEAP结构 - dt _heap_entry 005b8d00
-
查看堆项目信息
- !heap
-
显示当前进程的堆信息。(仅针对用户模式进程。!pool扩展命令用于系统进程。)
0:000:x86> !heap Heap Address NT/Segment Heap 460000 NT Heap 100000 NT Heap 6d0000 NT Heap 1280000 NT Heap 2c0000 NT Heap 1220000 NT Heap 1300000 NT Heap 3240000 NT Heap
- !heap -b 和!heap -B
-
在堆管理器中创建或者删除条件断点。
- !heap -l
-
检查泄露的堆块。它使用一种垃圾回收算法(garbage collector algorithm)来检测没有被进程地址空间中任何地方引用到的已占用块(busy blocks)。对很大的程序,可能需要花费数分钟才能完成。该命令只在Windows XP和之后版本中可以使用。
- !heap -x
-
搜索包含给定地址的堆块。如果使用了-v 选项,该命令还会搜索当前进程的整个虚拟地址空间,以获得指向该堆块的指针。这个命令仅在Windows XP和之后版本中可以使用。
- !heap -p
-
显示各种形式的页堆(page heap)信息。使用!heap -p之前,必须启用目标进程的页堆(page heap)。这可以通过全局标志 (gflags.exe) 实用工具实现。打开该工具,在Image File Name 文本框中填入目标进程的名字,选择Image File Options 以及Enable page heap ,然后点击Apply 。也可以从命令提示符窗口输入gflags /i xxx.exe +hpa来运行全局标志工具,xxx.exe是目标程序的名字。
- !heap -p -t[c|s]
-
在Windows XP之后就不支持了。可以使用调试器工具包中的UMDH工具来获得类似的结果。
- !heap -srch [Size] Pattern
-
显示包含指定模板的堆条目(heap entries)。
- !heap -flt
-
限制只显示分配大小为指定值的堆。该命令仅在Windows XP和之后版本中可用。
- !heap -stat
-
显示堆使用统计。该命令仅在Windows XP和之后版本可用。
- !heap -h
-
List all of the current process heap with start and end addresses.
- !heap -h HeapIndex
-
Display detailed heap information for heap with index HeapIndex.
- !heap -s 0
-
Display summary for all heaps including reserved and committed memory …
- !heap -flt s 0x50
-
Display all of the allocations of size 0x50.
- !heap -stat -h address
-
Display heap usage statistics for HeapHandle is equal to address.
- !heap -p -all
-
Display details of all allocations in all heaps in the process.
- !heap -s -v
-
检测堆问题。reveal a corrupt heap
0:008> !heap -s -v Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast (k) (k) (k) (k) length blocks cont. heap ----------------------------------------------------------------------------- .ERROR: Block 001842e8 previous size 0 does not match previous block size 4 HEAP 00140000 (Seg 00140640) At 001842e8 Error: invalid block Previous
bp、bu和bm命令设置一个或多个软断点(software breakpoints)。可以组合位置、条件和选项来设置各种不同类型的软断点。 语法
[~Thread] bp[ID] [Options] [Address [Passes]] ["CommandString"] [~Thread] bu[ID] [Options] [Address [Passes]] ["CommandString"] [~Thread] bm [Options] SymbolPattern [Passes] ["CommandString"]
bp[ID] [Options] [Address [Passes]] ["CommandString"] bu[ID] [Options] [Address [Passes]] ["CommandString"] bm [Options] SymbolPattern [Passes] ["CommandString"]
-
Thread
指定要应用该断点的线程。该语法的更多信息,查看线程语法。只能在用户模式下指定线程。如果没有指定线程,则断点应用到所有线程。
-
ID
指定用于标识该断点的十进制数字。
调试器在创建断点时指派ID,但是之后可以通过br (Breakpoint Renumber)命令来改变它。在其他调试器命令中使用ID来引用断点。要显示断点的ID,可以使用bl (Breakpoint List)命令。
在命令中使用ID时,不能在命令(bp 或bu)和ID号之间加入空格。
ID 参数总是可选的。如果不指定ID,调试器使用第一个可用的断点号。内核模式下只能设置32个断点。用户模式下可以设置任意多个断点。但是哪种情况下对ID号的值都没有限制。如果使用中括号([])将ID括起来,ID可以包含任意表达式。该语法的详细信息,查看数值表达式语法。
-
Options
指定断点选项。除非特别指出,可以设置任意数量的下面的选项:
-
/1
创建一个一次性("one-shot")断点。该断点触发之后就会被从断点列表中永远去除。
-
/f PredNum
(仅Itanium、仅用户模式) 指定一个断言号。该断点使用相应的断言寄存器(predicate register)进行判定(例如,bp /f 4 address设置一个使用p4断言寄存器进行判定的断点)。关于断言寄存器的更多信息,查看Itanium结构体系。
-
/p EProcess
(仅内核模式) 指定一个和该断点关联的进程。EProcess 必须是EPROCESS结构的实际地址,而不是PID。这种断点仅在指定的进程上下文内遇到的时候才会触发。
-
/t EThread
(仅内核模式) 指定一个和断点关联的线程。EThread必须是ETHREAD结构的实际地址而不是线程ID。这种断点仅在指定的线程上下文内遇到的时候才会触发。如果同时使用/p EProcess 和/t EThread ,它们可以按任意顺序排列。
-
/c MaxCallStackDepth
使得断点仅当调用堆栈小于MaxCallStackDepth 深度时才激活。不能将此选项和/C 组合使用。
-
/C MinCallStackDepth
使得断点仅当调用堆栈大于MinCallStackDepth深度时才激活。不能将此选项和/c 组合使用。
-
/a
(仅bm 使用) 在所有指定位置设置断点,不管他们在数据空间还是代码空间。由于数据上的断点可能造成程序错误,所以只能在确认安全的位置使用该选项。
-
/d
(仅bm 使用) 将断点位置转换为地址。因此,如果代码位置改变了,这个断点还是保持在原来的位置,而不是像使用SymbolPattern 来设置的一样。使用/d 来避免模块加载或重加载时重新求值对断点进行的改变。
-
/(
(仅bm 使用) 在SymbolString 定义的符号字符串中包含参数列表信息。
这个功能使得可以对具有相同名字但是不同参数列表的重载函数设置断点。例如, bm /( myFunc 同时在myFunc(int a)和myFunc(char a)上设置断点。如果没有"/(",对myFunc 设置的断点会失败,因为这样不能确定断点设置到哪一个myFunc 上。
-
Address
指定要设置断点处的指令的第一个字节位置。如果省略Address ,则使用当前指令指针。该语法的更多信息,查看地址和地址区域语法。
-
Passes
指定断点激活之前要忽略的次数。调试器跳过该断点指定次数。该数字可以是任何16位或32位值。
默认情况下,断点在第一次执行断点位置的代码时被激活。这种默认情况和把Passes 设置为1是一样的。要使得断点在程序至少执行该代码一次之后才激活,可以将这个值设置为2或更大。例如,值为2时,使得断点在第二次执行到该代码时被激活。
该参数创建一个在每次执行断点处的代码时被减少1的计数器。要查看Passes 计数器的初始值和当前值,使用bl (Breakpoint List)。
Passes 仅当程序响应g (Go)命令并执行通过断点时才减少。单步或跟踪(tracing)通过它是不会减少的。当Passes 到达1时,可以通过清除并重设断点来重置它。
-
CommandString
指定每次遭遇断点指定次数后需要执行的命令列表。必须将CommandString 放到引号中。使用分号来分隔多条命令。
CommandString 中的调试器命令可以包含参数。可以使用标准C控制字符(如\n 和\")。二级引号(\")中的分号被当作引号中的字符串的一部分。
CommandString命令仅当程序响应g (Go)命令并执行通过断点时才会执行。单步或跟踪(tracing)执行断点处的命令时是不会触发的。
任何在中断后恢复程序运行的命令(如g 或t)都会结束命令列表的执行。
-
SymbolPattern
指定符号模板。调试器尝试使用已存在的符号来匹配该模板,并在所有匹配项上设置断点。SymbolPattern可以包含各种通配符和修饰符。该语法的更多信息,查看字符串通配符语法。因为这些字符时用来匹配符号的,这种匹配不区分大小写,并且 头部的单个下划线(_)表示任意数量的起始下划线。
bp、bu和bm 命令设置新断点,但是它们有不同的特点:
-
bp (Set Breakpoint)命令在指定地址的断点位置上设置断点。如果设置断点时调试器还不能将地址表达式计算为断点位置,bp 断点被自动转换为bu 断点。使用bp 命令来设置在模块被卸载之后就不会再被激活的断点。
-
bu (Set Unresolved Breakpoint)命令设置延迟的或未定断点。bu 设置在命令中指定的符号引用的断点位置上(不是一个地址上),并且当所引用的模块能够确定时激活。
-
bm (Set Symbol Breakpoint) 命令在能够匹配指定模板的符号上设置断点。该命令可以设置多于一个的断点。默认情况下,当模板被匹配之后,bm 断点和bu 断点相同。即bm 断点是针对符号引用设置的延迟断点。但是bm /d 命令会创建一个或多个bp断点。每个断点都被设置在能够匹配的位置的地址上,并且不会跟随模块状态改变。
使用bp命令时,断点位置始终被转换成地址。如果bp 断点设置的代码被移动了,该断点仍然保持在相同位置并且可能指向不同的代码或者非法位置。
相反的,bu 断点始终和命令指定的符号化的断点位置关联(一般是符号加上一个可选的偏移)。这种关联在符号的值改变或者包含该位置的模块加载或卸载之后仍然保持。
使用bp 设置的断点会保持到使用bc (Breakpoint Clear)命令或WinDbg的Breakpoints 对话框移除为止。但是,因为这些断点指向的是一个地址,bp 断点在包含所引用的位置的模块卸载之后就不再有效了。
bu 设置的断点会保存在WinDbg工作空间中,但是bp 设置的断点不会保存。
当使用鼠标在WinDbg的反汇编窗口或源码窗口中设置断点时,调试器创建bu断点。
bm 在想使用包含通配符的符号模板来设置断点时很有用。bm SymbolPattern 语法和使用x SymbolPattern然后对搜索结果使用bu 是一样的。例如,要在Myprogram 模块中所有以字符串"mem"开头的符号上设置断点,可以使用如下命令。
0:000> bm myprogram!mem* 4: 0040d070 MyProgram!memcpy 5: 0040c560 MyProgram!memmove 6: 00408960 MyProgram!memset
由于bm命令设置软断点(不是处理器断点),它会自动避开数据位置,以避免破坏数据。
但是,当使用bp 和bm /a时要小心。这些命令可以在数据段中设置软断点。调试器在代码上设置软断点时,会将处理器指令替换为中断指令。但是当调试器在数据段设置软断点时,会把数据替换为中断指令。这种中断指令会破坏数据。只有在只当作代码进行执行的数据上设置软件断点才是安全的。要在数据段设置断点,使用ba (Break on Access)命令。该命令可以设置数据断点而不是软件断点。
要在例如C++公有类这样的任意文本上设置断点,或者在operator new 函数上设置断点,需要将表达式括在园括号中。例如,使用bp (??MyPublic) 或bp (operator new)。
要在MASM表达式类型的任意文本上设置断点,使用bu @!"text"。要使用C++语法文本设置断点,对C++兼容的符号使用bu @@C++(text)。
Bp、bu和bm命令通过将处理器命令替换为中断指令来设置软断点。要调试只读代码或不能改变的代码,使用ba e 命令,e用于设置执行访问。
如果单个逻辑代码行跨越了多个物理行,断点设置在语句或调用的最后一个物理行上。如果调试器在要求的位置不能设置断点,则会将断点放到下一个可用的位置上。
如果指定了Thread,断点设置在指定线程上。例如,~*bp在所有线程上设置断点, ~#bp在产生当前异常的线程上设置,而 ~123bp 在线程123上设置。~bp 和~.bp 命令都在当前线程设置断点。
在内核模是下调试多处理器系统时,使用bp 或ba (Break on Access)设置的断点会应用到所有处理器。例如,如果当前处理器是3,并且输入bp MemoryAddress来在 MemoryAddress上设置了断点。任何执行到该地址的处理器(不止是处理器3)都会产生断点陷阱。 示例
cdecl(C declaration,即C声明)是源起C语言的一种调用约定,也是C语言的事实上的标准。在x86架构上,其内容包括:
-
函数实参在线程栈上按照从右至左的顺序依次压栈。
-
函数结果保存在寄存器EAX/AX/AL中
-
浮点型结果存放在寄存器ST0中
-
编译后的函数名前缀以一个下划线字符
-
调用者负责从线程栈中弹出实参(即清栈)
-
8比特或者16比特长的整形实参提升为32比特长。
-
受到函数调用影响的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
-
不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
-
RET指令从函数被调用者返回到调用者(实质上是读取寄存器EBP所指的线程栈之处保存的函数返回地址并加载到IP寄存器)
Visual C++规定函数返回至如果是POD值且长度如果不超过32比特,用寄存器EAX传递;长度在33-64比特范围内,用寄存器EAX:EDX传递;长度超过64比特或者非POD值,则调用者为函数返回值预先分配一个空间,把该空间的地址作为隐式参数传递给被调函数。
GCC的函数返回至都是由调用者分配空间,并把该空间的地址作为隐式参数传递给被调函数,而不使用寄存器EAX。GCC自4.5版本开始,调用函数时,堆栈上的数据必须以16B对齐(之前的版本只需要4B对齐即可)。
cdecl调用约定通常作为x86 C编译器的默认调用规则。
在调用C++非静态成员函数时使用此约定。基于所使用的编译器和函数是否使用可变参数,有两个主流版本的thiscall。 对于GCC编译器,thiscall几乎与cdecl等同:调用者清理堆栈,参数从右到左传递。差别在于this指针,thiscall会在最后把this指针推入栈中,即相当于在函数原型中是隐式的左数第一个参数。
在微软Visual C编译器中,this指针通过ECX寄存器传递,其余同cdecl约定。当函数使用可变参数,此时调用者负责清理堆栈(参考cdecl)。thiscall约定只在微软Visual C 2005及其之后的版本被显式指定。其他编译器中,thiscall并不是一个关键字(反汇编器如IDA使用__thiscall)。
0:000:x86> dt ntdll!_LIST_ENTRY +0x000 Flink : Ptr32 _LIST_ENTRY +0x004 Blink : Ptr32 _LIST_ENTRY
0:000:x86> dt ntdll!_LIST_ENTRY +0x000 Flink : Ptr64 _LIST_ENTRY +0x008 Blink : Ptr64 _LIST_ENTRY
LONG WINAPI UnhandledExceptionFilter( _In_ struct _EXCEPTION_POINTERS *ExceptionInfo ); typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
-
执行
~*kb
,列出所有线程 -
找出调用
Kernel32!UnhandledExceptionFilter
的线程120 id: f0f0f0f0.a1c Suspend: 1 Teb 7ff72000 Unfrozen ChildEBP RetAddr Args to Child 09a8f334 77eb9b46 0000244c 00000001 00000000 ntdll!ZwWaitForSingleObject+0xb [i386\usrstubs.asm @ 2004] 09a8f644 77ea7e7a 09a8f66c 77e861ae 09a8f674 KERNEL32!UnhandledExceptionFilter+0x2b5 [D:\nt\private\windows\base\client\thread.c @ 1753] 09a8ffec 00000000 787bf0b8 0216fe94 00000000 KERNEL32!BaseThreadStart+0x65 [D:\nt\private\windows\base\client\support.c @ 453]
-
切换到有问题的线程
~120s
-
对Kernel32!UnhandledExceptionFilter的第一个参数进行
dd
,显示EXCEPTION_POINTERS结构的内容0:120> dd 09a8f66c 09a8f66c 09a8f738 09a8f754 09a8f698 77f8f45c 09a8f67c 09a8f738 09a8ffdc 09a8f754 09a8f710 09a8f68c 09a8ffdc 77f8f5b5 09a8ffdc 09a8f720 09a8f69c 77f8f3fa 09a8f738 09a8ffdc 09a8f754 09a8f6ac 09a8f710 77e8615b 09a8fad4 00000000 09a8f6bc 09a8f738 74a25336 09a8f6e0 09a8f910 09a8f6cc 01dc8ad8 0d788918 00000001 018d1f28 09a8f6dc 00000001 61746164 7073612e 09a8f71c
-
第一个DWORD是exception record,可以用
.exr
命令进行查看0:120> .exr 09a8f738 ExceptionAddress: 78011f32 (MSVCRT!strnicmp+0x00000092) ExceptionCode: c0000005 ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 00000000 Attempt to read from address 00000000
-
第二个DWORD是context record,可以用
.cxr
命令进行查看0:120> .cxr 09a8f754 eax=027470ff ebx=7803cb28 ecx=00000000 edx=00000000 esi=00000000 edi=09a8fad4 eip=78011f32 esp=09a8fa20 ebp=09a8fa2c iopl=0 nv up ei ng nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010286 MSVCRT!strnicmp+92: 78011f32 8a06 mov al,[esi]
-
通过
kv
命令查看发生异常是堆栈0:120> kv ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 09a8fa2c 780119ab 09a8fad4 00000000 09a8faa8 MSVCRT!strnicmp+0x92 09a8fa40 7801197c 09a8fad4 00000000 6d7044fd MSVCRT!stricmp+0x3c 09a8fa80 6e5a6ef6 09a8fad4 2193d68d 00e5e298 MSVCRT!stricmp+0xd 09a8fa94 6d7043bf 09a8fad4 09a8faa8 0000001c IisRTL!CLKRHashTable::FindKey+0x59 (FPO: [2,0,1]) 09a8faac 749fc22d 09a8fad4 01d553b0 0000001c ISATQ!CDirMonitor::FindEntry+0x1e (FPO: [Non-Fpo]) [D:\nt\private\inet\iis\svcs\infocomm\atq\dirmon.cpp @ 884] 09a8fac4 749fd1cb 09a8fad4 09a8fb10 525c3a46 asp!RegisterASPDirMonitorEntry+0x6e ...
KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
QUESTION: 有可能用的是PASCAL调用约定?
0:050:x86> kv 07eed780 01eed794 07eed7e4 07eed794 07eed7e4 ntdll_777a0000!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) 0:050:x86> .exr 07eed794 ExceptionAddress: 00000000777ce23e (ntdll_777a0000!RtlpLowFragHeapFree+0x0000000000000031) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 0000000000000000 Parameter[1]: 0000000036e88950 Attempt to read from address 0000000036e88950 0:050:x86> .cxr 07eed7e4 eax=00000000 ebx=0052d608 ecx=00460000 edx=0052d608 esi=36e8894c edi=0052d600 eip=777ce23e esp=07eedacc ebp=07eedb00 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202 ntdll_777a0000!RtlpLowFragHeapFree+0x31: 777ce23e 8b4604 mov eax,dword ptr [esi+4] ds:002b:36e88950=????????
可以在线下载需要的调试符号,可以设置环境变量或在windbg中的symbols path进行设置
-
设置环境变量
set _NT_SYMBOL_PATH = srv*DownstreamStore*SymbolStoreLocation
-
windbg中可以把
srv*DownstreamStore*SymbolStoreLocation
加入symbols path中
其中,
-
DownstreamStore是本地用于缓存符号表文件的目录
-
SymbolStoreLocation为symbol server的URL,如
https://msdl.microsoft.com/download/symbols
如
set _NT_SYMBOL_PATH=srv*d:\symbols*https://msdl.microsoft.com/download/symbols
dt ntdll_779d0000!* -d -b
.foreach(s {dt verifier!*}) {.echo s; dt -b ${s}}
.foreach(s {s -[1]b 0000000 L?100000000 d6 52 00}) {dd ${s}-1 L1}
- Your debugger is not using the correct symbols … Type referenced: wow64!_TEB32
-
Define _TEB32 in a file (say teb.h)
-
cl -c -Fd<fullpath of the ACTUAL wow64.pdb, whatever it is called> -TP teb.h