有没有办法查看DynamicMethod生成的x86汇编代码?

时间:2019-05-08 14:35:32

标签: c# .net .net-assembly cil

我正在使用ILGenerator即时插入OpCode来构建DynamicMethod。我正在使用Visual Studio插件查看DynamicMethod中的IL代码,所以这不是问题。

但是,我想看看JITer发出的最终x86代码。无论我如何尝试,Visual Studio 2017都不会让我介入x86汇编代码。它在堆栈中显示为“轻量级功能”,而VS会跳过它。

有没有办法查看通过编译DynamicMethod生成的x86汇编代码?

1 个答案:

答案 0 :(得分:2)

似乎找不到从Visual Studio(至少是VS2017)中执行此操作的方法。因此,使用WinDbg(作为Windows SDK的一部分提供)可能会带来更多的运气。

为了使事情变得容易,我建议让您的应用程序输出一些有用的数据,这将有助于使用WinDbg在内存中查找代码。具体来说,如果您可以输出从动态方法创建的委托上调用Marshal.GetFunctionPointerForDelegate()的结果,那么您将非常接近该方法的代码。您将需要为此使用非泛型委托,因此,例如您正在通过动态方法创建Func<...>委托,则需要使用非泛型临时替换它。

例如:

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

如果在应用程序在WinDbg下运行时调用此方法,则在自动进入调试器之前,我们应该在控制台窗口中获得类似以下输出的内容:

Function Pointer: 0x0000000012345678

从这里开始,只有几步之遥才能看到动态方法的代码。

首先在上面的函数指针输出上使用u(反汇编)命令:

0:000> u 0x0000000012345678 L1

00000000`12345678 49ba2143658700000000 mov r10,87654321h

这里第一条指令将指向实际动态方法代码的指针的地址加载到r10中,因此我们使用dp(显示内存-指针)命令来获取指针的目标:

0:000> dp 0x87654321 L1
00000000`87654321  000007fe`9abcdef0

在该地址上运行u(反汇编),或在“反汇编”窗口(查看->反汇编)中输入地址以获取动态方法的代码:

0:000> u 000007fe`9abcdef0

000007fe`9abcdef0 8d0411          lea     eax,[rcx+rdx]
000007fe`9abcdef3 c3              ret
...

默认情况下,unassemble命令输出8条指令,您可以在命令中添加一个长度说明符来更改此指令(例如,添加“ L20”将输出32条(0x20)指令)-由您决定功能。

或者,您可能会发现更容易使用WinDbg的.NET调试扩展名执行转储动态方法代码的最后一步,在这种情况下,您首先需要使用{加载扩展名(每个调试会话仅需要一次) {1}},然后在代码地址上使用.loadby sos clr

!u

以上所有示例均处于64位模式下,但在32位模式下该方法本质上是相同的。