如何从DynamicMethod获取一个IL字节数组?

12

作为一种新奇的尝试,我正在尝试查看在运行时生成的轻量级代码与VS编译器生成的代码之间的差异,因为我注意到VS代码在处理转换等方面具有不同的性能特征。

于是我编写了以下代码:

Func<object,string> vs = x=>(string)x;
Expression<Func<object,string>> exp = x=>(string)x;
var compiled = exp.Compile(); 
Array.ForEach(vs.Method.GetMethodBody().GetILAsByteArray(),Console.WriteLine);
Array.ForEach(compiled.Method.GetMethodBody().GetILAsByteArray(),Console.WriteLine);

不幸的是,这会抛出异常,因为GetMethodBody在表达式树生成的代码上显然是非法操作。我该如何以库的方式(即除非该工具具有API,否则不使用外部工具)查看使用轻量级代码生成器生成的代码?

编辑:错误发生在第5行,compiled.Method.GetMethodBody()抛出异常。

编辑2:有人知道如何恢复方法中声明的局部变量吗?或者说没有获取变量的方法?


哪一行触发了异常?您可以注释掉第一个Array.ForEach,看看是否可以正常运行?我怀疑第一个调用GetMethodBody()失败的原因只是因为该表达式尚未编译为IL。我看不出第二个调用会失败的原因。 - cdhowie
有趣的问题。在调用GetMethodBody时,我遇到了InvalidOperationException(“由于对象的当前状态,操作无效”)。我不确定作为CachedAnonymousDelegate与Expression开始生活会如何影响您作为Func的行为。我将继续努力解决这个问题。 - Sorax
应该更改所选答案,因为它并不涵盖所有情况,并且过于复杂。请参见此答案 - jnm2
对于本地变量,我认为您必须访问m_localSignature并根据ECMA 335进行解析。 - julx
5个回答

19

没用,这个方法是由Reflection.Emit生成的。IL代码存储在MethodBuilder的ILGenerator中,你可以挖出来,但必须非常绝望才会这么做。需要使用反射来获取内部和私有成员。这在.NET 3.5SP1上有效:

using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
...

        var mtype = compiled.Method.GetType();
        var fiOwner = mtype.GetField("m_owner", BindingFlags.Instance | BindingFlags.NonPublic);
        var dynMethod = fiOwner.GetValue(compiled.Method) as DynamicMethod;
        var ilgen = dynMethod.GetILGenerator();
        var fiBytes = ilgen.GetType().GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic);
        var fiLength = ilgen.GetType().GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic);
        byte[] il = fiBytes.GetValue(ilgen) as byte[];
        int cnt = (int)fiLength.GetValue(ilgen);
        // Dump <cnt> bytes from <il>
        //...

在.NET 4.0中,您需要使用ilgen.GetType().BaseType.GetField(...),因为IL生成器已更改,DynamicILGenerator派生自ILGenerator。


