我正在使用ILGenerator即时插入OpCode来构建DynamicMethod。我正在使用Visual Studio插件查看DynamicMethod中的IL代码,所以这不是问题。
但是,我想看看JITer发出的最终x86代码。无论我如何尝试,Visual Studio 2017都不会让我介入x86汇编代码。它在堆栈中显示为“轻量级功能”,而VS会跳过它。
有没有办法查看通过编译DynamicMethod生成的x86汇编代码?
答案 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位模式下该方法本质上是相同的。