Debugging a System.InvalidProgramException from generated IL

  • Thread starter Thread starter Nicholas Wilson ---
  • Start date Start date
N

Nicholas Wilson ---

Guest
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

Continue reading...
 
Back
Top