托管代码调试入门之旅(一)

//本文翻译自http://www.cnblogs.com/juqiang

EEStack

!EEStack [-short] [-EE] This command runs !DumpStack on all threads in the process. The -EE option is passed directly to !DumpStack. The -short option tries to narrow down the output to "interesting" threads only, which is defined by

1) The thread has taken a lock.

2) The thread has been "hijacked" in order to allow a garbage collection.

3) The thread is currently in managed code.

!EEStack [-short][EE]. 该命令相当于对进程中所有的线程执行!dumpstack命令。-EE选项可直接传递给!dumpstack command,-short选项的作用在于缩减不必要的内容以便只输出我们感兴趣的内容,包括:

1)该线程拥有一个lock

2)该线程被“劫持”以便进行内存回收。

3)该线程为托管代码线程。

Examining CLR data structures

 

DumpDomain

 

!DumpDomain [<Domain address>]

When called with no parameters, !DumpDomain will list all the AppDomains in the process. It enumerates each Assembly loaded into those AppDomains as well. In addition to your application domain, and any domains it might create, there are two special domains: the Shared Domain and the System Domain.

Any Assembly pointer in the output can be passed to !DumpAssembly. Any Module pointer in the output can be passed to !DumpModule. Any AppDomain pointer can be passed to !DumpDomain to limit output only to that AppDomain. Other functions provide an AppDomain pointer as well, such as !Threads where it lists the current AppDomain for each thread.

!Dumpdomain[<Domain address>]

当该命令被调用无参数时,该命令将会列出进程中所有的AppDomains,同时列出每个AppDomain中所载入的程序集(Assemlbly)。除了应用程序域和其他可能创建的domain之外,还有另外2个特殊的domain: 共享域(shared domain)和系统域(system domain)

该命令中的很多输出结果可以作为其他命令的参数进行传递,例如:

a.      !dumpAssembly  (any Assembly pointer)

b.     !dumpModule (any module pointer)

c.     !dumpdomain (any appdomain pointer):可以指定那些特定的appdomain进行输出

EEHeap

!EEHeap [-gc] [-loader]

!EEHeap enumerates process memory consumed by internal CLR data structures. You can limit the output by passing "-gc" or "-loader". All information will be displayed otherwise.

The information for the Garbage Collector lists the ranges of each Segment in the managed heap. This can be useful if you believe you have an object pointer. If the pointer falls within a segment range given by "!EEHeap -gc", then you do have an object pointer, and can attempt to run "!DumpObj" on it.

 By using !EEHeap to keep track of the growth of these private heaps, we are able to rule out or include them as a source of a memory leak.

!EEHeap [-gc] [-loader]

!EEHeap会列举出该进程中所有被内部CLR消耗的托管内存的信息。通过传入 -gc或者 -loader 可以限定输出,否则会输出所有信息

该信息会列出在托管堆里面垃圾回收器中的每一个段(segment)的范围。在以下情况下,该命令可以发挥很大的作用:

例如我们已经确认存在某个某个托管对象的存在。那么如果该指针的地址正好位于通过!EEHeap -gc所列出的segment区间的话,我们就可以通过!dumpobj [object pointer],来查询该object的详细信息。

另外:利用!EEHeap来追踪,如果发现(managed)private heap处在不断的增长,那么我们就可以断定存在内存泄露问题的存在。

 

SyncBlk

 

!SyncBlk [-all | <syncblk number>]

A SyncBlock is a holder for extra information that doesn't need to be created for every object. It can hold COM Interop data, HashCodes, and locking information for thread-safe operations.

When called without arguments, !SyncBlk will print the list of SyncBlocks corresponding to objects that are owned by a thread. For example, a

lock(MyObject)

 { .... }

statement will set MyObject to be owned by the current thread. A SyncBlock will be created for MyObject, and the thread ownership information stored there (this is an oversimplification, see NOTE below). If another thread tries to execute the same code, they won't be able to enter the block until the first thread exits.

This makes !SyncBlk useful for detecting managed deadlocks.

Consider that the following code is executed by Threads A & B:

Resource r1 = new Resource();
Resource r2 = new Resource();

