【问题标题】:Add a try-catch with Mono Cecil使用 Mono Cecil 添加 try-catch
【发布时间】:2012-06-19 22:20:50
【问题描述】:

我正在使用 Mono Cecil 在另一个方法中注入代码。我想在我的代码周围添加一个 Try-Catch 块。

所以我写了一个带有 try catch 块的 HelloWorld.exe 并反编译了它。

Try-Catch 的 Reflector 中看起来像这样:

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

如何通过 mono cecil 注入这样的 try catch?

【问题讨论】:

    标签: c# mono.cecil


    【解决方案1】:

    使用 Mono.Cecil 添加异常处理程序并不难,它只需要您知道异常处理程序在元数据中的布局方式。

    假设你有 C# 方法:

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

    如果你反编译它,它应该看起来有点像这样:

    .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 
    }
    

    现在假设你想在这个方法中注入代码,比如它类似于 C# 代码:

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

    也就是说,您只想将现有代码包装在 try catch 处理程序中。您可以通过 Cecil 轻松做到这一点:

        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);
    

    这段代码将之前的方法操作成如下所示:

    .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 
    }
    

    我们添加了三个新指令:对 Console.WriteLine 的调用、优雅地退出 catch 处理程序的离开,以及最后(双关语),一个 ret。然后我们简单地创建一个 ExceptionHandler 实例来表示一个 try catch 处理程序,它的 try 包含现有的主体,并且它的 catch 是 WriteLine 语句。

    需要注意的重要一点是范围的结束指令不包含在范围内。它基本上是一个 [TryStart:TryEnd[ 范围。

    【讨论】:

    • 不允许控制流从这样的 catch 处理程序中掉出来。 ECMA-335, §12.4.2.8.1 “退出受保护的块、过滤器或处理程序不能通过失败完成。” (尽管 Microsoft CLR 似乎没有强制执行此规则)
    • @Daniel,很好,让我添加缺少的假期。感谢您的提醒。
    • 感谢您的快速答复!工作正常 - 如此简单!非常感谢!
    • 我收到一个 InvalidProgramException,当使用 PEVerify 检查它失败并显示“Return out of try block”时,它已 ret 作为 try 中的最后一条 IL 指令,我必须将其更改为“离开LABEL”让它工作。
    • @wliao 显然这只是一个例子,而不是一个完整的解决方案。您必须将 IL 调整为有效,这包括将受保护块中的 ret 指令替换为其他内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-18
    • 2021-07-05
    • 2021-06-20
    • 1970-01-01
    • 2012-11-25
    相关资源
    最近更新 更多