Sunday, March 27, 2011

Verifying dynamic methods

While tinkering with reflection emit, the biggest problem is that there is no way to verify a dynamic method easily. After getting frustrated with VerificationException for the millionth time I did some searching and all I found was to write the IL in an .il, compile it with ilasm.exe and then verify it with peverify. Another route was to create a complete dynamic assembly and save it, then verify that dll with peverify.

Needless to say, I didn't want to do that each time manually. I started looking for a wrapper for PEVerify and found AssemblyVerifier. Because PEVerify is written in unmanaged code, the only way was to parse the output from the command line and throw custom verification exceptions, not ideal but it works most of the time.

I had to alter the code a bit because it couldn't locate peverify (so I embedded the version from .NET 4.0.

Right, so now I could have an assembly and verify it in code, dynamic methods can't be saved to an assembly (as far as I know), so in order be able to verify that method I had to save the same method with an MethodBuilder to a dynamic assembly.

This meant defining the same method (return type, parameters, attributes,..) using an MethodBuilder and saving the emitted IL using the dynamicMethod.GetILGenerator() to an methodBuilder.GetILGenerator(). Sadly I couldn't find a way to just bulk copy the entire emitted part so I had to write a wrapper.

This wrapper wraps an ILGenerator (to present the same methods) and takes both the methodbuilder and dynamic method in its constructor. All methods that are in the ILGenerator are then delegated to both the methodbuilder and dynamic method. I used a dictionary to map the original labels and localbuilders that I return (in methods such as DefineLabel) to their methodbuilder equivalents.

This is probably more clear with this snippet:


// Map labels from dmGen to mbGen
private Dictionary<Label, Label> labelMapping = new Dictionary<Label, Label>();

/// <summary>
/// Defines a label on the dynamic method
/// </summary>
public Label DefineLabel()
{
Label lblDm = dmGen.DefineLabel();
Label lblMb = mbGen.DefineLabel();
labelMapping.Add(lblDm, lblMb);
return lblDm;
}


public void Emit(OpCode opcode, Label label)
{
mbGen.Emit(opcode, labelMapping[label]);
dmGen.Emit(opcode, label);
}



This allows me to write the exact same IL for a dynamic method as for a methodbuilder.

One annoyance is the private constructor of an ILGenerator. Because of that I couldn't inherit from an ILGenerator to create a true adapter pattern construct (and which also disallows you to generalize the wrapper in existing code to an ILGenerator).

The rest was pretty easy to get an Verifiable IL Generator from a dynamic method:
- Build dynamic assembly
- Build module
- Build dynamic type
- Build method for dynamic type using methodbuilder
- Create the wrapper with the newly constructed methodbuilder and dynamic method (and assembly and such needed to verify it)
- Return the wrapper which has the same methods as a normal IL Generator.

And after all the generator.Emit shenanigans is done I can call the generator.Verify() method which passes the assembly builder to the AssemblyVerifier.Verify method.

To make it even more useful, this method that encapsulated all that can be defined as an extension method on a DynamicMethod itself, which allows you to do the following:



// define dynamic method and stuff
// ...

VerifiableILGenerator generator = dymMethod.GetILVerifiableGenerator();

// .. emitting ho!

generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Ret);

// ...

// verify the emitted IL
generator.Verify();

// create delegate from dynamic method



Can't get much easier in usage than that.

There's only 1 slight problem with this approach: dynamic methods allow you to skip JIT visibility checks (using skipRestrictedVisibility=true, so you can access private members). However methodbuilder doesn't have a similar parameter and PEVerify will curse you to death with 'Field not visible' if you try to verify the IL. I did searching on how to allow this (or at least skip the checks in PEVerify), but couldn't really find an answer. Something with ReflectionPermissionFlags.MemberAccess came up but either I used it wrong or it doesn't help. If anyone has some insight in this, please let me know.

