.NET 4.5 beta中此FatalExecutionEngineError的原因是什么?[关闭]


150

下面的示例代码是自然发生的。突然,我的代码出现了一个听起来很讨厌的FatalExecutionEngineError异常。我花了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 Framework 4和4.5上产生此错误:

FatalExecutionException屏幕截图

这是一个已知的错误吗,原因是什么,我应该怎么做才能缓解它?我当前的解决方法是不使用string.Empty,但是我是否吠错了树?更改该代码的任何内容都会使其发挥预期的作用,例如,删除空的static构造函数A,或将类型参数从更改objectint

我在笔记本电脑上尝试了此代码,但没有抱怨。但是,我确实尝试了我的主应用程序,并且它在笔记本电脑上也崩溃了。减少问题时,我一定已经整理了一些东西,我看看能否弄清楚那是什么。

我的笔记本电脑使用与框架4.0相同的代码崩溃,但即使在使用4.5时也崩溃了。两种系统都使用最新更新的VS'12(7月?)。

更多信息

  • IL代码(编译为Debug / Any CPU / 4.0 / VS2010(不是IDE应该重要吗?)):http : //codepad.org/boZDd98E
  • 没有看到VS 2010与4.0。在没有优化的情况下崩溃,没有目标CPU,连接了调试器/未连接调试器,等等。- Tim Medora
  • 如果我使用AnyCPU在2010年崩溃,在x86中可以。使用Platform Target = AnyCPU在Visual Studio 2010 SP1中崩溃,但对于Platform Target = x86则可以。该机器还安装了VS2012RC,因此4.5可能会就地更换。使用AnyCPU并且TargetPlatform = 3.5则它不会崩溃,因此看起来像Framework中的回归。- 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。- 韦恩

我不知道您可以和公共的构造器一起构造一个静态的构造器。哎呀,我从来不知道静态构造函数的存在。
科尔·约翰逊

我有一个主意:因为您正在将B从某种程度上从静态类更改为仅具有静态Main的类?
科尔·约翰逊

@ChrisSinclair,我不这么认为。我的意思是我在笔记本电脑上测试了此代码,并获得了相同的结果。
Gleno

@ColeJohnson是的,除了一个明显的位置以外,IL都匹配。在c#编译器中,这里似乎没有任何错误。
Michael Graczyk 2012年

14
感谢原始海报在这里进行报告,也感谢Michael出色的分析。我在CLR上的同行试图在此处重现该错误,并发现该错误是在64位CLR的“候选发布”版本上重现的,但是在最终的“发布到制造”版本上却没有,该版本在发布后已修复了许多错误RC。(RTM版本将提供给公众8月15日,2012年),因此,他们认为,这是同样的问题,因为这是此报告的一个:connect.microsoft.com/VisualStudio/feedback/details/737108/...
埃里克·利珀特(Eric Lippert)2012年

Answers:


114

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

我相信,如果没有.NET JIT团队的任何人回答,我会找到很好的解释。

更新

我看上去更加深入,我相信已经找到了问题的根源。这似乎是由于JIT类型初始化逻辑中的错误以及C#编译器中的更改所引起的,这些更改依赖于JIT按预期工作的假设。我认为JIT错误存在于.NET 4.0中,但未被.NET 4.5编译器的更改所揭示。

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

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

.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类型均带有beforefieldinit

.class public auto ansi serializable sealed beforefieldinit System.String

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

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在引用类型化通用类的方法中使用之前并未进行此分配。

最后,我认为该错误表明JIT类型初始化逻辑中存在更深层的问题。看来编译器中的更改是特殊情况System.String,但我怀疑JIT在这里已经做了特殊情况System.String

原版的

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

我跟踪了一段时间,但是StringBuilder没有在Trim代码路径上使用它,所以我决定它不可能是Thread静态问题。

我想我发现了同一错误的奇怪表现。

此代码因访问冲突而失败:

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使用任何引用类型进行了形式化,则程序将失败,但是如果A使用任何值类型进行了形式化,则代码不会失败。同样,如果您注释掉A的静态构造函数,则代码永远不会失败。深入研究Trim和之后Format,很明显问题是Length正在内联,并且在以上这些示例中,该String类型尚未初始化。特别地,主体内A的构造,string.Empty不正确地分配,虽然在身体内部Mainstring.Empty被正确分配。

