Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / MSIL

Automated MSIL/PE verification using ILSpy

5.00/5 (1 vote)
30 Sep 2013CPOL2 min read 16K   176  
Use ILSpy to perform a more comprehensive check on Emitted assembiles

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:

C#
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: 

Image 1

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:

C#
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: 

C#
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 

  • Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)