为什么 localloc 会破坏这个 CIL 方法?
Why does localloc break this CIL method?
我有以下一段简化的 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 为原始方法抛出之后达到了这一点,这是由于在运行时进行的一些检测。我的调试方法是:
- 使用
ildasm
反汇编错误的 DLL
- 将检测代码应用于崩溃方法
- 使用
ilasm
从修改后的 IL 重新创建 DLL
- 运行 程序再次运行,验证崩溃
- 逐渐减少崩溃方法的 IL 代码,减少到导致问题的最小场景(并尽量不在此过程中引入错误...)
遗憾的是,peverify.exe /IL
没有指出任何错误。
我试图对照 ECMA 规范和 Serge Lidin 的专家 .NET IL 书籍,但无法弄清楚哪里出了问题。
我缺少一些基本的东西吗?
编辑:
我稍微更新了有问题的 IL 代码,使其更完整(没有修改指令)。第二块指令,包括 ldarg
、newobj
等,原样取自工作代码——原始方法代码。
对我来说奇怪的是,通过删除 localloc
或 .try
-finally
,代码可以工作 - 但据我所知,其中的 none 应该与它们是否存在于代码中相比,更改堆栈的平衡。
这是使用 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:
更多观察:
- 获取 IL 代码的 localloc
块,并将其移动到函数的末尾,代码运行良好 - 因此代码本身似乎没问题。
- 将类似的 IL 代码粘贴到 hello world 测试函数时,问题不会重现。
我很纳闷...
我希望有一种方法可以从 InvalidProgramException 中获取更多信息。似乎 CLR 没有将确切的失败原因附加到异常对象。
我也考虑过使用 CoreCLR 调试版本进行调试,但不幸的是我正在调试的程序与它不兼容...
遗憾的是,我似乎遇到了 CLR 错误...
使用旧版 JIT 编译器时一切正常:
set COMPLUS_useLegacyJit=1
我无法找出可能导致此问题的特定 RyuJit 设置。
我遵循了这篇文章中的建议:
https://github.com/Microsoft/dotnet/blob/master/Documentation/testing-with-ryujit.md
感谢所有帮助过的人!
后果:
有时在我遇到遗留的 JIT 解决方法后,我意识到只有在将 localloc
(这是一个不可验证的操作码)检测到从安全透明方法。只有在这种情况下,RyuJit 会抛出 InvalidProgramException
,而 Legacy JIT 不会。
在我的复现中,我对有问题的DLL进行了反汇编和重组,并直接修改了功能代码,保持了安全属性的完整性——
特别是 AllowPartiallyTrustedCallers
程序集属性 - 这解释了为什么没有用一个孤立的例子重现这个问题。
可能是因为在 RyuJIT 中与 Legacy JIT 相比有一些安全强化,这暴露了这个问题,但是,localloc
的事实仍然会导致 CLR 在try-catch 的存在及其与 localloc
的相对位置似乎是一个微妙的错误。
运行 SecAnnotate.exe (.NET Security Annotator tool) 在失败的 DLL 上有助于揭示函数调用之间的安全问题。
有关安全透明代码的更多信息:
https://docs.microsoft.com/en-us/dotnet/framework/misc/security-transparent-code
我有以下一段简化的 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 为原始方法抛出之后达到了这一点,这是由于在运行时进行的一些检测。我的调试方法是:
- 使用
ildasm
反汇编错误的 DLL
- 将检测代码应用于崩溃方法
- 使用
ilasm
从修改后的 IL 重新创建 DLL - 运行 程序再次运行,验证崩溃
- 逐渐减少崩溃方法的 IL 代码,减少到导致问题的最小场景(并尽量不在此过程中引入错误...)
遗憾的是,peverify.exe /IL
没有指出任何错误。
我试图对照 ECMA 规范和 Serge Lidin 的专家 .NET IL 书籍,但无法弄清楚哪里出了问题。
我缺少一些基本的东西吗?
编辑:
我稍微更新了有问题的 IL 代码,使其更完整(没有修改指令)。第二块指令,包括 ldarg
、newobj
等,原样取自工作代码——原始方法代码。
对我来说奇怪的是,通过删除 localloc
或 .try
-finally
,代码可以工作 - 但据我所知,其中的 none 应该与它们是否存在于代码中相比,更改堆栈的平衡。
这是使用 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:
更多观察:
- 获取 IL 代码的 localloc
块,并将其移动到函数的末尾,代码运行良好 - 因此代码本身似乎没问题。
- 将类似的 IL 代码粘贴到 hello world 测试函数时,问题不会重现。
我很纳闷...
我希望有一种方法可以从 InvalidProgramException 中获取更多信息。似乎 CLR 没有将确切的失败原因附加到异常对象。 我也考虑过使用 CoreCLR 调试版本进行调试,但不幸的是我正在调试的程序与它不兼容...
遗憾的是,我似乎遇到了 CLR 错误...
使用旧版 JIT 编译器时一切正常:
set COMPLUS_useLegacyJit=1
我无法找出可能导致此问题的特定 RyuJit 设置。
我遵循了这篇文章中的建议:
https://github.com/Microsoft/dotnet/blob/master/Documentation/testing-with-ryujit.md
感谢所有帮助过的人!
后果:
有时在我遇到遗留的 JIT 解决方法后,我意识到只有在将 localloc
(这是一个不可验证的操作码)检测到从安全透明方法。只有在这种情况下,RyuJit 会抛出 InvalidProgramException
,而 Legacy JIT 不会。
在我的复现中,我对有问题的DLL进行了反汇编和重组,并直接修改了功能代码,保持了安全属性的完整性——
特别是 AllowPartiallyTrustedCallers
程序集属性 - 这解释了为什么没有用一个孤立的例子重现这个问题。
可能是因为在 RyuJIT 中与 Legacy JIT 相比有一些安全强化,这暴露了这个问题,但是,localloc
的事实仍然会导致 CLR 在try-catch 的存在及其与 localloc
的相对位置似乎是一个微妙的错误。
运行 SecAnnotate.exe (.NET Security Annotator tool) 在失败的 DLL 上有助于揭示函数调用之间的安全问题。
有关安全透明代码的更多信息:
https://docs.microsoft.com/en-us/dotnet/framework/misc/security-transparent-code