Monday, August 31, 2009

IL Generation, Optomizations, Debuggers and AccessViolationExceptions

Recently I have been working with some code that dynamically generates IL. Before we get into the guts of the issue I was running into let me say that yes I know that generating IL on the fly is a terrible idea, it not something that I like doing. But in this case it is necessary.

We'll start with the code:

namespace TestApplication1

{

    class Program

    {

        static void Main()

        {

            string methodName = "TestMethod";

 

            AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(

                new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);

 

            ModuleBuilder moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule");

            TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType");

 

            MethodBuilder method = typeBuilder.DefineMethod(methodName, MethodAttributes.Public);

 

            ILGenerator il = method.GetILGenerator();

 

            il.Emit(OpCodes.Newobj, typeof(TestClass).GetConstructor(Type.EmptyTypes));

 

            il.Emit(OpCodes.Callvirt, typeof(TestClass).GetMethod("StaticFunction", BindingFlags.Public | BindingFlags.Static));

 

            il.Emit(OpCodes.Pop);

 

            il.Emit(OpCodes.Ret);

 

 

            Type dynamicType = typeBuilder.CreateType();

 

            ConstructorInfo constructor = dynamicType.GetConstructor(Type.EmptyTypes);

 

 

            object o = constructor.Invoke(null);

            o.GetType().GetMethod(methodName).Invoke(o,null);

        }

    }

 

    public class TestClass

    {

        public static void StaticFunction()

        {

            Console.WriteLine("Static Hi");

        }

    }

}



Go ahead, paste into Visual Studio and hit F5, runs fine, StaticFunction is called, just like you would expect it to be.

Now instead of hitting F5 run it via Ctrl+F5:
System.Reflection.TargetInvocationException was unhandled
  Message="Exception has been thrown by the target of an invocation."
  Source="mscorlib"
  StackTrace:
       at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
       at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
       at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
       at TestApplication1.Program.Main() in C:\Documents and Settings\bwillard\Desktop\TestApplication1\Program.cs:line 38
  InnerException: System.AccessViolationException
       Message="Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
       Source="DynamicAssembly"
       StackTrace:
              at DynamicType.TestMethod()
       InnerException:


What the heck? The same code ran, it should have yielded the same results. Well it turns out that running with a debugger attached causes the code that is generated not to be optimized (this is independent of weather you compiled with the optimize code flag or not). It is the same as if you had put the following code in the previous sample:

Type debuggableAttribute = typeof(DebuggableAttribute);

ConstructorInfo daCtor = debuggableAttribute.GetConstructor(new[] { typeof(DebuggableAttribute.DebuggingModes) });

CustomAttributeBuilder daBuilder = new CustomAttributeBuilder(daCtor, new object[]

                                                                          { DebuggableAttribute.DebuggingModes.DisableOptimizations |

                                                                            DebuggableAttribute.DebuggingModes.Default});

asmBuilder.SetCustomAttribute(daBuilder);



Mystery #1 solved, but why doesn't the code work when not run in a debugger. This is a fairly straight forward bug. You can't Callvirt on a static method, the line should read:

il.Emit(OpCodes.Call, method);



Took me a while to figure those two things out.

I'll leave you with one more bonus tip about emiting code. If you are getting the exception:

System.Reflection.TargetInvocationException was unhandled
  Message="Exception has been thrown by the target of an invocation."
  Source="mscorlib"
  StackTrace:
       at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
       at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
       at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
       at TestApplication1.Program.Main() in C:\Documents and Settings\bwillard\Desktop\TestApplication1\Program.cs:line 49
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.InvalidProgramException
       Message="JIT Compiler encountered an internal limitation."
       Source="DynamicAssembly"
       StackTrace:
            at DynamicType.TestMethod()
       InnerException: 


What it means in this case is that there is an extra object on the stack when the function returned. That is why I need the last "pop" in my sample. Because I am calling a static function it doesn't consume the TestClass object that I created and that is now at the top of the stack.

Friday, August 28, 2009

Adding Search Paths to a Domain

Recently I was trying to load an assembly, call it assembly A, into my AppDomain and execute code in the assembly. This is usualy pretty easy thing to do, the problem comes from the fact that assembly A depends on another assembly B that is in the same directory as A.


If I was creating a new AppDomain this would be easy I could just add the search path to the AppDomainSetup when I create the domain. But in this case I wanted everything to be in one app domain so I couldn't do that.


The solution (that I found, there might be an easier way in which case please let me know) is to leverage closures and the AssemblyResolve event:



public static void LoadPackage(string dllPath, string className)
{
    string dllFullPath = Path.GetFullPath(dllPath);
    var assembly = Assembly.LoadFile(dllFullPath);
    var package = assembly.CreateInstance(className);
 
    if (null == package)
    {
        throw new Exception("Couldn't load type " + className + " from " + dllPath);
    }
 
    var asembliesInDirectory = new Dictionary<string, string>();
 
    foreach (string file in Directory.GetFiles(Path.GetDirectoryName(dllFullPath), "*.dll"))
    {
        try
        {
            asembliesInDirectory.Add(AssemblyName.GetAssemblyName(file).FullName, file);
        }
        catch{}
    }
 
    AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
    {
        if (!asembliesInDirectory.ContainsKey(eventArgs.Name))
        {
         return null;
        }
        return Assembly.LoadFile(asembliesInDirectory[eventArgs.Name]);
    };
}



The AssemblyResolve event gets called when the CLR find an assembly it needs to load. Handling this event lets you add your own assembly resolving logic into the process. This code will check the directory you loaded your first dll from any time the CLR can't find a dll. The reason for the dictionary is purely for performance, it is expensive to scan the directory every time you are trying to load something. So this just pre-caches all the assembly names for easy lookup.