我有以下一段简化的 CIL 代码。
执行此 CIL 方法时, CLR 将抛出InvalidProgramException:
.method assembly hidebysig specialname rtspecialname
instance void .ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class System.Windows.Input.StylusDeviceBase> styluses) cil managed
{
.locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<class System.Windows.Input.StylusDeviceBase> V_0,
class System.Windows.Input.StylusDeviceBase V_1)
ldc.i4.8 // These instructions cause CIL to break
conv.u //
localloc //
pop //
ldarg.0
newobj instance void class [mscorlib]System.Collections.Generic.List`1<class System.Windows.Input.StylusDevice>::.ctor()
call instance void class [mscorlib]System.Collections.ObjectModel.ReadOnlyCollection`1<class System.Windows.Input.StylusDevice>::.ctor(class [mscorlib]System.Collections.Generic.IList`1<!0>)
ldarg.1
callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<class System.Windows.Input.StylusDeviceBase>::GetEnumerator()
stloc.0
.try
{
leave.s IL_0040
}
finally
{
endfinally
}
IL_0040: ret
} // end of method StylusDeviceCollection::.ctor
我的问题是,为什么这个 CIL 代码无效?
几个观察:
- 如果localloc
被删除,代码运行良好。据我所知,localloc
用地址替换堆栈上的参数大小,因此堆栈保持平衡,AFAICT。
- 如果 try 和 finally 块被删除,代码运行良好。
- 如果包含的第一个指令块localloc
移动到 try-finally 块之后,则代码运行良好。
所以它看起来像是 localloc 和 try-finally 的结合。
一些背景:
由于在运行时进行了一些检测,在为原始方法引发InvalidProgramException之后,我到了这一点。我的调试方法是:
- 反汇编有问题的 DLL
ildasm
- 将检测代码应用于崩溃方法
- 使用修改后的 IL 重新创建 DLL
ilasm
- 再次运行程序,并验证它是否崩溃
- 逐渐减少崩溃方法的 IL 代码,直到导致问题的最小场景(并尽量不要在此过程中引入错误......)
不幸的是,peverify.exe /IL
没有指出任何错误。我试图安慰 ECMA 规范和 Serge Lidin 的 Expert .NET IL 书,但不知道出了什么问题。
我缺少一些基本的东西吗?
编辑:
我稍微更新了有问题的 IL 代码,使其更加完整(无需修改说明)。第二个指令块,包括ldarg
、newobj
等,是从工作代码——原始方法代码中提取的。
对我来说奇怪的是,通过删除localloc
or .try
- finally
,代码可以工作 - 但据我所知,与它们是否存在于代码中相比,这些都不应该改变堆栈的平衡。
这是使用 ILSpy 反编译成 C# 的 IL 代码:
internal unsafe StylusDeviceCollection(IEnumerable<StylusDeviceBase> styluses)
{
IntPtr arg_04_0 = stackalloc byte[(UIntPtr)8];
base..ctor(new List<StylusDevice>());
IEnumerator<StylusDeviceBase> enumerator = styluses.GetEnumerator();
try
{
}
finally
{
}
}
编辑2:
更多观察:
- 获取localloc
IL 代码块,并将其移动到函数的末尾,代码运行良好 - 所以看起来代码本身是可以的。
- 将类似的 IL 代码粘贴到 hello world 测试函数中时,该问题不会重现。
我很纳闷...
我希望有一种方法可以从InvalidProgramException获取更多信息。似乎 CLR 没有将确切的失败原因附加到异常对象。我还考虑过使用 CoreCLR 调试版本进行调试,但不幸的是我正在调试的程序与它不兼容......