{
lock(r1);

     {
     lock(r2)

     }
}

 

{
lock(r2);
     {

     lock(r1)
     }

}

This is a deadlock situation, as Thread A could take r1, and Thread B r2, leaving both threads with no option but to wait forever in the second lock statement.

!SyncBlk will detect this with the following output: 

0:003> !syncblk

Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner

238 001e40ec 3 1 001e4e60 e04 3 00a7a194 Resource

239 001e4124 3 1 001e5980 ab8 4 00a7a1a4 Resource

It means that Thread e04 owns object 00a7a194, and Thread ab8 owns object 00a7a1a4.

Combine that information with the call stacks of the deadlock: 

(threads 3 and 4 have similar output)

0:003> k

ChildEBP RetAddr

0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4
0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc
0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c
0404eb38 5d9def80 mscorwks!Thread::DoAppropriateAptStateWait+0x156
0404ecc4 5d9dd8bb mscorwks!Thread::DoAppropriateWaitWorker+0x360
0404ed20 5da628dd mscorwks!Thread::DoAppropriateWait+0xbb
0404ede4 5da4e2e2 mscorwks!CLREvent::Wait+0x29d
0404ee70 5da4dd41 mscorwks!AwareLock::EnterEpilog+0x132
0404ef34 5da4efa3 mscorwks!AwareLock::Enter+0x2c1
0404f09c 5d767880 mscorwks!AwareLock::Contention+0x483
0404f1c4 03f00229 mscorwks!JITutil_MonContention+0x2c0
0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79
...

By looking at the code corresponding to Worker.Work()+0x79 (run "!u 03f00229"), you can see that thread 3 is attempting to acquire the Resource 00a7a1a4, which is owned by thread 4.

NOTE: It is not always the case that a SyncBlock will be created for every object that is locked by a thread. In version 2.0 of the CLR and above, a mechanism called a ThinLock will be used if there is not already a SyncBlock for the object in question. ThinLocks will not be reported by the !SyncBlk command. You can use "!DumpHeap -thinlock" to list objects locked in this way.

!syncblk [-all| <syncblk number>]
所谓一个同步区域(SyncBlock) 是指不需要为每个对象都创建的某种特定信息的拥有者。 一个同步区域可以是一个COM interop 数据,hasdcodes,或者是那些基于线程安全操作的锁定。

当不带任何参数的时候,!syncblk会列出当前线程中对应对象的所有同步区域。例如:
lock(MyObject)

 { .... }
以上语句表明:Myobject对象将被当前thread所拥有。那么系统会为Myobject创建一个SyncBlock, 该拥有该syncblock的线程信息如下所示。如果此时另外某个线程想要执行该代码,那么这个线程将会被阻塞知道当前线程推出该SyncBlock.

利用!syncblk可以有效地检测出托管代码的死锁。假设以下代码,正在被线程A和B执行:

Resource r1 = new Resource();
Resource r2 = new Resource();

{
lock(r1);

     {
     lock(r2)

     }
}

 

{
lock(r2);
     {

     lock(r1)
     }

}

那么,这就是一个典型的死锁,因为线程A拥有r1,线程B拥有r2,同时A,B又在同时等待对方资源。利用!syncblk可以检测出这种deadlock

0:003> k

ChildEBP RetAddr

0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4
0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc
0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c
0404eb38 5d9def80 mscorwks!Thread::DoAppropriateAptStateWait+0x156
0404ecc4 5d9dd8bb mscorwks!Thread::DoAppropriateWaitWorker+0x360
0404ed20 5da628dd mscorwks!Thread::DoAppropriateWait+0xbb
0404ede4 5da4e2e2 mscorwks!CLREvent::Wait+0x29d
0404ee70 5da4dd41 mscorwks!AwareLock::EnterEpilog+0x132
0404ef34 5da4efa3 mscorwks!AwareLock::Enter+0x2c1
0404f09c 5d767880 mscorwks!AwareLock::Contention+0x483
0404f1c4 03f00229 mscorwks!JITutil_MonContention+0x2c0
0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79
...

