【问题标题】:Rewrite IL to inject try-finally around method call重写 IL 以在方法调用周围注入 try-finally
【发布时间】:2015-12-07 12:55:31
【问题描述】:

我想将 sql 日志记录注入到一些方法中。基本上我想改造

    public static object IDbCommandTest_ExecuteScalar(IDbCommand command)
    {
        // .. do stuff
        command.CommandText = "SELECT ...";
        var obj = command.ExecuteScalar();
        Console.WriteLine("SQL returned " + obj);
        // do other stuff
        return obj;
    }

进入

    public static object IDbCommandTest_ExecuteScalar_Transformed(IDbCommand command)
    {
        // .. do stuff
        command.CommandText = "SELECT ...";
        object obj;
        using (SqlLogger.Enter(command, "IDbCommand", "ExecuteScalar"))
        {
            obj = command.ExecuteScalar();
        }
        Console.WriteLine("SQL returned " + obj);
        // do other stuff
        return obj;
    }

我得到了简单的案例,我目前面临的问题是输入.try 时堆栈必须为空。

我最初的假设是 IDbCommand 本身是在调用 ExecuteScalar 之前直接加载的,所以我搜索 callvirt 然后使用 Instruction.Previous 作为 .try 的开头。

但是如果后面使用ExecuteScalar的返回值,编译器会生成如下IL:

    IL_004d: ldarg.0
    IL_004e: ldloc.1
    IL_004f: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
    IL_0054: call string Class_which_uses_obj::DoStuff(object)

使用我的原始算法,将其转换为

    IL_0050: ldarg.0
    .try
    {
        IL_0051: ldloc.1
        IL_0052: dup
        IL_0053: ldstr "IDbCommand"
        IL_0058: ldstr "get_FileName"
        IL_005d: call class [mscorlib]System.IDisposable SqlLogger::Enter(class [System.Data]System.Data.IDbCommand, string, string)
        IL_0062: stloc.3
        IL_0063: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
        IL_0068: stloc.s 4
        IL_006a: leave.s IL_0077
    } // end .try
    finally
    {
        IL_006c: nop
        IL_006d: ldloc.3
        IL_006e: brfalse.s IL_0076

        IL_0070: ldloc.3
        IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()

        IL_0076: endfinally
    } // end handler

    IL_0077: nop
    IL_0078: ldloc.s 4
    IL_007a: call string        IL_0050: ldarg.0
    .try
    {
        IL_0051: ldloc.1
        IL_0052: dup
        IL_0053: ldstr "IDbCommand"
        IL_0058: ldstr "ExecuteScalar"
        IL_005d: call class [mscorlib]System.IDisposable Nemetschek.Allready.SqlLogger::Enter(class [System.Data]System.Data.IDbCommand, string, string)
        IL_0062: stloc.3
        IL_0063: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
        IL_0068: stloc.s 4
        IL_006a: leave.s IL_0077
    } // end .try
    finally
    {
        IL_006c: nop
        IL_006d: ldloc.3
        IL_006e: brfalse.s IL_0076

        IL_0070: ldloc.3
        IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()

        IL_0076: endfinally
    } // end handler

    IL_0077: nop
    IL_0078: ldloc.s 4
    IL_007a: call string Nemetschek.Allready.Logistics.DbTools.CDbTools::GetSafeStringEmpty(object)(object)

然后 PEVERIFY 抱怨我输入了一个非空堆栈的 .try。

是否有一种简单的方法可以将 try-finally 注入到 ExecuteScalar 上,还是我需要对整个方法进行完整的流程分析,计算任意点的堆栈深度,然后加载/恢复之前/之后的值尝试/最后?

编辑:

我已经能够通过向上/向下扫描来使其工作,直到找到堆栈深度为 0 的两个点。在我有限的测试中,这似乎有效,但我仍然对“干净”感兴趣实现而不是盲目地扫描 IL。

【问题讨论】:

    标签: c# .net il mono.cecil fody


    【解决方案1】:

    我会将对ExecuteScalar 的调用重写为对辅助方法的调用:

    static object ExecuteScalarWrapper(SqlCommand command, string logString) {
            using (SqlLogger.Enter(command, logString))
            {
                return command.ExecuteScalar();
            }
    }
    

    ExecuteScalarWrapper 将是一个静态辅助方法,您可以用 C# 编写并引用它。

    然后,您不需要注入任何 try 块。你只需要替换模式

    ld... command
    call ExecuteScalar
    

    ld... command
    ld... logString
    call ExecuteScalarWrapper
    

    这应该更容易,因为堆栈布局和修改是本地定义的,不需要任何复杂的推理。

    现在 JIT 为您完成所有繁重的工作。

    【讨论】:

      猜你喜欢
      • 2012-09-27
      • 1970-01-01
      • 2018-06-15
      • 1970-01-01
      • 1970-01-01
      • 2021-05-02
      • 1970-01-01
      • 2013-12-16
      • 2011-06-17
      相关资源
      最近更新 更多