none
Debugging a System.InvalidProgramException from generated IL RRS feed

  • Question

  • I have a small program for generating static method shims for use with the CoreCLR on .Net Core, because only static methods can be called. I originally wrote it against System.Reflection but because AssemblyBuilder.Save is not cross-platform I have had to rewrite against Mono.Cecil. The original works fine, the new version throws System.InvalidProgramException from one of the generated methods.

    The original (edited for brevity is):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.IO;
    using System.Runtime.InteropServices;
    
    class CLRBuilder
    {
        Type t;          // reflected upon type
        TypeBuilder tb;  // type under construction
        string fname;    // name of file
        private string idir;
    
        CLRBuilder(string name, string aidir)
        {
            fname = name;
            idir = aidir;
            sw = new StreamWriter( fname + ".d", false, Encoding.UTF8);
            useCls = true;
        }
        static void Main(string[] args)
        {
            new CLRBuilder(args[0],args[1]).run();
    
        }
        void run()
        {
            writeHeader();
            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                           new AssemblyName(fname + "static"),
                                           AssemblyBuilderAccess.Save);
    
            // To avoid duplicately adding types
            HashSet<String> visitedTypes = new HashSet<String>();
    
            ModuleBuilder mb = ab.DefineDynamicModule(fname + "static.dll", fname + "static.dll", true);
            foreach (Type _ in Assembly.LoadFile(Path.GetFullPath(Path.Combine(idir,fname + ".dll"))).GetExportedTypes())
            {
                t = _;
                //Assume that duplicates are identical (e.g. interface / class pairs)
                if (visitedTypes.Contains(t.Name)) continue;
                visitedTypes.Add(t.Name);
    
                tb = mb.DefineType(t.Name + "static", TypeAttributes.Public);
                foreach (MemberInfo mi in t.GetMembers())
                {
                    if (mi.Name == "assert")
                        continue;
    
                    if (mi.MemberType == MemberTypes.Method)
                        addMethod((MethodInfo)mi);
                    else if (mi.MemberType == MemberTypes.Constructor)
                        addCtor((ConstructorInfo)mi);
                }
                tb.CreateType();
    
            }
            mb.CreateGlobalFunctions();
            // N.B. Cannot save this to anywhere other than cwd
            // due to Save 
            ab.Save(fname + "static.dll"); 
        }
    
        void addMethod(MethodInfo mi)
        {
            List<Type> tl = new List<Type>();
            tl.Insert(0, t);
            tl.AddRange(mi.GetParameters().Select(p => p.ParameterType));
            Type[] tps = tl.ToArray();
            MethodBuilder mb = tb.DefineMethod(mi.Name,
                                               MethodAttributes.Public |
                                                       MethodAttributes.Static,
                                               mi.ReturnType,
                                               tps
                                               );
            writeDMethod(mi.IsStatic, mi.ReturnType, mi.Name, tps);
    
            {
                ILGenerator ilg = mb.GetILGenerator();
                ilg.Emit(OpCodes.Nop);
                if (mi.IsStatic)
                {
                    emitArgs(ilg,tps);
                    ilg.Emit(OpCodes.Call, mi);
                }
                else
                {
                    ilg.Emit(OpCodes.Ldarg_0);
                    ilg.Emit(OpCodes.Call, typeof(GCHandle).GetMethod("FromIntPtr"));
                    ilg.Emit(OpCodes.Stloc_0);
                    ilg.Emit(OpCodes.Ldloc_0);
                    ilg.Emit(OpCodes.Call, typeof(GCHandle).GetMethod("get_Target"));
                    ilg.Emit(OpCodes.Stloc_1);
                    ilg.Emit(OpCodes.Ldloc_1);
                    ilg.Emit(OpCodes.Castclass, t);
                    ilg.Emit(OpCodes.Stloc_2);
                    ilg.Emit(OpCodes.Ldloc_2);
                    for (byte x = 1; x < tps.Length; x++)
                    {
                        ilg.Emit(OpCodes.Ldarg_S, x);
                    }
                    ilg.Emit(t.IsSealed ? OpCodes.Call : OpCodes.Callvirt, mi);
                }
                ilg.Emit(OpCodes.Nop);
                ilg.Emit(OpCodes.Ret);
    
            }
        }
    
        void addCtor(ConstructorInfo ci)
        {
            Type[] tps = ci.GetParameters().Select(p => p.ParameterType).ToArray();
    
            {
                MethodBuilder mb = tb.DefineMethod("make",
                                               MethodAttributes.Public |
                                                       MethodAttributes.Static,
                                               typeof(IntPtr),
                                               tps
                                               );
                ILGenerator ilg = mb.GetILGenerator();
                ilg.DeclareLocal(t, );
                ilg.DeclareLocal(typeof(GCHandle));
                ilg.DeclareLocal(typeof(IntPtr));
                // Copy what ildasm says csc does modulo redundant direct branches
                ilg.Emit(OpCodes.Nop);
    
                emitArgs(ilg, tps);
                ilg.Emit(OpCodes.Newobj, ci);
                ilg.Emit(OpCodes.Stloc_0);
                ilg.Emit(OpCodes.Ldloc_0);
                ilg.Emit(OpCodes.Call, typeof(GCHandle).GetMethod("Alloc", new[] { typeof(Object) }));
                ilg.Emit(OpCodes.Stloc_1);
                ilg.Emit(OpCodes.Ldloc_1);
                ilg.Emit(OpCodes.Call, typeof(GCHandle).GetMethod("ToIntPtr"));
                ilg.Emit(OpCodes.Stloc_2);
                ilg.Emit(OpCodes.Ldloc_2);
                ilg.Emit(OpCodes.Ret);
            }
            {
                MethodBuilder mb = tb.DefineMethod("unpin",
                                               MethodAttributes.Public |
                                                       MethodAttributes.Static,
                                               typeof(void),
                                               new[] { typeof(IntPtr) }
                                               );
                ILGenerator ilg = mb.GetILGenerator();
                ilg.DeclareLocal(typeof(GCHandle));
                ilg.Emit(OpCodes.Nop);
                ilg.Emit(OpCodes.Ldarg_0);
                ilg.Emit(OpCodes.Call, typeof(GCHandle).GetMethod("FromIntPtr"));
                ilg.Emit(OpCodes.Stloc_0);
                ilg.Emit(OpCodes.Ldloca_S,0);
                ilg.Emit(OpCodes.Call, typeof(GCHandle).GetMethod("Free"));
                ilg.Emit(OpCodes.Nop);
                ilg.Emit(OpCodes.Ret);
            }
        }
    
        static void emitArgs(ILGenerator ilg, Type[] tps)
        {
            if (tps.Length > 0)
                ilg.Emit(OpCodes.Ldarg_0);
            if (tps.Length > 1)
                ilg.Emit(OpCodes.Ldarg_1);
            if (tps.Length > 2)
                ilg.Emit(OpCodes.Ldarg_2);
            if (tps.Length > 3)
                ilg.Emit(OpCodes.Ldarg_3);
            for (byte x = 4; x < tps.Length; x++)
            {
                ilg.Emit(OpCodes.Ldarg_S, x);
            }
        }
    }
    



    The new version is:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Mono.Cecil;
    using Mono.Cecil.Cil;
    using Mono.CompilerServices.SymbolWriter;
    using System.Reflection;
    
    using System.IO;
    using System.Runtime.InteropServices;
    
    class CLRBuilder
    {
        Type t;          // reflected upon type
        TypeDefinition tb;  // type under construction
        string fname;    // name of file
        private string idir;
        ModuleDefinition md;
        CLRBuilder(string name, string aidir)
        {
            fname = name;
            idir = aidir;
        }
        static void Main(string[] args)
        {
            new CLRBuilder(args[0],args[1]).run();
        }
    
        void run()
        {
            writeHeader();
            var resolver = new DefaultAssemblyResolver();
            resolver.AddSearchDirectory(Directory.GetCurrentDirectory());
            md = ModuleDefinition.CreateModule(fname + "static",
                                                   new ModuleParameters { Kind = ModuleKind.Dll, AssemblyResolver = resolver });
    
            // To avoid duplicately adding types
            HashSet<String> visitedTypes = new HashSet<String>();
    
            foreach (Type _ in Assembly.LoadFile(Path.GetFullPath(Path.Combine(idir,fname + ".dll"))).GetExportedTypes())
            {
                t = _;
                //Assume that duplicates are identical (e.g. interface / class pairs)
                if (visitedTypes.Contains(t.Name)) continue;
                visitedTypes.Add(t.Name);
                writeAggHdr();
                tb = new TypeDefinition(t.Namespace,
                                        t.Name + "static",
                                        Mono.Cecil.TypeAttributes.Public,
                                        md.ImportReference(typeof(object)) /*base class*/);
                foreach (MemberInfo mi in t.GetMembers())
                {
                    if (mi.Name == "assert")
                        continue;
    
                    if (mi.MemberType == MemberTypes.Method)
                        addMethod((MethodInfo)mi);
                    else if (mi.MemberType == MemberTypes.Constructor)
                        addCtor((ConstructorInfo)mi);
                }
                md.Types.Add(tb);
            }
            md.Write(idir+ "/"+fname + "static.dll");
        }
    
        void addMethod(MethodInfo mi)
        {
            List<Type> tl = new List<Type>();
            //tl.Insert(0, t);
            tl.AddRange(mi.GetParameters().Select(p => p.ParameterType));
            Type[] tps = tl.ToArray();
            log("mi.Name = " + mi.Name);
            string methname;
            if (mi.Name == "ToString")
                methname = "toString";
            else if (mi.Name == "GetType")
            {
                return;
            }
            else methname = mi.Name;
    
            var mb = new MethodDefinition(methname,
                                       Mono.Cecil.MethodAttributes.Public |
                                           Mono.Cecil.MethodAttributes.Static,
                                       md.ImportReference(mi.ReturnType));
            mb.Parameters.Add(new ParameterDefinition(md.ImportReference(typeof(IntPtr))));
            foreach (Type _t in tps)
            {
                mb.Parameters.Add(new ParameterDefinition(md.ImportReference(_t)));
            }
            {
                var ilg = mb.Body.GetILProcessor();
                ilg.Emit(OpCodes.Nop);
                if (mi.IsStatic)
                {
                    emitArgs(ilg,tps);
                    ilg.Emit(OpCodes.Call, md.ImportReference(mi));
                }
                else
                {
                    newVar(mb, typeof(IntPtr));
                    newVar(mb, typeof(GCHandle));
                    newVar(mb, t);
                    md.ImportReference(typeof(GCHandle));
                    ilg.Emit(OpCodes.Ldarg_0);
                    
                    MethodReference mr = md.ImportReference(typeof(GCHandle).GetMethod("FromIntPtr", new[] {typeof(IntPtr)}));
                    ilg.Emit(OpCodes.Call, mr);
                    ilg.Emit(OpCodes.Stloc_0);
                    ilg.Emit(OpCodes.Ldloc_0);
                    ilg.Emit(OpCodes.Call, md.ImportReference(typeof(GCHandle).GetMethod("get_Target")));
                    ilg.Emit(OpCodes.Stloc_1);
                    ilg.Emit(OpCodes.Ldloc_1);
                    ilg.Emit(OpCodes.Castclass, md.ImportReference(t));
                    ilg.Emit(OpCodes.Stloc_2);
                    ilg.Emit(OpCodes.Ldloc_2);
                    for (byte x = 0; x < tps.Length; x++)
                    {
                        ilg.Emit(OpCodes.Ldarg_S, x);
                    }
                    ilg.Emit(t.IsSealed ? OpCodes.Call : OpCodes.Callvirt, md.ImportReference(mi));
                }
                ilg.Emit(OpCodes.Nop);
                ilg.Emit(OpCodes.Ret);
            }
            tb.Methods.Add(mb);
        }
    
        void addCtor(ConstructorInfo ci)
        {
            Type[] tps = ci.GetParameters().Select(p => p.ParameterType).ToArray();
            {
                log("here");
                var mb = new MethodDefinition("make",
                                           Mono.Cecil.MethodAttributes.Public |
                                           Mono.Cecil.MethodAttributes.Static,
                                           md.ImportReference(typeof(IntPtr)));
                tb.Methods.Add(mb);
                foreach (Type _t in tps)
                    mb.Parameters.Add(new ParameterDefinition(md.ImportReference(_t)));
    
                var ilg = mb.Body.GetILProcessor();
                newVar(mb, t);
                newVar(mb, typeof(GCHandle));
                newVar(mb, typeof(IntPtr));
                // Copy what ildasm says csc does modulo redundant direct branches
                ilg.Create(OpCodes.Nop);
    
                emitArgs(ilg, tps);
    
                MethodReference mr = md.ImportReference(ci);
                ilg.Emit(OpCodes.Newobj, mr);
    
                ilg.Emit(OpCodes.Stloc_0);
                ilg.Emit(OpCodes.Ldloc_0);
                ilg.Emit(OpCodes.Call, md.ImportReference(typeof(GCHandle).GetMethod("Alloc", new[] { typeof(Object) })));
    
                ilg.Emit(OpCodes.Stloc_1);
                ilg.Emit(OpCodes.Ldloc_1);
                ilg.Emit(OpCodes.Call, md.ImportReference(typeof(GCHandle).GetMethod("ToIntPtr")));
                ilg.Emit(OpCodes.Stloc_2);
                ilg.Emit(OpCodes.Ldloc_2);
                ilg.Emit(OpCodes.Ret);
            }
            {
                var mb2 = new MethodDefinition("unpin",
                                          Mono.Cecil.MethodAttributes.Public |
                                          Mono.Cecil.MethodAttributes.Static,
                                          md.ImportReference(typeof(void)));
                tb.Methods.Add(mb2);
                mb2.Parameters.Add(new ParameterDefinition(md.ImportReference(typeof(IntPtr))));
    
                var ilg2 = mb2.Body.GetILProcessor();
                newVar(mb2, typeof(GCHandle));
    
                ilg2.Emit(OpCodes.Nop);
                ilg2.Emit(OpCodes.Ldarg_0);
                ilg2.Emit(OpCodes.Call, md.ImportReference(typeof(GCHandle).GetMethod("FromIntPtr")));
                ilg2.Emit(OpCodes.Stloc_0);
                ilg2.Emit(OpCodes.Ldloca_S,mb2.Body.Variables[0]);
                ilg2.Emit(OpCodes.Call, md.ImportReference(typeof(GCHandle).GetMethod("Free")));
                ilg2.Emit(OpCodes.Nop);
                ilg2.Emit(OpCodes.Ret);
            }
        }
        void newVar(MethodDefinition mb,Type tt)
        {
            mb.Body.Variables.Add(new VariableDefinition(md.ImportReference(tt)));
        }
    
        static void emitArgs(ILProcessor ilg, Type[] tps)
        {
            if (tps.Length > 0)
                ilg.Emit(OpCodes.Ldarg_0);
            if (tps.Length > 1)
                ilg.Emit(OpCodes.Ldarg_1);
            if (tps.Length > 2)
                ilg.Emit(OpCodes.Ldarg_2);
            if (tps.Length > 3)
                ilg.Emit(OpCodes.Ldarg_3);
            for (byte x = 4; x < tps.Length; x++)
            {
                ilg.Emit(OpCodes.Ldarg_S, x);
            }
        }
    }

    The goal of the program in each case is given:

    public class Class1 { int a; public Class1(int aa) { a = aa; } } 

    it should generate

    class Class1static {

    public static IntPtr make(int a)

    {

    var ret = new Class1(a);

    GCHandle gch = GCHandle.Alloc(ret);

    return GCHandle.ToIntPtr(gch);

    }

    public static string toString(IntPtr pthis) {

    var gch = GCHandle.FromIntPtr(pthis);

    var targ = gch.Target;

    Class2 actual = (Class2)targ;

    return actual.ToString();

    }

    public static void unpin(IntPtr pthis) {

    GCHandle gch = GCHandle.FromIntPtr(pthis);

    gch.Free(); return;

    }

    }


    Then then generated dll is by a native application that calls coreclr_initialize and calls coreclr_create_delegate and calls it on make, ToString and unpin. make and unpin seem to work fine, ToString fails with InvalidProgramException. monodis shows sensible code with the reference test code, but I can't for the life of me get it to show anything useful, all I get is 

      .class public auto ansi Class1static
      	extends [System.Private.CoreLib]System.Object
      {
    
        // method line 1
        .method public static 
               default string toString (native int A_0)  cil managed 
        {
            // Method begins at RVA 0x2050
        } // end of method Class1static::toString
    
        // method line 2
        .method public static 
               default bool Equals (native int A_0, object A_1)  cil managed 
        {
            // Method begins at RVA 0x207c
        } // end of method Class1static::Equals
    
        // method line 3
        .method public static 
               default int32 GetHashCode (native int A_0)  cil managed 
        {
            // Method begins at RVA 0x20a8
        } // end of method Class1static::GetHashCode
    
        // method line 4
        .method public static 
               default native int make (int32 A_0)  cil managed 
        {
            // Method begins at RVA 0x20d4
        } // end of method Class1static::make
    
        // method line 5
        .method public static 
               default void unpin (native int A_0)  cil managed 
        {
            // Method begins at RVA 0x20f8
        } // end of method Class1static::unpin
    
      } // end of class Class1static
    


    I note that when I was generating obviously (in hindsight) invalid code (forgot to declare/reserve Variable) it did show the code. So I'm not sure what's going in there at all.

    All the output I get is 

    here 104AD11A0
    
    Unhandled Exception: System.InvalidProgramException: Common Language Runtime detected an invalid program.
       at Class1static.toString(IntPtr )
    Program exited with code -6

    The "here [hex]" is logging code in the native app printing the value of the IntPtr I get back from "make" (no it is not uninitialised).

    I have a number of questions:

    Is there a way to get more useful information from the crash dump?

    In the same vein, what flags can I pass to monodis to get the body of the generated functions (assuming I'm generating them?)?

    What do I need to do differently to generate valid code? (I'm assuming this is an IL generation problem and not something else?I have very little experience with .Net so I'm not sure.)

    Sorry for the rather lengthy question, but I'm completely stuck. For what is worth I'm on OSX64 10.13.5 running .net core 2.2

    Any and all help greatly appreciated.

    Thanks

    Nic

    Thursday, June 20, 2019 12:16 PM

All replies

  • I don't know. Have you considered using a tyr/catch and capturing the stacktrace which will give you the line number of the exception if you don't know what line in throwing the exception?
    Friday, June 21, 2019 2:58 AM
  • Hi Nicholas,

    Thank you for posting here.

    I noted that your new version code contains the Mono.Cecil, it is a third-party product. Therefore, I suggest that you could post it in the following link.

    https://csharpforums.net/forums/third-party-add-ins.26/

    The Visual C# forum discusses and asks questions about the C# programming language, IDE, libraries, samples, and tools.

    Note:This response contains a reference to a third party World Wide Web site. Microsoft is providing this information as a convenience to you. Microsoft does not control these sites and has not tested any software or information found on these sites; Therefore, Microsoft cannot make any representations regarding the quality, safety, or suitability of any software or information found there. There are inherent dangers in the use of any software found on the Internet, and Microsoft cautions you to make sure that you completely understand the risk before retrieving any software from the Internet.

    Best Regards,

    Jack


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Friday, June 21, 2019 3:17 AM