How to debug the dynamic code generated by C# Emit?

First of all, let me declare that this is a very deep topic, and it is also a friend who actually encountered it. It uses DynamicMethod + ILGenerator to generate many dynamic methods. However, sometimes overflow exceptions are often encountered in this dynamic method. How to debug the dynamic method body , I know that if I use visual studio to debug, I personally find it difficult. At this time, I can only use windbg. Next, I will talk about the specific debugging steps.

1. Test code

For the convenience of explanation, the previous test code.

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));
        }
    }

This is a dynamically generated Add(int a,int b) method, so how to debug its method body? There are two tricks here.

First: Use Debugger.Break(); This statement can notify the Debugger attached to the process to interrupt, that is, Windbg.

Second: Use Marshal.GetFunctionPointerForDelegate to get the function pointer address of the delegate method.

Based on the above two points, modify the code as follows:

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));
        }

Next, you can use windbg to start the exe program, and you can see the output on the console as follows:

picture

2. Find the method body bytecode on codeheap

Next, we decompile the function pointer 0x00000000023d062e.

0:000> !U 0x00000000023d062e
Unmanaged code
023d062e b818063d02      mov     eax,23D0618h
023d0633 e9e4c934fe      jmp     0071d01c
023d0638 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]

The above 23D0618h is the final real dynamic method pointer address, and then we use dp to see the value on the pointer.

0:000> dp 23D0618h L1
023d0618  00a90050

Next, we decompile the 00a90050 address to see the assembly code of the method body.

0:000> !U 00a90050
Normal JIT generated code
DynamicClass.Add(Int32, Int32)
Begin 00a90050, size 5
>>> 00a90050 8bc1            mov     eax,ecx
00a90052 03c2            add     eax,edx
00a90054 c3              ret

Then there are two paths:

  • familiar mode

Use the unmanaged command bp 00a90050 to directly set a breakpoint for debugging.

  • hard mode

Use the managed command !bpmd xxx to find breakpoint debugging under the method descriptor.

Here I choose difficult mode to deal with.

3. Use bpmd to set a breakpoint

To use !bpmd to set a breakpoint, there must be a method descriptor. Now that we have codeaddr, how do we find the descriptor in reverse? !mln is available here.

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

The MD=0071537c output above is the address of the method descriptor, and then you can use !bpmd -md 0071537c to set a breakpoint.

0:000> !bpmd -md 0071537c
MethodDesc = 0071537c
Setting breakpoint: bp 00A90050 [DynamicClass.Add(Int32, Int32)]
0:000> g
Breakpoint 0 hit
eax=02505fe8 ebx=0019f5ac ecx=0000000a edx=00000014 esi=0250230c edi=0019f4fc
eip=00a90050 esp=0019f488 ebp=0019f508 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
00a90050 8bc1            mov     eax,ecx

From the output, the breakpoint has been successfully hit, and the clr has automatically transferred to bp 00A90050 for me. Next, look at the hit breakpoint diagram:

picture

The above two assembly instructions are the result of a+b, that is, a is placed in ecx, and b is placed in edx. If you don't believe it, you can step twice.

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

Here ecx=0000000a edx=00000014 is.

Guess you like

Origin blog.csdn.net/wangonik_l/article/details/132693573