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

发布于 2024-10-02 02:18:52 字数 667 浏览 13 评论 0原文

有点新奇的是,我试图看看运行时生成的轻量级代码的 IL 与 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)查看使用轻量级 codegen 的代码生成的代码?

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

编辑2: 有谁知道如何恢复方法中声明的局部变量?或者没有办法GetVariables?

As a bit of a novelty, I'm trying to see how different the IL from light weight code generated at runtime looks vs code generated by the VS compiler, as I noticed that VS code tends to run with a different performance profile for things like casts.

So I wrote the following code::

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

Unfortunately, this throws an exception as GetMethodBody is apparently an illegal operation on code generated by expression trees. How can I in a library manner (i.e. not with an external tool unless the tool has an API) look at the code generated by code using lightweight codegen?

Edit: the error occurs on line 5, compiled.Method.GetMethodBody() throws the exception.

Edit2:
Does anyone know how to recover the local variables declared in the method? Or is there no way to GetVariables?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

蛮可爱 2024-10-09 02:18:52

是的,不起作用,该方法是由 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 生成器已更改,即从 ILGenerator 派生的 DynamicILGenerator。

Yeah, doesn't work, the method is generated by Reflection.Emit. The IL is stored in the MethodBuilder's ILGenerator. You can dig it out but you have to be pretty desperate. Reflection is needed to get to the internal and private members. This worked on .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>
        //...

On .NET 4.0 you'll have to use ilgen.GetType().BaseType.GetField(...) because the IL generator was changed, DynamicILGenerator, derived from ILGenerator.

南风几经秋 2024-10-09 02:18:52

当前的解决方案并不能很好地解决 .NET 4 中的当前情况。您可以使用 DynamicILInfoILGenerator 创建动态方法,但此处列出的解决方案不适用于 DynamicILInfo 动态方法全部。

无论您使用 DynamicILInfo 方法生成 IL 还是 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);
}

请参阅此答案以获取更多辅助方法和 DynamicMethod 令牌解析问题的解决方案。

The current solutions here aren't addressing the current situation in .NET 4 very well. You can use either DynamicILInfo or ILGenerator to create the dynamic method, but the solutions listed here do not work with DynamicILInfo dynamic methods at all.

Whether you use the DynamicILInfo method of generating IL or the ILGenerator method, the IL bytecode ends up in DynamicMethod.m_resolver.m_code. You don't have to check both methods and it's a less complex solution.

This is the version you should be using:

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

See this answer for more helper methods and a solution for the DynamicMethod token resolution issue.

念三年u 2024-10-09 02:18:52

这里的 ILReader 应该可以工作。

ILVisualizer 2010 解决方案

The ILReader here should work.

ILVisualizer 2010 Solution

奶茶白久 2024-10-09 02:18:52

基于 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's 的问题,这对我的工作会有帮助。

Based off Hans Passant's work I was able to dig a little deeper there appears to be a method that you should call, called BakeByteArray so the following works::

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[];

This certainly helps, but I still have no way to resolve VariableInfo's just yet which is something that would help in my work.

Hello爱情风 2024-10-09 02:18:52

我刚刚将 @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://stackoverflow.com/a/4147132/1248295
        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));

    }

}

I just merged @Hans Passant and @jnm2 solutions into a extension method and added useful comments:

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://stackoverflow.com/a/4147132/1248295
        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));

    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文