web-dev-qa-db-ja.com

.NET 4.5ベータ版のこのFatalExecutionEngineErrorの原因は何ですか?

以下のサンプルコードは自然に発生しました。突然、私のコードは非常に厄介な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 screenshot

これは既知のバグですか、原因は何ですか、それを軽減するために何ができますか?私の現在の回避策はstring.Emptyを使用しないことですが、間違ったツリーを起動していますか?そのコードについて何かを変更すると、期待どおりに機能します。たとえば、Aの空の静的コンストラクターを削除したり、型パラメーターをobjectからintに変更したりします。

私はラップトップでこのコードを試しましたが、文句は言いませんでした。ただし、メインアプリを試してみたところ、ラップトップでもクラッシュしました。問題を軽減するときに何かを壊してしまったに違いありません。それが何であるかを理解できるかどうかを確認します。

私のラップトップは上記と同じコード、フレームワーク4.0でクラッシュしましたが、4.5でもメインクラッシュします。両方のシステムは、最新の更新(7月?)でVS'12を使用しています。

詳細情報

  • ILコード(コンパイルされたDebug/Any CPU/4.0/VS2010(それ以外IDEが重要ですか?))): http://codepad.org/boZDd98E
  • VS 2010 with 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を使用すると、クラッシュしないので、フレームワークの回帰のように見えます。 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は残ります。 - ウェイン
150
Gleno

これも完全な答えではありませんが、いくつかのアイデアがあります。

。NET JITチームの誰かが答えることなく見つけるのと同じくらい良い説明を見つけたと思います。

[〜#〜] update [〜#〜]

私はもう少し深く見て、問題の原因を見つけたと思います。これは、JIT型初期化ロジックのバグと、JITが意図したとおりに動作するという前提に依存するC#コンパイラの変更の組み合わせによって引き起こされるようです。 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の間で2つのことが変わったと思います:

最初:EEは、アンマネージコードから_String.Empty_を自動的に初期化するように変更されました。この変更は、おそらく.NET 4.0で行われたものです。

2番目:コンパイラは、_String.Empty_がアンマネージ側から割り当てられることを認識して、文字列の静的コンストラクターを発行しないように変更しました。この変更は、.NET 4.5で行われたようです。

EEは、最適化パスに沿って_String.Empty_を十分に早く割り当てないように見えます。コンパイラに加えられた変更(または_String.cctor_を非表示にするために変更された変更)は、EEがユーザーコードを実行する前にこの割り当てを行うことを予期していましたが、EEは_String.Empty_が使用される前にこの割り当てを行わないようです参照型のメソッドでは、ジェネリッククラスを具体化しました。

最後に、このバグは、JIT型初期化ロジックのより深い問題を示していると考えています。コンパイラの変更は_System.String_の特殊なケースであるように見えますが、JITが_System.String_の特殊なケースを作成したとは思えません。

オリジナル

まず第一に、WOW The BCLの人々は、いくつかのパフォーマンスの最適化によって非常に創造的になりました。 多くのStringメソッドは、静的なスレッドStringBuilderオブジェクトを使用して実行されます。

私はしばらくそのリードに従いましたが、StringBuilderTrimコードパスで使用されないため、スレッドの静的な問題にはなり得ないと判断しました。

私は同じバグの奇妙な症状を見つけたと思う。

このコードはアクセス違反で失敗します:

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

ただし、Main//new A<int>(out s);のコメントを外すと、コードは正常に機能します。実際、Aが任意の参照型で具体化されると、プログラムは失敗しますが、Aが任意の値型で具体化されると、コードは失敗しません。また、Aの静的コンストラクターをコメントアウトした場合、コードは失敗しません。 TrimFormatを掘り下げた後、問題はLengthがインライン化されていることと、これらのサンプルではString型が初期化されていません。特に、Aのコンストラクターの本体内では、Mainの本体内では_string.Empty_が正しく割り当てられていますが、_string.Empty_は正しく割り当てられていません。

Stringの型の初期化が、何らかの形でAが値型で具体化されるかどうかに依存することは驚くべきことです。私の唯一の理論は、すべての型で共有される汎用型初期化のための最適化JITコードパスがあり、そのパスはBCL参照型(「特殊型?」)とその状態について仮定するということです。 _public static_フィールドを持つ他のBCLクラスをざっと見てみると、基本的にallが静的コンストラクター(空のコンストラクターと_System.DBNull_や_System.Empty_などのデータ。_public static_フィールドを持つBCL値型は、静的コンストラクター(たとえば、_System.IntPtr_)を実装していないようです。これは、 JITは、BCL参照型の初期化についていくつかの仮定を行います。

参考までに、2つのバージョンのJITコードを次に示します。

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)は、2つのバージョン間で同一です。

[〜#〜] edit [〜#〜]

さらに、2つのバージョンのILは、B.Main()の_A.ctor_の呼び出しを除いて同一です。最初のバージョンのILには次が含まれます。

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

versus

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

第二に。

もう1つ注意すべきことは、A<int>.ctor(out string):のJITedコードは、非ジェネリックバージョンと同じであるということです。

113
Michael Graczyk

.NET 4.0の この最適化(BeforeFieldInitに関連) が原因だと強く思います。

もし私が正確に覚えていれば:

静的コンストラクターを明示的に宣言すると、beforefieldinitが出力され、静的メンバーが静的メンバーアクセスの前に実行される必要があることをランタイムに伝えます

私の推測:

私は彼らが何らかの形でx64 JITerでこの事実を台無しにして、adifferenttype's静的メンバーは、own静的コンストラクターが既に実行されているクラスからアクセスされ、何らかの形でskips実行中(または間違った順序)静的コンストラクター-したがって、クラッシュを引き起こします。 (nullで初期化されていないため、nullポインター例外、おそらくを取得しません。)

私はnotあなたのコードを実行しているので、この部分は間違っているかもしれませんstring.Format(または同様のConsole.WriteLine)は、おそらく明示的なstaticを必要とするlocale関連クラスなど、クラッシュを引き起こす内部にアクセスする必要があります建設。

繰り返しますが、私はそれをテストしていませんが、データを推測するのが最善です。

私の仮説をテストして、それがどうなるか教えてください。

3
Mehrdad

観察ですが、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);
    }
}
1
lesscode