啟用Unmanaged 程式碼調試

來源:互聯網
上載者:User
Visual Studio 作為一種強大的開發平台,已經提供了非常多的調試手段。但這些調試手段相對來說還是停留在表面上,無非是設定斷點、變數查看以及呼叫堆疊列表等。某些時候 我們希望瞭解更多的東西,尤其是那些被隱藏到背後和運行期的東西,諸如對象運行狀態、記憶體分布等等,這些相對底層的知識可以讓我們更好地理解 .NET CLR / JIT 的一些行為。當然,並不是所有人都需要瞭解這些知識,畢竟彙編和進階調試器使用起來還是非常麻煩的。

SOS.dll 是 Microsoft 提供的一種調試擴充,全稱是 Son of Strike,可用來調試Managed 程式碼。SOS.dll 擁有非常強大的功能,包括 Cracker 常用的記憶體脫殼等。本文的目的並不是研究如何破解,而是如何使用 SOS.dll 來協助我們學習 .NET CLR / JIT 的一些知識。我們也不打算使用專業層級的 WinDbg,而是直接將 SOS.dll 載入到 VS 中使用。

1. 準備工作

(1) 開啟 VS2005,在 "工具 > 選項 > 調試 > 符號" 對話方塊中添加符號檔案位置,諸如 "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\symbols"。

(2) 建立一個 ConsoleApplication 工程,輸入測試代碼。namespace ConsoleApplication1
{
   class Base
   {
     public virtual void Test() { }
   }

   class Derived : Base
   {
     public override void Test() { }
   }

   public class Program
   {
     static void Main(string[] args)
     {
       Derived o = new Derived();
       o.Test();

       (o as Base).Test();

       Console.WriteLine("Press any key to exit...");
       Console.ReadKey(true);
       Environment.Exit(0);
     }
   }
}

(3) 開啟項目屬性對話方塊,在 "調試" 頁選中 "啟用Unmanaged 程式碼調試"。

(4) 在 "Console.WriteLine("Press any key to exit...");" 行設定斷點。

2. 操作示範

按 F5 啟動程式調試,執行到斷點後,開啟 "即時視窗",輸入 ".load sos" 載入調試擴充。

輸入 "!help" 可以查看全部的調試指令。!help
PDB symbol for mscorwks.dll not loaded
-------------------------------------------------------------------------------
SOS is a debugger extension DLL designed to aid in the debugging of managedprograms. Functions are listed by category, then roughly in order of importance. Shortcut names for popular functions are listed in parenthesis.

Type "!help <functionname>" for detailed info on that function.

... ...

接下來,我們看看對象 o 是如何?虛方法調用的。(上面代碼中 Main 方法中的變數 o)

(1) 查看當前堆棧資訊。!clrstack -a
PDB symbol for mscorwks.dll not loaded
OS Thread Id: 0xfc8 (4040)
ESP EIP
0012f434 00f500bc ConsoleApplication1.Program.Main(System.String[])
PARAMETERS:
args = 0x013f1c20
LOCALS:
<CLR reg> = 0x013f1c74

0012f69c 79e79dd3 [GCFrame: 0012f69c]

LOCALS 中的對象就是我們的目標。(如何你看過我寫的 MSIL 系列文章,想必對此理解會更深。)

(2) 查看對象資訊。!dumpobj 0x013f1c74
Name: ConsoleApplication1.Derived
MethodTable: 00a73120
EEClass: 00a714d4
Size: 12(0xc) bytes
(ConsoleApplication1.exe)
Fields:
None

找到 MethodTable 的記憶體位址了,接下來看看這個表裡面有什麼東西。

