摘录自《WinDbg用法详解》

http://cuijingbing.blog.163.com/blog/static/46825825201187105423613/


选取了自己认为比较新和重要的知识作为摘录内容

 

 

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文件能够方便的保存发生问题时进程的状态,方便事后分析。

猜你喜欢

转载自blog.csdn.net/xiaohua_de/article/details/78354762