Search code examples
c#mono.cecil

Add a try-catch with Mono Cecil


I am using Mono Cecil to inject Code in another Method. I want to add a Try-Catch block around my code.

So i wrote a HelloWorld.exe with a try catch block and decompiled it.

It looks like this in Reflector for the Try-Catch:

.try L_0001 to L_0036 catch [mscorlib]System.Exception handler L_0036 to L_003b

How can i inject a try catch like this via mono cecil?


Solution

  • Adding exception handlers with Mono.Cecil is not difficult, it just requires you to know how exception handlers are laid out in the metadata.

    Let say you have the C# method:

    static void Throw ()
    {
        throw new Exception ("oups");
    }
    

    If you decompile it, it should look somewhat similar to this:

    .method private static hidebysig default void Throw ()  cil managed 
    {
        IL_0000:  ldstr "oups"
        IL_0005:  newobj instance void class [mscorlib]System.Exception::.ctor(string)
        IL_000a:  throw 
    }
    

    Now let say that you want to inject code in this method such as it's similar to the C# code:

    static void Throw ()
    {
        try {
            throw new Exception ("oups");
        } catch (Exception e) {
            Console.WriteLine (e);
        }
    }
    

    That is, you simply want to wrap the existing code in a try catch handler. You can do it easily with Cecil this way:

        var method = ...;
        var il = method.Body.GetILProcessor ();
    
        var write = il.Create (
            OpCodes.Call,
            module.Import (typeof (Console).GetMethod ("WriteLine", new [] { typeof (object)})));
        var ret = il.Create (OpCodes.Ret);
        var leave = il.Create (OpCodes.Leave, ret);
    
        il.InsertAfter (
            method.Body.Instructions.Last (), 
            write);
    
        il.InsertAfter (write, leave);
        il.InsertAfter (leave, ret);
    
        var handler = new ExceptionHandler (ExceptionHandlerType.Catch) {
            TryStart = method.Body.Instructions.First (),
            TryEnd = write,
            HandlerStart = write,
            HandlerEnd = ret,
            CatchType = module.Import (typeof (Exception)),
        };
    
        method.Body.ExceptionHandlers.Add (handler);
    

    This code is manipulating the previous method to look like this:

    .method private static hidebysig default void Throw ()  cil managed 
    {
        .maxstack 1
        .try { // 0
          IL_0000:  ldstr "oups"
          IL_0005:  newobj instance void class [mscorlib]System.Exception::'.ctor'(string)
          IL_000a:  throw 
        } // end .try 0
        catch class [mscorlib]System.Exception { // 0
          IL_000b:  call void class [mscorlib]System.Console::WriteLine(object)
          IL_0010:  leave IL_0015
        } // end handler 0
        IL_0015:  ret 
    }
    

    We're adding three new instructions: a call to Console.WriteLine, a leave to gracefully exit the catch handler, and finally (pun intended), a ret. Then we're simply creating a ExceptionHandler instance to represent a try catch handler whose try encompasses the existing body, and whose catch is the WriteLine statement.

    One important thing to note is that the end instruction of a range is not contained inside the range. It's basically a [TryStart:TryEnd[ range.