Reputation: 397
I'm building a DynamicMethod on the fly inserting OpCodes using ILGenerator. I'm using a Visual Studio plugin to view the IL code in the DynamicMethod, so that's not a problem.
However I'd like to see the final x86 code emitted by JITer. Visual Studio 2017 will not let me step into the x86 assembly code, no matter what I try. It shows up as "lightweight function" in the stack and VS will just step over it.
Is there a way to see the x86 assembly code produced by compiling the DynamicMethod?
Upvotes: 3
Views: 612
Reputation: 23721
There appears to be no way that I can find to do this from Visual Studio (at least VS2017). You may therefore have more luck using WinDbg (available as part of the Windows SDK).
To make things easier I suggest getting your application to output some useful data which will aid in finding the code in memory using WinDbg. Specifically, if you can output the result of calling Marshal.GetFunctionPointerForDelegate()
on the delegate created from your dynamic method, this will get you very close to the method's code. You will need to use a non-generic delegate for this, so if e.g. you're creating a Func<...>
delegate from your dynamic method you'll need to temporarily replace this with something non-generic.
As an example:
private delegate int AddDelegate(int a, int b);
public static void DynamicMethodTest()
{
// Create a DynamicMethod that adds its two int parameters
// Passing "true" as the final (restrictedSkipVisibility) parameter causes the method to be JITted immediately when you call .CreateDelegate()
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);
// Use the non-generic AddDelegate defined above, rather than a generic one like Func<int, int, int> so that Marshal.GetFunctionPointerForDelegate() works
var addDelegate = (AddDelegate)dynamicAdd.CreateDelegate(typeof(AddDelegate));
Console.WriteLine("Function Pointer: 0x{0:x16}", Marshal.GetFunctionPointerForDelegate(addDelegate).ToInt64());
Debugger.Break();
}
If we call this method while the application is running under WinDbg, we should get something like the following output in the console window, before automatically breaking into the debugger:
Function Pointer: 0x0000000012345678
From here were a couple of steps away from seeing the dynamic method's code.
Firstly use the u
(unassemble) command on the function pointer output above:
0:000> u 0x0000000012345678 L1
00000000`12345678 49ba2143658700000000 mov r10,87654321h
Here the first instruction loads the address of a pointer to the actual dynamic method's code into r10
, so we use the dp
(Display memory - pointer) command to get the pointer's target:
0:000> dp 0x87654321 L1
00000000`87654321 000007fe`9abcdef0
Run u
(unassemble) on this address, or enter the address into the Disassembly window (View -> Disassembly) to get the dynamic method's code:
0:000> u 000007fe`9abcdef0
000007fe`9abcdef0 8d0411 lea eax,[rcx+rdx]
000007fe`9abcdef3 c3 ret
...
By default the unassemble command outputs 8 instructions, you can add a length specifier to the command to change this (e.g. adding "L20" would output 32 (0x20) instructions) - it's up to you to determine the full extent of the function.
Alternatively you might find it easier to use the .NET debugging extension for WinDbg to perform the final step of dumping the dynamic method's code, in which case you first need to load the extension (only required once per debugging session) using .loadby sos clr
, then use !u
on the code address instead:
0:000> !u 000007fe`9abcdef0
Normal JIT generated code
DynamicClass.Add(Int32, Int32)
Begin 000007fe9abcdef0, size 4
>>> 000007fe`9abcdef0 8d0411 lea eax,[rcx+rdx]
000007fe`9abcdef3 c3 ret
All of the above examples are in 64-bit mode, but in 32-bit mode the method is essentially identical.
Upvotes: 7