现在很多的程序都是多语言混合编程的,比如我司的产品,就是用C++/.net clr混合编制的。那么当我们调试这样的程序时,一定要注意,比如有时我们只看到c++的栈和名称,而.net clr的代码确看不到。比如

windbg调试托管代码 .Net clr

那要怎样才能看到.net clr的代码和栈呢,下面简单讲一下。

一、SOS.DLL

还可以通过将 SOS 调试扩展加载到 WinDbg.exe 调试器中并在 WinDbg.exe 中执行命令来使用此扩展。在调试托管代码时,有两个扩展DLL需要注意,它们分别是SOS和SOSEX。

1.1、在Windbg里加载SOS

若要将 SOS 调试扩展加载到 WinDbg.exe 调试器中,请在工具中运行以下命令:

.loadby sos clr

 .loadby sos clrjit

默认情况下,应使用与当前版本的 Mscorwks.dll 匹配的 SOS.dll 版本。若要使用在其他计算机上创建的转储文件,请确保该安装所附带的 Mscorwks.dll 文件存在于符号路径中,并加载相应的 SOS.dll 版本。

.load <full path to sos.dll>

如果使用的.Net版本不同,那么还可以手动指定版本,如手动指定载入4.0版本命令:

.load C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/sos.dll
.load C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/sos.dll

如果是64位,那么应该加载如下sos.dll

.load C:/Windows/Microsoft.NET/Framework64/v2.0.50727/sos.dll
.load C:/Windows/Microsoft.NET/Framework64/v2.0.50727/sos.dll

SOS.dll 随 .NET Framework 安装在 %windir%\microsoft.net\framework\<.NET 版本> 目录下。

1.2、SOS扩展命令

语法

shell
![command] [options]

命令

 

