windbg常见命令
WinDbg
WinDbg支持以下三种类型的命令:
· 常规命令,用来调试进程
· 点命令,用来控制调试器
· 扩展命令,可以添加叫WinDbg的自定义命令,一般由扩展dll提供这些命令
PDB文件
PDB文件是由链接器产生的程序数据库文件。私有PDB文件包含私有和公有符号,源代码行,类型,本地和全局变量信息。公有PDB文件不包含类型,本地变量和源代码行信息,且只包含共有成员的调试信息。
Dump文件
利用Dump工具,你可以获得进程的快照信息。一个mini-dump包含当前进程的所有线程,线程栈信息和已加载模块信息。一个full-dump包括更多信息,如堆信息。
使用WinDbg调试
1. 启动WinDbg
要用WinDbg(x86)调式32位程序,用WinDbg(x64)调试64位程序。
2. 使用帮助
任何时候都可以使用!help命令来获取帮助,查看命令的使用方法。
3. 设置SymbolFile Path,指定了符号库,我们才能看到详细的类型信息
SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
WinDbg会将微软的符号库下载指定的本地目录中,以上设置可以使用以下命令来实现:
.symfixc:\symbols,此命令表示要连接到Microsoft服务器下载调试符号文件,符号文件将被下载到c:\symbols目录中。
还可以使用.sympath命令来显示当前的符号路径设置。
4. 重新加载符号
如果进入调试之后才指定的符号路径,需要使用命令来重新加载符号
.reload
5. 加载调试扩展
调试扩展定义了很多命令来调试.net程序。使用调式非托管代码的命令来调试.net程序是非常困难的。下面的命令加载了用于调试.net程序的sos模块。
a. 对于.Net Runtime 2.0
.loadby sos mscorwks
b. 对于.Net Runtime 4.0
.loadby sos clr
sos是一个dll,定义了很多针对.net assembly的调试命令,sos.dll针对不同的runtime有不同的版本。如果使用.load命令,需要为sos.dll指定完整路径。如果使用.loadby命令,则表示要在mscorwks.dll和clr.dll的同一目录下查找sos。对于.net程序,基本上都会加载mscorwks.dll(2.0)和clr.dll(4.0)。
6. 查看线程
a. 包括托管线程和非托管线程
~
b. 查看托管线程
!threads
c. 显示有关托管线程池的信息,包括队列中工作请求的数目、完成端口线程的数目和计时器的数目。
!ThreadPool
d. 切换线程(中间的数字表示线程号)
~0s
e. 查看线程栈, 仅提供托管代码的堆栈跟踪。
!clrstack [-a] [-l] [-p] [-n]
-p 选项显示托管函数的参数。
-l 选项将显示有关帧中的局部变量的信息。若SOSdebugging extension 未能检索本地名称,因此,本地名称的输出格式<local address>= <value>。
-a (all) 选项是一个表示 -l 和 -p 的组合的快捷方式。
-n 选项禁止显示源文件名和行号。
在基于x64 和IA-64 的平台上,SOS调试扩展不显示过渡帧。
f. 查看线程栈,只能正常显示非托管部分
k
g. 显示线程栈全部信息,包括托管和非托管部分
!dumpstack [-EE] [-n] [top stack [bottom stack]]
-EE 选项使DumpStack 命令仅显示托管函数。使用top 和bottom 参数可限制x86 平台上显示的堆栈帧。
-n 选项禁止显示源文件名和行号。如果调试器已指定选项
显示所有线程栈信息
h. 对一个进程中的所有线程运行 DumpStack 命令
!EEStack [-short] [-EE]
将-EE 选项直接传递给DumpStack 命令。-short 参数将输出限制为以下类型的线程:
· 已获取锁的线程。
· 己停止运行以允许垃圾回收的线程。
· 当前在托管代码中的线程。
i. 显示所有线程的堆栈
~*e !clrstack
j. 显示在当前堆栈的边界内找到的所有托管对象
!dumpstackobjects [-verify] [top stack [bottom stack]] 或 !dso
7. 查看堆中的所有对象信息,包括类型信息,个数,大小等
!dumpheap –stat
a. 指定对象类型,如果需要结果准确,需要使用全名称,类型名称是大小写敏感的
!dumpheap -type FlowThrottle –stat
b. 以上命令输出的第一列是mt(method table)信息,表示类型对象的地址。我们可以使用此信息来明确指定我们感兴趣的对象在堆中的信息。
!dumpheap -mt 000007feee769c00 -stat
注意,如果不指定-stat,那么输出每个对象的信息,使用-stat,将会看到统计信息。
c. 使用如下命令只输出对象地址
!dumpheap -mt 000007feee769c00 -short
d. -short 选项将输出限制为只是每个对象的地址。这使您方便地以管道方式将输出从该命令转移到另一个调试器命令,以便自动化。
e. -min 选项忽略小于 size 参数指定的大小(单位为字节)的对象。-max 选项忽略大于size 参数指定的大小(单位为字节)的对象。
8. 显示有关内部公共语言运行时数据结构所使用的进程内存的信息。
EEHeap [-gc][-loader]
-gc 和-loader 选项将此命令的输出限制为垃圾回收器或加载程序数据结构。有关垃圾回收器的信息列出了托管堆中每个段的范围。如果指针落在由-gc 给出的段范围内,则该指针是一个对象指针。
9. 打印对象信息,指定任何有效的对象地址,就能查看该对象的内容
!dumpobj <address>或者 !do<address>
输出的对象信息中有每个属性的地址信息,所以可以继续使用!do命令来打印属性指向的对象信息。
!dumpobj –nofields<address>或者!do <address>
nofields选项指示不要输出对象字段信息,当对象是string类型时,该选项就非常有用。
10. 显示有关指定地址处的值类字段的信息。
DumpVC<MethodTable address> <Address>
MethodTable 参数使DumpVC 命令可以正确解释字段。值类不使用方法表作为它的第一个字段。
11. 输出多个对象信息
.foreach (myobj{!dumpheap -mt 008f4104-short}) {!do ${myobj}}
以上命令指示,对于堆中所有类型为008f4104的对象,依次调用!do命令。
12. 打印数组信息
!dumparray [-start<startIndex>] [-length <length>] [-details] [-nofields] <arrayobject address>或者!da
· -start 选项指定开始显示元素的起始索引。
· -length 选项指定要显示的元素数量。
· -details 选项使用 DumpObj 和 DumpVC 格式显示元素的详细信息。
· -nofields 选项可阻止显示数组。此选项只有在指定了-detail 选项之后才可用。
下面的命令显示在地址 00ad28d0 处的数组内容。显示从第二个元素开始,连续显示五个元素。
!dumparray -start2 -length 5 -detail 00ad28d0
13. 输出app domain信息
!dumpdomain
枚举在指定的 AppDomain 对象地址内加载的每个Assembly 对象。若在调用DumpDomain 命令时不提供任何参数,则将列出进程中的所有AppDomain 对象。
14. 输出程序集信息
!dumpassembly
15. 打印方法表
!dumpmt [-MD]<MethodTable address>
显示有关指定地址处的方法表的信息。指定-MD 选项将显示与对象一起定义的所有方法的列表。每个托管对象都包含一个方法表指针。
16. 打印EEClass结构,其中可以看到类型静态变量信息
!dumpclass<EEClass address>
显示有关与类型关联的 EEClass 结构的信息。DumpClass命令显示静态字段值,但不显示非静态字段值。使用DumpMT、DumpObj,Name2EE 或Token2EE 命令获取EEClass 结构的地址。
17. 显示异常
a. 使调试器在引发指定异常时停止,但在引发其他异常时继续运行。
!StopOnException [-derived] [-create | -create2]<Exception> <Pseudo-register number>
-derived 选项用于捕获指定异常以及从指定异常派生的每个异常。
b. 显示当前活动线程上的最后一个异常
!PrintException [-nested] [-lines] [<Exception objectaddress>] 或 !PE
显示从指定地址处的Exception 类派生的任何对象的字段并设置这些字段的格式。如果不指定地址,PrintException命令将显示在当前线程上引发的最后一个异常。
-nested 选项显示有关嵌套异常对象的详细信息。
-lines 选项显示源信息(如果可用)。
c. 显示发生在所有线程上的最后的异常
~*e !pe
18. 调试GC相关信息
a. 显示有关对指定地址处的对象的引用(或根)的信息。
!GCRoot [-nostacks] <Object address>
GCRoot 命令将检查整个托管堆和句柄表以查找其他对象内的句柄和堆栈上的句柄。然后,在每个堆栈中搜索对象的指针,同时还搜索终结器队列。此命令无法确定堆栈根是有效的还是已丢弃。使用CLRStack 和U 命令可对本地或参数值所属的帧进行反汇编,以便确定堆栈根是否仍在使用中。
-nostacks 选项将搜索限制为垃圾回收器句柄和 Freachable 对象。
b. 显示所有已进行终结注册的对象。
!FinalizeQueue [-detail] | [-allReady] [-short]
-detail 选项显示有关需要清理的任何 SyncBlocks 的额外信息以及有关等待清理的任何RuntimeCallableWrappers (RCW) 的额外信息。这两种数据结构都由终结器线程在运行时进行缓存和清理。
-allReady 选项显示所有准备终止的对象,无论它们已被垃圾回收标记成这样,还是将被下一个垃圾回收标记。在“准备终止”列表中的对象为不再为根的可终止对象。此选项可能耗费大量资源,因为它验证可终止队列中的所有对象是否仍然为根对象。
-short 选项将输出限制为每个对象的地址。如果与-allReady 一起使用,则将枚举具有不再为根的终结器的所有对象。如果单独使用,则将列出终结和“准备终结”队列中的所有对象。
19. 设置断点
!bpmdSystem.Windows.Forms.dllSystem.Windows.Forms.MessageBox.Show
第一个参数是dll文件名,第二个是完整的方法名。
20. 查看所有断点列表
bl
21. 释放当前断点, 让程序继续运行。让程序运行到断点后WinDBG会自动停下来。
g
22. 显示公共语言运行时版本。
!eeversion
23. 清除屏幕信息,该命令还你一个清洁的屏幕
.cls
24. 退出当前调试
q
//断点相关
bp + 地址设置断点
bl 显示已经设定的断点
bu + 地址设置断点,但是这种类型断点再下一次启动时被记录
bc 清除断点
对于断点范围,可以用*匹配,-表示一个范围,表达多个可用,号隔开
程序入口伪寄存器
WinDbg里有个伪寄存器叫$exentry,里面记录了程序的入口点。所以我们只要在命令输入栏里输入
bp $exentry
(bp就是用来下断点的命令,详细用法可以参考WinDbg的帮助文档)
//调试符号
ld kernerl32 //加载kernerl32模块的符号
lm m k* //显示已经加载的,以k开头的模块
ln //显示最近操作过的模块名
dt dbg2 //检测模块
[[[[[[[[[[[[]]]]]]]]]]]]
x kernerl32!k* 显示模块kernerl32中所有以k开头的函数
dv 显示局部变量值
dv /i/t/v 显示局部变量的类型,值相关信息。
x <module>!* / ? 显示指定模块的符号
x argc 查看变量argc的值。
dt argc 查看变量值
dt _PEB 7ffdd00 将内存地址7ffdd00开始的内容以PEB结构的方式显示出来。
dd 12000 L4 查看地址12000 后面的四个字
dds 12000 L100 查看堆栈上地址12000开始,后面的100个dword的内容,如果有调试符号,会将符号显示。此方法来追踪堆栈。(先看ebp,再用此方法)
dd ebp + 4, 返回地址, ebp + 8 第一个参数
[[[[[[[[[]]]]]]]]]
.kill 杀死调试进程
.restart 重新调试
[[[[[]]]]]]]]]]]]]]
k 显示调用堆栈
,kn加序号而已。
kb 显示前三个参数。第一个参数ebp+8;第二个ebp+0x0C;第三个ebp+0x10;dd ebp+0x14是第四个参数
kp 显示函数参数类型,数值
kp f f开关显示相邻栈基之差,从而可以推断出栈的健康状况。
[[[[[[[[[[]]]]]]]]]]
| 显示进程
~显示线程
~0 s 切换到 0号线程
[[[[[[[[[]]]]]]]]]
dv 显示函数参数&局部变量,注意,dv是跟栈帧相关的,对不同的栈帧显示不同的局部变量。
@1, kn 显示所有栈帧
@2, .frame选择想要查看的栈帧
@3, dv /i/v/t显示该栈帧里局部变量信息
@3, dv /i /V /t 显示变量基于栈帧的地址
如果没有私有符号,dv是不能显示变量信息的。
vc 生成的调试符号*.pdb windbg不认识,需要设置为c++/General/DebugInfo= C7 compatible
=====
sympath + c:\nasm 添加符号搜索路径
.sympath 显示符号搜索路径
//显示一定范围内存
!db L 32 : results in 32 bytes being displayed (as hexadecimal bytes),
//查看pe信息
!dh [Options] Address : 查看模块pe信息
!dh -f : display file headers
!dh -s : section headers
!dh -a : all header informations
查看结构体成员
dt nt!_EPROCESS
查看当前的irql
!irql
查看Verifier 检测统计信息
!verifier
查看某个内存地址属于那一个模块
!pool 地址
!lmi Address : 查看模块的主要信息
!pcr 可以查看当前执行的线程及irql, 等信息
//
Why doesn't the WinDBG command !irql always return the correct IRQL for my target?
[Answer by Jake Oshins, jakeo_at_windows_dot_microsoft_dot_com. Workaround provided by James Antognini, antognini_at_mindspring_dot_nospam_dot_com, 27 August 2003]
!irql currently only produces useful results on a crashdump, not a live system. To retrieve the current IRQL on a live system you should instead use the !pcr command.
!processfield:列出EPROCESS的成员
该命令前的!号,意味着它来自于调试器的扩展模块―kdextx86.dll。该命令可显示内核用来代表一个进程的EPROCESS结构(该结构并没有正式的说明文档)的成员及其偏移量。
尽管该命令仅列出了成员的偏移量,但你也能很容易的猜出其正确的类型。例如,LockEvent位于0x70处,其下一个成员的偏移量为0x80。则该成员占用了16个字节,这与KEVENT结构非常类似。
!threadfields:列出ETHREAD成员
这是kdextx86.dll提供的另一个强大的选项。和!processfields类似,它列出未文档化的ETHREAD结构的成员及其偏移量。内核使用它表示一个线程.
//进程信息
!tep
!peb ,显示peb(进程信息)
//显示相关
dt ntdll!*teb* 列出匹配通配符的结构名
dt -v -r ntdll!_TEB
列出结构_TEB的成员信息
//显示变量地址
r $peb 显示模块peb的地址
//查看错误信息
!gle
//设置断点的技巧
可以直接把断点设在: kernel32!BaseProcessStart
1), 先用lm 显示所有已经加载的模块
2), dt our_exe_name!*main* //在我们的程序模块中搜索包含main的地址(注意:如果未加载symbol是不能显示的!)
3), 如果存在,在our_exe_name!*main 处设置断点
=======
Command SoftICE OllyDbg
Run F5 F9
Step Into F11 F7
Step Over F10 F8
Set Break Point F8 F2
搜索内存
5、查找字符串
在步骤1我们运行程序时就记录了提示注册错误的字符串“Wrong Serial, try again!”,现在我们就要在内存找到该字符串的位置。
输入命令
s –a 00400000 L53000 “Wrong”
该命令的意思是以ASCII码形式在内存地址00400000往后53000个字节搜索字符串“Wrong”。
s,就是要调用查找的命令
-a,指定使用ASCII码的形式查找
00400000,指定要开始寻找的内存地址。
L53000,说明要在00400000往后的53000字节搜索。这个数值和00400000都可以从Stud_PE获得。00400000是程序的装入地址,而53000是映像的大小,也就是程序载入内存后占用的内存大小。使用这两个数值,基本上可以搜索到程序使用的整个内存范围。
“Wrong”,就不用多解释了,就是我们要寻找的字符串。不过WinDbg不支持模糊搜索,所以这里输入的字符串必定要完全正确。
内存访问断点
6、下内存访问断点
WinDbg中,ba命令代表Break On Access,即访问时中断。
我们在命令行输入:
ba r 1 0044108c
命令的意思是在内存0044108c的位置下字节的读断点。命令中各元素的含义可以参考帮助文档,这里不啰嗦。
输入bl,查看断点使用情况:
地址运算
? 0x33 + 0x44
运行后将得到计算和
3.查看和修改数据
调试中不可避免的要查看和修改数据
查看内存:
db/dw/dd/dq [Address] 字节/字/双字/四字方式查看数据
da/du [Address] ASCII字符串/Unicode字符串方式查看指定地址
其它常用的如查看结构
dt nt!_EPROCESS
dt nt!_EPROCESS 89330da0 (把0x89330da0作为对象指针)
修改内存:
eb/ew/ed/eq/ef/ep Address [Values]
字节/字/双字/四字/浮点数/指针/
ea/eu/eza/ezu Address [Values]
ASCII字符串/Unicode字符串/以NULL结尾的ASCII字符串/以NULL结尾的Unicode字符串
搜索内存:
s -[b/w/d/q/a/u] Range Target
搜索字节/字/双字/四字/ASCII字符串/Unicode字符串
2.断点
断点之于调试当然是非常重要的
常用命令:
bp [Address]or[Symbol] 在指定地址下断
可以使用地址或符号,如
bp 80561259(Windbg默认使用16进制)
bp MyDriver!GetKernelPath
bp MyDriver!GetKernelPath+0x12
bp [Address] /p eprocess 仅当当前进程为eprocess时才中断
这个很常用,比如你bp nt!NtTerminateProcess,但是只想在某一进程触发此断点时才断下来,那就加上这个参数吧,因为内核中的代码是各个进程共用的,所以此命令很实用
bp [Address] /t ethread 仅当当前线程为ethread时才中断,用法跟/p参数类似
bu [Address]or[Symbol] 下一个未解析的断点(就是说这个断点需要延迟解析)
这个也很常用,比如我们的驱动名为MyDriver.sys,那么在驱动加载之前下断bu MyDriver!DriverEntry,
然后加载这个驱动时就可以断在驱动入口,并且这个是不需要调试符号支持的
bl 列出所有断点,L=List
bc[id] 清除断点,c=Clear,id是bl查看时的断点编号
bd[id] 禁用断点,d=Disable,id即断点编号
be[id] 启用断点,e=Enable,id为断点编号
windbg u只能显示几行怎么多显示 加 l 后面跟数字茹 u NtOpenProcess l100
u NtOpenKey NtOpenkey+50 (显示50字节) 要查看的地址+要显示的字节
条件断点(condition breakpoint)的是指在上面3种基本断点停下来后,执行一些自定义的判断。
在基本断点命令后加上自定义调试命令,可以让调试器在断点触发停下来后,执行调试器命令。每个命令之间用分号分割。
语法格式如:
0:000> bp Address "j (Condition) 'OptionalCommands'; 'gc' "
0:000> bp Address ".if (Condition) {OptionalCommands} .else {gc}"
这两条是等价的.
当然
.if
{
}
.else
{
}
更好理解.
0:000> bp `mysource.cpp:143` "j (poi(MyVar)>0n20) ''; 'gc' "
0:000> bp `mysource.cpp:143` ".if (poi(MyVar)>0n20) {} .else {gc}" 使用POI这里用的不是[] 而是()
若MyVar大于20则不stop,
否则stop下来进行调试.
MyVar符号表示符号所在的内存地址,而不是符号的数值,相当于C语言中的 &操作符的作用。Windbg命令poi的作用是取这个地址上的值,相当于C语言中的*操作符.因此这里取得MyVar的值.
伪寄存器,帮助保存调试的中间信息
考虑这样的情况,如果要记录某一个函数被执行了多少次,应该怎么做?简单的做法就是修改代码,在对应的函数入口做记录。可是,如果要记录的函数是系统API呢?
设置寄存器 条件断点
当eax内的值为0xa3时断点Sop. 没问题,Hah.
0:000> bp mydriver!myFunction "j @eax = 0xa3 '';'gc'"
0:000> bp mydriver!myFunction ".if @eax = 0xa3 {} .else {gc}"
但以下就不一定了,当eax中人值为0xc0004321时,
不一定会断下来.
为什么呢?
原因是内核态时,MASM会对EAX中的值进行符号扩展.
那么0xc0004321 会变成0xFFFFFFFFc0004321
这样当然断不下来啦。
0:000> bp mydriver!myFunction "j @eax = 0xc0004321 '';'gc'"
0:000> bp mydriver!myFunction ".if @eax = 0xc0004321 {} .else {gc}"
如何处理呢?看看下面就知道了.
0:000> bp mydriver!myFunction "j (@eax & 0x0`ffffffff) = 0x0`c0004321 '';'gc'"
0:000> 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> bp kernel32!VirtualAllocEx "r $t0=@$t0+1;.printf
/"function executes: %d times /",@$t0;.echo;g"
0:009> g
function executes: 1 times
function executes: 2 times
function executes: 3 times
function executes: 4 times
…
哈哈,这确实是一个好方法.