有没有办法查看 DynamicMethod 生成的 x86 汇编代码?
Is there a way to view DynamicMethod resulting x86 assembly code?
我正在构建一个 DynamicMethod 动态方法,使用 ILGenerator 插入 OpCodes。我正在使用 Visual Studio 插件查看 DynamicMethod 中的 IL 代码,所以这不是问题。
不过,我希望看到 JITer 发出的最终 x86 代码。 Visual Studio 2017不让我踏入x86汇编代码,无论我怎么努力。它在堆栈中显示为 "lightweight function",VS 将跳过它。
有没有办法查看编译 DynamicMethod 产生的 x86 汇编代码?
我似乎无法从 Visual Studio(至少 VS2017)中找到这样做的方法。因此,使用 WinDbg(作为 Windows SDK 的一部分提供)可能会更幸运。
为了让事情变得更简单,我建议让您的应用程序输出一些有用的数据,这将有助于使用 WinDbg 在内存中查找代码。具体来说,如果您可以输出对从动态方法创建的委托调用 Marshal.GetFunctionPointerForDelegate()
的结果,这将使您非常接近该方法的代码。您将需要为此使用 non-generic 委托,所以如果例如您正在从您的动态方法创建一个 Func<...>
委托,您需要暂时将其替换为 non-generic.
举个例子:
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 下 运行 时调用此方法,在自动进入调试器之前,我们应该在控制台 window 中得到类似于以下输出的内容:
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
(反汇编)在这个地址上,或者把地址输入Disassembly window(View -> Disassembly)得到动态方法的代码:
0:000> u 000007fe`9abcdef0
000007fe`9abcdef0 8d0411 lea eax,[rcx+rdx]
000007fe`9abcdef3 c3 ret
...
默认情况下,unassemble 命令输出 8 条指令,您可以在命令中添加一个长度说明符来更改它(例如,添加 "L20" 将输出 32 (0x20) 条指令)——由您决定功能的完整范围。
或者,您可能会发现使用 WinDbg 的 .NET 调试扩展来执行转储动态方法代码的最后一步更容易,在这种情况下,您首先需要加载扩展(每个调试会话只需要一次)使用.loadby sos clr
,然后在代码地址上使用!u
:
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
以上例子都是64位模式,32位模式下方法基本相同
我正在构建一个 DynamicMethod 动态方法,使用 ILGenerator 插入 OpCodes。我正在使用 Visual Studio 插件查看 DynamicMethod 中的 IL 代码,所以这不是问题。
不过,我希望看到 JITer 发出的最终 x86 代码。 Visual Studio 2017不让我踏入x86汇编代码,无论我怎么努力。它在堆栈中显示为 "lightweight function",VS 将跳过它。
有没有办法查看编译 DynamicMethod 产生的 x86 汇编代码?
我似乎无法从 Visual Studio(至少 VS2017)中找到这样做的方法。因此,使用 WinDbg(作为 Windows SDK 的一部分提供)可能会更幸运。
为了让事情变得更简单,我建议让您的应用程序输出一些有用的数据,这将有助于使用 WinDbg 在内存中查找代码。具体来说,如果您可以输出对从动态方法创建的委托调用 Marshal.GetFunctionPointerForDelegate()
的结果,这将使您非常接近该方法的代码。您将需要为此使用 non-generic 委托,所以如果例如您正在从您的动态方法创建一个 Func<...>
委托,您需要暂时将其替换为 non-generic.
举个例子:
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 下 运行 时调用此方法,在自动进入调试器之前,我们应该在控制台 window 中得到类似于以下输出的内容:
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
(反汇编)在这个地址上,或者把地址输入Disassembly window(View -> Disassembly)得到动态方法的代码:
0:000> u 000007fe`9abcdef0
000007fe`9abcdef0 8d0411 lea eax,[rcx+rdx]
000007fe`9abcdef3 c3 ret
...
默认情况下,unassemble 命令输出 8 条指令,您可以在命令中添加一个长度说明符来更改它(例如,添加 "L20" 将输出 32 (0x20) 条指令)——由您决定功能的完整范围。
或者,您可能会发现使用 WinDbg 的 .NET 调试扩展来执行转储动态方法代码的最后一步更容易,在这种情况下,您首先需要加载扩展(每个调试会话只需要一次)使用.loadby sos clr
,然后在代码地址上使用!u
:
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
以上例子都是64位模式,32位模式下方法基本相同