命令 说明
AnalyzeOOM (ao) (在服务器垃圾回收中,它将在每个垃圾回收堆上显示 OOM(如果有))。
BPMD [ -nofuturemodule] [<module name> <method name>] [ -md <MethodDesc>] -list -clear <pending breakpoint number> -clearall 使用 -clear-clearall 选项可从该列表中移除挂起断点。
CLRStack [ -a] [ -l] [ -p] [ -n] 在基于 x64 和 IA-64 的平台上,SOS 调试扩展不显示过渡帧。
COMState 列出每个线程的 COM 单元模型和 Context 指针(如果可用)。
DA [ -start <startIndex>] [ -length <length>] [ -detail] [ -nofields] array object address> 此选项仅在指定 -detail 选项后可用。
DumpAssembly <assembly address> 可以通过使用 DumpDomain 命令获取程序集地址。
DumpClass <EEClass address> 使用 DumpMTDumpObjName2EEToken2EE 命令获取 EEClass 结构地址。
DumpDomain [<domain address>] AppDomain 对象 。
DumpHeap [ -stat] [ -strings] [ -short] [ -min <size>] [ -max <size>] [ -thinlock] [ -startAtLowerBound] [ -mt <MethodTable address>] [ -type <partial type name>][start [end]] end 参数在指定的地址处停止列出。
DumpIL <Managed DynamicMethod object> | <DynamicMethodDesc pointer> | <MethodDesc pointer> 动态 MSIL 引用托管对象数组中的对象而不是引用元数据标记。
DumpLog [ -addr <addressOfStressLog>] [<Filename>] 利用可选的 -addr 选项,你可以指定压力日志而非默认日志。
DumpMD <MethodDesc address> 可以使用 IP2MD 命令从托管函数中获取 MethodDesc 结构地址。
DumpMT [ -MD] <MethodTable address> 每个托管对象均包含一个方法表指针。
DumpMethodSig <sigaddr> <moduleaddr> 显示有关指定地址处的 MethodSig 结构的信息。
DumpModule [ -mt] <Module address> 可以使用 DumpDomainDumpAssembly 命令检索模块的地址。
DO <object address> -nofields 选项可阻止显示对象的字段,它对 String 这样的对象很有用。
DumpRuntimeTypes 显示垃圾回收器堆中的运行时类型对象并列出其关联的类型名称和方法表。
DumpStack [ -EE] [ -n] [top stack [bottom stack]] 在基于 IA-64 的平台上,将忽略 topbottom 参数。
DumpSig <sigaddr> <moduleaddr> 显示有关指定地址处的 Sig 结构的信息。
DumpSigElem <sigaddr> <moduleaddr> 但是,如果签名已在某种程度上被损坏,则可使用 DumpSigElem 读取它的有效部分。
DumpStackObjects [ -verify] [top stack [bottom stack]] DumpStackObject 命令与堆栈跟踪命令(如 K 命令和 CLRStack 命令)一起使用以确定局部变量和参数的值。
DumpVC <MethodTable address> <Address> 值类不将方法表作为其第一个字段。
EEHeap [ -gc] [ -loader] 如果指针落在由 -gc 给定的段范围内,则该指针是一个对象指针。
EEStack [ -short] [ -EE] 当前在托管代码中的线程。
EEVersion 显示 CLR 版本。
EHInfo [<MethodDesc address>] [<Code address>] 此命令显示子句块(try 块)和处理程序块(catch 块)的代码地址和偏移量。
常见问题解答 显示常见问题。
FinalizeQueue [ -detail] | [ -allReady] [ -short] 如果单独使用此选项,则将列出终结和“准备终结”队列中的所有对象。
FindAppDomain <Object address> 确定指定地址处的对象的应用程序域。
FindRoots -gen <N> | -gen any |<object address> 此时,调试对象处于正确的状态,以便 FindRoots 从当前已报废的生成中识别出对象的根。
GCHandles [ -perdomain] 例如,当代码由于强垃圾回收器句柄仍指向一个大型数组而保留该数组时,若不释放句柄就将其放弃,则会发生内存泄漏。
GCHandleLeaks 如果在内存中找不到句柄,此命令将显示一个通知。
GCInfo <MethodDesc address><Code address> 如果发生垃圾回收,回收器必须知道对象引用的位置,以便可以使用新的对象指针值更新相应的对象引用。
GCRoot [ -nostacks] <Object address> -nostacks 选项将搜索限制为垃圾回收器句柄和 reachable 对象。
GCWhere <object address> 如果自变量位于托管堆中,但不是有效的对象地址,则大小显示为 0(零)。
help [<command>] [faq] faq 参数显示常见问题的答案。
HeapStat [ -inclUnrooted | -iu] 如果指定 -inclUnrooted 选项,则报告将包括有关不再为根的垃圾回收堆中的托管对象的信息。
HistClear 通常,你不必显式调用 HistClear,因为每个 HistInit 都会清理以前的资源。
HistInit 从保存在调试对象中的压力日志初始化 SOS 结构。
HistObj <obj_address> 检查所有压力日志的重定位记录,并显示可能已将地址作为自变量传入的垃圾回收重定位链。
HistObjFind <obj_address> 显示在指定地址处引用对象的所有日志项。
HistRoot <root> 根值可用于通过垃圾回收来跟踪对象的移动。
IP2MD <Code address> 显示已 JIT 编译的代码中指定地址处的 MethodDesc 结构。
ListNearObj (lno) <obj_address> 该命令在垃圾回收堆和自变量地址之后的对象中寻找地址,而该垃圾回收堆看上去像托管对象(基于有效的方法表)的有效开头。
MinidumpMode [0] [1] 此选项可防止你对小型转储运行不安全的命令。
Name2EE <module name> ! <type or method name> 类型必须是完全限定的。
ObjSize [<Object address>] | [ -aggregate] [ -stat] 通过使用 !dumpheap -stat!objsize -aggregate -stat,可以确定不再为根的对象并诊断各种内存问题。
PE [ -nested] [<Exception object address>] 可以使用此命令设置 _stackTrace 字段的格式并查看该字段(它是一个二进制数组)。
ProcInfo [ -env] [ -time] [ -mem] 显示进程的环境变量、内核 CPU 时间和内存使用统计信息。
RCWCleanupList <RCWCleanupList address> 显示在指定地址处等待清理的运行时可调用包装器的列表。
SaveModule <Base address> <Filename> 将加载到内存中指定地址的图像写入指定文件。
SOSFlush 刷新内部 SOS 缓存。
StopOnException [ -derived] [ -create | -create2] <Exception> <Pseudo-register number> -derived 选项用于捕获指定异常以及从指定异常派生的每个异常。
SyncBlk [ -all | <syncblk number>] 它可以存放 COM 互操作数据、哈希代码和用于线程安全操作的锁定信息。
ThreadPool 显示有关托管线程池的信息,包括队列中工作请求的数目、完成端口线程的数目和计时器的数目。
Token2EE <module name> <token> 也可以传递某个模块的调试器名称,如 mscorlibimage00400000
Threads [ -live] [ -special] AppDomain 卸载线程和线程池计时器线程。
ThreadState < State value field > 示例:

