当前位置:首页 >综合 >如何调试 C# Emit 生成的动态代码? 态代这是何调一个很深的话题

如何调试 C# Emit 生成的动态代码? 态代这是何调一个很深的话题

2024-06-28 10:41:21 [百科] 来源:避面尹邢网

如何调试 C# Emit 生成的何调动态代码?

作者:一线码农聊技术 开发 前端 这是一个动态生成的 Add(int a,int b) 方法,那如何调试它的试C生成方法体呢?这里有两个技巧。第一:使用 Debugger.Break();​ 这个语句可以通知附加到该进程的态代 Debugger 中断,也就是何调 Windbg。第二:使用 Marshal.GetFunctionPointerForDelegate​ 获取 委托方法 的试C生成函数指针地址。

首先声明一下,态代这是何调一个很深的话题,也是试C生成朋友真实遇到的,它用 DynamicMethod + ILGenerator 生成了很多动态方法,态代然而这动态方法中有时候经常会遇到溢出异常,何调寻求如何调试 动态方法体,试C生成我知道如果用 visual studio 来调试的态代话,我个人觉得很难,何调这时候只能用 windbg 了,试C生成接下来我聊一下具体调试步骤。态代

1. 测试代码

为了方便讲解,上一段测试代码。

如何调试 C# Emit 生成的动态代码? 态代这是何调一个很深的话题

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));            Console.WriteLine(addDelegate(10, 20));        }    }

这是一个动态生成的 Add(int a,int b) 方法,那如何调试它的方法体呢?这里有两个技巧。

如何调试 C# Emit 生成的动态代码? 态代这是何调一个很深的话题

第一:使用 Debugger.Break(); 这个语句可以通知附加到该进程的 Debugger 中断,也就是 Windbg。

如何调试 C# Emit 生成的动态代码? 态代这是何调一个很深的话题

第二:使用 Marshal.GetFunctionPointerForDelegate 获取 委托方法 的函数指针地址。

基于上面两点,修改代码如下:

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));            Console.WriteLine("Function Pointer: 0x{ 0:x16}", Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64());            Debugger.Break();            Console.WriteLine(addDelegate(10, 20));        }

接下来可以用 windbg 把 exe 程序启动起来,可以看到console上的输出如下:

图片图片

2. 寻找 codeheap 上的方法体字节码

接下来我们反编译下 0x00000000023d062e 这个函数指针。

0:000> !U 0x00000000023d062eUnmanaged code023d062e b818063d02      mov     eax,23D0618h023d0633 e9e4c934fe      jmp     0071d01c023d0638 ab              stos    dword ptr es:[edi]023d0639 ab              stos    dword ptr es:[edi]023d063a ab              stos    dword ptr es:[edi]023d063b ab              stos    dword ptr es:[edi]023d063c ab              stos    dword ptr es:[edi]023d063d ab              stos    dword ptr es:[edi]023d063e ab              stos    dword ptr es:[edi]023d063f ab              stos    dword ptr es:[edi]

上面的 23D0618h 才是最后真实的 动态方法 指针地址,接下来我们用 dp 看看指针上的值。

0:000> dp 23D0618h L1023d0618  00a90050

接下来我们反编译下 00a90050 地址看看方法体的汇编代码。

0:000> !U 00a90050Normal JIT generated codeDynamicClass.Add(Int32, Int32)Begin 00a90050, size 5>>> 00a90050 8bc1            mov     eax,ecx00a90052 03c2            add     eax,edx00a90054 c3              ret

接下来有两条路:

  • 熟路模式

使用非托管命令 bp 00a90050  直接下断点调试。

  • 困难模式

使用托管命令 !bpmd xxx 寻找方法描述符下断点调试。

这里我就选择 困难模式 来处理。

3. 使用 bpmd 下断点

要用 !bpmd 下断点,必须要有 方法描述符, 现在我们有了 codeaddr 如何反向找描述符呢?这里可用 !mln。

0:000> !mln 00a90050Method instance: (BEGIN=00a90050)(MD=0071537c disassemble)[DynamicClass.Add(Int32, Int32)]

上面输出的 MD=0071537c 就是方法描述符的地址,接下来就可以用 !bpmd -md 0071537c 设置断点即可。

0:000> !bpmd -md 0071537cMethodDesc = 0071537cSetting breakpoint: bp 00A90050 [DynamicClass.Add(Int32, Int32)]0:000> gBreakpoint 0 hiteax=02505fe8 ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fceip=00a90050 esp=0019f488 ebp=0019f508 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=0000020600a90050 8bc1            mov     eax,ecx

从输出看,已经成功命中断点,而且 clr 也帮我自动转接到了 bp 00A90050,接下来看下命中的断点图:

图片图片

上面的二条汇编指令就是 a+b 的结果,也就是 ecx 放了 a, edx 放了 b,不信的话可以 step 二次。

0:000> teax=0000000a ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fceip=00a90052 esp=0019f488 ebp=0019f508 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=0000020600a90052 03c2            add     eax,edx0:000> teax=0000001e ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fceip=00a90054 esp=0019f488 ebp=0019f508 iopl=0         nv up ei pl nz na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=0000020600a90054 c3              ret

这里的 ecx=0000000a edx=00000014 便是。

责任编辑:武晓燕 来源: 一线码农聊技术 C#动态代码

(责任编辑:探索)

    推荐文章
    热点阅读