.NET高级调试 | 通过JIT拦截无侵入调试 C# Emit 生成的动态代码
大家還記得上一篇的測試代碼嗎?我們用了:
Console.WriteLine("Function?Pointer:?0x{0:x16}",?Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64());來獲得 委托 的 函數指針 地址,通過這個突破口最終實現了 動態代碼 的調試,這種方式可以是可以,但很顯然這是侵入式的,那有沒有辦法實現 非侵入 調試動態代碼呢?在 .NET高級調試 這本書上還真給找到了,方法就是在 ?JIT 編譯動態方法時進行攔截,獲取其中的 方法描述符。
為了方便講解,先上測試代碼:
class?Program{private?delegate?int?AddDelegate(int?a,?int?b);static?void?Main(string[]?args){var?dynamicAdd?=?new?DynamicMethod("Add",?typeof(int),?new[]?{?typeof(int),?typeof(int)?},?true);var?il?=?dynamicAdd.GetILGenerator();il.Emit(OpCodes.Ldarg_0);il.Emit(OpCodes.Ldarg_1);il.Emit(OpCodes.Add);il.Emit(OpCodes.Ret);var?addDelegate?=?(AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate));//Debugger.Break();//Console.WriteLine("Function?Pointer:?0x{0:x16}",?Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64());Console.WriteLine(addDelegate(10,?20));}}可以看到,我把上面兩行侵入式的代碼給屏蔽掉了,接下來在 il.Emit(OpCodes.Ret); 處下斷點,目的是為了在 clr 加載后尋找 JIT的 compileMethod 方法。
0:000>?!mbp?Program.cs?28 The?CLR?has?not?yet?been?initialized?in?the?process. Breakpoint?resolution?will?be?attempted?when?the?CLR?is?initialized. 0:000>?g ModLoad:?76910000?7698a000???C:\Windows\SysWOW64\ADVAPI32.dll ... ModLoad:?77190000?77226000???C:\Windows\SysWOW64\OLEAUT32.dll Breakpoint:?JIT?notification?received?for?method?ConsoleApp1.Program.Main(System.String[])?in?AppDomain?00783758. Breakpoint?set?at?ConsoleApp1.Program.Main(System.String[])?in?AppDomain?00783758. Breakpoint?1?hit eax=00000001?ebx=0019f5ac?ecx=023c3684?edx=ffffffff?esi=023c230c?edi=0019f4fc eip=048a0a02?esp=0019f4ac?ebp=0019f508?iopl=0?????????nv?up?ei?pl?zr?na?pe?nc cs=0023??ss=002b??ds=002b??es=002b??fs=0053??gs=002b?????????????efl=00000246 048a0a02?b901000000??????mov?????ecx,1接下來可以用 x 命令模糊搜索 compileMethod 簽名,找出簽名是為了更好的下斷點。
0:000>?x?*!*compileMethod* ... 61413700??????????clrjit!CILJit::compileMethod?(class?ICorJitInfo?*,?struct?CORINFO_METHOD_INFO?*,?unsigned?int,?unsigned?char?**,?unsigned?long?*)可以看到 compileMethod 的完整簽名是 clrjit!CILJit::compileMethod, 并且它的方法入口點地址是 61413700,有了它就可以對其下斷點啦!
0:000>?bp?61413700 0:000>?g Breakpoint?0?hit eax=61494698?ebx=80000004?ecx=61413700?edx=00005c10?esi=6148b3fc?edi=0019efa4 eip=61413700?esp=0019ede0?ebp=0019ee38?iopl=0?????????nv?up?ei?ng?nz?na?po?nc cs=0023??ss=002b??ds=002b??es=002b??fs=0053??gs=002b?????????????efl=00000282 clrjit!CILJit::compileMethod: 61413700?55??????????????push????ebp 0:000>?kb#?ChildEBP?RetAddr??????Args?to?Child?????????????? 00?0019ee38?62a4ccc3?????61494698?0019efa4?0019ef1c?clrjit!CILJit::compileMethod?[f:\dd\ndp\clr\src\jit32\ee_il_dll.cpp?@?151]? 01?0019ee38?62a4cd9b?????0019ef1c?0019f06c?0019f024?clr!invokeCompileMethodHelper+0x10b很開心,成功命中,接下來提取 compileMethod 方法的第三個參數,它就是需要編譯方法所指向的 方法描述符 地址,可以用 dp 給提取出來。
0:000>?dp?0019ef1c?L1 0019ef1c??0071537c 0:000>?!dumpmd?0071537c Method?Name:??DynamicClass.Add(Int32,?Int32) Class:????????007152e8 MethodTable:??0071533c mdToken:??????06000000 Module:???????00714ea8 IsJitted:?????no CodeAddr:?????ffffffff Transparency:?Transparent方法描述符果然給調出來了,但這里的方法字節碼是 CodeAddr: ffffffff ,說明此時動態方法還沒有開始編譯,為了能夠使其編譯,我們在 Console.WriteLine(addDelegate(10, 20)); 處再下一個斷點,因為代碼到此處時, JIT 肯定編譯了該辦法,自然就能看到編譯后的 CodeAddr 地址。
0:000>?!mbp?Program.cs?35 Breakpoint?set?at?ConsoleApp1.Program.Main(System.String[])?in?AppDomain?00783758. 0:000>?g Breakpoint?3?hit eax=023c5f88?ebx=0019f5ac?ecx=023c5f3c?edx=00008f17?esi=023c230c?edi=0019f4fc eip=048a0a9b?esp=0019f4ac?ebp=0019f508?iopl=0?????????nv?up?ei?pl?zr?na?pe?nc cs=0023??ss=002b??ds=002b??es=002b??fs=0053??gs=002b?????????????efl=00000246 048a0a9b?6a14????????????push????14h 0:000>?!dumpmd?0071537c Method?Name:??DynamicClass.Add(Int32,?Int32) Class:????????007152e8 MethodTable:??0071533c mdToken:??????06000000 Module:???????00714ea8 IsJitted:?????yes CodeAddr:?????04a00050 Transparency:?Transparent可以看到,此時的 CodeAddr: 04a00050 ,也就表明已經編譯完成了,接下來繼續 bp 。
0:000>?bp?04a00050 0:000>?g Breakpoint?4?hit eax=023c5f98?ebx=0019f5ac?ecx=0000000a?edx=00000014?esi=023c230c?edi=0019f4fc eip=04a00050?esp=0019f4a8?ebp=0019f508?iopl=0?????????nv?up?ei?pl?nz?na?po?nc cs=0023??ss=002b??ds=002b??es=002b??fs=0053??gs=002b?????????????efl=00000202 04a00050?8bc1????????????mov?????eax,ecx可以看到,全部搞定,非侵入式,🐂
總結
以上是生活随笔為你收集整理的.NET高级调试 | 通过JIT拦截无侵入调试 C# Emit 生成的动态代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分享一个基于.NET6包含DDD,ES,
- 下一篇: Blazor University (1