0:003> !Threads ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer) 0:003> !ThreadState b220 Legal to Join Background CLR Owns CoInitialized In Multi Threaded Apartment
TraverseHeap [ -xml] <filename> Microsoft 下载中心下载 CLR 探查器。
U [ -gcinfo] [ -ehinfo] [ -n] <MethodDesc address> | <Code address> 可以指定 -n 选项来禁用此行为。
VerifyHeap 错误构造的平台调用可能导致堆损坏。
VerifyObj <object address> 检查作为自变量传递的对象是否有损坏迹象。
VMMap 遍历虚拟地址空间并显示应用于每个区域的保护类型。
VMStat “TOTAL”列显示“AVERAGE”列乘以“BLK COUNT”列的结果。

二、Windbg调试SOS.DLL和CLR 不匹配问题

CLR.dll 是一个原生C++ Win32 Native code 编写的托管代码运行时,它是托管代码的运行环境,它从我们.net编写的生成的dll中抽取IL中间代码和元数据,通过JIT即时编译来生成内存中的原生native Code. Windbg是一个针对原生Native code 的调试器。那在原生调试器和托管代码世界之间,我们需要一座“桥梁”这就是SOS.dll。但是CLR的内部数据结构可能是要不断变化的,这时如果有一个针对调试器的一个抽象层就非常重要,调试器通过一个抽象层来访问CLR的内部数据结构。mscordacwks.dll  就是这样一个抽象层 ( Data-Access-Component  (DAC) ),它实现了让调试器SOS.dll 以比较稳定的接口来访问CLR内部不断的数据结构的目的。但CLR和SOS.dll 以及 mscordacwkd.dll 还是耦合的非常紧密的,以至乎 他们的版本必须一致才可以正常工作。一般,我们的机器上安装好.net 运行时后,都有 clr.dll ,sos.dll 和mscordacwk.dll 三个版本一致的dll。

我们在用windbg调试客户发来的dump文件的时候,如果不是相同的环境的话,很容易出现这个现象,简单的说就是程序运行机器上的CLR与当前开发调试人员机器上的CLR是不同的版本,从而导致开发人员机器上的SOS.dll与dump file中要求的SOS.dll不一样(比如应用程序要求是.net framework 4.6,所以客户机安装的就是.net 4.6,但是开发人员机器上安装的是.net 4.7)。我们在open crash dump之后。输入命令.loadby sos clr,这时候是成功的,但是在运用扩展命令的时候就显示失败,比如!dumpheap,这时候显示CLR version和SOS version 不匹配,这个时候你就需要将客户机器(相同环境机器也行)上的sos.dll复制到调试机器上随便一个文件夹,比如C:\temp\目录下,然后运行.load c:\temp\sos.dll,这时候也会成功。在执行上述过程之后,还是不能调试,因为sos.dll还对应一个mscordacwks.dll, 同时你还需要将这个dll从客户机器上复制过来,放到symbol所在的目录或者source文件所在的目录下都行, 但是需要改名字,名字的规则如下:
mscordacwks_AAA_AAA_x.x.x.xxxx.dll 。其中AAA是dump的bit位对应的(x86 or AMD64),x.x.x.x实际上就是mscordacwks.dll的版本号(可以从文件的property dialog中查到),比如2.0.50272.8763, 4.7.2114.0等。
然后运行.cordll -ve -u -l   命令,就能查看出该dll被成功加载,并且显示之前的dll不匹配SOS.dll版本。经过上面的过程之后就能利用SOS 扩展命令正常调试了。.cordll –ve –u –l (小写的L ) 这个命令是控制调试和控制CLR的命令,-ve是显示详细信息,-u 卸载模块,-l (小写的L) 是加载模块,上面这个命令就是卸载CLR 调试模块,然后再加载CLR调试模块,并显示详细信息。如果你不知道怎么改mscordacwks.dll名字,可以先运行.cordll -ve -u -l,就会显示当前的版本不匹配SOS的版本,需要的dll的名字是什么。客户机dll的路径:c:\windows\microsoft.NET\framework\各个版本目录下,根据上面命令的提示可以找到对应的文件夹,比如4.0之后的都是在4.0.*目录下,2.0的都是在2.0.*目录下。

除了到客户机上拷贝对应版本的动态库外,还可以到http://www.mskbfiles.com/sos.dll.php这里找对应的版本。

相关文章: