Monday, May 31, 2010

CLR: Verification for Runtime Code Generation

The CLR's lightweight code generation via DynamicMethod is pretty useful, but it's sometimes difficult to debug the generated code and ensure that it verifies. In order to verify generated code, you must save the dynamic assembly to disk and run the peverify.exe tool on it, but DynamicMethod does not have any means to do so. In order to save the assembly, there's a more laborious process of creating dynamic assemblies, modules and types, and then finally adding a method to said type.

This is further complicated by the fact that a MethodBuilder and DynamicMethod don't share any common interfaces or base types for generating IL, despite both of them supporting a GetILGenerator() method.

This difficulty in switching between saved codegen and pure runtime codegen led me to add a CodeGen class to Sasa, which can generate code for either case based on a bool parameter. Since no common interface is available for code generation, it also accepts a delegate to which it dispatches for generating the code:
/// <summary>
/// Create a dynamic method.
/// </summary>
/// <typeparam name="T">The type of the dynamic method to create.</typeparam>
/// <param name="type">The type to which this delegate should be a member.</param>
/// <param name="methodName">The name of the delegate's method.</param>
/// <param name="attributes">The method attributes.</param>
/// <param name="saveAssembly">Flag indicating whether the generated code should be saved to a dll.</param>
/// <param name="generate">A call back that performs the code generation.</param>
/// <returns>An dynamically created instance of the given delegate type.</returns>
public static T Function<T>(
Type type,
string methodName,
MethodAttributes attributes,
bool saveAssembly,
Action<ILGenerator> generate)
where T : TypeConstraint<Delegate>;

You can also see Sasa's ilrewrite tool at work here with the T : TypeConstraint<Delegate>. This function will generate either a DynamicMethod or a dynamic assembly and save that assembly to disk, based on the 'saveAssembly' parameter. The assembly name is generated based on the type and methodName parameters.

In debugging the Sasa.Dynamics reflection code, I also came across a strange error which was not adequately explained anywhere that I could find. peverify.exe spit out an error to the effect of:
[X.dll : Y/Z][offset 0x0000001D] Unable to resolve token

Where X is the name of the generated dll, Y the namespace path, and Z is the class name. In my case, this occurred when the dynamically generated code was referencing a private class, which should not be possible from a separate dll.

1 comment:

Qwertie said...

Ahh, this is nice. I thought it was insane, even a bug, that my dynamic methods were not verified, so I filed a bug report: http://connect.microsoft.com/VisualStudio/feedback/details/332363/dynamic-methods-cannot-be-error-checked-lcg-lightweight-codegen

But they decided not to offer a verification option and AFAIK didn't even bother to document anywhere that dynamic methods are unverified in fulltrust environments.