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

Gle*_*eno 150 c# clr

以下示例代码自然发生.突然间,我的代码成了一个非常令人讨厌的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>(); }
}
Run Code Online (Sandbox Code Playgroud)

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

FatalExecutionException截图

这是一个已知的错误,原因是什么,我可以做些什么来减轻它?我目前的工作是不使用string.Empty,但我是在吠叫错误的树吗?更改有关该代码的任何内容使其按预期运行 - 例如删除空的静态构造函数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仍然存在.- 韦恩

Mic*_*zyk 114

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

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

UPDATE

我看得更深一点,我相信我找到了问题的根源.它似乎是由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
Run Code Online (Sandbox Code Playgroud)

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

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

在这两个版本中,String类型都装饰有beforefieldinit:

.class public auto ansi serializable sealed beforefieldinit System.String
Run Code Online (Sandbox Code Playgroud)

我试图创建一个类似的类型,以便编译为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(); } 
}
Run Code Online (Sandbox Code Playgroud)

我的猜测是.NET 4.0和4.5之间发生了两件事:

第一:更改了EE,以便String.Empty从非托管代码自动初始化.这种变化可能是针对.NET 4.0进行的.

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

EE 似乎不会String.Empty很快分配一些优化路径.对编译器所做的更改(或任何更改为make的更改String.cctor)预计EE会在执行任何用户代码之前进行此分配,但看起来EE之前String.Empty未在引用类型实现的通用类的方法中使用此赋值.

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

原版的

首先,WOW BCL人员在进行一些性能优化方面非常有创意. 很多String方法,现在使用线程静态缓存的执行StringBuilder对象.

我跟着那个引导了一段时间,但StringBuilder没有在Trim代码路径上使用,所以我认为它不能是一个Thread静态问题.

我想我发现了同一个bug的一个奇怪的表现.

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果你取消注释//new A<int>(out s);,Main那么代码就可以了.实际上,如果A使用任何引用类型进行了修改,程序将失败,但如果A使用任何值类型进行了修改,则代码不会失败.另外,如果你注释掉A静态构造函数,代码永远不会失败.挖掘到后TrimFormat,很明显的是,问题是,Length被内联的,并且这些样本在上述在String式尚未被初始化.特别是,在A构造函数体内,string.Empty未正确分配,尽管在体内Main,string.Empty正确分配.

令我惊讶的是,类型初始化以String某种方式取决于是否A使用值类型进行了实现.我唯一的理论是,在所有类型之间共享通用类型初始化的优化JIT代码路径,并且该路径对BCL引用类型("特殊类型?")及其状态进行假设.就让我们来看看,虽然其他BCL类与public static字段显示,基本上所有的人实现一个静态构造函数(即使是空的构造函数和任何数据,如System.DBNullSystem.Empty.BCL值类型的public static字段似乎没有实现静态构造函数(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 
    }
Run Code Online (Sandbox Code Playgroud)

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 
    }
Run Code Online (Sandbox Code Playgroud)

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

编辑

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

newobj     instance void class A`1<object>::.ctor(string&)
Run Code Online (Sandbox Code Playgroud)

... A`1<int32>...
Run Code Online (Sandbox Code Playgroud)

在第二.

另一件需要注意的是:JITed代码A<int>.ctor(out string):与非泛型版本相同.

  • 好分析!我会将它传递给BCL团队.谢谢! (49认同)
  • @JeppeStigNielsen:你的问题的答案是:"也许","很容易,显然","这是一个问答网站,所以是的,如果你想更好地回答你的问题,这是一个好主意比'可能''. (4认同)
  • 我沿着一条非常相似的路径寻找答案,但它似乎并没有引领任何地方.这似乎是一个字符串类问题,希望不是一个更普遍的问题.所以现在我正在等待某人(Eric)的源代码出现并解释出现了什么问题,以及是否还有其他问题.作为一个小小的好处,这个讨论已经解决了争论是否应该使用`string.Empty`或``"`... :) (3认同)
  • @EricLippert和其他人:我发现代码像`typeof(string).GetField("Empty").SetValue(null,"Hello world!"); Console.WriteLine(string.Empty);`在.NET 4.0与.NET 4.5上给出不同的结果.此更改是否与上述更改有关?.NET 4.5在技术上如何_ignore_我改变字段值?也许我应该问一个关于这个的新问题? (2认同)