重写 IL 以在方法调用周围注入 try-finally
Rewrite IL to inject try-finally around method call
我想将 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
作为 [=] 的开始14=].
但是如果之后使用ExecuteScalar
的return值,编译器生成如下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。
有没有简单的方法通过 ExecuteScalar 注入 try-finally,或者我是否需要对整个方法进行完整的流程分析,计算任意点的堆栈深度,然后 loading/restoring 值before/after try/finally?
编辑:
我已经能够通过扫描 up/down 来让它工作,直到我找到堆栈深度为 0 的两个点。在我有限的测试中,这似乎有效,但我仍然会对'clean' 实现而不是盲目扫描 IL。
我会将对 ExecuteScalar
的调用重写为对辅助方法的调用:
static object ExecuteScalarWrapper(SqlCommand command, string logString) {
using (SqlLogger.Enter(command, logString))
{
return command.ExecuteScalar();
}
}
ExecuteScalarWrapper
将是一个静态辅助方法,您可以用 C# 编写并引用。
然后,您不需要注入任何 try-blocks。你只需要更换模式
ld... command
call ExecuteScalar
和
ld... command
ld... logString
call ExecuteScalarWrapper
这应该更容易,因为堆栈布局和修改是本地定义的,不需要任何复杂的推理。
现在 JIT 会为您完成所有繁重的工作。
我想将 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
作为 [=] 的开始14=].
但是如果之后使用ExecuteScalar
的return值,编译器生成如下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。
有没有简单的方法通过 ExecuteScalar 注入 try-finally,或者我是否需要对整个方法进行完整的流程分析,计算任意点的堆栈深度,然后 loading/restoring 值before/after try/finally?
编辑:
我已经能够通过扫描 up/down 来让它工作,直到我找到堆栈深度为 0 的两个点。在我有限的测试中,这似乎有效,但我仍然会对'clean' 实现而不是盲目扫描 IL。
我会将对 ExecuteScalar
的调用重写为对辅助方法的调用:
static object ExecuteScalarWrapper(SqlCommand command, string logString) {
using (SqlLogger.Enter(command, logString))
{
return command.ExecuteScalar();
}
}
ExecuteScalarWrapper
将是一个静态辅助方法,您可以用 C# 编写并引用。
然后,您不需要注入任何 try-blocks。你只需要更换模式
ld... command
call ExecuteScalar
和
ld... command
ld... logString
call ExecuteScalarWrapper
这应该更容易,因为堆栈布局和修改是本地定义的,不需要任何复杂的推理。
现在 JIT 会为您完成所有繁重的工作。