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 toobig;'My value acceptable;gc' "”

其中“bp'my.cpp:122'”表示在my.cpp文件的第122行插入断点,后跟“XXXX”表示在撞击断点时执行的命令;poi()表示取该地址的值,类似C语言的“*”;“gc”表示继续执行。

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

  “~1 r;~2 r;~3 k *查看线程12的寄存器和线程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 displayedin CR3 register in X86)

17、系统将寄存器保存到内存中当发生异常或者线程切换时,我们查看寄存器的值的时候线程往往是挂起的,所以修改寄存器的值其实也就是修改内存中的值。

18、使用“.thread <implicit threadaddress>”设置寄存器上下文所针对的线程。“.thread”(或“.cxr”)恢复成原来的线程地址。(当调试用户态的转储文件时可以使用“.ecxr”将转储文件中保存的异常上下文设置成当前寄存器上下文)

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

20、局部上下文:当前所使用的参数和变量所基于的函数。使用“.frame”可以列出当前的局部上下文,“.frame <threadnumber>”切换局部上下文,使用“.kn”查看当前帧栈(为单个过程分配的栈)列表,“.dv”列出当前局部上下文使用的参数和变量。

21、VC编译器缺省将默认类型符号放在VCx0.pdb文件中,WinDbg没有很好的处理该文件,所以会显示很多的 NO Typeinformation,解决办法是将符号设置为C7 Compatable(Settings->C++->General->DebugInfo)。

22、使用“symsrv*ServerDLL*[DownsreamStore*]ServerPath”来设置搜索符号文件的路径。其中ServerDLL是符号服务器DLL的位置,DownstreamStore是下游符号库的位置,ServerPath是符号服务器的URL。

可以使用简化模式:“SRV*[Downstream Store*]Server Path”(其中SRV 相当于symsrv*symsrv.dll[symsrv.dll是windbg自带的符号服务器DLL,用户也可自己编写])。

23、使用“lm”显示加载的模块,“lm v”为每个模块提供更详细的信息,“!lm<ModuleName>”显示一个详细的模块信息,“lmo”显示已经加载的模块,“lml”显示已经加载符号文件的模块,“lme”显示有符号问题的模块,“lmm *<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菜单的SourceCode菜单项(或者使用命令“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(modestatus register)寄存器和标准寄存器后恢复程序执行,因此更高效)

37、g命令语法:

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

a代表将断点设置为硬件断点,不指定则为软件断点BreakAddress用来指定一个隐藏的断点地址,当执行到该地址时windbg自动删除该断点(SourceCode窗口或AssembelCode窗口下Debug选项中的Runto 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命令对调试动态加载的模块的入口函数或者初始化代码特别有用。如:“buDriver!DriverEntry”

41、bm命令在设置断点前需要确认匹配的符号对应的是代码不是数据,所以使用bm命令时要求目标模块的调试符号有类型信息,能够判断出一个符号的类型。这通常需要所谓的私有符号文件,也就是调试版本的符号文件。对于公用的符号文件,如我们输入“xntdll!DbgPrint*”会显示WinDbg抱怨没有符号类型信息,再输入“bmntdll!DbgPrint*”时显示没有找到匹配的代码符号,类似解决办法有两个:

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

(2)使用调试器告诉你的方法:用dll内部的exportsymbols,具体做法是将该符号文件路径清空(清空路径后.reload一下),再输入“xntdll!DbgPrint*”,目的是让调试器自动使用dll内部的exportsymbols,发现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; dupoi(poi(argv+4))';'gc'"”

该命令对dbgee程序的wmain函数设置一个断点,只有当命令行的参数的个数大于1时,执行'dd argc l1; dupoi(poi(argv+4))'并中断给用户,否则继续执行。(dd argc l1表示显示argc参数的值,dupoi(poi(argv+4)表示显示第一个命令行参数的字符串内容)

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

注:该命令使用了poi命令,是因为WinDbg缺省使用MASM语法的表达式评估器(expressionevaluator),在MASM的语法中,argc代表一个地址,要取它的值必须使用poi操作符,poi表示从地址中取出指针长度的数据(pointer-sizeddata),类似的还有by、wo、dwo、qwo分别表示从指定地址取1、2、4、8个字节的数据。(关于MASM表达式的更多内容,参考WinDbg帮助文件的MASMNumbers 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”或“bpMyClass::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 spaceaddress,即内存地址,而V附加显示了该变量对于ebp的偏移地址。

命令“!for_each_local”用于枚举当前栈帧的所以变量,后面可跟附加命令。“!for_each_frame”用于枚举当前线程的所有帧栈后面可跟附加命令,如附加“dv/i /t /V”查看所有帧栈的变量及参数信息。使用“.frame <frameNumber>”可以切换当前局部上下文。

注:(1)dv命令只能对加载了私有符号文件的模块使用,若没有加载私有符号,可以使用如下方法查看局部变量。一、直接使用内存观察窗口观察帧栈空间,利用栈帧分布的知识。二、通过使用汇编指令中对局部变量的引用来观察该内存地址的内容,对于没有使用FPO的函数,局部变量一般都是在EBPXXXX的地址上。

(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”,若省略模块名,则自动查找所有已加载的模块,如“dtntdll!*”显示ntdll里的所有类型信息。其中,-b开关指定递归的显示所有子类型的信息。-r加数字指定递归显示的深度,如-r0表示不显示子类型信息。若不想显示全部字段,可以使用开关-ny附加字段过滤搜索信息,如“dt_TEB -ny LastError”。

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

(3)第三种用法是显示类型的实例,如全局变量、静态变量和函数。同样可以枚举函数符号,此时同x命令的功能相似,如“dtdbgee!*wmain*”,若是指定的函数,则会显示该函数的参数取值和返回值类型。

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

(1)“s-[[Flags]]sa|suRange”用来搜索ASCII(UNICODE)字符串,可以用l加整数指定字符串的长度。例如“s-[l5] sa 0X600000 0X800000”。

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

(3)“s [-[[Flags]]Type] RangePattern”,Type决定了匹配搜索内容的方式,可以为b、w、d、q、a、u,Pattern参数用来指定要搜索的内容,可以用空格分隔依次搜索的数值,如:

“s -w -0X400000 l2A000 41 64 76 44 62 67”,要搜索的内容也可以表示为ASCII码,如:“s -w0X400000 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} AddressValue”,其中,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、多线程调试......

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值