令我惊讶的是,String某种程度上的类型初始化取决于是否A使用值类型进行了形式化。我唯一的理论是,存在针对所有类型共享的一些优化的用于通用类型初始化的JIT代码路径,并且该路径对BCL引用类型(“特殊类型?”)及其状态进行了假设。快速浏览一下其他带有public static字段的BCL类,表明它们基本上实现了静态构造函数(即使是那些带有空构造函数且没有数据的类,例如System.DBNullSystem.Empty。带有public static字段的BCL值类型似乎也没有实现静态构造函数(System.IntPtr例如)这似乎表明JIT对BCL引用类型初始化进行了一些假设。

仅供参考,这是两个版本的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两个版本之间的其余代码()相同。

编辑

此外,两个版本的IL相同,不同之处在于对A.ctorin 的调用B.Main(),其中第一个版本的IL包含:

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

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

在第二。

要注意的另一件事是:的JITed代码A<int>.ctor(out string)与非通用版本中的代码相同。


3
我已经沿着非常相似的路径搜索了答案,但似乎没有找到任何答案。这似乎是一个字符串类问题,希望不是更一般的问题。因此,现在我正在等待有人(Eric)使用源代码,并解释出了什么问题以及是否有其他影响。作为一个小好处,这个讨论已经解决了人们是否应该使用string.Empty""... 的辩论::)
Gleno 2012年

它们之间的IL是否相同?
科尔·约翰逊

49
好分析!我会将其传递给BCL团队。谢谢!
埃里克·利珀特

2
@EricLippert和其他人:我发现类似的代码typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);在.NET 4.0和.NET 4.5上给出了不同的结果。此更改与上述更改相关吗?.NET 4.5从技术上如何忽略我更改字段值?也许我应该问一个新的问题?
杰普·斯蒂格·尼尔森

4
@JeppeStigNielsen:您的问题的答案是:“也许”,“很显然很容易”和“这是一个问答网站,所以是的,如果您想更好地回答问题,那是个好主意。比'也许'”。
埃里克·利珀特

3

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

如果我没记错的话:

当您显式声明静态构造函数时,beforefieldinit将发出,告诉运行时静态构造函数必须在任何静态成员access之前运行

我猜:

我猜想,他们莫名其妙地搞砸了对64 JITer这个事实,所以,当一个不同类型的静态成员是从类,它的访问自己的静态构造函数已经运行,它在某种程度上跳过运行(或者执行错误的顺序)静态构造函数-从而导致崩溃。(您没有得到空指针异常,可能是因为它没有被空初始化。)

没有运行您的代码,所以这部分可能是错误的-但是,如果我不得不再做一个猜测,我会说可能是需要内部访问的某些内容string.Format(或Console.WriteLine类似的内容),例如可能是与语言环境相关的类,需要显式的静态构造。

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

随时检验我的假设,并让我知道它的进展。


B没有静态构造函数时,该错误仍然会发生;而在A使用值类型进行实化时,则不会发生。我认为这有点复杂。
Michael Graczyk 2012年

@MichaelGraczyk:我想我可以再解释一次(凭猜测)。B拥有静态构造函数没什么大不了的。由于A具有静态ctor,因此与其他命名空间中与语言环境相关的类进行比较时,运行时会混淆其运行顺序。因此该字段尚未初始化。但是,如果您A使用值类型实例化,则可能是运行时实例化的第二次遍历A(CLR可能已经使用引用类型预先实例化了它,作为一种优化),因此该顺序在第二次运行时便会确定。
user541686

@MichaelGraczyk:即使这还不能完全解释,但我想我很相信给定的beforefieldinit优化是根本原因。可能某些实际的解释与我提到的有所不同,但根本原因可能是同一件事。
user541686

我对IL进行了更多研究,我认为您正在有所作为。我认为第二遍想法与这里无关,因为如果我任意多次调用,代码仍然会失败A<object>.ctor()
Michael Graczyk

@MichaelGraczyk:很好听,感谢您的测试。不幸的是,我无法在自己的笔记本电脑上复制它。(2010 4.0 x64)您可以检查它是否确实与字符串格式相关(即与语言环境相关)吗?如果删除该部分会怎样?
user541686

1

一个观察值,但是DotPeek显示了反编译的字符串。

/// <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);
    }
}

具有该属性?我们已经建立了""解决方案。
Henk Holterman

“性能关键...”属性会影响属性构造函数本身,而不是属性装饰的方法。
迈克尔·格拉奇克

是内部的。当我定义自己的相同属性时,它仍然不会导致MDA。并不是我期望的那样-如果JITter正在寻找该特定属性,它将找不到我的属性。
lesscode 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.