以下示例代码自然发生。突然间,我的代码出现了一个非常令人讨厌的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上产生此错误:
这是一个已知的错误,原因是什么,我可以做些什么来减轻它?我目前的工作是不使用string.Empty
,但我是在吠叫错误的树吗?更改有关该代码的任何内容使其按预期运行 - 例如删除A
的空静态构造函数,或将类型参数从object
更改为int
。
我在笔记本电脑上尝试了这个代码并没有抱怨。但是,我确实尝试了我的主应用程序,它也在笔记本电脑上崩溃了。我必须在减少问题的时候把一些东西弄错了,我会看看能不知道那是什么。
我的笔记本电脑使用与上面相同的代码崩溃,使用框架4.0,但主要崩溃,即使是4.5。两个系统都使用VS'12和最新更新(7月?)。
更多信息:
答案 0 :(得分:113)
这也不是一个完整的答案,但我有一些想法。
我相信我已经找到了一个很好的解释,我们会发现没有.NET JIT团队的人回答。
<强>更新强>
我看得更深一些,我相信我找到了问题的根源。它似乎是由JIT类型初始化逻辑中的错误和C#编译器中的更改引起的,该更改依赖于JIT按预期工作的假设。我认为JIT错误存在于.NET 4.0中,但是.NET 4.5的编译器发生了变化。
我不认为beforefieldinit
是唯一的问题。我认为这比那更简单。
.NET 4.0中mscorlib.dll中的System.String
类型包含一个静态构造函数:
.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
在.NET 4.5版本的mscorlib.dll中,String.cctor
(静态构造函数)显然不存在:
.....没有静态构造函数:( .....
在这两个版本中,String
类型都装饰有beforefieldinit
:
.class public auto ansi serializable sealed beforefieldinit System.String
我尝试创建一个类似于编译为IL的类型(因此它具有静态字段但没有静态构造函数.cctor
),但我无法做到。所有这些类型在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已更改,以便从非托管代码自动初始化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);
}
}
但是,如果您在//new A<int>(out s);
中取消注释Main
,那么代码就可以了。实际上,如果A
与任何引用类型一致,则程序将失败,但如果使用任何值类型实现A
,则代码不会失败。另外,如果你注释掉A
的静态构造函数,代码永远不会失败。在深入研究Trim
和Format
之后,问题显然是Length
被内联,而String
类型以上的这些示例中的问题尚未初始化。特别是,在A
的构造函数体内,string.Empty
未正确分配,尽管在Main
的正文中,string.Empty
已正确分配。
令我惊讶的是,String
的类型初始化在某种程度上取决于A
是否用值类型来实现。我唯一的理论是,在所有类型之间共享通用类型初始化的优化JIT代码路径,并且该路径对BCL引用类型(“特殊类型?”)及其状态进行假设。快速查看其他带有public static
字段的BCL类,可以看出基本上所有都实现了一个静态构造函数(即使是那些带有空构造函数且没有数据的类,如System.DBNull
和{{ 1}}。带有System.Empty
字段的BCL值类型似乎没有实现静态构造函数(例如public static
)。这似乎表明JIT对BCL引用类型初始化做了一些假设。 / p>
FYI以下是两个版本的JITed代码:
<强> System.IntPtr
强>:
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
中A.ctor
的调用外,两个版本的IL相同,其中第一个版本的IL包含:
B.Main()
与
newobj instance void class A`1<object>::.ctor(string&)
在第二个。
需要注意的另一件事是 ... A`1<int32>...
:的JITed代码与非通用版本中的相同。
答案 1 :(得分:3)
BeforeFieldInit
)引起的。当您显式声明静态构造函数时,会发出beforefieldinit
,告诉运行时必须在任何静态成员访问之前运行静态构造函数。
我猜他们在x64 JITer上搞砸了这个事实,所以当 不同类型的静态成员从的类中访问时自己的静态构造函数已经运行,它以某种方式跳过运行(或以错误的顺序执行)静态构造函数 - 因此导致崩溃。 (你没有得到空指针异常,可能是因为它没有空初始化。)
我有不运行你的代码,所以这部分可能是错误的 - 但如果我不得不做出另一个猜测,我会说它可能是string.Format
(或{ {1}},类似的)需要在内部访问导致崩溃的内容,例如可能是需要显式静态构造的 locale 相关类。
同样,我还没有测试过,但这是我对数据的最佳猜测。
随意测试我的假设,让我知道它是怎么回事。
答案 2 :(得分: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);
}
}