控制调试目标
中断执行
按ctrl + c 来中断程序执行
恢复执行
g 不带任何参数,只是恢复调试目标的执行,直到下一次发生某个调试事件
如果不希望调试器在初始启动时停止程序的执行,在启动调试器时加 -g 如 ntsd -g a.exe
单步调试代码
p step
p 的变种 pt 会一直执行指令,直到遇见一个Ret指令。
pc可以很快地执行到下一个Call指令
t trace 执行单条指令,并显示所有寄存器的结果。命令t在执行call指令或者中断指令时的行为
t的变种
ta <address> 执行到address指定的地址,并将包含被调用函数的单步执行显出来
tc 执行到下一个call指令,并将包含被调用函数的单步执行显示出来。
tt 执行到下一个ret 指令,并将包含被调用函数的单步执行显示出来。
退出调试会话
q 退出调试会话并终止调试目标
qd quit and detach 结束调试会话,但让调试目标继续运行。
加载托管代码调试的扩展命令
非托管调试器中可以使用两种不同类型的命令
元命令 指在调试引擎中内置的命令。如help sympath cls 等,必须在命令前面加上前缀"."
扩展命令 是在调试器引擎之外的独立DLL中实现的,这些DLL也被称为调试器扩展。在执行扩展命令时,要在命令前面加上前缀 “!” ,如!htrace -enable ,在调试托管代码时,有两个DLL需要注意,它们是SOS 和SOSEX
在使用这些扩展DLL之前,必须通过元命令load 来通知调试器。 如.load c:\abc.dll
加载SOS调试器扩展
SOS调试器扩展的DLL (sos.dll)与程序使用的CLR版本是相关的。因此需要加载与目标程序CLR版本一致的sos.dll
如.load c:\windows\Microsoft.NET\Framework\v2.0.50727\sos.dll
这个方法太难用,我们可以用另一个元命令 loadby 语法如下
.loadby DLLName ModuleName
元命令将会找出由ModuleName指定的路径,并且使用这个路径来加载指定的DLLName
例如正在查找的模块是 mscorwk,则只需执行以下命令
.loadby sos.dll mscorwks
如果目标程序的mscorwks模块还没有被加载,那么loadby 将提示错误信息。
如果需要在加载mscorwks模块时立即加载SOS调试器扩展,那么可以使用sxe 命令
sxe ld 可以使得在加载某个特定的模块后,立即中断进入到调试器,然后加载SOS调试器扩展
如 sxe ld mscorwks.dll
加载SOSEX调试器扩展
SOSEX可以用于调试托管代码。增强了SOS的功能,使某些特定的调试任务更高效。
加载SOSEX的命令
.load sosex.dll
或指定sosex.dll的完整路径。
虽然在没有加载mscorwkd.dll的情况下也能加载sosex扩展,但这些命令本身并不能工作。
控制CLR的调试
在调试.Net程序时,调试器可以加载一个辅助DLL 称为mscordacwks.dll ,这个dll用于输出托管代码调试过程中的各种信息,这个dll的路径取决于mscorwks.dll的路径。在实时调试中不会有问题,但在事后调试时可能出现版本不匹配的情况。可以使用cordll来告诉调试器加载mscordacwks.dll的确切位置
.cordll -lp c:\x\y\z 从文件夹c:\x\y\z下加载mscordacwks.dll
如果要卸载mscordacwks.dll可以使用-u 开关。
设置断点
非托管调试中使用bp命令来断点。
使用X 来显示包含 指定函数字符串的所有符号 ,bp的参数可以是 函数名,或地址。
在JIT编译生成的函数上设置断点
先让目标程序运行一次函数,确保已经被JIT编译器编译,然后通过SOS命令 name2ee 来判断
!name2ee <module name> <type or method name>
如果在最后一行输出中 这个方法的状态为JITTED 表示已被 JIT编译器编译过了,并且会给出地址。
可以通过命令U 对这一段代码做一个简单的完整性检查。
!U <address>
!ClrStack 来查看代码位置
在还没有被JIT编译的函数上设置断点
bpmd 用来在还没有被JIT编译的代码上设置断点。它采取的做法是设置一个延时断点,在设置该断点时,断点的位置是未知的,只有将来某个事件发生时,才会真正地设置断点。
在预编译的程序集中设置断点
使用ngen.exe 预编译 !name2ee 可以直接断点指定的函数。
在泛型方法上设置断点
!bpmd aaa.exe XXX`1.method
1
对象检查
内存转储
dd <address> 查看内存
du <address> 把被转储的内存视作Unicode字符
da <address> 把被转储的内存视作ASCII字符
dw <address> 把被转储的内存视作字word
db <address> 把被转储的内存视作字节值和ASCII字符
dq <address> 把被转储的内存视作四字quad word 值
!dumpobj <addr> 可以转储更多信息
值类型的转储
如何判断一个指针指向的是否是值类型?用DumpObj命令试一下就知道了,如果给定的指针指向一个值类型,会报错。
!DumpObj <addr>
如果要显示插管调用栈及相关的局部变量,可以通过ClrStack命令来获取。
!ClrStack -a
如果ClrStack提示错误,指出当前线程上下文不是一个有效的托管线程,需要先切换线程上下文。使用 ~ 将上下文切换到线程0 再执行ClrStack
~0s
通过命令r将寄存器转储出来。
!DumpVC <方法表地址> <地址> 给出方法更为详细的信息,如值类型的名字和大小。
转储基本的引用类型
!DumpObj [-nofields] <obj addr> DumpObj会转储出类型信息及相关的域
这个命令可以缩写为 do
数组的转储
!DumpArray -details <addr> 输出数组的详细信息 DumpArray会自动识别出正在处理的是值类型还是引用类型。
栈上对象的转储
大多时候可以用ClrStack 命令来找出每个栈帧的参数和局部变量
DumpStackObjects 可以对栈进行遍历,并输出栈上的所有托管对象。语法:
!DumpStackObjects [-verify] [top stack [bottom stack]]
如果没有指定任何参数,那么DumpStackObjects 会输出当前线程的所有托管对象。
DumpStackObjects 太长,可以缩写成dso ??
找出对象的大小
对象的大小表示这个类型所占据的内存字节数量。
!DumpObj 可以获得对象大小
通常,对象会引用其它对象,如果要获得对象的总体大小(包括遍历每个类型域的大小)可以使用ObjSize命令
!ObjSize <addr> 如果没有指定地址,那么这个命令将列出进程中所有托管线程中所有对象大小。
异常的转储
Windows在实现异常模型时采用的方法之一就是结构化异常处理(SEH),CLR在每个异常内携带的额外信息被保存在托管堆上。异常是一种引用类型,所有CLR异常都以SHE异常形式出现,错误码为0xe0434f4d
用 kb来输出调用栈。
SOS调试器扩展中包含一个命令 PrintException 参数是托管异常的地址,能以更容易理解的形式输出异常信息。
!PrintException <addr>
另一个有用的命令是 Threads 能显示出系统中各个托管线程的信息,包括该线程抛出的最后一个异常。
!Threads
StopOnException 这个命令的作用是在抛出特定异常时设置一个断点。语法
!StopOnException [-derived] [-create | -create2] <Exception> [<pseudo-register number>]
!StopOnException -create System.ArgumentException
线程的操作
SOS调试器扩展提供了一组线程命令
ClrStack
!ClrStack 可以输出线程ID和托管调用栈的所有栈帧
!ClrStack -l 用于显示局部变量信息(没有名字)
得到变量的地址后,可以用!DumpObj <addr> 来输出变量信息
!ClrStack -p 将显示调用栈上每个托管代码栈帧的所有参数。
Threads
!Threads 可以枚举进程中的所有托管代码线程。
Threads命令包含了一组开关,-live开关将Threads命令限制为只输出那些活跃状态的线程信息。 -special 开关表示输出进程的所有特殊的线程,如垃圾收集线程,调试器线程,线程池定时器线程等。
DumpStack
ClrStack只给出托管代码调用栈,k系列命令只给出非托管调用栈。要同时转储出托管代码调用栈和非托管代码调用栈,可以使用DumpStack命令。
DumpStack 使用-EE 开关表示只显示托管函数。
EEStack
EEStack 会对进程中每个活跃的线程调用DumpStack
两个开关: -short 只输出感兴趣的线程调用栈。即 (这个线程持有一个锁。线程被劫持以执行一个垃圾收集操作。线程当前正在托管代码中执行) -EE 这个开关会直接传给DumpStack命令,表示只显示托管代码调用栈。
COMState
当与COM子系统一起使用时,重点是要知道COM提供的不同套间模型。COM提供了两种主要的套间模型。1,单线程套间 STA 2,多线程套间 MTA 。每当一个线程希望使用COM对象时,它必须告诉COM子系统它需要使用哪一种套间模型。在具体套间模型中对线程进行初始化的概念非常重要,在调用COM互用性问题时,找出线程的套间模型是一个更为重要的方面。
COMState 可以找出系统中每个线程的套间模型。
!COMState
代码审查
反汇编代码
命令u 把代码字节流反汇编为汇编指令,因而能很容易推断出代码所要执行或者曾经执行的功能。
u适用于非托管代码,对于托管代码可以使用 !U <addr> U命令除了指定代码地址外,还可以指定一个方法描述符。
从代码地址上获得方法描述符
!IP2MD <code addr> 将任意的托管代码地址转为一个方法描述符,然后就可以使用DumpMD命令获得进一步的信息。
显示中间语言指令
!DumpIL <addr> 来查看函数的IL
该命令以方法描述符的地址作为参数,从IP2MD 可以得到方法描述符地址。
CLR内部命令
获得CLR的版本
!EEVersion
根据名字找到方法描述符
如果有了某个方法的名字后,找出方法描述符最有用的方法之一就是Name2EE命令 ,其参数是模块的名字和方法的全名,这个命令将输出方法的一些信息,包括方法描述符。
对象同步块的转储
每个CLR托管类型都有一个相应的同步块,用于实现同步行为。 SyncBlk 命令可以用来获得这个同步块的详细信息,这个命令在分析死锁问题时非常有用。
对象方法表的转储
每个托管对象都有一个相应的方法表,其中包含了该对象的一些信息。DumpMT命令可以用来显示方法表的信息,命令参数是方法表的地址。
托管堆和垃圾收集器信息的转储
命令 描述
DumpHeap 遍历托管堆,收集并输出这个堆以及位于堆上所有对象的详细信息
GCRoot 显示对某个对象的引用(根对象)信息。当要找出某个对象为什么还没有被收集时,这将是非常有用的信息
TraverseHeap 遍历托管堆,并把遍历结果输出到一个文件中,由CLR分析器来进行分析
VerifyHeap 与任何堆一样,托管堆可能被破坏,这个命令将验证托管堆的完整性
诊断命令
找出对象的应用程序域
要找出指定的对象实例位于哪一个应用程序域中,可以使用 FindAppDomain 命令,知道了对象的应用程序域之后,可以使用 DumpDomain 来获取应用程序域的进一步信息。
进程信息
用ProcInfo命令
!ProcInfo [-env] [-time] [-mem] 如果不加参数会显示所有这三类信息。
SOSEX扩展命令
扩展的断点支持
断点列表
!mbl 显示所有的断点
mbc 将从列表中清除指定的断点,或清除所有断点
mbd 将禁用列表中指定的断点,或禁用所有的断点
mbe 将启用列表中指定的断点,或启用所有的断点
设置断点
mbp 这个命令可以在任意给定的源代码位置上设置断点。
mbm 可以在特定类型的指定IL偏移处设置一个断点。
!mbm *!Advanced.NET.Debugging.Chapter3.Simple.Main 0
mbm 的选项
!mbm <strTypeAndMethodFilter> <intILOffset> [Options]
其中Options 可以是 /1 只触发这个断点一次。 /p: 表示在第几次执行到断点时才停止执行程序。 /t: 只有由threadID指定线程才可以触发这个断点。
mbm 支持在strTypeAndMethodFilter中使用通配符。
托管元数据
如果想找出类型名和方法名,可以使用mx命令,语法为 !mx <filterString> 其中参数 filter string 表示查找元数据的通配符。
另一个有用的命令是 mln 参数是一个地址,可以识别出与该地址上内容相关的代码,可以准确地识别出JIT已编译的代码,堆及栈对象
————————————————
版权声明:本文为CSDN博主「Pelva」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33074817/article/details/121899511