C# IL 代码修改 - 保持堆栈完整
C# IL code modification - keep stack intact
这个问题是关于自定义 C# IL 代码的静态堆栈分析以及如何设计操作码以满足编译器的要求。
我有代码可以通过将我自己的代码附加到它来修改现有的 C# 方法。为了避免在执行我的代码之前使用原始方法 returns,它将所有 RET 操作码替换为 BR endlabel 并添加该标签到原始代码的末尾。然后我在那里添加更多代码,最后添加一个 RET。
这一切在一般情况下都可以正常工作,但在某些方法上会失败。这是一个简单的例子:
public static string SomeMethod(int val)
{
switch (val)
{
case 0:
return "string1".convert();
case 1:
return "string2".convert();
case 2:
return "string3".convert();
// ...
}
return "";
}
用这个 IL 代码表示:
.method public hidebysig static string SomeMethod(int32 val) cil managed
{
.maxstack 1
.locals val ([0] int32 num)
L_0000: ldarg.0
L_0001: stloc.0
L_0002: ldloc.0
L_0003: switch (L_002e, L_004f, L_0044, ...)
L_002c: br.s L_0091
L_002e: ldstr "string1"
L_0033: call string Foo::convert(string)
L_0038: ret
L_0039: ldstr "string2"
L_003e: call string Foo::convert(string)
L_0043: ret
L_0044: ldstr "string3"
L_0049: call string Foo::convert(string)
L_004e: ret
...
L_0091: ldstr ""
L_0096: ret
}
经过我的程序修改后,代码是这样的:
.method public hidebysig static string SomeMethod(int32 val) cil managed
{
.maxstack 1
.locals val ([0] int32 num)
L_0000: ldarg.0
L_0001: stloc.0
L_0002: ldloc.0
L_0003: switch (L_002e, L_004f, L_0044, ...)
L_002c: br.s L_0091
L_002e: ldstr "string1"
L_0033: call string Foo::convert(string)
L_0038: br L_009b // was ret
L_0039: ldstr "string2"
L_003e: call string Foo::convert(string)
L_0043: br L_009b // was ret
L_0044: ldstr "string3"
L_0049: call string Foo::convert(string)
L_004e: br L_009b // was ret
...
L_0091: ldstr ""
L_0096: br L_009b // was ret
L_009b: my code here
...
L_0200: ret
}
我得到一个编译错误:
Could not execute post-long-event action. Exception:
System.TypeInitializationException: An exception was thrown by the
type initializer for FooBar ---> System.InvalidProgramException:
Invalid IL code in (wrapper dynamic-method) Foo:SomeMethod (int):
IL_0000: ldnull
是否有任何简单的方法以通用方式替换 RET 并让静态分析器满意?
问题原来是所有短跳转指令可能变得太远,因为插入 BR 而不是 RET 会增加操作码尺寸。
我通过用相应的跳远版本替换所有以“_S”结尾的操作码来解决它。有关此的更多详细信息,请查看对我的项目的提交:Fixed illegal short jumps
这个问题是关于自定义 C# IL 代码的静态堆栈分析以及如何设计操作码以满足编译器的要求。
我有代码可以通过将我自己的代码附加到它来修改现有的 C# 方法。为了避免在执行我的代码之前使用原始方法 returns,它将所有 RET 操作码替换为 BR endlabel 并添加该标签到原始代码的末尾。然后我在那里添加更多代码,最后添加一个 RET。
这一切在一般情况下都可以正常工作,但在某些方法上会失败。这是一个简单的例子:
public static string SomeMethod(int val)
{
switch (val)
{
case 0:
return "string1".convert();
case 1:
return "string2".convert();
case 2:
return "string3".convert();
// ...
}
return "";
}
用这个 IL 代码表示:
.method public hidebysig static string SomeMethod(int32 val) cil managed
{
.maxstack 1
.locals val ([0] int32 num)
L_0000: ldarg.0
L_0001: stloc.0
L_0002: ldloc.0
L_0003: switch (L_002e, L_004f, L_0044, ...)
L_002c: br.s L_0091
L_002e: ldstr "string1"
L_0033: call string Foo::convert(string)
L_0038: ret
L_0039: ldstr "string2"
L_003e: call string Foo::convert(string)
L_0043: ret
L_0044: ldstr "string3"
L_0049: call string Foo::convert(string)
L_004e: ret
...
L_0091: ldstr ""
L_0096: ret
}
经过我的程序修改后,代码是这样的:
.method public hidebysig static string SomeMethod(int32 val) cil managed
{
.maxstack 1
.locals val ([0] int32 num)
L_0000: ldarg.0
L_0001: stloc.0
L_0002: ldloc.0
L_0003: switch (L_002e, L_004f, L_0044, ...)
L_002c: br.s L_0091
L_002e: ldstr "string1"
L_0033: call string Foo::convert(string)
L_0038: br L_009b // was ret
L_0039: ldstr "string2"
L_003e: call string Foo::convert(string)
L_0043: br L_009b // was ret
L_0044: ldstr "string3"
L_0049: call string Foo::convert(string)
L_004e: br L_009b // was ret
...
L_0091: ldstr ""
L_0096: br L_009b // was ret
L_009b: my code here
...
L_0200: ret
}
我得到一个编译错误:
Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for FooBar ---> System.InvalidProgramException: Invalid IL code in (wrapper dynamic-method) Foo:SomeMethod (int): IL_0000: ldnull
是否有任何简单的方法以通用方式替换 RET 并让静态分析器满意?
问题原来是所有短跳转指令可能变得太远,因为插入 BR 而不是 RET 会增加操作码尺寸。
我通过用相应的跳远版本替换所有以“_S”结尾的操作码来解决它。有关此的更多详细信息,请查看对我的项目的提交:Fixed illegal short jumps