关于C #:.NET 4.5 beta中FatalExecutionEngineError的原因是什么?

What's the cause of this FatalExecutionEngineError in .NET 4.5 beta?

下面的示例代码是自然发生的。突然间,我的代码出现了一个听起来很讨厌的FatalExecutionEngineError例外。我花了整整30分钟来隔离和最小化罪魁祸首样本。使用Visual Studio 2012作为控制台应用程序进行编译:

1
2
3
4
5
6
7
8
9
10
11
class A<T>
{
    static A() { }

    public A() { string.Format("{0}", string.Empty); }
}

class B
{
    static void Main() { new A<object>(); }
}

应在.NET Framework 4和4.5上产生此错误:

FatalExecutionException screenshot

这是一个已知的错误吗?原因是什么?我能做些什么来减轻它?我目前的工作是不使用string.Empty,但我是不是找错了树?更改该代码的任何内容都会使其按预期的方式运行,例如删除A的空静态构造函数,或将类型参数从object更改为int

我在我的笔记本电脑上试过这个代码,但没有抱怨。但是,我确实尝试了我的主应用程序,它也在笔记本电脑上崩溃了。我一定是在减少问题的时候弄坏了什么东西,我看看能不能弄清楚是什么。

我的笔记本电脑和上面的代码一样崩溃了,框架4.0,但主要崩溃了,即使是4.5。两个系统都在使用最新更新的vs'12(7月?).

