5

我有以下一段简化的 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之后,我到了这一点。我的调试方法是:

  • 反汇编有问题的 DLLildasm
  • 将检测代码应用于崩溃方法
  • 使用修改后的 IL 重新创建 DLLilasm
  • 再次运行程序,并验证它是否崩溃
  • 逐渐减少崩溃方法的 IL 代码,直到导致问题的最小场景(并尽量不要在此过程中引入错误......)

不幸的是,peverify.exe /IL没有指出任何错误。我试图安慰 ECMA 规范和 Serge Lidin 的 Expert .NET IL 书,但不知道出了什么问题。

我缺少一些基本的东西吗?

编辑:

我稍微更新了有问题的 IL 代码,使其更加完整(无需修改说明)。第二个指令块,包括ldargnewobj等,是从工作代码——原始方法代码中提取的。

对我来说奇怪的是,通过删除locallocor .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:

更多观察:
- 获取locallocIL 代码块,并将其移动到函数的末尾,代码运行良好 - 所以看起来代码本身是可以的。
- 将类似的 IL 代码粘贴到 hello world 测试函数中时,该问题不会重现。

我很纳闷...

我希望有一种方法可以从InvalidProgramException获取更多信息。似乎 CLR 没有将确切的失败原因附加到异常对象。我还考虑过使用 CoreCLR 调试版本进行调试,但不幸的是我正在调试的程序与它不兼容......

4

1 回答 1

2

可悲的是,我似乎遇到了一个 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会导致 CLRInvalidProgramException在存在 try-catch 及其相对位置的情况下抛出依赖的事实,localloc看起来确实像一个微妙的错误。

在失败的 DLL 上运行SecAnnotate.exe.NET 安全注释器工具)有助于揭示函数调用之间的安全问题。

有关安全透明代码的更多信息:
https ://docs.microsoft.com/en-us/dotnet/framework/misc/security-transparent-code

于 2017-10-26T11:54:24.447 回答