I've uploaded the altered source code for the AssemblyVerifier here for the moment. I've also contacted the original author so he could add it (if he wants to do so). Aside from that extra class I had to modify some stuff here and there to allow verification of dynamic assemblies. I also fixed a bug where the path of the assembly was constructed with new Uri(.. CodeBase) rather than EscapedCodeBase (which crapped out on my 'C# Projects' folder name.

However, if the above code ever dissappears or the author disallows my spreading an altered version, this is the full code for the extra class that allows verifying dynamic methods:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using System.Threading;
using System.Security.Permissions;

namespace AssemblyVerifier
{
public static class DynamicMethodVerification
{

private static AssemblyBuilder asmBuilder = null;
private static ModuleBuilder modBuilder = null;

/// <summary>
/// Returns an IL generator for the dynamic method which allows the emitted IL to be verified
/// </summary>
/// <param name="dm">The dynamic method to write the IL for</param>
/// <returns>IL generator for the dynamic method</returns>
public static VerifiableILGenerator GetILVerifiableGenerator(this DynamicMethod dm)
{
if (asmBuilder == null)
{
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "TestAssembly";
AppDomain thisDomain = Thread.GetDomain();
asmBuilder = thisDomain.DefineDynamicAssembly(assemblyName,
AssemblyBuilderAccess.RunAndSave);

ReflectionPermission p = new ReflectionPermission( ReflectionPermissionFlag.MemberAccess);

var caBuilder = new CustomAttributeBuilder(typeof(ReflectionPermission).GetConstructor(new Type[] { typeof(ReflectionPermissionFlag) }),
new object[] { ReflectionPermissionFlag.MemberAccess | ReflectionPermissionFlag.AllFlags });
asmBuilder.SetCustomAttribute(caBuilder);

modBuilder = asmBuilder.DefineDynamicModule(
asmBuilder.GetName().Name, "testdll.dll", false);
}

TypeBuilder tb = CreateType(modBuilder, "TestClass");


MethodBuilder mb = tb.DefineMethod("TestMethod",
MethodAttributes.Static | MethodAttributes.Public,
dm.ReturnType,
dm.GetParameters().Select(p => p.ParameterType).ToArray());
VerifiableILGenerator vilGen = new VerifiableILGenerator(asmBuilder, tb, mb, dm);

return vilGen;
}

private static TypeBuilder CreateType(ModuleBuilder modBuilder, string typeName)
{
TypeBuilder typeBuilder = modBuilder.DefineType(typeName,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
typeof(object));

return typeBuilder;
}


}

/// <summary>
/// Wrapper for IL generator. Delegates the methods to the generator for the dynamic method and
/// also to a method that will be built into an assembly, which in turn will be validated by PEVerify
/// </summary>
public class VerifiableILGenerator
{
private TypeBuilder tb;
private AssemblyBuilder asmBuilder;

private MethodBuilder mb;
private DynamicMethod dm;

private ILGenerator mbGen;
private ILGenerator dmGen;

internal VerifiableILGenerator(AssemblyBuilder asmBuilder, TypeBuilder tb, MethodBuilder mb, DynamicMethod dm)
{
this.mb = mb;
this.dm = dm;

this.tb = tb;
this.asmBuilder = asmBuilder;

mbGen = mb.GetILGenerator();
dmGen = dm.GetILGenerator();
}

public void BeginCatchBlock(Type exceptionType)
{
mbGen.BeginCatchBlock(exceptionType);
dmGen.BeginCatchBlock(exceptionType);
}

// Map labels from dmGen to mbGen
private Dictionary<Label, Label> labelMapping = new Dictionary<Label, Label>();

// Map localbuilder from dmGen to mbGen
private Dictionary<LocalBuilder, LocalBuilder> localBuilderMapping = new Dictionary<LocalBuilder, LocalBuilder>();

public void BeginExceptFilterBlock()
{
mbGen.BeginExceptFilterBlock();
dmGen.BeginExceptFilterBlock();
}

public Label BeginExceptionBlock()
{
Label lblDm = dmGen.BeginExceptionBlock();
Label lblMb = mbGen.BeginExceptionBlock();
labelMapping.Add(lblDm, lblMb);
return lblDm;
}

public void BeginFaultBlock()
{
mbGen.BeginFaultBlock();
dmGen.BeginFaultBlock();
}

public void BeginFinallyBlock()
{
mbGen.BeginFinallyBlock();
dmGen.BeginFinallyBlock();
}

public void BeginScope()
{
mbGen.BeginScope();
dmGen.BeginScope();
}

/// <summary>
/// Defines a label on the dynamic method
/// </summary>
/// <returns></returns>
public Label DefineLabel()
{
Label lblDm = dmGen.DefineLabel();
Label lblMb = mbGen.DefineLabel();
labelMapping.Add(lblDm, lblMb);
return lblDm;
}

public LocalBuilder DeclareLocal(Type localType)
{
LocalBuilder lbDm = dmGen.DeclareLocal(localType);
LocalBuilder lbMb = mbGen.DeclareLocal(localType);
localBuilderMapping.Add(lbDm, lbMb);
return lbDm;
}

public LocalBuilder DeclareLocal(Type localType, bool pinned)
{
LocalBuilder lbDm = dmGen.DeclareLocal(localType, pinned);
LocalBuilder lbMb = mbGen.DeclareLocal(localType, pinned);
localBuilderMapping.Add(lbDm, lbMb);
return lbDm;
}

public void Emit(OpCode opcode)
{
mbGen.Emit(opcode);
dmGen.Emit(opcode);
}

public void Emit(OpCode opcode, byte arg)
{
mbGen.Emit(opcode, arg);
dmGen.Emit(opcode, arg);
}

public void Emit(OpCode opcode, ConstructorInfo con)
{
mbGen.Emit(opcode, con);
dmGen.Emit(opcode, con);
}

public void Emit(OpCode opcode, double arg)
{
mbGen.Emit(opcode, arg);
dmGen.Emit(opcode, arg);
}

public void Emit(OpCode opcode, FieldInfo field)
{
mbGen.Emit(opcode, field);
dmGen.Emit(opcode, field);
}

public void Emit(OpCode opcode, float arg)
{
mbGen.Emit(opcode, arg);
dmGen.Emit(opcode, arg);
}

public void Emit(OpCode opcode, int arg)
{
mbGen.Emit(opcode, arg);
dmGen.Emit(opcode, arg);
}

public void Emit(OpCode opcode, Label label)
{
mbGen.Emit(opcode, labelMapping[label]);
dmGen.Emit(opcode, label);
}

public void Emit(OpCode opcode, Label[] labels)
{
mbGen.Emit(opcode, labels.Select(l => labelMapping[l]).ToArray());
dmGen.Emit(opcode, labels);
}

public void Emit(OpCode opcode, LocalBuilder local)
{
mbGen.Emit(opcode, localBuilderMapping[local]);
dmGen.Emit(opcode, local);
}

public void Emit(OpCode opcode, long arg)
{
mbGen.Emit(opcode, arg);
dmGen.Emit(opcode, arg);
}

public void Emit(OpCode opcode, MethodInfo meth)
{
mbGen.Emit(opcode, meth);
dmGen.Emit(opcode, meth);
}

public void Emit(OpCode opcode, short arg)
{
mbGen.Emit(opcode, arg);
dmGen.Emit(opcode, arg);
}

public void Emit(OpCode opcode, SignatureHelper signature)
{
mbGen.Emit(opcode, signature);
dmGen.Emit(opcode, signature);
}

public void Emit(OpCode opcode, string str)
{
mbGen.Emit(opcode, str);
dmGen.Emit(opcode, str);
}

public void Emit(OpCode opcode, Type cls)
{
mbGen.Emit(opcode, cls);
dmGen.Emit(opcode, cls);
}

public void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[] optionalParameterTypes)
{
mbGen.EmitCall(opcode, methodInfo, optionalParameterTypes);
dmGen.EmitCall(opcode, methodInfo, optionalParameterTypes);
}

public void EmitCalli(OpCode opcode, CallingConventions callingConvention, Type returnType, Type[] parameterTypes, Type[] optionalParameterTypes)
{
mbGen.EmitCalli(opcode, callingConvention, returnType, parameterTypes, optionalParameterTypes);
dmGen.EmitCalli(opcode, callingConvention, returnType, parameterTypes, optionalParameterTypes);
}

public void EmitCalli(OpCode opcode, System.Runtime.InteropServices.CallingConvention unmanagedCallConv, Type returnType, Type[] parameterTypes)
{
mbGen.EmitCalli(opcode, unmanagedCallConv, returnType, parameterTypes);
dmGen.EmitCalli(opcode, unmanagedCallConv, returnType, parameterTypes);
}

public void EmitWriteLine(FieldInfo fld)
{
mbGen.EmitWriteLine(fld);
dmGen.EmitWriteLine(fld);
}

public void EmitWriteLine(LocalBuilder localBuilder)
{
mbGen.EmitWriteLine(localBuilderMapping[localBuilder]);
dmGen.EmitWriteLine(localBuilder);
}

public void EmitWriteLine(string value)
{
mbGen.EmitWriteLine(value);
dmGen.EmitWriteLine(value);
}

public void EndExceptionBlock()
{
mbGen.EndExceptionBlock();
dmGen.EndExceptionBlock();
}

public void EndScope()
{
mbGen.EndScope();
dmGen.EndScope();
}

public int ILOffset
{
get
{
// mbGen.ILOffset should be the same
return dmGen.ILOffset;
}
}

public void MarkLabel(Label loc)
{
mbGen.MarkLabel(labelMapping[loc]);
dmGen.MarkLabel(loc);
}

public void MarkSequencePoint(System.Diagnostics.SymbolStore.ISymbolDocumentWriter document, int startLine, int startColumn, int endLine, int endColumn)
{
mbGen.MarkSequencePoint(document, startLine, startColumn, endLine, endColumn);
dmGen.MarkSequencePoint(document, startLine, startColumn, endLine, endColumn);
}

public void ThrowException(Type excType)
{
mbGen.ThrowException(excType);
dmGen.ThrowException(excType);
}

public void UsingNamespace(string usingNamespace)
{
mbGen.UsingNamespace(usingNamespace);
dmGen.UsingNamespace(usingNamespace);
}


/// <summary>
/// Verifies the generated IL from the dynamic method
/// </summary>
public void Verify()
{
Type t = tb.CreateType();

AssemblyVerifier.AssemblyVerification.Verify(asmBuilder);
}

}
}


Now to put it to good use :)

No comments:

Post a Comment