.NET 4.5 beta中这个FatalExecutionEngineError的原因是什么?
下面的示例代码自然发生。 突然我的代码哇,一个非常讨厌的冠冕堂皇的FatalExecutionEngineError
exception。 我花了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上应该产生这个错误:
这是一个已知的错误,是什么原因,我能做些什么来缓解呢? 我目前的工作是不使用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.String
types包含一个静态构造函数:
.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
(静态构造函数)显然不存在:
…..没有静态构造函数:( …..
在这两个版本中, String
types都是用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
的静态构造函数,代码永远不会失败。 在挖掘Trim
和Format
,显然问题是Length
被内联,并且在这些String
types上面的样本中还没有被初始化。 特别是在A
的构造函数体内, string.Empty
没有正确的赋值,虽然在Main
的体内, string.Empty
被正确赋值。
对我而言, String
的types初始化在某种程度上取决于A
是否被赋值为一个值types。 我唯一的理论是有一些优化的JIT代码path为所有types之间共享的genericstypes初始化,并且该path假设有关BCL引用types(“特殊types?”)及其状态。 快速浏览一下其他具有public static
字段的BCL类,显示出它们基本上都实现了一个静态构造函数(即使是带有空构造函数但没有数据的构造函数,比如System.DBNull
和System.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); } }