Rob The Quant
Rob The Quant

Reputation: 397

Is there a way to view DynamicMethod resulting x86 assembly code?

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

Answers (1)

Iridium
Iridium

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

Related Questions