今天是《Net 高級調試》的第十篇文章。說起來,高級調試,調試的內容還是挺多的,技巧也不少,但是,要想做一個合格的高級調試人員,還需要掌握如何調試動態生成的IL代碼。今天要探討的高級調試的技巧是如何調試通過 Emit 動態生成 IL 代碼。可能有人會問,我們不是編寫 C# 代碼,或者是 VB.Net 代碼嗎?怎麼還要動態生成 IL 代碼,這些工作不是編譯器做的嗎?當然,一般情況是這樣的,但是,當我們編寫一些高性能的框架的時候,使用 IL 代碼編寫也是常事。既然也可以直接使用 IL 編寫代碼,那對它的調試也是少不了的,調試機會雖然很少,具有這個本領,等遇到這樣的問題,就不至於慌亂了,俗話說的好:藝多不壓身。當然了,第一次看視頻或者看書,是很迷糊的,不知道如何操作,還是那句老話,一遍不行,那就再來一遍,還不行,那就再來一遍,俗話說的好,書讀千遍,其意自現。
如果在沒有說明的情況下,所有代碼的測試環境都是 Net Framewok 4.8,但是,有時候爲了查看源碼,可能需要使用 Net Core 的項目,我會在項目章節裏進行說明。好了,廢話不多說,開始我們今天的調試工作。
調試環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
操作系統:Windows Professional 10
調試工具:Windbg Preview(可以去Microsoft Store 去下載)
開發工具:Visual Studio 2022
Net 版本:Net Framework 4.8
CoreCLR源碼:源碼下載
二、基礎知識
1、動態代碼調試
動態代碼調試的機會雖然不多,但是掌握動態代碼調試的技巧還是很有必要的,俗話說的好,藝多不壓身。當我們遇到有這樣寫代碼,由此引發的問題的時候,我們也可以做到遇事不驚。
今天我們就討論三種調試的技巧。
2、三種調試策略
2.1、捕獲 JIT 的 CompileMethod 方法。
C# 代碼的編譯過程分爲兩個階段,第一個階段就是編譯器的階段,這個階段編譯器將我們編寫的C#源代碼編譯成 IL 代碼,第二階段,就是在運行的時候,JIT 將 IL 代碼編譯爲機器代碼,我們的程序就運行了。現在是動態生成的 IL 代碼,沒有第一個階段,直接就是第二個 JIT 編譯的階段,我們可以嘗試在 JIT 的某個方法中設置斷點進行攔截,拿到方法的描述符,也就是 MD,有了 MD,我們就可以使用 bp md 命令,爲這個方法下斷點,這樣就可以對動態生成的代碼進行調試了。
2.2、從代碼中獲取委託的函數指針(我的測試沒有實現)。
這種方法比較簡單,但需要在方法中加一行代碼,具有一定的破壞性。代碼:Marshal.GetFunctionPointerForDelegate(委託實例).ToInt64()。
2.3、在動態的代碼中注入 Debugger.Break()。
我們在很多的調試中都會用到 Debugger.Break() 函數,讓程序中斷到調試器,如果這個動態代碼,我們具有完整的控制權,我們就可以通過注入這個方法:Debugger.Break(),實現 int 3 中斷,進而獲取想要的數據。代碼如下:IL.Emit(OpCodes.Call,typeof(Debugger).GetMethod("Break"))。
3、程序集
3.1、程序集崩潰
程序集泄露是指什麼呢?就是程序的內存暴漲,產生大量的程序集。
三、調試過程
廢話不多說,這一節是具體的調試操作的過程,又可以說是眼見爲實的過程,在開始之前,我還是要囉嗦兩句,這一節分爲兩個部分,第一部分是測試的源碼部分,沒有代碼,當然就談不上測試了,調試必須有載體。第二部分就是根據具體的代碼來證實我們學到的知識,是具體的眼見爲實。
1、測試源碼
以下項目代碼就是在眼見爲實用到的測試用例。
1.1、Example_10_1_1
1 namespace Example_10_1_1 2 { 3 internal class Program 4 { 5 private delegate int AddDelegate(int a, int b); 6 static void Main(string[] args) 7 { 8 var dynamicAdd = new DynamicMethod("Add", typeof(int), new Type[] { typeof(int), typeof(int) }); 9 var il = dynamicAdd.GetILGenerator(); 10 il.Emit(OpCodes.Ldarg_0); 11 il.Emit(OpCodes.Ldarg_1); 12 il.Emit(OpCodes.Add); 13 il.Emit(OpCodes.Ret); 14 15 var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate)); 16 Console.WriteLine(addDelegate(10, 20)); 17 } 18 } 19 }
1.2、Example_10_1_2
1 namespace Example_10_1_2 2 { 3 internal class Program 4 { 5 private delegate int AddDelegate(int a, int b); 6 static void Main(string[] args) 7 { 8 var dynamicAdd = new DynamicMethod("Add", typeof(int), new Type[] { typeof(int), typeof(int) }); 9 var il = dynamicAdd.GetILGenerator(); 10 il.Emit(OpCodes.Ldarg_0); 11 il.Emit(OpCodes.Ldarg_1); 12 il.Emit(OpCodes.Add); 13 il.Emit(OpCodes.Ret); 14 15 var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate)); 16 17 Console.WriteLine("Function Pointer:0x{0:x16}", Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64()); 18 19 Debugger.Break(); 20 21 Console.WriteLine(addDelegate(10, 20)); 22 } 23 } 24 }
1.3、Example_10_1_3
1 namespace Example_10_1_3 2 { 3 internal class Program 4 { 5 private delegate int AddDelegate(int a, int b); 6 static void Main(string[] args) 7 { 8 var dynamicAdd = new DynamicMethod("Add", typeof(int), new Type[] { typeof(int), typeof(int) }); 9 var il = dynamicAdd.GetILGenerator(); 10 11 il.Emit(OpCodes.Call,typeof(Debugger).GetMethod("Break")); 12 il.Emit(OpCodes.Ldarg_0); 13 il.Emit(OpCodes.Ldarg_1); 14 il.Emit(OpCodes.Add); 15 il.Emit(OpCodes.Ret); 16 17 var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate)); 18 19 Console.WriteLine(addDelegate(10, 20)); 20 } 21 } 22 }
1.4、Example_10_1_4
1 namespace Example_10_1_4 2 { 3 internal class Program 4 { 5 //XmlSerializer序列化程序集泄露解決辦法。 6 //static readonly XmlSerializer xmlSerializer = new XmlSerializer(typeof(Customer), new XmlRootAttribute("FabrikamCustomer")); 7 static void Main(string[] args) 8 { 9 var xml = @"<FabrikamCustomer><Id>001</Id><FirstName>John</FirstName><LastName>Dow</LastName></FabrikamCustomer>"; 10 11 Enumerable.Range(0, 30000) 12 .Select(i => GetCustomer(i, "FabrikamCustomer", xml)) 13 .ToList(); 14 15 Console.WriteLine("處理完成!"); 16 Console.ReadLine(); 17 } 18 19 public static Customer GetCustomer(int i, string rootElementName, string xml) 20 { 21 var xmlSerializer = new XmlSerializer(typeof(Customer), new XmlRootAttribute("FabrikamCustomer")); 22 using (var textReader = new StringReader(xml)) 23 { 24 using (var xmlReader = XmlReader.Create(textReader)) 25 { 26 Console.WriteLine(i); 27 28 return (Customer)xmlSerializer.Deserialize(xmlReader); 29 } 30 } 31 } 32 } 33 34 public class Customer 35 { 36 public string Id { get; set; } 37 public string FirstName { get; set; } 38 public string LastName { get; set; } 39 } 40 }
2、眼見爲實
項目的所有操作都是一樣的,所以就在這裏說明一下,但是每個測試例子,都需要重新啓動,並加載相應的應用程序,加載方法都是一樣的。流程如下:我們編譯項目,打開 Windbg,點擊【文件】----》【launch executable】附加程序,打開調試器的界面,程序已經處於中斷狀態。
2.1、捕獲 JIT 的 CompileMethod 方法,爲 Add 方法設置斷點,進行調試。
測試源碼:Example_10_1_1
上圖是我們的代碼,我們使用【!mbp】命令在18行下斷點,【mbp】命令是 SOSEX的擴展命令,執行前必須加載 SOSEX.dll,我們需要使用【g】命令,繼續運行程序,然後到達指定斷點處停止後。
1 0:000> !mbp Program.cs 18 2 The CLR has not yet been initialized in the process. 3 Breakpoint resolution will be attempted when the CLR is initialized.
我們已經在我們設置的斷點處暫停。我們開始對 CompileMethod 這個方法下斷點。首先我們通過【lm(load module)】命令找到clrjit.dll 這個dll。
1 0:000> lm 2 start end module name 3 00f80000 00f88000 Example_10_1_1 C (service symbols: CLR Symbols with PDB: C:\ProgramData\...\Example_10_1_1.pdb) 4 5bd10000 5bdaf000 apphelp (deferred) 5 6dbc0000 6efce000 mscorlib_ni (service symbols: CLR Symbols with PDB: C:\ProgramData\...88BB3460CAC52\mscorlib.pdb)
6 6efd0000 6f780000 clr (pdb symbols) C:\ProgramData\Dbg\sym\clr.pdb\5138062248484B79BCF6F6B3F3B59A3D2\clr.pdb 7 721b0000 7223d000 mscoreei (pdb symbols) C:\ProgramData\Dbg\sym\mscoreei.pdb\3353E43482934F9AB820643DF51EC4692\mscoreei.pdb 8 72240000 72292000 MSCOREE (pdb symbols) C:\ProgramData\Dbg\sym\mscoree.pdb\3A1E9FD59D013FF42905FC8655F33DCB1\mscoree.pdb 9 722d0000 7235a000 clrjit (deferred) 10 72380000 7242b000 ucrtbase_clr0400 (deferred) 11 ...... 12 77810000 77828000 win32u (deferred) 13 77830000 77ab0000 combase (deferred) 14 77ac0000 77c62000 ntdll (pdb symbols) C:\ProgramData\Dbg\sym\wntdll.pdb\DBC8C8F74C0E3696E951B77F0BB8569F1\wntdll.pdb 15 16 Unloaded modules: 17 77830000 77ab0000 combase.dll
紅色標註的,說明CLR 和 JIT 都已經加載了。然後,我們通過【x】命令查找 CompileMethod方法。
1 0:000> x clrjit!*CompileMethod* 2 722d3700 clrjit!CILJit::compileMethod (class ICorJitInfo *, struct CORINFO_METHOD_INFO *, unsigned int, unsigned char **, unsigned long *)
我們找到了 CompileMethod 方法,給這個方法下一個斷點。
1 0:000> bp clrjit!CILJit::compileMethod
我們【g】了兩次,已經在 CILJit::compileMethod 方法處斷住了。
1 0:000> g 2 Breakpoint 1 hit 3 eax=00000002 ebx=0133ef84 ecx=03383840 edx=00000000 esi=033824c8 edi=0133eedc 4 eip=016c0a3d esp=0133ee8c ebp=0133eee8 iopl=0 nv up ei pl zr na pe nc 5 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 6 Example_10_1_1!COM+_Entry_Point <PERF> (Example_10_1_1+0x740a3d): 7 016c0a3d b9144e6601 mov ecx,1664E14h 8 0:000> g 9 Breakpoint 0 hit 10 eax=72354698 ebx=80000004 ecx=722d3700 edx=00005c10 esi=7234b3fc edi=0133e970 11 eip=722d3700 esp=0133e7ac ebp=0133e804 iopl=0 nv up ei ng nz na po nc 12 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000282 13 clrjit!CILJit::compileMethod: 14 722d3700 55 push ebp
我們在 CILJit::compileMethod 這個方法已經斷住了,然後我們使用【!kb】命令,查看一下它的參數。
1 0:000> kb 1 2 # ChildEBP RetAddr Args to Child 3 00 0133e804 6f09ccc3 72354698 0133e970 0133e8e8 clrjit!CILJit::compileMethod [f:\dd\ndp\clr\src\jit32\ee_il_dll.cpp @ 151]
72354698 0133e970 0133e8e8 這個三個參數的第三參數其實就是包含方法描述符的地址。
1 0:000> dp 0133e8e8 l1 2 0133e8e8 01665384
我們可以使用【!dumpmd】命令查看這個方法的信息。
1 0:000> !dumpmd 01665384 2 Method Name: DynamicClass.Add(Int32, Int32)(我們要查找的方法) 3 Class: 016652f0 4 MethodTable: 01665344 5 mdToken: 06000000 6 Module: 01664eac 7 IsJitted: no 8 CodeAddr: ffffffff(說明方法還沒有編譯) 9 Transparency: Transparent
我們使用【k】命令,看看我的調用棧。
1 0:000> k 2 # ChildEBP RetAddr 3 00 0133e804 6f09ccc3 clrjit!CILJit::compileMethod [f:\dd\ndp\clr\src\jit32\ee_il_dll.cpp @ 151] 4 01 0133e804 6f09cd9b clr!invokeCompileMethodHelper+0x10b 5 02 0133e84c 6f09cdf8 clr!invokeCompileMethod+0x3d 6 03 0133e8b8 6f09d0a7 clr!CallCompileMethodWithSEHWrapper+0x39 7 04 0133ec78 6f09caac clr!UnsafeJitFunction+0x431 8 05 0133ed6c 6f09e703 clr!MethodDesc::MakeJitWorker+0x40b 9 06 0133ede4 6f08f78f clr!MethodDesc::DoPrestub+0x5f3 10 07 0133ee60 6efdf4bb clr!PreStubWorker+0xe0 11 08 0133ee84 016c0aab clr!ThePreStub+0x11 12 09 0133eee8 6efdf036 Example_10_1_1!COM+_Entry_Point <PERF> (Example_10_1_1+0x740aab) [E:\Visual Studio 2022\Program.cs @ 19] 13 0a 0133eef4 6efe22da clr!CallDescrWorkerInternal+0x34 14 0b 0133ef48 6efe859b clr!CallDescrWorkerWithHandler+0x6b 15 0c 0133efb0 6f18b11b clr!MethodDescCallSite::CallTargetWorker+0x16a 16 0d 0133f0d4 6f18b7fa clr!RunMain+0x1b3 17 0e 0133f340 6f18b727 clr!Assembly::ExecuteMainMethod+0xf7 18 0f 0133f824 6f18b8a8 clr!SystemDomain::ExecuteMainMethod+0x5ef 19 10 0133f87c 6f18b9ce clr!ExecuteEXE+0x4c 20 11 0133f8bc 6f187305 clr!_CorExeMainInternal+0xdc 21 12 0133f8f8 721bfa84 clr!_CorExeMain+0x4d 22 13 0133f930 7224e81e mscoreei!_CorExeMain+0xd6 23 14 0133f940 72254338 MSCOREE!ShellShim__CorExeMain+0x9e 24 15 0133f958 765ff989 MSCOREE!_CorExeMain_Exported+0x8 25 16 0133f958 77b27084 KERNEL32!BaseThreadInitThunk+0x19 26 17 0133f9b4 77b27054 ntdll!__RtlUserThreadStart+0x2f 27 18 0133f9c4 00000000 ntdll!_RtlUserThreadStart+0x1b
我們生成的 Add 方法,會首先進入 clr!MethodDesc::DoPrestub 樁函數,開始引導JIT編譯,我們就在這個方法設斷點。
1 0:000> bp 6f08f78f
我們【g】繼續運行,就會在 DoPrestub 方法斷住。我們在通過【!dumpmd】命令查看方法的描述符。
1 0:000> g 2 Breakpoint 0 hit 3 eax=016c0470 ebx=00000000 ecx=6f08f6aa edx=01665384 esi=01703978 edi=01665384 4 eip=6f08f78f esp=0133edf0 ebp=0133ee60 iopl=0 nv up ei pl zr na pe nc 5 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 6 clr!PreStubWorker+0xe0:(斷點處) 7 6f08f78f 8bf8 mov edi,eax 8 0:000> !dumpmd 01665384 9 Method Name: DynamicClass.Add(Int32, Int32) 10 Class: 016652f0 11 MethodTable: 01665344 12 mdToken: 06000000 13 Module: 01664eac 14 IsJitted: yes 15 CodeAddr: 05940050(方法已經編譯) 16 Transparency: Transparent
我們有了CodeAddr: 05940050 方法的地址,我們就可以在這個地址上下斷點。
1 0:000> bp 05940050 2 0:000> g 3 Breakpoint 1 hit 4 eax=016c0470 ebx=0133ef84 ecx=0000000a edx=00000014 esi=033824c8 edi=0133eedc 5 eip=05940050 esp=0133ee88 ebp=0133eee8 iopl=0 nv up ei pl zr na pe nc 6 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 7 05940050 8bc1 mov eax,ecx
我們下了斷點,【g】繼續運行。執行進入 Add 方法第一行就被斷住了,我們可以使用【!clrstack】命令,來驗證。
1 0:000> !clrstack 2 OS Thread Id: 0x4168 (0) 3 Child SP IP Call Site 4 0133ee88 05940050 DynamicClass.Add(Int32, Int32)(這就是我們動態生成的方法) 5 0133ee8c 016c0aab Example_10_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\Example_10_1_1\Program.cs @ 19] 6 0133f054 6efdf036 [GCFrame: 0133f054]
我們已經成功命中了 Add 方法,還是挺不容易的。
1 0:000> !U /d 05940050 2 Normal JIT generated code 3 DynamicClass.Add(Int32, Int32) 4 Begin 05940050, size 5 5 >>> 05940050 8bc1 mov eax,ecx 6 05940052 03c2 add eax,edx 7 05940054 c3 ret
ecx 是方法的第一個參數,edx:是方法的第二個參數
mov eax, ecx 將 ecx 賦值給 eax,
add eax, edx 將 edx 和 eax 相加,eax 作爲返回值返回
ret
我們對方法代碼也做了一些解釋。
2.2、通過委託的函數指針找方法的描述符。
測試源碼:Example_10_1_2
我們通過 Windbg 直接加載 Example_10_1_2 項目,【g】命令直接運行,會到【Debugger.Break()】這行代碼處暫停,也就是一個 int 3 中斷。我們的程序輸出:Function Pointer:0x0000000004cd062e。然後我們使用【u 0x0000000004cd062e】命令,查看彙編代碼。
1 0:000> u 0x0000000004cd062e 2 04cd062e b81806cd04 mov eax,4CD0618h 3 04cd0633 e9e4c9dbfb jmp 00a8d01c 4 04cd0638 ab stos dword ptr es:[edi] 5 04cd0639 ab stos dword ptr es:[edi] 6 04cd063a ab stos dword ptr es:[edi] 7 04cd063b ab stos dword ptr es:[edi] 8 04cd063c ab stos dword ptr es:[edi] 9 04cd063d ab stos dword ptr es:[edi]
4CD0618h 紅色標註的就是包含方法描述符的地址。我們使用【dp 4CD0618h l1】命令,查看詳情。
1 0:000> dp 4CD0618h l1 2 04cd0618 00b20480
00b20480 這個地址就是方法描述符,我們可以使用【!dumpmd 00b20480】命令。
0:000> dp 4CD0618h L1 04cd0618 00b20480 0:000> !U 00b20480 Unmanaged code 00b20480 e88bec4b6e call clr!PrecodeFixupThunk (6efdf110) 00b20485 5e pop esi 00b20486 001b add byte ptr [ebx],bl 00b20488 e883ec4b6e call clr!PrecodeFixupThunk (6efdf110) 00b2048d 5e pop esi 00b2048e 091a or dword ptr [edx],ebx 00b20490 e87bec4b6e call clr!PrecodeFixupThunk (6efdf110) 00b20495 5e pop esi 00b20496 1219 adc bl,byte ptr [ecx] 00b20498 e873ec4b6e call clr!PrecodeFixupThunk (6efdf110)
我這裏沒有得到 Add 的方法描述符。沒有實現。
2.3、在動態代碼中注入 Debugger.Break()實現代碼的調試。
測試源碼:Example_10_1_3
我們通過 windbg 正常加載項目,直接【g】運行,會在16行處暫停,如圖:
執行效果如下:
1 0:000> !clrstack 2 OS Thread Id: 0x482c (0) 3 Child SP IP Call Site 4 00b5f3a8 760df262 [HelperMethodFrame: 00b5f3a8] System.Diagnostics.Debugger.BreakInternal() 5 00b5f424 6e74f195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91] 6 00b5f44c 00f10053 DynamicClass.Add(Int32, Int32) 7 00b5f458 00ee0b56 Example_10_1_3.Program.Main(System.String[]) [E:\Visual Studio 2022\...\Example_10_1_3\Program.cs @ 24] 8 00b5f654 6efdf036 [GCFrame: 00b5f654]
00f10053 紅色標註的就是我們要找的Add 方法。我們可以使用【!u】命令查看它的彙編代碼。
1 0:000> !U /d 00f10053 2 Normal JIT generated code 3 DynamicClass.Add(Int32, Int32) 4 Begin 00f10048, size 12 5 00f10048 57 push edi 6 00f10049 56 push esi 7 00f1004a 8bf9 mov edi,ecx 8 00f1004c 8bf2 mov esi,edx 9 00f1004e e8e5f0836d call mscorlib_ni!System.Diagnostics.Debugger.Break (6e74f138) 10 >>> 00f10053 03fe add edi,esi 11 00f10055 8bc7 mov eax,edi 12 00f10057 5e pop esi 13 00f10058 5f pop edi 14 00f10059 c3 ret
2.4、程序集泄露
測試源碼:Example_10_1_4
我們加載完 Example_10_1_4 項目,【g】繼續運行,運行一段時間,我們點擊【break】按鈕暫停,當然,在運行的時候,我們可以通過任務管理器,查看這個應用程序的內存,發現內存一直在增長。
我們使用【!dumpdomain】命令,查看一下應用程序域,不看不知道,一看嚇一跳。
1 0:006> !dumpdomain 2 -------------------------------------- 3 System Domain: 6f71caf8 4 LowFrequencyHeap: 6f71ce1c 5 HighFrequencyHeap: 6f71ce68 6 StubHeap: 6f71ceb4 7 Stage: OPEN 8 Name: None 9 -------------------------------------- 10 Shared Domain: 6f71c7a8 11 LowFrequencyHeap: 6f71ce1c 12 HighFrequencyHeap: 6f71ce68 13 StubHeap: 6f71ceb4 14 Stage: OPEN 15 Name: None 16 Assembly: 00c9d6f8 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 17 ClassLoader: 00c9d7c0 18 Module Name 19 6dbc1000 C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 20 21 -------------------------------------- 22 Domain 1: 00c4d7c8 23 LowFrequencyHeap: 00c4dc34 24 HighFrequencyHeap: 00c4dc80 25 StubHeap: 00c4dccc 26 Stage: OPEN 27 SecurityDescriptor: 00c4ece0 28 Name: Example_10_1_4.exe 29 Assembly: 00c9d6f8 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll] 30 ClassLoader: 00c9d7c0 31 SecurityDescriptor: 00c9b0d0 32 Module Name 33 6dbc1000 C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 34 35 Assembly: 00caa098 [E:\Visual Studio 2022\Source\Projects\Example_10_1_4.exe] 36 ClassLoader: 00ca7268 37 SecurityDescriptor: 00ca7160 38 Module Name 39 00c04044 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_10_1_4\bin\Debug\Example_10_1_4.exe 40 41 Assembly: 00cab1c0 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll] 42 ClassLoader: 00cab288 43 SecurityDescriptor: 00ca88e0 44 Module Name 45 6c591000 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Core\v4.0_4.0.0.0__b77a5c561934e089\System.Core.dll 46 47 Assembly: 00caafc0 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll] 48 ClassLoader: 00ca9b18 49 SecurityDescriptor: 00cac6f0 50 Module Name 51 6d041000 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll 52 53 Assembly: 00cabee0 [C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll] 54 ClassLoader: 00cb3bd0 55 SecurityDescriptor: 00cabe48 56 Module Name 57 6bc01000 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Xml\v4.0_4.0.0.0__b77a5c561934e089\System.Xml.dll 58 59 Assembly: 00cb8b80 [C:\Windows\Microsoft.Net\assembly\\System.Configuration.dll] 60 ClassLoader: 00cb7538 61 SecurityDescriptor: 00cb8ae8 62 Module Name 63 6dab1000 C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Configuration\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Configuration.dll 64 65 Assembly: 00cc3418 (Dynamic) [] 66 ClassLoader: 00cc34e0 67 SecurityDescriptor: 00cc3380 68 Module Name 69 00c08b6c Dynamic Module 70 71 Assembly: 00cc8f58 (Dynamic) [] 72 ClassLoader: 00cc9020 73 SecurityDescriptor: 00cc8ec0 74 Module Name 75 00c0954c Dynamic Module 76 77 Assembly: 00cd49e0 (Dynamic) [] 78 ClassLoader: 00cd4aa8 79 SecurityDescriptor: 00cd4948 80 Module Name 81 00c09ce4 Dynamic Module 82 83 ......(我清理了大量的動態模塊,顯示太多了)
我們查看一下模塊的方法表,使用【!dumpmodule -mt】命令。
1 0:006> !dumpmodule -mt 09e65b2c 2 Name: Unknown Module 3 Attributes: Reflection 4 Assembly: 097f86c0 5 LoaderHeap: 00000000 6 TypeDefToMethodTableMap: 09e8195c 7 TypeRefToMethodTableMap: 09e81970 8 MethodDefToDescMap: 09e81984 9 FieldDefToDescMap: 09e819ac 10 MemberRefToDescMap: 00000000 11 FileReferencesMap: 09e819fc 12 AssemblyReferencesMap: 09e81a10 13 14 Types defined in this module 15 16 MT TypeDef Name 17 ------------------------------------------------------------------------------ 18 09e65f68 0x02000002 <Unloaded Type> 19 09e6605c 0x02000003 <Unloaded Type> 20 09e660e4 0x02000004 <Unloaded Type> 21 09e66184 0x02000005 <Unloaded Type> 22 09e66260 0x02000006 <Unloaded Type> 23 24 Types referenced in this module 25 26 MT TypeRef Name 27 ------------------------------------------------------------------------------
我們查找紅色標記 09e65f68 方法表的所有方法描述符。
1 0:006> !dumpmt -md 09e65f68 2 EEClass: 09e82bf8 3 Module: 09e65b2c 4 Name: <Unloaded Type> 5 mdToken: 02000002 6 File: Unknown Module 7 BaseSize: 0x44 8 ComponentSize: 0x0 9 Slots in VTable: 8 10 Number of IFaces in IFaceMap: 0 11 -------------------------------------- 12 MethodDesc Table 13 Entry MethodDe JIT Name 14 6dfc97b8 6dbcc838 PreJIT System.Object.ToString() 15 6dfc96a0 6dd08978 PreJIT System.Object.Equals(System.Object) 16 6dfd21f0 6dd08998 PreJIT System.Object.GetHashCode() 17 6df84f2c 6dd089a0 PreJIT System.Object.Finalize() 18 09e4a47d 09e65f44 NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer.InitCallbacks() 19 09e4a481 09e65f4c NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer..ctor() 20 09e4a475 09e65f2c NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer.Write3_FabrikamCustomer(...) 21 09e4a479 09e65f38 NONE Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer.Write2_Customer(... Boolean)
這裏大量的有 Serialization.GeneratedAssembly 這個方法,估計判斷是程序集泄露。既然這麼多程序集產生,應該就有一個創建程集的方法,我們使用【x】命令,查找一下。
1 0:006> x clr!*CreateDynamic* 2 6f385b06 clr!Assembly::CreateDynamicModule (public: class ReflectionModule * __thiscall Assembly::CreateDynamicModule(unsigned short const *,unsigned short const *,int,int *)) 3 6f5b0aff clr!HENUMInternal::CreateDynamicArrayEnum (public: static long __stdcall HENUMInternal::CreateDynamicArrayEnum(unsigned long,struct HENUMInternal * *)) 4 6f166b49 clr!Assembly::CreateDynamic (public: static class Assembly * __stdcall Assembly::CreateDynamic(class AppDomain *,struct CreateDynamicAssemblyArgs *)) 5 6f167440 clr!AppDomainNative::CreateDynamicAssembly (public: static class Object * __fastcall AppDomainNative::CreateDynamicAssembly(class AppDomainBaseObject *,class AssemblyNameBaseObject *,enum SecurityContextSource,int,int,class Array<unsigned char> *,class Array<unsigned char> *,class Object *,class Object *,class Object *,enum StackCrawlMark *,class Object *)) 6 6f3dd86f clr!DynamicMethodTable::CreateDynamicMethodTable (public: static void __stdcall DynamicMethodTable::CreateDynamicMethodTable(class DynamicMethodTable * *,class Module *,class AppDomain *))
就是這個方法創建程序集,我們可以對這個方法下一個斷點。
1 0:006> bp clr!Assembly::CreateDynamic
【g】繼續運行,在 CreateDynamic 方法內暫停。
1 0:006> g 2 Breakpoint 0 hit 3 eax=00c4d7c8 ebx=00000000 ecx=00c4d7c8 edx=008fecb4 esi=02ad7a5c edi=00000000 4 eip=6f166b49 esp=008fec3c ebp=008fed00 iopl=0 nv up ei pl nz na po nc 5 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 6 clr!Assembly::CreateDynamic:(成功斷住) 7 6f166b49 6814070000 push 714h
然後我們使用【k】命令,查看是誰調用了這個函數。
1 0:000> k 2 *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Xml\040fa6ee0be6d987f3e8edf9010ce68a\System.Xml.ni.dll 3 *** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\System.Core\44e36f78b5e2f34aba2d7b5667796954\System.Core.ni.dll 4 # ChildEBP RetAddr 5 00 010feef0 6f167543 clr!Assembly::CreateDynamic 6 01 010feef0 6dfef391 clr!AppDomainNative::CreateDynamicAssembly+0xdf 7 02 010fef78 6dfef261 mscorlib_ni!System.Reflection.Emit.AssemblyBuilder..ctor+0xed [f:\dd\ndp\clr\src\BCL\system\reflection\emit\assemblybuilder.cs @ 424] 8 03 010fefd4 6e016558 mscorlib_ni!System.Reflection.Emit.AssemblyBuilder.InternalDefineDynamicAssembly+0x89 [f:\dd\ndp\clr\src\BCL\system\reflection\emit\assemblybuilder.cs @ 569] 9 04 010feffc 6e016527 mscorlib_ni!System.AppDomain.InternalDefineDynamicAssembly+0x28 [f:\dd\ndp\clr\src\BCL\system\appdomain.cs @ 1515] 10 05 010ff02c 6bd100bd mscorlib_ni!System.AppDomain.DefineDynamicAssembly+0x2b [f:\dd\ndp\clr\src\BCL\system\appdomain.cs @ 1221] 11 06 010ff044 6bd0fa1b System_Xml_ni!System.Xml.Serialization.CodeGenerator.CreateAssemblyBuilder+0x8d 12 07 010ff0dc 6bd0f696 System_Xml_ni!System.Xml.Serialization.TempAssembly.GenerateRefEmitAssembly+0xcf 13 08 010ff110 6c09db2a System_Xml_ni!System.Xml.Serialization.TempAssembly..ctor+0xae 14 09 010ff138 6c09da72 System_Xml_ni!System.Xml.Serialization.XmlSerializer.GenerateTempAssembly+0x6a 15 0a 010ff164 6c09d8e7 System_Xml_ni!System.Xml.Serialization.XmlSerializer..ctor+0xd2 16 0b 010ff18c 017710aa System_Xml_ni!System.Xml.Serialization.XmlSerializer..ctor+0x2f 17 WARNING: Frame IP not in any known module. Following frames may be wrong. 18 0c 010ff1e8 01770ffc 0x17710aa 19 0d 010ff200 01770f88 0x1770ffc 20 0e 010ff210 6dfaaa67 0x1770f88 21 0f 010ff244 6c77c5de mscorlib_ni!System.Collections.Generic.List`1..ctor+0xf7 [f:\dd\ndp\clr\src\BCL\system\collections\generic\list.cs @ 99] 22 10 010ff258 017708f2 System_Core_ni+0x1ec5de 23 11 010ff288 6efdf036 0x17708f2 24 12 010ff294 6efe22da clr!CallDescrWorkerInternal+0x34 25 13 010ff2e8 6efe859b clr!CallDescrWorkerWithHandler+0x6b 26 14 010ff354 6f18b11b clr!MethodDescCallSite::CallTargetWorker+0x16a 27 15 010ff478 6f18b7fa clr!RunMain+0x1b3 28 16 010ff6e4 6f18b727 clr!Assembly::ExecuteMainMethod+0xf7 29 17 010ffbc8 6f18b8a8 clr!SystemDomain::ExecuteMainMethod+0x5ef 30 18 010ffc20 6f18b9ce clr!ExecuteEXE+0x4c 31 19 010ffc60 6f187305 clr!_CorExeMainInternal+0xdc 32 1a 010ffc9c 721bfa84 clr!_CorExeMain+0x4d 33 1b 010ffcd4 7224e81e mscoreei!_CorExeMain+0xd6 34 1c 010ffce4 72254338 MSCOREE!ShellShim__CorExeMain+0x9e 35 1d 010ffcfc 765ff989 MSCOREE!_CorExeMain_Exported+0x8 36 1e 010ffcfc 77b27084 KERNEL32!BaseThreadInitThunk+0x19 37 1f 010ffd58 77b27054 ntdll!__RtlUserThreadStart+0x2f 38 20 010ffd68 00000000 ntdll!_RtlUserThreadStart+0x1b
或者我們使用【!clrstack】命令,查看一下調用堆棧,這個命令看的更清楚。
1 0:000> !clrstack 2 OS Thread Id: 0x3534 (0) 3 Child SP IP Call Site 4 010fee44 6f166b49 [HelperMethodFrame_PROTECTOBJ: 010fee44] System.Reflection.Emit.AssemblyBuilder.nCreateDynamicAssembly(System.AppDomain, System.Reflection.AssemblyName, System.Security.Policy.Evidence, System.Threading.StackCrawlMark ByRef, System.Security.PermissionSet, System.Security.PermissionSet, System.Security.PermissionSet, Byte[], Byte[], System.Reflection.Emit.AssemblyBuilderAccess, System.Reflection.Emit.DynamicAssemblyFlags, System.Security.SecurityContextSource) 5 010fef20 6dfef391 System.Reflection.Emit.AssemblyBuilder..ctor(System.AppDomain, System.Reflection.AssemblyName, System.Reflection.Emit.AssemblyBuilderAccess, System.String, System.Security.Policy.Evidence, System.Security.PermissionSet, System.Security.PermissionSet, System.Security.PermissionSet, System.Threading.StackCrawlMark ByRef, System.Collections.Generic.IEnumerable`1<System.Reflection.Emit.CustomAttributeBuilder>, System.Security.SecurityContextSource) [f:\dd\ndp\clr\src\BCL\system\reflection\emit\assemblybuilder.cs @ 424] 6 010fefa8 6dfef261 System.Reflection.Emit.AssemblyBuilder.InternalDefineDynamicAssembly(System.Reflection.AssemblyName, System.Reflection.Emit.AssemblyBuilderAccess, System.String, System.Security.Policy.Evidence, System.Security.PermissionSet, System.Security.PermissionSet, System.Security.PermissionSet, System.Threading.StackCrawlMark ByRef, System.Collections.Generic.IEnumerable`1<System.Reflection.Emit.CustomAttributeBuilder>, System.Security.SecurityContextSource) [f:\dd\ndp\clr\src\BCL\system\reflection\emit\assemblybuilder.cs @ 569] 7 010feffc 6e016558 System.AppDomain.InternalDefineDynamicAssembly(System.Reflection.AssemblyName, System.Reflection.Emit.AssemblyBuilderAccess, System.String, System.Security.Policy.Evidence, System.Security.PermissionSet, System.Security.PermissionSet, System.Security.PermissionSet, System.Threading.StackCrawlMark ByRef, System.Collections.Generic.IEnumerable`1<System.Reflection.Emit.CustomAttributeBuilder>, System.Security.SecurityContextSource) [f:\dd\ndp\clr\src\BCL\system\appdomain.cs @ 1515] 8 010ff028 6e016527 System.AppDomain.DefineDynamicAssembly(System.Reflection.AssemblyName, System.Reflection.Emit.AssemblyBuilderAccess) [f:\dd\ndp\clr\src\BCL\system\appdomain.cs @ 1221] 9 010ff038 6bd100bd System.Xml.Serialization.CodeGenerator.CreateAssemblyBuilder(System.AppDomain, System.String) 10 010ff04c 6bd0fa1b System.Xml.Serialization.TempAssembly.GenerateRefEmitAssembly(System.Xml.Serialization.XmlMapping[], System.Type[], System.String, System.Security.Policy.Evidence) 11 010ff0ec 6bd0f696 System.Xml.Serialization.TempAssembly..ctor(System.Xml.Serialization.XmlMapping[], System.Type[], System.String, System.String, System.Security.Policy.Evidence) 12 010ff128 6c09db2a System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(System.Xml.Serialization.XmlMapping, System.Type, System.String, System.String, System.Security.Policy.Evidence) 13 010ff14c 6c09da72 System.Xml.Serialization.XmlSerializer..ctor(System.Type, System.Xml.Serialization.XmlAttributeOverrides, System.Type[], System.Xml.Serialization.XmlRootAttribute, System.String, System.String, System.Security.Policy.Evidence) 14 010ff184 6c09d8e7 System.Xml.Serialization.XmlSerializer..ctor(System.Type, System.Xml.Serialization.XmlRootAttribute) 15 010ff198 017710aa Example_10_1_4.Program.GetCustomer(Int32, System.String, System.String) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_10_1_4\Program.cs @ 27] 16 010ff1f4 01770ffc Example_10_1_4.Program+c__DisplayClass0_0.b__0(Int32) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_10_1_4\Program.cs @ 18] 17 010ff208 01770f88 System.Linq.Enumerable+WhereSelectEnumerableIterator`2[[System.Int32, mscorlib],[System.__Canon, mscorlib]].MoveNext() 18 010ff218 6dfaaa67 System.Collections.Generic.List`1[[System.__Canon, mscorlib]]..ctor(System.Collections.Generic.IEnumerable`1<System.__Canon>) [f:\dd\ndp\clr\src\BCL\system\collections\generic\list.cs @ 99] 19 010ff24c 6c77c5de System.Linq.Enumerable.ToList[[System.__Canon, mscorlib]](System.Collections.Generic.IEnumerable`1<System.__Canon>) 20 010ff260 017708f2 Example_10_1_4.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_10_1_4\Program.cs @ 17] 21 010ff3f8 6efdf036 [GCFrame: 010ff3f8]
我們終於找到問題了。代碼中把解決辦法已經寫好了。
四、總結
終於寫完了,寫作的過程是累並快樂着。學習過程真的沒那麼輕鬆,還好是自己比較喜歡這一行,否則真不知道自己能不能堅持下來。老話重談,《高級調試》的這本書第一遍看,真的很暈,第二遍稍微好點,不學不知道,一學嚇一跳,自己欠缺的很多。好了,不說了,不忘初心,繼續努力,希望老天不要辜負努力的人。