(3) 查看方法表資訊。!dumpmt -md 00a73120
EEClass: 00a714d4
Module: 00a72c3c
Name: ConsoleApplication1.Derived
mdToken: 02000004 (ConsoleApplication1.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79369154 791474f8 PreJIT System.Object.ToString()
79367ec0 79147500 PreJIT System.Object.Equals(System.Object)
79367eb0 79147518 PreJIT System.Object.GetHashCode()
7935e4c0 79147520 PreJIT System.Object.Finalize()
00a7c0b0 00a73110 JIT ConsoleApplication1.Derived.Test()
00a7c0c0 00a73118 JIT ConsoleApplication1.Derived..ctor()

JIT 會將基類的虛方法插入到當前類型的方法表中。

要是我們將 Derived Test() 刪除,方法表會是下面這個樣子。EEClass: 00a714d0
Module: 00a72c3c
Name: ConsoleApplication1.Derived
mdToken: 02000004 (ConsoleApplication1.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79369154 791474f8 PreJIT System.Object.ToString()
79367ec0 79147500 PreJIT System.Object.Equals(System.Object)
79367eb0 79147518 PreJIT System.Object.GetHashCode()
7935e4c0 79147520 PreJIT System.Object.Finalize()
00a7c070 00a730a8 JIT ConsoleApplication1.Base.Test()
00a7c0a0 00a73110 JIT ConsoleApplication1.Derived..ctor()

而改成 "public new void Test()" 則又有所不同。EEClass: 00a714d4
Module: 00a72c3c
Name: ConsoleApplication1.Derived
mdToken: 02000004 (ConsoleApplication1.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79369154 791474f8 PreJIT System.Object.ToString()
79367ec0 79147500 PreJIT System.Object.Equals(System.Object)
79367eb0 79147518 PreJIT System.Object.GetHashCode()
7935e4c0 79147520 PreJIT System.Object.Finalize()
00a7c070 00a730a8 JIT ConsoleApplication1.Base.Test()
00a7c0b0 00a73110 JIT ConsoleApplication1.Derived.Test()
00a7c0c0 00a73118 JIT ConsoleApplication1.Derived..ctor()

對比這些差異能協助我們更好地理解多態。好了,回到主題,我們看看 Main() 中的調用代碼。

(4) 查看 IL 代碼。!name2ee ConsoleApplication1.exe ConsoleApplication1.Program.Main

Module: 00a72c3c (ConsoleApplication1.exe)
Token: 0x06000009
MethodDesc: 00a73040
Name: ConsoleApplication1.Program.Main(System.String[])
JITTED Code Address: 00f50070

這次我們使用 "!name2ee" 來尋找某個類型或方法的地址。然後使用 "!dumpil" 來看看編譯器產生的 IL 代碼。!dumpil 00a73040

ilAddr = 004020c0
IL_0000: nop
IL_0001: newobj ConsoleApplication1.Derived::.ctor
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt ConsoleApplication1.Base::Test
IL_000d: nop
IL_000e: ldloc.0
IL_000f: callvirt ConsoleApplication1.Base::Test
IL_0014: nop
IL_0015: ldstr "Press any key to exit..."
IL_001a: call System.Console::WriteLine
IL_001f: nop
IL_0020: ldc.i4.1
IL_0021: call System.Console::ReadKey
IL_0026: pop
IL_0027: ldc.i4.0
IL_0028: call System.Environment::Exit
IL_002d: nop
IL_002e: ret

callvirt 在 MSIL 系列文章中已經說過很多次,這就不重複囉嗦了。

除了上面這些,我們還可以做更多的事情。

(5) 查看對象資訊。!clrstack -a
OS Thread Id: 0xfc8 (4040)
ESP EIP
0012f434 00f500bc ConsoleApplication1.Program.Main(System.String[])
PARAMETERS:
args = 0x013f1c20
LOCALS:
<CLR reg> = 0x013f1c74

0012f69c 79e79dd3 [GCFrame: 0012f69c]

!dumpobj 0x013f1c74
Name: ConsoleApplication1.Derived
MethodTable: 00a73120
EEClass: 00a714d4
Size: 12(0xc) bytes
(ConsoleApplication1.exe)
Fields:
None

(6) 查看託管堆狀態。!eeheap
Loader Heap:
--------------------------------------
System Domain: 7a3c4690
LowFrequencyHeap: Size: 0x0(0)bytes.
HighFrequencyHeap: 00a62000(8000:1000) Size: 0x1000(4096)bytes.
StubHeap: 00a6a000(2000:1000) Size: 0x1000(4096)bytes.
Virtual Call Stub Heap:
IndcellHeap: Size: 0x0(0)bytes.
LookupHeap: Size: 0x0(0)bytes.
ResolveHeap: Size: 0x0(0)bytes.
DispatchHeap: Size: 0x0(0)bytes.
CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x2000(8192)bytes
--------------------------------------
Shared Domain: 7a3c4330
LowFrequencyHeap: 00a90000(2000:1000) Size: 0x1000(4096)bytes.
HighFrequencyHeap: Size: 0x0(0)bytes.
StubHeap: Size: 0x0(0)bytes.
Virtual Call Stub Heap:
IndcellHeap: Size: 0x0(0)bytes.
LookupHeap: Size: 0x0(0)bytes.
ResolveHeap: Size: 0x0(0)bytes.
DispatchHeap: Size: 0x0(0)bytes.
CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x1000(4096)bytes
--------------------------------------
Domain 1: 14c2d8
LowFrequencyHeap: 00a70000(2000:2000) Size: 0x2000(8192)bytes.
HighFrequencyHeap: 00a72000(8000:2000) Size: 0x2000(8192)bytes.
StubHeap: Size: 0x0(0)bytes.
Virtual Call Stub Heap:
IndcellHeap: Size: 0x0(0)bytes.
LookupHeap: Size: 0x0(0)bytes.
ResolveHeap: Size: 0x0(0)bytes.
DispatchHeap: Size: 0x0(0)bytes.
CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x4000(16384)bytes
--------------------------------------
Jit code heap:
LoaderCodeHeap: 00f50000(10000:1000) Size: 0x1000(4096)bytes.
Total size: 0x1000(4096)bytes
--------------------------------------
Module Thunk heaps:
Module 790c2000: Size: 0x0(0)bytes.
Module 00a72c3c: Size: 0x0(0)bytes.
Total size: 0x0(0)bytes
--------------------------------------
Module Lookup Table heaps:
Module 790c2000: Size: 0x0(0)bytes.
Module 00a72c3c: Size: 0x0(0)bytes.
Total size: 0x0(0)bytes
--------------------------------------
Total LoaderHeap size: 0x8000(32768)bytes
=======================================
Number of GC Heaps: 1
generation 0 starts at 0x013f1018
generation 1 starts at 0x013f100c
generation 2 starts at 0x013f1000
ephemeral segment allocation context: none
segment begin allocated size
001967a0 790d7f90 790f76fc 0x0001f76c(128876)
013f0000 013f1000 013f1ff4 0x00000ff4(4084)
Large object heap starts at 0x023f1000
segment begin allocated size
023f0000 023f1000 023f3250 0x00002250(8784)
Total Size 0x229b0(141744)
------------------------------
GC Heap Size 0x229b0(141744)

(7) 查看應用程式定義域狀態。

domain 地址可以使用 !eeheap 指令擷取。!dumpdomain 14c2d8
--------------------------------------
Domain 1: 0014c2d8
LowFrequencyHeap: 0014c2fc
HighFrequencyHeap: 0014c354
StubHeap: 0014c3ac
Stage: OPEN
SecurityDescriptor: 0014d608
Name: Learn.CUI.exe
Assembly: 00192db0 [C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]
ClassLoader: 00192e48
SecurityDescriptor: 00193fc0
Module Name
790c2000 C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll

Assembly: 0019e4c8 [ConsoleApplication1.exe]
ClassLoader: 0019e560
SecurityDescriptor: 0019e3f8
Module Name
00a72c3c ConsoleApplication1.exe

(8) 查看線程池狀態。!ThreadPool
CPU utilization 0%
Worker Thread: Total: 0 Running: 0 Idle: 0 MaxLimit: 0 MinLimit: 0
Work Request in Queue: 0
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 0 Free: 0 MaxFree: 0 CurrentLimit: 0 MaxLimit: 1000 MinLimit: 0

SOS.dll 提供了大量的命令,大家可以通過 !help 指令查看其使用方法,本文不再一一詳述。!help 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.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.