哦,该死,那太丑了。干得好...可惜代码不能更美观。:( - cdhowie
刚试了一下,在.NET 4中好像无法工作,它告诉我fiBytes为null :( - Michael B
1
@Michael - 是的,这就是使用反射的风险。IL 生成器已经改变了。你需要使用 ilgen.GetType().BaseType.GetField(...)。 - Hans Passant
1
注意,这并不适用于所有的DynamicMethod。请参考此答案获取当前解决方案。 - jnm2

2

当前的解决方案并不能很好地解决.NET 4中的当前情况。您可以使用DynamicILInfoILGenerator来创建动态方法,但是这里列出的解决方案根本无法与DynamicILInfo动态方法一起使用。

无论您使用生成IL的DynamicILInfo方法还是ILGenerator方法,IL字节码最终都会在DynamicMethod.m_resolver.m_code中。您不必检查两种方法,这是一个更简单的解决方案。

这是您应该使用的版本:

public static byte[] GetILBytes(DynamicMethod dynamicMethod)
{
    var resolver = typeof(DynamicMethod).GetField("m_resolver", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(dynamicMethod);
    if (resolver == null) throw new ArgumentException("The dynamic method's IL has not been finalized.");
    return (byte[])resolver.GetType().GetField("m_code", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(resolver);
}

查看这个答案,了解更多有关辅助方法和解决动态方法令牌解析问题的方案。


1
如果像我这样缺乏动态方法经验的人,将会难以理解为什么此答案的ArgumentException总是被抛出,以及"The dynamic method's IL has not been finalized."错误消息的含义。原因是在尝试检索"m_resolver"字段之前,必须通过调用DynamicMethod.CreateDelegate()函数完成动态方法,否则该字段始终为空。完成方法会改变通过调用DynamicMethod.GetILGenerator(streamSize)函数创建的MSIL流的大小。 - ElektroStudios

2

1
基于 Hans Passant 的工作,我得以深入探究,似乎有一个应该调用的方法,名为 BakeByteArray,因此以下代码可行:
var dynMethod = fiOwner.GetValue(compiled.Method) as DynamicMethod;
var ilgen =dynamicMethod.GetILGenerator();
byte[] il = ilgen.GetType().GetMethod("BakeByteArray", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(ilgen, null) as byte[];

这确实有所帮助,但我还没有解决VariableInfo的方法,这是在我的工作中会有所帮助的。


2
你正在烤它两次。不确定是否会烧焦,可能不会。 - Hans Passant
我不确定,但是这段代码完全没有用,因为我无法使用这种方法实际上反汇编我所制作的内容,因为我无法将metadataToken解析为任何有用的东西,因为列在该对象上的模块似乎毫无价值。 - Michael B
ILVisualizer是来自AbdelRaheim的答案中使用反射的BakeByteArray - riQQ

0
我把 @Hans Passant 和 @jnm2 的解决方案合并成一个扩展方法,并添加了有用的注释:
public static byte[] GetIlAsByteArray(this DynamicMethod dynMethod)
{

    // First we try to retrieve the value of "m_resolver" field,
    // which will always be null unless the dynamic method is completed
    // by either calling 'dynMethod.CreateDelegate()' or 'dynMethod.Invoke()' function.
    // Source: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.dynamicmethod.getilgenerator
    // (Remarks section)

    // Note that the dynamic method object does not know when it is ready for use
    // since there is not API which indicates that IL generation has completed.
    // Source: https://referencesource.microsoft.com/#mscorlib/system/reflection/emit/dynamicmethod.cs,7fc135a2ceea0854,references
    // (Comment lines)

    // So, if the dynamic method is not completed, we will retrieve the "m_ILStream" field instead.
    // The only difference I notice between "m_resolver" and "m_ILStream" fields is that the IL bytes in "m_ILStream"
    // will have trailing zeros / null bytes depending on the amount of unused bytes in this buffer.
    // ( The buffer size for "m_ILStream" is allocated by a call to 'dynMethod.GetILGenerator(streamSize)' function. )

    BindingFlags bindingFlags = bindingFlags.Instance | bindingFlags.NonPublic;

    object fiResolver = typeof(DynamicMethod).GetField("m_resolver", bindingFlags).GetValue(dynMethod);
    if (fiResolver == null)
    {

        ILGenerator ilGen = dynMethod.GetILGenerator();
        FieldInfo fiIlStream = null;

        // Conditional for .NET 4.x because DynamicILGenerator class derived from ILGenerator.
        // Source: https://dev59.com/k2855IYBdhLWcg3wyniC#4147132
        if (Environment.Version.Major >= 4)
        {
            fiIlStream = ilGen.GetType().BaseType.GetField("m_ILStream", bindingFlags);
        }
        else // This worked on .NET 3.5
        {
            fiIlStream = ilGen.GetType().GetField("m_ILStream", bindingFlags);
        }
        return fiIlStream.GetValue(ilGen) as byte[];

    }
    else
    {
        return (byte[])(fiResolver.GetType().GetField("m_code", bindingFlags).GetValue(fiResolver));

    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接