天天看點

托管代碼調試入門之旅(一)

//本文翻譯自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

轉載于:https://www.cnblogs.com/Winston/archive/2008/12/22/1359906.html