none
DynamicMethod disassembling RRS feed

  • Question

  • I'm currently evaluating the power of DynamicMethod to create an environment that based on user input can create more or less volatile methods.

    The user input finally forming these methods may be buggy and throw exceptions.

    There's a ton of questions that arise from this fact since it virtually seems to be impossible to support users where their error is. I would like to know how Dynamic method compilation internally works and what exactly the reasons are for omitting tables containing IL to native sequence point mappings in the .NET framework when working with dynamic methods.

    I've also been experimenting with dynamic types concerning the scenario and found it unlikely that we can use them. Reason 1 is that the customer induced hosting platform for the project may not even support .NET 4.0, so getting rid again of the method code may be virtually impossible. Running different Appdomains is out of the question because of performance reasons. Reason 2: The memory overhead induced by loading tons of dynamic assemblies (and in worst case additionally AppDomains even) would kill the project anyway, since the dynamically generated code may be long living.

    When evaluating the possibilities, I also noticed that is is impossible to step into the native machine code generated by the JIT-compiler for a dynamic method. I. e. you can't even have a look at the code you just generated from the IL you emitted. Why is that so?

    Furthermore, emitting the same IL through ILGenerators from DynamicMethod and a MethodBuilder seems to lead to different byte code. The differences seem to be in the encoding of addresses for methods we called from within the emitted IL (In a test simply Console.WriteLine - two different encodings for the same OpCodes.Call). That was where I really lost it. I would like to have more information about what is happening there and why this should be the case at all.

    The best way to realize the planned environment would be to precompile the dynamic method code just as the IL byte-array and serialize it to disk (or maybe even put it in a database) and only recompile when the user input file has changed. When the functionality is needed, the dynamicmethod would be quickly instantiated from the saved IL-byte-array. However, regarding the aforementioned issue, I am quite uncertain whether that would work. If the method addresses change on a restart of the environment, things would simply break. What do the emitted addresses depend on? The loaded assemblies? Their order of loading?

    Next question: Is there a guarantee that the JITted code for a static 'DynamicType' method (generated by a MethodBuilder (maybe even with sequence points)) and a (also static) DynamicMethod will be the same if they based on the same IL code? Is there a possibility to access the Native-to-IL mapping of the MethodBuilder method? If so, this could be a workaround to map the native offset when an exception occurs (the only information you seem to be able to obtain for an exception in a DynamicMethod) to the IL that was generated.

    I would appreciate direct feedback from Microsoft clarifying these points.

    Following is some example code we used for evaluation:

    using System;
    using System.Text;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Diagnostics;
    using System.Diagnostics.SymbolStore;


    namespace test
    {
        class MainClass
        {
            internal delegate int callconv(int i);

            public static void Main(string[] args)
            {
                try
                {
                    Assembly asm = AppDomain.CurrentDomain.GetAssemblies()[0];
                    Module mod = asm.GetModules()[0];
                    AssemblyBuilder asmb = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("test"), AssemblyBuilderAccess.RunAndSave);
                    ModuleBuilder modb = asmb.DefineDynamicModule("test", @"TestAssembly.dll", true);
                    ISymbolDocumentWriter doc = modb.DefineDocument(@"F:\fantasy\path\mymodule.xlg", Guid.Empty, Guid.Empty, Guid.Empty);
                    TypeBuilder typb = modb.DefineType("test");
                    MethodBuilder mb = typb.DefineMethod("tesmeth", MethodAttributes.Static | MethodAttributes.Public,
                                CallingConventions.Standard, typeof(int), new Type[] { typeof(int) });
                    DynamicMethod x = new DynamicMethod("test", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(int), new Type[] { typeof(int) }, modb, true);
                    MethodInfo writelineint = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string), typeof(object) });
                    ILGenerator ilg = x.GetILGenerator();
                    //ilg.MarkSequencePoint(doc, 1, 2, 1, 32);
                    ilg.Emit(OpCodes.Ldc_I4_4);
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Ldc_I4_2);
                    ilg.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
                    ilg.Emit(OpCodes.Ldc_I4_3);
                    ilg.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
                    //ilg.MarkSequencePoint(doc, 5, 2, 5, 32);
                    ilg.Emit(OpCodes.Div);
                    //ilg.MarkSequencePoint(doc, 7, 2, 7, 32);
                    ilg.Emit(OpCodes.Box, typeof(int));
                    ilg.Emit(OpCodes.Starg, 0);
                    ilg.Emit(OpCodes.Ldstr, String.Intern("testres={0}"));
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Call, writelineint);
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Unbox, typeof(int));
                    ilg.Emit(OpCodes.Ldobj, typeof(int));
                    ilg.Emit(OpCodes.Ret);
                    FieldInfo ilpropCode = ilg.GetType().GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic);
                    FieldInfo ilpropCodeLength = ilg.GetType().GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic);
                    byte[] bytes = (byte[])ilpropCode.GetValue(ilg);
                    int length = (int)ilpropCodeLength.GetValue(ilg);
                    byte[] byIL = new byte[length];
                    Array.Copy(bytes, byIL, length);
                    object[] ats = x.GetCustomAttributes(true);

                    callconv meth = (callconv)(x.CreateDelegate(typeof(callconv)));
                    meth(2);
                    Console.WriteLine("exec: {0}", meth(1));  // change to 0 here to get an exception for the dynamic method

                    mb.DefineParameter(1, ParameterAttributes.In, "invalue");
                    ilg = mb.GetILGenerator();
                    ilg.MarkSequencePoint(doc, 2, 2, 2, 32);
                    ilg.Emit(OpCodes.Ldc_I4_4);
                    ilg.MarkSequencePoint(doc, 3, 2, 3, 32);
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Ldc_I4_2);
                    ilg.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
                    ilg.Emit(OpCodes.Ldc_I4_3);
                    ilg.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
                    ilg.MarkSequencePoint(doc, 5, 2, 5, 32);
                    ilg.Emit(OpCodes.Div);
                    ilg.MarkSequencePoint(doc, 7, 2, 7, 32);
                    ilg.Emit(OpCodes.Box, typeof(int));
                    ilg.Emit(OpCodes.Starg, 0);
                    ilg.Emit(OpCodes.Ldstr, String.Intern("testres={0}"));
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Call, writelineint);
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Unbox, typeof(int));
                    ilg.Emit(OpCodes.Ldobj, typeof(int));
                    ilg.Emit(OpCodes.Ret);

                    //mb.CreateMethodBody(byIL, byIL.Length);  // this will fail horribly
                    Type typ = typb.CreateType();

                    ilpropCode = ilg.GetType().GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic);
                    ilpropCodeLength = ilg.GetType().GetField("m_length", BindingFlags.Instance | BindingFlags.NonPublic);
                    bytes = (byte[])ilpropCode.GetValue(ilg); // x.GetMethodBody().GetILAsByteArray(); // (byte[])getter.Invoke(d, null);
                    length = (int)ilpropCodeLength.GetValue(ilg);
                    byte[] byIL2 = new byte[length];
                    Array.Copy(bytes, byIL2, length);

                    if (!System.Linq.Enumerable.SequenceEqual<byte>(byIL, byIL2))
                    {
                        Console.WriteLine("Code still differs!");
                    }
                    else
                    {
                        Console.WriteLine("Equal code.");
                    }

                    MethodInfo mi = typ.GetMethod("tesmeth");

                    callconv meth2 = (callconv)Delegate.CreateDelegate(typeof(callconv), mi);
                    meth2(2);
                    ats = meth2.Method.GetCustomAttributes(true);
                    foreach (object o in ats)
                    {
                        Console.WriteLine(o.ToString());
                    }
                    Console.WriteLine("exec2: {0}", meth2(0));


                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                    StackTrace trc = new StackTrace(ex, true);
                    StackFrame sf = trc.GetFrames()[0];

                    Console.WriteLine("meth:{2}, nat:{0}, il:{1}", sf.GetNativeOffset(), sf.GetILOffset(), sf.GetMethod().Name);
                }

                Console.ReadKey(true);
            }
        }
    }




    • Edited by Xaver111 Friday, May 24, 2013 6:51 PM
    Friday, May 24, 2013 6:44 PM

All replies