12

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?

Michael B
  • 7,512
  • 3
  • 31
  • 57
  • Which line is throwing the exception? Can you comment out the first Array.ForEach and see if that works? I suspect that the first call to GetMethodBody() is failing simply because that expression has not been compiled to IL. I see no reason why the second call should fail. – cdhowie Nov 10 '10 at 16:58
  • Interesting question. I'm getting an InvalidOperationException("Operation in not valid due to the current state of the object") on the GetMethodBody call. I'm not sure how starting life as a CachedAnonymousDelegate vs Expression would effect your behavior as a Func. I'm going to keep working this one. – Sorax Nov 10 '10 at 17:31
  • The selected answer should be switched because it does not cover all cases and is unnecessarily complex. Please see [this answer](http://stackoverflow.com/a/35711507/521757). – jnm2 Feb 29 '16 at 23:05
  • For local variables I believe you have to access m_localSignature and parse it according to ECMA 335 – julx Jun 13 '20 at 13:20

5 Answers5

19

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Oh, damn that's ugly. Good work... it's a shame the code can't be more beautiful. :( – cdhowie Nov 10 '10 at 18:12
  • Just tried it,This doesn't seem to work in .NET 4. it tells me that fiBytes is null :( – Michael B Nov 10 '10 at 18:33
  • 1
    @Michael - Yup, that's the risk of Reflection. The IL generator was changed. You'll have to use ilgen.GetType().BaseType.GetField(...) – Hans Passant Nov 10 '10 at 18:49
  • 1
    Watch out, this doesn't work with all `DynamicMethod`s. [See this answer](http://stackoverflow.com/a/35711507/521757) for a current solution. – jnm2 Feb 29 '16 at 23:17
2

The ILReader here should work.

ILVisualizer 2010 Solution

riQQ
  • 9,878
  • 7
  • 49
  • 66
1

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.

Community
  • 1
  • 1
jnm2
  • 7,960
  • 5
  • 61
  • 99
  • 1
    If someone is as inexperienced with dynamic methods like me, will have a problem to understand the reason why the ArgumentException of this answer is always thrown and the meaning of "The dynamic method's IL has not been finalized." error message. The reason is that the dynamic method must be completed by calling DynamicMethod.CreateDelegate() function before trying to retrieve the "m_resolver" field, which will always be null if you didn't completed the dynamic method. Completing the method alters the size of the MSIL stream created by a call to DynamicMethod.GetILGenerator(streamSize) func. – ElektroStudios Jan 10 '23 at 07:59
1

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.

Michael B
  • 7,512
  • 3
  • 31
  • 57
  • 2
    You are baking it twice. Not sure if it gets burned, probably not. – Hans Passant Nov 10 '10 at 18:51
  • I'm not sure, however this code is completely useless as I can't disassemble that which I make realistically using this approach as I can't get the metadataToken's resolved to anything useful as the module that is listed on the thing seems worthless. – Michael B Nov 10 '10 at 19:08
  • ILVisualizer from AbdelRaheim's [answer](https://stackoverflow.com/a/9367087) also uses `BakeByteArray` with reflection. – riQQ Nov 23 '20 at 21:45
0

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

    }

}
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417