Introduction
There may be many reasons why you would want to
emit code, in .NET, But the first thing you will find tricky is debugging, since
the generation aspect of the code is also under question when some functionality
does not work. The best strategy here is to develop in small steps and a good
test rig.
Checking IL generation will help eliminate a lot of uncertainty in the generated code
and "program invalid exceptions". This article will show 2 methods on how to
check the validity of the generated code.
Background
One of the first things that should be setup is PEVerify to check the validity of
the generated assembly. PEVerify is distributed via the Windows SDK, so can
easily be launched with the following:
var verifierPath = Path.Combine(
ToolLocationHelper.GetPathToDotNetFrameworkSdk(TargetDotNetFrameworkVersion.Version40),
@"bin\NETFX 4.0 Tools\peverify.exe");
var args = string.Format("\"{0}\" /verbose /nologo /hresult", modulePath);
var process = new Process
{
StartInfo =
{
CreateNoWindow = true,
FileName = verifierPath,
RedirectStandardOutput = true,
UseShellExecute = false,
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
Arguments = args
}
};
process.Start();
Note that ToolLocationHelper
is defined in the Microsoft.Build.Utilities.v4.0
assembly. Although PEVerify found many errors, there was one case where an
inconsistent stack size error was not detected:
The
error in the above generated IL is the un-used return value from ListLength() which was not used, and should have been popped from the stack
Dumping
the IL to a DLL and opening it ILSpy gave the following exception:
ICSharpCode.Decompiler.DecompilerException: Error decompiling
System.Void TestObjSerializer::DeserializeTestObj(Callin.Global,System.Object)
---> System.Exception: Inconsistent stack size at IL_6D
at ICSharpCode.Decompiler.ILAst.ILAstBuilder.StackAnalysis(MethodDefinition methodDef)
at ICSharpCode.Decompiler.ILAst.ILAstBuilder.Build(MethodDefinition
methodDef, Boolean optimize, DecompilerContext context)
at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(IEnumerable`1 parameters)
at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(
MethodDefinition methodDef, DecompilerContext context, IEnumerable`1 parameters)
--- End of inner exception stack trace ---
at ICSharpCode.Decompiler.Ast.AstMethodBodyBuilder.CreateMethodBody(
MethodDefinition methodDef, DecompilerContext context, IEnumerable`1 parameters)
at ICSharpCode.Decompiler.Ast.AstBuilder.CreateMethod(MethodDefinition methodDef)
at ICSharpCode.Decompiler.Ast.AstBuilder.AddMethod(MethodDefinition method)
at ICSharpCode.ILSpy.CSharpLanguage.DecompileMethod(
MethodDefinition method, ITextOutput output, DecompilationOptions options)
at ICSharpCode.ILSpy.TextView.DecompilerTextView.DecompileNodes(
DecompilationContext context, ITextOutput textOutput)
at ICSharpCode.ILSpy.TextView.DecompilerTextView.<>c__DisplayClass17.<DecompileAsync>b__16()
The
exception indicates that the decompiler got confused processing the stack at
instruction 6D which is where PEVerify would have got confused if it found the
error.
Using the code
Adding ILSpy's decompiler to a test rig is a matter of
collecting the right DLLs and passing the MSIL instructions to the AstBuilder
class.
The attached solution shows the list of assemblies which
are required for decompiling the IL code into C# (Which performs the
validation):
- ICSharpCode.Decompiler.dll
- ICSharpCode.NRefactory.CSharp.dll
- ICSharpCode.NRefactory.dll
- Mono.Cecil.dll
- Mono.Cecil.Mdb.dll
- Mono.Cecil.Pdb.dll
- Mono.Cecil.Rocks.dll
Only
ICSharpCode.Decompiler.dll and Mono.Cecil.dll need to be referenced from the test project.
To
pass the generated IL Code to the validation you need to first load the method
using Mono.Cecil:
var assemblyDefinition =
AssemblyDefinition.ReadAssembly(dllPath, new ReaderParameters(ReadingMode.Immediate));
var typeDef = assemblyDefinition.MainModule.GetType(string.Empty, "EmitClass");
var method = typeDef.Methods.First(m => m.Name == "EmitCode");
And
adding it to the AstBuilder:
var astBuilder =
new AstBuilder(new DecompilerContext(method.Module) { CurrentType = method.DeclaringType });
astBuilder.AddMethod(method);
If
there is any problem with MSIL, an exception similar to the one shown above
will be thrown.
Points of Interest
It
is interesting to note that ILSpy decompiler finds more issues than PEVerify,
but I suppose that this is because ILSpy needs to be more strict to decompile and actually
show the MSIL as C#.
ILSpy distributable and source can be found at http://ilspy.net/
History