.NET 4.5ベータ版で、この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ですが、私は間違った木を吠えていますか?そのコードについて何かを変更すると、期待どおりに機能します。たとえば、の空の静的コンストラクターを削除しAたり、型パラメーターをからobjectに変更したりしintます。

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

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

詳細

  • ILコード(コンパイルされたデバッグ/任意のCPU / 4.0 / VS2010(そのIDEは重要ではありませんか?)):http ://codepad.org/boZDd98E
  • VS 2010と4.0では見られません。最適化あり/なしでクラッシュしない、異なるターゲットCPU、デバッガーが接続されている/接続されていない、など-Tim Medora
  • AnyCPUを使用すると2010年にクラッシュしますが、x86では問題ありません。プラットフォームターゲット= AnyCPUを使用してVisual Studio 2010 SP1でクラッシュしますが、プラットフォームターゲット= 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が残ります。- ウェイン

静的コンストラクターをパブリックコンストラクターと共に作成できることを知りませんでした。静的コンストラクタが存在することを私は知りませんでした。
Cole Johnson、

私はアイデアを持っています。あなたはBをいくらか静的なクラスから静的なMainを持つクラスだけに変更しているからですか?
Cole Johnson

@ChrisSinclair、私はそうは思いません。私のラップトップでこのコードをテストしたところ、同じ結果が得られました。
グレノ2012

@ColeJohnsonはい、ILは1つの明白な場所を除いてすべて一致します。ここのc#コンパイラには、バグはないようです。
Michael Graczyk

14
ここに報告してくれた元のポスターと、優れた分析をしてくれたMichaelに感謝します。CLRの私の対応者は、ここでバグを再現しようとしましたが、64ビットCLRの「リリース候補」バージョンでは再現するが、多数のバグ修正がポストされた最終的な「リリース済み製造」バージョンでは再現しないことを発見しました。 RC。彼らは、したがって、これは、ここで報告されたものと同じ問題であると信じる(RTM版は8月15日、2012年に公衆に利用できるようになります):connect.microsoft.com/VisualStudio/feedback/details/737108/...
エリックリッペルト

回答:


114

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

私は、.NET JITチームの誰かが答えずに見つけるのと同じくらい良い説明を見つけたと思います。

更新

私はもう少し深く見て、問題の原因を見つけたと思います。これは、JITタイプ初期化ロジックのバグと、JITが意図したとおりに機能するという前提に依存するC#コンパイラの変更の組み合わせが原因で発生するようです。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はないように)、できませんでした。これらすべてのタイプには、.cctorILにメソッドがあります。

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つの点が変更されたと思います。

最初にString.Empty、アンマネージコードから自動的に初期化されるようにEEが変更されました。この変更は、おそらく.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 BCLの人々は、いくつかのパフォーマンスの最適化によって非常にクリエイティブになりました。 メソッドの多くは、Stringスレッドの静的キャッシュStringBuilderオブジェクトを使用して実行されるようになりました。

私はしばらくリードをたどりStringBuilderましたが、Trimコードパスでは使用されていないため、スレッドの静的な問題ではない可能性があると判断しました。

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

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

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の静的コンストラクターをコメントアウトしても、コードは失敗しません。掘り下げる後TrimFormat、問題はそれがされていることは明らかであるLengthインライン化され、そして上記のこれらのサンプルでそのStringタイプが初期化されていません。具体的には、本体内部Aのコンストラクタ、string.Empty正しくの体内が、割り当てられていないMainstring.Empty正しく割り当てられています。

