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

时间:2017-10-25 19:01:15

标签: c# .net 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

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

几个obervations:
- 如果删除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代码,使其更加完整(无需修改说明)。第二个指令块,包括ldargnewobj等,将从工作代码中获取 - 原始方法代码。

对我而言,奇怪的是,通过移除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
    {
    }
}

编辑2:

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

我很困惑......

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

1 个答案:

答案 0 :(得分:2)

可悲的是,我似乎遇到了CLR错误......

使用旧版JIT编译器时一切正常:

set COMPLUS_useLegacyJit=1

我无法隔离可能导致此问题的特定RyuJit设置。 我遵循了本文的建议:
https://github.com/Microsoft/dotnet/blob/master/Documentation/testing-with-ryujit.md

感谢所有帮助过的人!

<强>后果:

在我遇到遗留JIT解决方法之后的某个时间,我意识到只有在将localloc(这是一个不可验证的操作码)变为从安全透明方法调用的安全关键方法时才会出现此问题< / strong>即可。只有在这种情况下,RyuJit才会抛出InvalidProgramException,而Legacy JIT则不会。​​{/ p>

在我的复制中,我反汇编并重新组装了有问题的DLL并直接修改了功能代码,保持了安全属性的完整性 -  特别是AllowPartiallyTrustedCallers汇编属性 - 这解释了为什么问题没有用一个孤立的例子再现。

可能是在RyuJIT中,与Legacy JIT相比,存在一些安全性问题,这表明了这个问题,但是localloc将导致CLR在存在时抛出InvalidProgramException depdending的事实try-catch及其与localloc的相对位置确实看起来像一个微妙的错误。

在发生故障的DLL上运行 SecAnnotate.exe .NET Security Annotator tool)有助于揭示函数调用之间的安全问题。

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