更多信息:

  • IL代码(编译的调试/any cpu/4.0/vs2010(IDE不重要吗?):http://codepad.org/bozdd98e
  • 与2010年4.0相比,还没有见过。不与优化一起崩溃/不与优化一起崩溃、不同的目标CPU、附加/不附加调试程序等。—蒂姆·梅多拉
  • 如果我使用anycpu,在2010年崩溃,在x86中很好。在使用平台target=anycpu的Visual Studio 2010 SP1中崩溃,但在平台target=x86时很好。这台机器也安装了VS2012RC,因此4.5可能会进行就地更换。使用anycpu和targetplatform=3.5,那么它不会崩溃,因此看起来像是框架中的回归。
  • 无法在带有4.0的vs2010中的x86、x64或anycpu上复制。富士
  • 仅适用于X64(2012RC,FX4.5)-Henk Holterman
  • 在Win8 RP上的VS2012 RC。最初在以.NET 4.5为目标时没有看到此MDA。当切换到目标.NET 4.0时,出现了MDA。然后在切换回.NET 4.5之后,MDA仍然存在。-韦恩


这也不是一个完整的答案,但我有一些想法。好的。

我相信我已经找到了一个很好的解释,因为我们会发现没有来自.NET JIT团队的人回答。好的。

更新好的。

我看得更深了一点,我相信我找到了问题的根源。这似乎是由JIT类型初始化逻辑中的错误和依赖于JIT按预期工作的假设的C编译器中的更改组合造成的。我认为JIT错误存在于.NET 4.0中,但被.NET 4.5编译器中的更改所发现。好的。

我不认为beforefieldinit是这里唯一的问题。我觉得这比那简单。好的。

.NET 4.0 mscorlib.dll中的System.String类型包含静态构造函数:好的。

1
2
3
4
5
6
7
8
9
.method private hidebysig specialname rtspecialname static
    void  .cctor() cil managed
{
  // Code size       11 (0xb)
  .maxstack  8
  IL_0000:  ldstr     ""
  IL_0005:  stsfld     string System.String::Empty
  IL_000a:  ret
} // end of method String::.cctor

在mscorlib.dll的.NET 4.5版本中,String.cctor(静态构造函数)明显不存在:好的。

..... No static constructor :( .....

Ok.

在这两个版本中,String型都用beforefieldinit装饰:好的。

1
.class public auto ansi serializable sealed beforefieldinit System.String

我试图创建一个可以类似地编译到IL的类型(这样它就有了静态字段,但没有静态构造函数.cctor),但我做不到。所有这些类型在IL中都有一个.cctor方法:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyString1 {
    public static MyString1 Empty = new MyString1();        
}

public class MyString2 {
    public static MyString2 Empty = new MyString2();

    static MyString2() {}  
}

public class MyString3 {
    public static MyString3 Empty;

    static MyString3() { Empty = new MyString3(); }
}

我猜在.NET 4.0和4.5之间有两种情况发生了变化:好的。

首先:EE被更改,这样它就可以从非托管代码自动初始化String.Empty。此更改可能是为.NET 4.0进行的。好的。

第二:编译器进行了更改,因此它不会为字符串发出静态构造函数,因为知道将从非托管端分配String.Empty。此更改似乎是针对.NET 4.5进行的。好的。

似乎EE没有足够快地沿着一些优化路径分配String.Empty。对编译器所做的更改(或为使String.cctor消失所做的任何更改)预期ee在任何用户代码执行之前进行此赋值,但似乎ee在String.Empty被用于引用类型的已具体化的泛型类的方法之前没有进行此赋值。好的。

最后,我相信这个bug表明了JIT类型初始化逻辑中的一个更深层次的问题。似乎编译器中的更改是System.String的一个特殊情况,但我怀疑JIT在这里为System.String做了一个特殊情况。好的。

原件好的。

首先,哇哦,BCL的人通过一些性能优化变得非常有创造力。许多String方法现在使用线程静态缓存的StringBuilder对象执行。好的。

我跟着这条线索走了一段时间,但是StringBuilder没有用于Trim代码路径,所以我决定它不能是线程静态问题。好的。

不过,我觉得我发现了同一种虫子的奇怪表现。好的。

此代码失败,出现访问冲突:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A<T>
{
    static A() { }

    public A(out string s) {
        s = string.Empty;
    }
}

class B
{
    static void Main() {
        string s;
        new A<object>(out s);
        //new A<int>(out s);
        System.Console.WriteLine(s.Length);
    }
}

然而,如果你不理解//new A(out s);Main中,那么代码就很好了。事实上,如果A是与任何参考类型,程序失败,但如果A是在代码不失灵的情况下,用任何价值型重新计算的。另外,如果你如何离开静态制造商,密码永远不会失效。在数字化到TrimFormat之后,很明显问题是Length是内在的,而在这些样品中,8种样品没有被初始化。特别是,在A的体内,string.Empty是不正确的分配,尽管在Main的体内,string.Empty是正确的分配的。

okay.

对我来说,String类型的初始化取决于是否有A类型。我唯一的理论是,有一些优化的JIT代码路径用于通用类型-初始化,在所有类型中都是共享的,而且路径对BCL Reference Types("Special Types?")他们的国家一个快捷的想法是,以public static为基础的其他碱性催化分解类别表明,它们基本上都是一个静态制造商(即使是那些有空气制造商和无数据的制造商,如System.DBNullSystem.Empty)。Fields do not seem to implement a static constructor(EDOCX1&19),for instance.This seems to indicate that the JIT makes some submissions about BCL reference type initiatization.

okay.

FYI here is the jited code for the two versions:

okay.

A.ctor(out string)

okay.ZZU1

A.ctor(out string)

okay.

1
2
3
4
5
6
7
8
9
10
11
12
    public A(out string s) {
00000000  sub         rsp,28h
00000004  mov         rax,rdx
            s = string.Empty;
00000007  mov         rdx,12353250h
00000011  mov         rdx,qword ptr [rdx]
00000014  mov         rcx,rax
00000017  call        000000005F691160
0000001c  nop
0000001d  add         rsp,28h
00000021  ret
    }

《守则》的其余部分(EDOCX1&1)在两个版本中是相同的。

okay.

编辑

okay.

此外,两种版本中的IL是相同的,但A.ctorB.Main()中,第一种版本的IL包括:

okay.

1
newobj     instance void class A`1<object>::.ctor(string&)

版本

okay.

1
... A`1<int32>...

第二

okay.

另一个值得注意的是,A.ctor(out string)的吉特守则是非一般版本中的一样。

okay.好吧


我强烈怀疑这是由.NET 4.0中的优化(与BeforeFieldInit相关)引起的。如果我记错了:

显式声明静态构造函数时,将发出BeforeFieldInit,通知运行时静态构造函数必须在任何静态成员访问之前运行。

我猜想:

我猜他们在X64 Jiter上搞砸了这个事实,所以当从一个已经运行了自己的静态构造函数的类中访问不同类型的静态成员时,它会以某种方式跳过静态构造函数的运行(或以错误的顺序执行),从而导致崩溃。(您不会得到空指针异常,可能是因为它不是空初始化的。)

我没有运行您的代码,所以这一部分可能是错误的——但是如果我不得不做另一个猜测,我会说可能是string.FormatConsole.WriteLine,这是相似的)需要访问内部导致崩溃的东西,例如可能是一个需要显式静态构造的与区域设置相关的类。

同样,我还没有测试过它,但这是我对数据的最好猜测。

随时测试我的假设,让我知道它是如何发展的。