151

以下のサンプルコードは自然に発生しました。突然、私のコードは非常に不快な音の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>(); }
}

.NETFramework4および4.5でこのエラーが発生するはずです。

FatalExecutionExceptionスクリーンショット

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

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

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

詳細情報

  • ILコード(コンパイルされたデバッグ/任意のCPU / 4.0 / VS2010(IDEは重要ではありませんか?)):http ://codepad.org/boZDd98E
  • 4.0のVS2010は見られません。最適化の有無にかかわらずクラッシュしない、異なるターゲット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
  • Win8RP上のVS2012RC。.NET 4.5をターゲットにした場合、最初はこのMDAが表示されません。.NET 4.0をターゲットに切り替えると、MDAが表示されました。その後、.NET 4.5に切り替えた後、MDAは残ります。-ウェイン
4

3 に答える 3

115

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

.NETJITチームの誰かが答えなくてもわかるのと同じくらい良い説明を見つけたと思います。

アップデート

もう少し深く見て、問題の原因を見つけたと思います。これは、JIT型初期化ロジックのバグと、JITが意図したとおりに機能するという前提に依存するC#コンパイラの変更の組み合わせが原因であると思われます。JITのバグは.NET4.0に存在したと思いますが、.NET4.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の.NET4.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(); } 
}

私の推測では、.NET4.0と4.5の間で2つの変更がありました。

最初:EEはString.Empty、アンマネージコードから自動的に初期化されるように変更されました。この変更は、おそらく.NET4.0で行われました。

2番目:コンパイラーは、文字列の静的コンストラクターを発行しないように変更されました。これString.Emptyは、アンマネージ側から割り当てられることを認識しています。この変更は、.NET4.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また、の静的コンストラクターをコメントアウトしても、コードが失敗することはありません。Trimとを掘り下げた後Format、問題はLengthインライン化されていることであり、上記のサンプルではStringタイプが初期化されていないことは明らかです。特に、Aのコンストラクターの本体内では、string.Emptyが正しく割り当てられていませんが、の本体内では正しく割り当てMainられていstring.Emptyます。

どういうわけか型の初期化が値型で具体化Stringされているかどうかに依存しているのは私にとって驚くべきことです。A私の唯一の理論は、すべての型の間で共有される汎用型初期化のための最適化JITコードパスがあり、そのパスはBCL参照型(「特殊型?」)とその状態についての仮定を行うというものです。フィールドを持つ他のBCLクラスをざっと見てみるとpublic static、基本的にすべてが静的コンストラクターを実装していることがわかります(たとえば、フィールドを持つBCL値型は、静的コンストラクターを実装System.DBNullしていないようです(たとえば)。 。これは、JITがBCL参照型の初期化についていくつかの仮定をしていることを示しているようです。System.Emptypublic staticSystem.IntPtr

参考までに、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は、の呼び出しを除いて同一です。ここA.ctorB.Main()、最初のバージョンのILには次のものが含まれています。

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

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

第二に。

もう1つの注意点は、:のJITされたコードがA<int>.ctor(out string)非汎用バージョンと同じであるということです。

于 2012-08-09T02:48:12.370 に答える
3

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

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

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

私の推測:

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

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

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

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

于 2012-08-11T01:20:54.693 に答える
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);
    }
}
于 2012-08-09T00:08:58.230 に答える