为什么localloc会破坏这个CIL方法?

val*_*ano 5 .net c# cil invalidprogramexception

我有以下一段简化的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
Run Code Online (Sandbox Code Playgroud)

我的问题是,为什么这个CIL代码无效?

几个obervations:
- 如果localloc删除,代码运行正常.据我所知,localloc用一个地址替换堆栈上的参数大小,因此堆栈保持平衡,AFAICT.
- 如果删除了try和finally块,代码运行正常.
- 如果第一个包含的指令块localloc被移动到try-finally块之后,则代码运行正常.

所以它似乎是localloc和try-finally组合的东西.

一些背景:

由于在运行时进行了一些检测,因此在为原始方法抛出InvalidProgramException之后我达到了这一点.为了弄清楚仪器的问题,我调试它的方法是:

  • 用PHP拆卸有故障的DLL ildasm
  • 将检测代码应用于崩溃方法
  • 使用修改的IL重新创建DLL ilasm
  • 再次运行程序,并验证它崩溃
  • 继续逐步减少崩溃方法的IL代码,直至导致问题的最小情况(并尝试不引入错误......)

不幸的是,peverify.exe /IL没有表明任何错误.我试图安装ECMA规范和Serge Lidin的专家.NET IL书籍,但无法弄清楚它出了什么问题.

我有什么基本的东西吗?

编辑:

我略微更新了有问题的IL代码,使其更加完整(无需修改指令).的指令,包括第二块ldarg,newobj等等,被取为是从工作代码-原来的方法的代码.

对我来说奇怪的是,通过删除localloc或者.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
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑2:

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

我很困惑......

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

val*_*ano 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程序集属性 - 这解释了为什么没有用一个孤立的示例重现该问题。

与旧版 JIT 相比,RyuJIT 可能有一些安全强化,这导致了这个问题,但事实上,在存在 try-catch 及其相对位置的情况下,locallocCLR 会抛出一个依赖项,看起来确实像一个微妙的错误。InvalidProgramExceptionlocalloc

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

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

  • 您可能想针对 CoreCLR 存储库(其中包含 RyuJIT)提出问题;`localloc` 已经有[几个](https://github.com/dotnet/coreclr/search?type=Issues)。在“finally”中不允许使用“localloc”,因此抖动可能会变得混乱,并在此处完全禁止它,即使它不在块中。 (2认同)