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:
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:
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.