通过查找Worker.Work()+0x79对应的代码,可以看出线程3在申请已经被4号线程占有的资源00a7a1a4。

说明:
利用!syncblk列出被每个线程锁定的所有对象的syncblock,并不是所有情况都适用。 在CLR 2.0或者更高版本中,利用了一种叫做轻量级锁(thinLock)的机制。我们可以利用!dumpheap -thinlock来列举出所有被锁定的对象

 

DumpMT

!DumpMT [-MD] <MethodTable address>

Examine a MethodTable. Each managed object has a MethodTable pointer at the start.

For example:

0:000>!dumpmt 7a7a0dd4   (7a7a0dd4   is a methodTable address that is contained by a Class)    
EEClass: 7a80b8bc (using !dumpClass 7a80b8bc, we can get the whole information about this class)
Module: 7a726000
Name: System.IO.FileSystemWatcher+FSWAsyncResult
mdToken: 020006f8  (C:\WINDOWS\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 1
Slots in VTable: 9
!Dumpmt methot-table-address

该命令的作用是列出具有制定method-table-address的简要class信息。如果要列出详细的class 信息,可以利用EEClass Address 作为参数,调用!dumpClass EEClass-address来列举出类的详细信息。

DumpClass

!DumpClass <EEClass address>

The EEClass is a data structure associated with an object type.

!DumpClass will show attributes, as well as list the fields of the type.

The output is similar to !DumpObj. Although static field values will be displayed, non-static values won't because you need an instance of an object for that.

You can get an EEClass to look at from !DumpMT, !DumpObj, !Name2EE, and !Token2EE among others.

!dumpclass <eeclass address>

该命令会列举出所有与该类型相关的类成员和attribute。 该命令与!dumpobj作用相类似,但是该命令可以显示出类的静态变量,而!dumpobj不会,因为!dumpobj显示的是对象信息,而静态变量属于类的成员而非对象。

DumpMD

!DumpMD <MethodDesc address>

This command lists information about a MethodDesc. You can use !IP2MD to turn a code address in a managed function into a MethodDesc: 

0:000> !dumpmd 902f40

Method Name: Mainy.Main()
Class: 03ee1424
MethodTable: 009032d8
mdToken: 0600000d Module: 001caa78
IsJitted: yes
m_CodeOrIL: 03ef00b8

 If IsJitted is "yes," you can run !U on the m_CodeOrIL pointer to see a disassembly of the JITTED code.
You can also call !DumpClass, !DumpMT, !DumpModule on the Class, MethodTable and Module fields above.

 该命令列举出一个methodDesc的相关信息。例如:

0:000> !dumpmd 902f40

Method Name: Mainy.Main()
Class: 03ee1424
MethodTable: 009032d8
mdToken: 0600000d Module: 001caa78
IsJitted: yes
m_CodeOrIL: 03ef00b8

 

如果IsJitted=yes的话,那么我们可以利用!U 带上m_CodeOrIL pointer 所指向的地址来显示出其所有的Jitted后的汇编代码。

EEVersion

!EEVersion

This prints the Common Language Runtime version. It also tells you if the code is running in "Workstation" or "Server" mode, a distinction which affects the garbage collector.

该命令可以打印出CLR版本,也可以告知该application运行的模式:Workstation或者Server模式,不同模式会有着不同的垃圾回收器。其最大的区别是:在server mode中,对于每一个cpu,都会有一个专门的垃圾回收线程。例如:

0:016> !eeversion
2.0.50727.1433 free
Server mode with 8 gc heaps
SOS Version: 2.0.50727.1434 retail build

DumpModule

!DumpModule [-mt] <Module address>

You can get a Module address from !DumpDomain, !DumpAssembly and other functions.

我们可以通过!dumpdomain,!dumpAssembly来获得module address, 从而作为!DumpModule的参数,答应出相应的module的相关信息。

ThreadPool

!ThreadPool

This command lists basic information about the ThreadPool, including the number of work requests in the queue, number of completion port threads, and number of timers.

该命令列举出关于线程池的所有基本信息,包括队列中的工作请求数量,处理已结束的线程数量,以及计数器数量。

例如:

0:016> !threadpool
CPU utilization 81%
Worker Thread: Total: 10 Running: 9 Idle: 1 MaxLimit: 800 MinLimit: 8
Work Request in Queue: 1
Unknown Function: 6a2aa6fb  Context: 246526d8
--------------------------------------
Number of Timers: 113
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 16 CurrentLimit: 2 MaxLimit: 800 MinLimit: 8

 

 

DumpAssembly  

!DumpAssembly <Assembly address>

Example output: 0:000> !dumpassembly 1ca248
Parent Domain: 0014f000
Name: C:\pub\unittest.exe
ClassLoader: 001ca060
Module Name
001caa50 C:\pub\unittest.exe

An assembly can consist of multiple modules, and those will be listed.

You can get an Assembly address from the output of !DumpDomain.

一个Assembly可以包括多个模块,该命令会列出指定地址中的所有module。

 

FindAppDomain

 

!FindAppDomain <Object address>

!FindAppDomain will attempt to resolve the AppDomain of an object.

For example, using an Object Pointer from the output of !DumpStackObjects:

 

SaveModule

 

!SaveModule <Base address> <Filename>

This command allows you to take a image loaded in memory and write it to a file. This is especially useful if you are debugging a full memory dump, and don't have the original DLLs or EXEs. This is most often used to save a managed binary to a file, so you can disassemble the code and browse types with ILDASM.

The base address of an image can be found with the "LM" debugger command:

0:000> lm

start end module name

00400000 00408000 image00400000 (deferred)

10200000 102ac000 MSVCR80D (deferred)

5a000000 5a0b1000 mscoree (deferred)

5a140000 5a29e000 mscorjit (deferred)

5b660000 5c440000 mscorlib_dll (deferred)

5d1d0000 5e13c000 mscorwks (deferred) ...

If I wanted to save a copy of mscorwks.dll, I could run:

0:000> !SaveModule 5d1d0000 c:\pub\out.tmp 

 

IP2MD 映像内存地址到托管函数名

用法介绍:

例如通过~*e!clrstack可以列出所有call stack里面的信息。在发现可疑的thread后,如果想知道当前执行的函数所对应的module 信息,可以调用此command给出该module的大致信息,然后利用lmvm [modulename]给出该module的具体信息。

0:00>~*e!clrstack

OS Thread Id: 0x14c8 (32)
ESP       EIP    
1ca5e8d0 7c9685ec [HijackFrame: 1ca5e8d0]
1ca5e910 653c41cb System.Data.DataTable.OnRowChanging(System.Data.DataRowChangeEventArgs, System.Data.DataRow, System.Data.DataRowAction)
1ca5e924 651fe8a1 System.Data.DataTable.RaiseRowChanging(System.Data.DataRowChangeEventArgs, System.Data.DataRow, System.Data.DataRowAction)
1ca5e95c 651fea33 System.Data.DataTable.RaiseRowChanging(System.Data.DataRowChangeEventArgs, System.Data.DataRow, System.Data.DataRowAction, Boolean)
1ca5e98c 651fed36 System.Data.DataTable.SetNewRecordWorker(System.Data.DataRow, Int32, System.Data.DataRowAction, Boolean, Int32, Boolean, System.Exception ByRef)
1ca5e9f4 651fbc39 System.Data.DataTable.InsertRow(System.Data.DataRow, Int32, Int32, Boolean)
1ca5ea40 651ff591 System.Data.DataTable.LoadDataRow(System.Object[], Boolean)
1ca5ea74 652dbb55 System.Data.ProviderBase.SchemaMapping.LoadDataRow()
1ca5eaa0 6525839a System.Data.Common.DataAdapter.FillLoadDataRow(System.Data.ProviderBase.SchemaMapping)
1ca5ead8 65258182 System.Data.Common.DataAdapter.FillFromReader(System.Data.DataSet, System.Data.DataTable, System.String, System.Data.ProviderBase.DataReaderContainer, Int32, Int32, System.Data.DataColumn, System.Object)
1ca5eb30 65257ff4 System.Data.Common.DataAdapter.Fill(System.Data.DataTable[], System.Data.IDataReader, Int32, Int32)
1ca5eb7c 652664ee System.Data.Common.DbDataAdapter.FillInternal(System.Data.DataSet, System.Data.DataTable[], Int32, Int32, System.String, System.Data.IDbCommand, System.Data.CommandBehavior)
1ca5ebd4 65266333 System.Data.Common.DbDataAdapter.Fill(System.Data.DataTable[], Int32, Int32, System.Data.IDbCommand, System.Data.CommandBehavior)
1ca5ec14 652660fc System.Data.Common.DbDataAdapter.Fill(System.Data.DataTable)
1ca5ec48 1ec4a0e9 AC.SlipData.SlipStatusListDSTableAdapters.SlipStatusListCRUD.FillByWHERERole(SlipStatusListDataTable, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.Nullable`1<Int32>, System.String)
1ca5ed30 1ec48877 SlipStatusListWS.GetSlipStatusListByWHERERole(System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String)
1ca5f090 79e7c74b [CustomGCFrame: 1ca5f090]
1ca5f074 79e7c74b [GCFrame: 1ca5f074]
1ca5f058 79e7c74b [GCFrame: 1ca5f058]
1ca5f260 79e7c74b [HelperMethodFrame_1OBJ: 1ca5f260] System.RuntimeMethodHandle._InvokeMethodFast(System.Object, System.Object[], System.SignatureStruct ByRef, System.Reflection.MethodAttributes, System.RuntimeTypeHandle)
1ca5f2d0 793a44bd System.RuntimeMethodHandle.InvokeMethodFast(System.Object, System.Object[], System.Signature, System.Reflection.MethodAttributes, System.RuntimeTypeHandle)
1ca5f31c 793a4224 System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo, Boolean)
1ca5f368 793a40a2 System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
1ca5f388 65d0212c System.Web.Services.Protocols.Logical

0:000> !ip2md 1ec4a0e9
MethodDesc: 1eaf8818
Method Name: AC.SlipData.SlipStatusListDSTableAdapters.SlipStatusListCRUD.FillByWHERERole(SlipStatusListDataTable, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.String, System.Nullable`1<Int32>, System.String)
Class: 1ec3846c
MethodTable: 1eaf8844
mdToken: 0600067f
Module: 1e038330
IsJitted: yes
m_CodeOrIL: 1ec49938
0:000> !dumpmodule 1e038330
Name: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\ac\9c122b0c\f51c4e92\assembly\dl3\b6a2b88b\2ce11f1f_6727c901\AC.SlipData.DLL
Attributes: PEFile
Assembly: 1da87dd8
LoaderHeap: 00000000
TypeDefToMethodTableMap: 1e180010
TypeRefToMethodTableMap: 1e1802a4
MethodDefToDescMap: 1e1804b4
FieldDefToDescMap: 1e185288
MemberRefToDescMap: 1e186384
FileReferencesMap: 1e186718
AssemblyReferencesMap: 1e18671c
MetaData start address: 1e41e4c8 (334568 bytes)

0:000> lmvm AC_SlipData
start    end        module name
1b760000 1b826000   AC_SlipData   (deferred)            
    Image path: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\ei\ce535704\e6211e41\assembly\dl3\872b2e67\cac87995_0934c901\AC.SlipData.DLL
    Image name: AC.SlipData.DLL
    Using CLR debugging support for all symbols
    Has CLR image header, track-debug-data flag not set
    Timestamp:        Wed Oct 22 13:46:50 2008 (48FEBE4A)
    CheckSum:         000CF231
    ImageSize:        000C6000
    File version:     1.0.0.0
    Product version:  1.0.0.0
    File flags:       0 (Mask 3F)
    File OS:          4 Unknown Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0000.04b0
    CompanyName:      onecompany
    ProductName:      AC.SlipData
    InternalName:     AC.SlipData.dll
    OriginalFilename: AC.SlipData.dll
    ProductVersion:   1.0.0.0
    FileVersion:      1.0.0.0
    FileDescription:  AC.SlipData
    LegalCopyright:   Copyright ?onecompany 2005

你可能感兴趣的:(代码)