.NET高級調(diào)試 | 通過JIT攔截無侵入調(diào)試 C# Emit 生成的動態(tài)代碼
大家還記得上一篇的測試代碼嗎?我們用了:
Console.WriteLine("Function?Pointer:?0x{0:x16}",?Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64());
來獲得 委托 的 函數(shù)指針 地址,通過這個突破口最終實現(xiàn)了 動態(tài)代碼 的調(diào)試,這種方式可以是可以,但很顯然這是侵入式的,那有沒有辦法實現(xiàn) 非侵入 調(diào)試動態(tài)代碼呢?在 .NET高級調(diào)試 這本書上還真給找到了,方法就是在 ?JIT 編譯動態(tài)方法時進行攔截,獲取其中的 方法描述符。
為了方便講解,先上測試代碼:
????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 方法的第三個參數(shù),它就是需要編譯方法所指向的 方法描述符 地址,可以用 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
方法描述符果然給調(diào)出來了,但這里的方法字節(jié)碼是 CodeAddr: ffffffff ,說明此時動態(tài)方法還沒有開始編譯,為了能夠使其編譯,我們在 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 ,也就表明已經(jīng)編譯完成了,接下來繼續(xù) 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

可以看到,全部搞定,非侵入式,??