Stringなんとかして型の初期化がA値型で具体化されているかどうかに依存しているのは私にとって驚くべきことです。私の唯一の理論は、すべてのタイプ間で共有される一般的なタイプ初期化のための最適化JITコードパスがあり、そのパスはBCL参照タイプ(「特殊タイプ?」)とその状態についての仮定を行うというものです。他のBCLクラスけれども簡単に見てpublic static、基本的にはフィールドショーのすべてのそれらの静的コンストラクタ(でも、それらのような空のコンストラクタとデータがない、との実装System.DBNullSystem.Emptyしている。BCL値の型public staticフィールドは静的コンストラクタを(実装していないようです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つのバージョンで同じです。

編集

さらに、2つのバージョンのILは、最初のバージョンのILに含まれるA.ctorin の呼び出しを除いて、同じですB.Main()

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

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

第二に。

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


3
私は非常によく似た方法で回答を検索しましたが、どこにも誘導されていないようです。これは文字列クラスの問題のようであり、より一般的な問題ではないようです。だから今私はソースコードを持っている誰か(エリック)がやって来て、何がうまくいかなかったか、そして何か他に影響があるかどうかを説明するのを待っています。小さな利点として、この議論はすでに1を使用すべきかどうか議論を決着string.Empty""... :)
Gleno

それらの間のILは同じですか?
Cole Johnson

49
良い分析!それをBCLチームに渡します。ありがとう!
Eric Lippert

2
@EricLippertなど:typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);.NET 4.0と.NET 4.5では、次のようなコードが異なる結果をもたらすことを発見しました。この変更は上記の変更に関連していますか?.NET 4.5は、フィールド値の変更を技術的に無視するにはどうすればよいですか?多分私はこれについて新しい質問をする必要がありますか?
Jeppe Stig Nielsen 2013

4
@JeppeStigNielsen:質問への回答は次のとおりです:「たぶん」、「明らかに簡単に見える」、「これは質問と回答のサイトです。そのため、質問に対する回答をもっとよくしたい場合は、そうすることをお勧めします。 「たぶん」より」
Eric Lippert 2013年

3

これは、.NET 4.0の(に関連するBeforeFieldInit)この最適化が原因であると強く考えます。

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

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

私の推測:

彼らが何らかの形でx64 JITerでこの事実を台無しにしたため、独自の静的コンストラクタが既に実行されているクラスから別の型の静的メンバーにアクセスする、なんらかの理由で実行がスキップされる(または間違った順序で実行される)静的コンストラクタ-したがって、クラッシュを引き起こします。(おそらく nullで初期化されていないため、nullポインター例外は発生しません。)

私はあなたのコードを実行していないので、この部分は間違っているかもしれません-しかし、私が別の推測をしなければならなかった場合、それは何かをstring.Format(またはConsole.WriteLine、類似した)内部的にアクセスする必要があり、クラッシュを引き起こしているかもしれないと言いますおそらく、明示的な静的構築を必要とするロケール関連のクラスです。

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

私の仮説を自由にテストして、どうなるか教えてください。


このバグBは、に静的コンストラクタがない場合でも発生Aし、が値型で具体化されている場合には発生しません。もう少し複雑だと思います。
Michael Graczyk

@MichaelGraczyk:私はそれを説明できると思います(もう一度、推測して)。B静的コンストラクタがあることはそれほど重要ではありません。A静的なctorがあるため、ランタイムは、他の名前空間にあるロケール関連のクラスと比較すると、実行される順序を混乱させます。そのため、そのフィールドはまだ初期化されていません。ただし、A値型でインスタンス化する場合は、ランタイムの2番目のパススルーのインスタンス化である可能性がありますA(CLRは、最適化として参照型で事前にインスタンス化している可能性が高いため)、2回目に実行したときに順序が機能します。 。
user541686 2012

@MichaelGraczyk:これが説明ではない場合でも、与えられたbeforefieldinit最適化が根本的な原因であると私は確信しています。実際の説明のいくつかは私が述べたものとは異なるかもしれませんが、根本的な原因はおそらく同じものです。
user541686 2012

ILをもっと調べたところ、あなたは何かに夢中になっていると思います。ここで2番目のパスのアイデアが適切であるとは思いません。なぜなら、を何度も呼び出してもコードは失敗するからA<object>.ctor()です。
Michael Graczyk

@MichaelGraczyk:聞いて良かった。テストに感謝したい。残念ながら自分のラップトップでは再現できません。(2010 4.0 x64)実際に文字列フォーマットに関連しているかどうか(つまり、ロケールに関連しているかどうか)を確認できますか?その部分を削除するとどうなりますか?
user541686 2012

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、2012

その「パフォーマンスクリティカル...」属性は、属性を構成するメソッドではなく、属性コンストラクター自体に影響します。
Michael Graczyk

それは内部です。独自の同じ属性を定義しても、MDAは発生しません。JITterがその特定の属性を探している場合、私はそれを見つけられません。
lesscode、2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.