.NET 4.5 beta中这个FatalExecutionEngineError的原因是什么?

下面的示例代码自然发生。 突然我的代码哇,一个非常讨厌的冠冕堂皇的FatalExecutionEngineErrorexception。 我花了30分钟,试图隔离和最小化罪魁祸首的样本。 使用Visual Studio 2012将其编译为控制台应用程序:

 class A<T> { static A() { } public A() { string.Format("{0}", string.Empty); } } class B { static void Main() { new A<object>(); } } 

在.NET框架4和4.5上应该产生这个错误:

FatalExecutionException截图

这是一个已知的错误,是什么原因,我能做些什么来缓解呢? 我目前的工作是不使用string.Empty ,但我吠叫错了树? 改变代码的任何东西都会使得它的function如你所期望的那样 – 例如移除A的空静态构造函数,或者将types参数从object改为int

我在我的笔记本上试过这个代码,它没有抱怨。 不过,我尝试了我的主要应用程序,它也在笔记本电脑上崩溃。 在减less问题的时候,我一定已经把事情弄糟了,我会看看能不能弄清楚是什么。

我的笔记本电脑与上述相同的代码崩溃,与框架4.0,但主要崩溃,甚至4.5。 两个系统都使用VS'12进行最新更新(7月?)。

更多信息

  • IL代码(编译debugging/任何CPU / 4.0 / VS2010(不是IDE应该重要?)): http : //codepad.org/boZDd98E
  • 没有见过VS 2010 4.0。 没有崩溃/没有优化,不同的目标CPU,debugging附加/不附加,等等 – Tim Medora
  • 如果我使用AnyCPU,在2010年崩溃,在x86中罚款。 在Visual Studio 2010 SP1中崩溃,使用Platform Target = AnyCPU,但与Platform Target = x86一致。 这台机器也安装了VS2012RC,所以4.5可能做一个就地更换。 使用AnyCPU和TargetPlatform = 3.5,那么它不会崩溃,所以看起来像在框架中的回归.- colinsmith
  • 无法在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保持。 – 韦恩

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

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

UPDATE

我看起来更深一点,我相信我find了问题的根源。 它似乎是由JITtypes初始化逻辑中的一个错误和C#编译器中的一个改变所导致的,这个改变依赖于JIT按预期工作的假设。 我认为JIT错误存在于.NET 4.0中,但被.NET 4.5编译器的变化所揭示。

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

.NET 4.0的mscorlib.dll中的System.Stringtypes包含一个静态构造函数:

 .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 (静态构造函数)显然不存在:

…..没有静态构造函数:( …..

在这两个版本中, Stringtypes都是用beforefieldinit装饰的:

 .class public auto ansi serializable sealed beforefieldinit System.String 

我试图创build一个类似的IL编译(所以它有静态字段,但没有静态构造.cctor ),但我不能这样做。 所有这些types在IL中都有一个.cctor方法:

 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被改变了,它会自动从非托pipe代码初始化String.Empty 。 这个改变可能是.NET 4.0的。

第二:编译器发生了变化,因此它不会为string发出静态构造函数,因为它知道String.Empty将从非托pipe端分配。 这个改变似乎是为.NET 4.5做的。

看起来,EE 并没有很快地沿着一些优化path分配String.Empty 。 对编译器所做的更改(或者任何改变以使String.cctor消失)期望EE在任何用户代码执行之前做出这个赋值,但是看起来EE并没有在String.Empty用于引用方法之前做这个赋值types通用类。

最后,我相信这个bug是JITtypes初始化逻辑中更深层的问题的表示。 看来在编译器中的变化是System.String的特例,但我怀疑JIT在这里为System.String做了一个特例。

原版的

首先,BCL人员已经非常有创意,并且有一些性能优化。 现在很多 String方法都是使用一个Thread静态caching的StringBuilder对象来执行的。

我跟随了一段时间,但StringBuilder没有使用Trim代码path,所以我决定它不能是一个线程静态问题。

我想我发现了同样的错误的一个奇怪的performance。

此代码失败,访问冲突:

 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<int>(out s);Main那么代码工作得很好。 实际上,如果A用任何引用types来引用,那么程序将失败,但是如果A被赋值为任何值types,那么代码不会失败。 另外,如果你注释掉A的静态构造函数,代码永远不会失败。 在挖掘TrimFormat ,显然问题是Length被内联,并且在这些Stringtypes上面的样本中还没有被初始化。 特别是在A的构造函数体内, string.Empty没有正确的赋值,虽然在Main的体内, string.Empty被正确赋值。

对我而言, String的types初始化在某种程度上取决于A是否被赋值为一个值types。 我唯一的理论是有一些优化的JIT代码path为所有types之间共享的genericstypes初始化,并且该path假设有关BCL引用types(“特殊types?”)及其状态。 快速浏览一下其他具有public static字段的BCL类,显示出它们基本上实现了一个静态构造函数(即使是带有空构造函数但没有数据的构造函数,比如System.DBNullSystem.Empty 。来实现一个静态构造函数(例如System.IntPtr ),这似乎表明JIT对BCL引用types初始化做了一些假设。

FYI这是两个版本的JITed代码:

A<object>.ctor(out string)

  public A(out string s) { 00000000 push rbx 00000001 sub rsp,20h 00000005 mov rbx,rdx 00000008 lea rdx,[FFEE38D0h] 0000000f mov rcx,qword ptr [rcx] 00000012 call 000000005F7AB4A0 s = string.Empty; 00000017 mov rdx,qword ptr [FFEE38D0h] 0000001e mov rcx,rbx 00000021 call 000000005F661180 00000026 nop 00000027 add rsp,20h 0000002b pop rbx 0000002c ret } 

A<int32>.ctor(out string)

  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 } 

其余的代码( Main )在两个版本之间是相同的。

编辑

另外,除了在B.Main()调用A.ctor之外,两个版本中的IL是相同的,其中第一个版本的IL包含:

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

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

在第二。

另外需要注意的是, A<int>.ctor(out string)代码与非generics版本相同。

我强烈怀疑这是由.NET 4.0中的这种优化(与BeforeFieldInit相关)引起的。

如果我没有记错的话:

当你声明一个静态构造函数时, beforefieldinit被发出,告诉运行时静态构造函数必须在任何静态成员访问之前运行

我猜:

我猜测他们以某种方式搞砸了这个事实在x64 JITer,所以当一个不同types的静态成员从一个自己的静态构造函数已经运行的类访问,它以某种方式跳过运行(或以错误的顺序执行)静态构造函数 – 并因此导致崩溃。 (你不会得到一个空指针exception, 可能是因为它不是空初始化的。)

没有运行你的代码,所以这部分可能是错的 – 但是如果我不得不做另一个猜测,我会说这可能是一些string.Format (或Console.WriteLine ,类似)需要访问内部导致崩溃,例如可能需要明确静态构build的与语言环境相关的类。

再次,我没有testing它,但这是我对数据的最好猜测。

随意testing我的假设,让我知道如何去。

一个观察,但DotPeek显示反编译的string.Empty因此:

 /// <summary> /// Represents the empty string. This field is read-only. /// </summary> /// <filterpriority>1</filterpriority> [__DynamicallyInvokable] public static readonly string Empty; internal sealed class __DynamicallyInvokableAttribute : Attribute { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public __DynamicallyInvokableAttribute() { } } 

如果我以同样的方式声明自己的Empty ,除非没有属性,我不再得到MDA:

 class A<T> { static readonly string Empty; static A() { } public A() { string.Format("{0}", Empty); } }