利用SOS分析调试托管代码--(1)

  • 综述
      SOS是一个非托管调试器扩展,用来帮助对CLR托管代码进行分析与调试。可以通过加载到非托管调试器WinDbgNTSD或者VisualStudio调试器中使用。不同的CLR版本都自带有对应的SOS,在对应的版本目录下。
  .NET框架为开发人员隐藏了底层系统的复杂性,这虽然极大地提升软件开发效率,但也同时增加了对底层的技术细节了解的难度,使得一些问题调试起来更为困难,比如内存溢出或者死锁问题。
  通过SOS提供的命令,可以直观的了解CLR托管代码的底层结构,更可以方便的进行调试,并诊断一些具体问题(如GC、锁)。
  将调试器附加到目标进程后,就可以通过.loadby sos mscorwks(CLR4.0要用.loadby sos clr来加载SOSVisualStudio的加载命令是.load sos,需要先对项目启用非托管调试。
  SOS的命令格式为:![command] [options]
  下面是通过!help得到的针对CLR4.0SOS命令: 
   

对象查看

代码/堆栈检查

CLR数据结构检查

诊断工具

GC历史检查

其他

DumpObj

DumpArray

DumpStackObjects

DumpHeap

DumpVC

GCRoot

ObjSize

FinalizeQueue

PrintException

TraverseHeap

Threads

ThreadState

IP2MD

U

DumpStack

EEStack

CLRStack

GCInfo

EHInfo

BPMD

COMState

DumpDomain

EEHeap

Name2EE

SyncBlk

DumpMT

DumpClass

DumpMD

Token2EE

EEVersion

DumpModule

ThreadPool

DumpAssembly

DumpSigElem

DumpRuntimeTypes

DumpSig

RCWCleanupList

DumpIL

VerifyHeap

VerifyObj

FindRoots

HeapStat

GCWhere

ListNearObj

GCHandles

GCHandleLeaks

FinalizeQueue

FindAppDomain

SaveModule

ProcInfo

StopOnException

DumpLog

VMMap

VMStat

MinidumpMode

AnalyzeOOM

HistInit

HistRoot

HistObj

HistObjFind

HistClear

FAQ

  
  以下通过对一段运行的目标托管代码的调试分析,介绍SOS一些主要的命令用法。目标代码内容略过。
 
  • 查看CLR基本的数据结构
1.查看应用程序域
    首先可以用DumpDomain列出进程中的所有AppDomain对象。
  
dumpdomain
!dumpdomain
--------------------------------------
System
Domain: 61fe3478
LowFrequencyHeap: 61fe3784
HighFrequencyHeap: 61fe37d0
StubHeap: 61fe381c
Stage: OPEN
Name: None
--------------------------------------
Shared
Domain: 61fe3140
LowFrequencyHeap: 61fe3784
HighFrequencyHeap: 61fe37d0
StubHeap: 61fe381c
Stage: OPEN
Name: None
Assembly: 00432838 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 004328d8
Module Name
602e1000
C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

--------------------------------------
Domain
1: 003e16b0
LowFrequencyHeap: 003e1a2c
HighFrequencyHeap: 003e1a78
StubHeap: 003e1ac4
Stage: OPEN
SecurityDescriptor: 003e2e28
Name: Wuhong.SOSTest.exe
Assembly: 00432838 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 004328d8
SecurityDescriptor: 0042d3a0
Module Name
602e1000
C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

Assembly: 0043ac50 [F:\Wuhong.SOSTest\bin\Debug\Wuhong.SOSTest.exe]
ClassLoader: 0043acf0
SecurityDescriptor: 0043d688
Module Name
00132e9c
F:\Wuhong.SOSTest\bin\Debug\Wuhong.SOSTest.exe
    
  我们可以看到进程内有3个AppDomain:系统Domain、共享Domain以及Domain1,还可以看到AppDomain内加载的Assembly和Module。
 
2.查看模块信息
  使用命令dumpmodule可以显示有关指定地址处的Module的信息。-mt选项显示Module中定义的类型和Module所引用的类型。
  我们可以查看Domain1中具体某个Module的信息,比如Module名为F:\Wuhong.SOSTest\bin\Debug\Wuhong.SOSTest.exe的信息。
 
dumpmodule
!dumpmodule -mt 00132e9c
Name: F:\Wuhong.SOSTest\bin\Debug\Wuhong.SOSTest.exe
Attributes: PEFile
Assembly: 0043ac50
LoaderHeap: 00000000
TypeDefToMethodTableMap: 001300c4
TypeRefToMethodTableMap: 001300d4
MethodDefToDescMap: 00130134
FieldDefToDescMap: 00130164
MemberRefToDescMap: 00130170
FileReferencesMap: 001301dc
AssemblyReferencesMap: 001301e0
MetaData start
address: 00d62248 (2436 bytes)

Types defined in this module

MT TypeDef Name
------------------------------------------------------------------------------
0013381c
0x02000002 Wuhong.SOSTest.Program
00133918 0x02000003 Wuhong.SOSTest.Complex

Types referenced in this module

MT TypeRef Name
------------------------------------------------------------------------------
605ff5e8
0x01000001 System.Object
606075f4
0x01000013 System.Console
60601ae0
0x01000015 System.Threading.Monitor
 
  我们可以看到Module的一些基本信息:Module格式是PEFile、相应的Assembly地址002ed3b0(查看Assembly使用DumpAssembly命令)、元数据起始地址013a22b8等等。-mt选项让我们还可以看到在Module之定义了2个类型,引用了3个类型。
 
 3.查看方法表
  我们可以利用DumpMT命令查看具体类型的方法表,DumpMT显示指定地址处的方法表的信息。-MD选项将显示与对象一起定义的所有方法的列表。
  例如我们要查看类型Wuhong.SOSTest.Complex的方法表的信息。
 
dumpmt
!dumpmt -md 00133918
EEClass: 001314cc
Module: 00132e9c
Name: Wuhong.SOSTest.Complex
mdToken: 4050d2302000003
File: F:\Wuhong.SOSTest\bin\Debug\Wuhong.SOSTest.exe
BaseSize: 0x18
ComponentSize: 0x0
Slots in
VTable: 12
Number of IFaces in
IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
0013c068 001338fc NONE Wuhong.SOSTest.Complex.ToString()
6050e2e0 602e493c PreJIT System.Object.Equals(System.Object)
6050e1f0 602e495c PreJIT System.Object.GetHashCode()
60591600 602e4970 PreJIT System.Object.Finalize()
0013c030 0013388c JIT Wuhong.SOSTest.Complex.set_Real(Double)
0013c038 0013389c NONE Wuhong.SOSTest.Complex.get_Real()
0013c040 001338ac JIT Wuhong.SOSTest.Complex.set_Imaginary(Double)
0013c048 001338bc NONE Wuhong.SOSTest.Complex.get_Imaginary()
0013c050 001338cc JIT Wuhong.SOSTest.Complex..ctor(Double, Double)
0013c058 001338dc NONE Wuhong.SOSTest.Complex.Multiply(Wuhong.SOSTest.Complex)
0013c060 001338ec JIT Wuhong.SOSTest.Complex.MultiplyBySelf()
0013c070
00133908 NONE Wuhong.SOSTest.Complex.Clone()
 
  可以看到Multiply ()方法的CodeAddr值为ffffffff,表明还没有本地代码,IsJitted为no也表明方法还未经过JIT编译。
 
  • 查看对象
1.显示托管调用栈
      我们首先可以查看当前托管代码的堆栈跟踪。利用利用CLRStack命令,-p选项显示托管函数的参数。-l选项将显示有关帧中的局部变量的信息。-a选项是一个表示-l-p的组合的快捷方式。
  另外还可以用DumpStackObjects命令显示显示在当前堆栈的边界内找到的所有托管对象。如果要查看完整的调用栈,包括非托管部分,需要使用!dumpstack命令。
 
CLRStack
!CLRStack -a
OS Thread
Id: 0x2e2c (11820)
Child SP IP
Call Site
0028f040 002902aa Wuhong.SOSTest.Complex.MultiplyBySelf()
PARAMETERS:
this (
0x0028f09c) = 0x01b0b22c
LOCALS:
0x0028f098 = 0x02bf8098
0x0028f094 = 0x00000000
0x0028f090 = 0x00000001
0x0028f08c = 0x01b0b22c
0x0028f088 = 0x00000000

0028f0e0 002900f4 Wuhong.SOSTest.Program.Main(System.String[])
PARAMETERS:
args (
0x0028f0f8) = 0x01b0b210
LOCALS:
0x0028f0f0 = 0x00000000
0x0028f0e8 = 0x00000000
0x0028f0e4 = 0x01b0b22c

0028f360 619f21bb [
GCFrame: 0028f360]
 
  从调用栈上可以看到,代码运行到Main函数中的MultiplyBySelf()方法。-a选项让我们看到方法传入的参数以及局部变量。MultiplyBySelf()参数是this对象,说明是一个实例方法。
 
 2.查看引用对象
   利用DumpObj命令可以显示有关指定地址处的对象的信息。比如我们查看上节MultiplyBySelf()方法的参数,这个参数是个Wuhong.SOSTest.Complex类型的对象。
 
DumpObj
!DumpObj 0x01b0b22c
Name: Wuhong.SOSTest.Complex
MethodTable: 00133918
EEClass: 001314cc
Size: 24(0x18) bytes
File: F:\Wuhong.SOSTest\bin\Debug\Wuhong.SOSTest.exe
Fields:
MT Field Offset Type VT Attr Value Name
605fa380
4000001 4 System.Double 1 instance 1.000000 <Real>k__BackingField
605fa380
4000002 c System.Double 1 instance 2.000000 <Imaginary>k__BackingField
 
  我们不仅能看到对象类型一些元数据(MethodTable、EEClass等等),更重要的是能看到对象的具体信息,包括字段的信息。比如这个Complex对象有2个double类型的字段,值分别是1.0和2.0。
 
3.查看值类型
  我们可以进一步查看值类型的信息,利用DumpVC命令。比如可以查看上节Complex对象中Real字段。
 
DumpVC
!DumpVC 605fa380 4000001
Name: System.Double
MethodTable: 605fa380
EEClass: 60335424
Size: 16(0x10) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
605fa380 40003e5
0 System.Double 1 instance 0.000000 m_value
605fa380 40003e6 b28 System.Double
1 shared static NegativeZero
>>
Domain:Value 003e16b0:NotInit <<
 
4.查看数组
  利用DumpArray命令可以查看数组对象的元素。-start选项指定开始显示元素的起始索引。-length选项指定要显示的元素数量。
  比如Wuhong.SOSTest.Program.Main()方法的参数是一个string数组,我们就可以用DumpArray进行检索。
 
DumpArray
!DumpArray 0x01b0b210
Name: System.String[]
MethodTable: 605b6c28
EEClass: 60339698
Size: 16(0x10) bytes
Array: Rank 1, Number of elements 0, Type CLASS
Element
Methodtable: 605ff9ac
 
  可以看到这是一个空数组。
  结果同时列出了数组元素的类型的方法表地址。
 
  • 查看代码
1.查看IL
      前面我们能得到具体方法的MethodDesc地址,利用dumpil命令可以显示与托管方法关联的中间语言(MSIL)
  比如查看方法Wuhong.SOSTest.Complex.Multiply(Wuhong.SOSTest.Complex)IL
   
DumpIL
!DumpIL 001338dc
ilAddr = 00d620ec
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call Wuhong.SOSTest.Complex::get_Real
IL_0007: ldarg.1
IL_0008: callvirt Wuhong.SOSTest.Complex::get_Real
IL_000d: mul
IL_000e: ldarg.0
IL_000f: call Wuhong.SOSTest.Complex::get_Imaginary
IL_0014: ldarg.1
IL_0015: callvirt Wuhong.SOSTest.Complex::get_Imaginary
IL_001a: mul
IL_001b: sub
IL_001c: ldarg.0
IL_001d: call Wuhong.SOSTest.Complex::get_Real
IL_0022: ldarg.1
IL_0023: callvirt Wuhong.SOSTest.Complex::get_Imaginary
IL_0028: mul
IL_0029: ldarg.0
IL_002a: call Wuhong.SOSTest.Complex::get_Imaginary
IL_002f: ldarg.1
IL_0030: callvirt Wuhong.SOSTest.Complex::get_Real
IL_0035: mul
IL_0036: add
IL_0037: newobj Wuhong.SOSTest.Complex::.ctor
IL_003c: stloc.0
IL_003d: ldstr "{0} * {1} = {2}"
IL_0042: ldarg.0
IL_0043: ldarg.1
IL_0044: ldloc.0
IL_0045: call System.Console::WriteLine
IL_004a: nop
IL_004b: ldloc.0
IL_004c: stloc.1
IL_004d: br.s IL_004f
IL_004f: ldloc.1
IL_0050: ret
  
  如果对IL有所了解,可以看到这段代码是个复数的乘法计算,最后将结果输出到Console
 
2.查看反汇编代码
  利用u命令可以直接显示由方法的MethodDesc结构指针或方法体内的代码地址指定的托管方法的本地反汇编代码。但没有JIT编译的方法还没有本地代码,所以无法使用这个命令。
  我们选择已经JIT编译的方法Wuhong.SOSTest.Complex.MultiplyBySelf(),查看本地代码。
 
U
!U 001338ec
Normal JIT generated code
Wuhong.SOSTest.Complex.MultiplyBySelf()
Begin
00290230, size 199
00290230 55 push ebp
00290231 8BEC mov ebp,esp
00290233 57 push edi
00290234 56 push esi
00290235 53 push ebx
00290236 81EC8C000000 sub esp,8Ch
0029023C 8BF1 mov esi,ecx
0029023E 8D7DB4 lea edi,[ebp-4Ch]
00290241 B910000000 mov ecx,10h
00290246 33C0 xor eax,eax
00290248 F3AB rep stos dword ptr es:[edi]
0029024A 8BCE mov ecx,esi
0029024C 33C0
xor eax,eax
0029024E 8945E4 mov dword ptr [ebp-1Ch],eax
00290251 894DC4 mov dword ptr [ebp-3Ch],ecx
00290254 833D3C31130000 cmp dword ptr ds:[0013313Ch],0
0029025B
7405 je 00290262
0029025D E8B961A161
call 61CA641B (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
00290262 33D2 xor edx,edx
00290264 8955BC mov dword ptr [ebp-44h],edx
00290267 33D2 xor edx,edx
00290269 8955B4 mov dword ptr [ebp-4Ch],edx
0029026C C745B000000000 mov dword ptr [ebp-50h],
0
00290273 33D2 xor edx,edx
00290275 8955C0 mov dword ptr [ebp-40h],edx
00290278 90 nop
00290279 8B4DC4 mov ecx,dword ptr [ebp-3Ch]
0029027C 8B01 mov eax,dword ptr [ecx]
0029027E 8B4028 mov eax,dword ptr [eax+28h]
00290281 FF5008 call dword ptr [eax+8]
00290284 8945AC mov dword ptr [ebp-54h],eax
00290287 8B45AC mov eax,dword ptr [ebp-54h]
0029028A 8945C0 mov dword ptr [ebp-40h],eax
0029028D 33D2
xor edx,edx
0029028F 8955BC mov dword ptr [ebp-44h],edx
00290292 33D2 xor edx,edx
00290294 8955B8 mov dword ptr [ebp-48h],edx
00290297 8B45C4 mov eax,dword ptr [ebp-3Ch]
0029029A 8945B4 mov dword ptr [ebp-4Ch],eax
0029029D 8D55B8 lea edx,[ebp-48h]
002902A0 8B4DB4 mov ecx,dword ptr [ebp-4Ch]
002902A3 E808DE2F60
call 6058E0B0 (System.Threading.Monitor.Enter(System.Object, Boolean ByRef), mdToken: 0600159a)
002902A8
90 nop
002902A9
90 nop
002902AA 8B4DC4 mov ecx,dword ptr [ebp-3Ch]
002902AD E8BEBDEAFF
call 0013C070 (Wuhong.SOSTest.Complex.Clone(), mdToken: 0600000b)
002902B2 8945A8 mov dword ptr [ebp-58h],eax
002902B5 8B45A8 mov eax,dword ptr [ebp-58h]
002902B8 8945BC mov dword ptr [ebp-44h],eax
002902BB 8B45C4 mov eax,dword ptr [ebp-3Ch]
002902
BE 8945A4 mov dword ptr [ebp-5Ch],eax
002902C1 8B4DBC mov ecx,dword ptr [ebp-44h]
002902C4
3909 cmp dword ptr [ecx],ecx
002902C6 E86DBDEAFF
call 0013C038 (Wuhong.SOSTest.Complex.get_Real(), mdToken: 06000004)
002902CB DD5D9C fstp qword ptr [ebp-64h]
002902CE 8B4DBC mov ecx,dword ptr [ebp-44h]
002902D1
3909 cmp dword ptr [ecx],ecx
002902D3 E860BDEAFF
call 0013C038 (Wuhong.SOSTest.Complex.get_Real(), mdToken: 06000004)
002902D8 DD5D94 fstp qword ptr [ebp-6Ch]
002902DB DD459C fld qword ptr [ebp-64h]
002902DE DC4D94 fmul qword ptr [ebp-6Ch]
002902E1 DD5D8C fstp qword ptr [ebp-74h]
002902E4 8B4DBC mov ecx,dword ptr [ebp-44h]
002902E7
3909 cmp dword ptr [ecx],ecx
002902E9 E85ABDEAFF
call 0013C048 (Wuhong.SOSTest.Complex.get_Imaginary(), mdToken: 06000006)
002902EE DD5D84 fstp qword ptr [ebp-7Ch]
002902F1 8B4DBC mov ecx,dword ptr [ebp-44h]
002902F4
3909 cmp dword ptr [ecx],ecx
002902F6 E84DBDEAFF
call 0013C048 (Wuhong.SOSTest.Complex.get_Imaginary(), mdToken: 06000006)
002902FB DD9D7CFFFFFF fstp qword ptr [ebp+FFFFFF7Ch]
00290301 DD4584 fld qword ptr [ebp-7Ch]
00290304 DC8D7CFFFFFF fmul qword ptr [ebp+FFFFFF7Ch]
0029030A DC6D8C fsubr qword ptr [ebp-74h]
0029030D 83EC08
sub esp,8
00290310 DD1C24 fstp qword ptr [esp]
00290313 8B4DA4 mov ecx,dword ptr [ebp-5Ch]
00290316 E815BDEAFF call 0013C030 (Wuhong.SOSTest.Complex.set_Real(Double), mdToken: 06000003)
0029031B
90 nop
0029031C 8B45C4 mov eax,dword ptr [ebp-3Ch]
0029031F 898578FFFFFF mov dword ptr [ebp+FFFFFF78h],eax
00290325 8B4DBC mov ecx,dword ptr [ebp-44h]
00290328 3909 cmp dword ptr [ecx],ecx
0029032A E809BDEAFF
call 0013C038 (Wuhong.SOSTest.Complex.get_Real(), mdToken: 06000004)
0029032F DD9D70FFFFFF fstp qword ptr [ebp+FFFFFF70h]
00290335 8B4DBC mov ecx,dword ptr [ebp-44h]
00290338 3909 cmp dword ptr [ecx],ecx
0029033A E809BDEAFF
call 0013C048 (Wuhong.SOSTest.Complex.get_Imaginary(), mdToken: 06000006)
0029033F DD9D68FFFFFF fstp qword ptr [ebp+FFFFFF68h]
00290345 DD8570FFFFFF fld qword ptr [ebp+FFFFFF70h]
0029034B DC8D68FFFFFF fmul qword ptr [ebp+FFFFFF68h]
00290351 D80DCC032900 fmul dword ptr ds:[002903CCh]
00290357 83EC08 sub esp,8
0029035A DD1C24 fstp qword ptr [esp]
0029035D 8B8D78FFFFFF mov ecx,dword ptr [ebp+FFFFFF78h]
00290363 E8D8BCEAFF call 0013C040 (Wuhong.SOSTest.Complex.set_Imaginary(Double), mdToken: 06000005)
00290368 90 nop
00290369 90 nop
0029036A
90 nop
0029036B C745E000000000 mov dword ptr [ebp-20h],
0
00290372 C745E4FC000000 mov dword ptr [ebp-1Ch],0FCh
00290379 68B7032900 push 2903B7h
0029037E EB00 jmp
00290380
00290380 0FB645B8 movzx eax,byte ptr [ebp-48h]
00290384 85C0 test eax,eax
00290386 0F94C0 sete al
00290389 0FB6C0 movzx eax,al
0029038C 8945B0 mov dword ptr [ebp-50h],eax
0029038F 837DB000 cmp dword ptr [ebp-50h],
0
00290393 7509 jne 0029039E
00290395 8B4DB4 mov ecx,dword ptr [ebp-4Ch]
00290398 E8EB2A7661 call 619F2E88 (System.Threading.Monitor.Exit(System.Object), mdToken: 0600159b)
0029039D
90 nop
0029039E
58 pop eax
0029039F FFE0 jmp eax
002903A1
90 nop
002903A2 FF75C4 push dword ptr [ebp-3Ch]
002903A5 8B0D3020B002 mov ecx,dword ptr
ds:[02B02030h] ("{0} MultiplyBySelf = {1}")
002903AB 8B55BC mov edx,dword ptr [ebp-44h]
002903AE E8C1D12F60
call 6058D574 (System.Console.WriteLine(System.String, System.Object, System.Object), mdToken: 0600091b)
002903B3
90 nop
002903B4
90 nop
002903B5 EB09 jmp 002903C0
002903B7 C745E400000000 mov dword ptr [ebp-1Ch],
0
002903
BE EBE1 jmp 002903A1
002903C0
90 nop
002903C1 8D65F4 lea esp,[ebp-0Ch]
002903C4 5B
pop ebx
002903C5 5E
pop esi
002903C6 5F
pop edi
002903C7 5D
pop ebp
002903C8 C3
ret
 
3.根据反汇编代码地址查询方法
  查看上节的本地反汇编代码,我们看到有的语句是对托管方法的调用,命令u无法直接显示具体被调用的方法代码,但可以利用ip2md命令显示已JIT编译的代码中指定地址处的MethodDesc结构。
  比如上一小节中地址002903AE处调用了托管方法System.Console.WriteLine(),而这个方法的地址是6058D574
 
IP2MD
!IP2MD 6058D574
MethodDesc: 603685ac
Method Name: System.Console.WriteLine(System.String, System.Object, System.Object)
Class: 6033c734
MethodTable: 606075f4
mdToken: 6f101cb0600091b
Module: 602e1000
IsJitted: yes
CodeAddr: 6058d574
Transparency: Transparent
 
   利用MethodDesc地址,就可以继续查看方法System.Console.WriteLine()的IL和本地反汇编代码了。
 
  • 断点
1.给方法入口设置断点
      利用BPMD命令在指定模块中的指定方法处创建断点。 如果尚未加载指定的模块和方法,则此命令将在创建断点之前等待已加载并JIT编译模块的通知。-list选项生成所有挂起断点的列表。-clear-clearall选项从列表中移除挂起断点。
  比如我们可以给方法Wuhong.SOSTest.Complex.Multiply()入口设置断点。
 
BPMD
!BPMD-md 001338dc
MethodDesc = 001338dc
Failed to set code notification
Adding pending breakpoints...
 
  SOS不提供在反汇编代码中设置断点的命令,但因为能取得本地代码地址,所以可以利用调试器命令bp [CodeAddr]在具体反编译代码中设置断点。另一个方法是利用另外一个调试器扩展SOSEX的命令mbp。
 
 
 
posted @ 2011-04-28 17:47  reni  阅读(1336)  评论(1编辑  收藏  举报