Was ist die Ursache für diesen FatalExecutionEngineError in .NET 4.5 Beta? [geschlossen]

150

Der folgende Beispielcode ist natürlich vorgekommen. Plötzlich war mein Code eine sehr böse klingende FatalExecutionEngineErrorAusnahme. Ich habe gut 30 Minuten damit verbracht, die Täterprobe zu isolieren und zu minimieren. Kompilieren Sie dies mit Visual Studio 2012 als Konsolen-App:

class A<T>
{
    static A() { }

    public A() { string.Format("{0}", string.Empty); }
}

class B
{
    static void Main() { new A<object>(); }
}

Sollte diesen Fehler in .NET Framework 4 und 4.5 erzeugen:

Screenshot der FatalExecutionException

Ist dies ein bekannter Fehler, was ist die Ursache und was kann ich tun, um ihn zu mindern? Meine derzeitige Arbeit besteht darin, sie nicht zu verwenden string.Empty, aber belle ich den falschen Baum an? Wenn Sie etwas an diesem Code Aändern, funktioniert er wie erwartet - zum Beispiel entfernen Sie den leeren statischen Konstruktor von oder ändern Sie den Typparameter von objectnach int.

Ich habe diesen Code auf meinem Laptop ausprobiert und er hat sich nicht beschwert. Ich habe jedoch meine Haupt-App ausprobiert und sie stürzte auch auf dem Laptop ab. Ich muss etwas verstümmelt haben, als ich das Problem reduziert habe. Ich werde sehen, ob ich herausfinden kann, was das war.

Mein Laptop stürzte mit dem gleichen Code wie oben ab, mit Framework 4.0, aber Main stürzte sogar mit 4.5 ab. Beide Systeme verwenden VS'12 mit den neuesten Updates (Juli?).

Weitere Informationen :

  • IL-Code (kompiliertes Debug / Beliebige CPU / 4.0 / VS2010 (sollte diese IDE keine Rolle spielen?)): Http://codepad.org/boZDd98E
  • Nicht gesehen VS 2010 mit 4.0. Nicht mit / ohne Optimierungen abstürzen, andere Ziel-CPU, Debugger angeschlossen / nicht angeschlossen, etc. - Tim Medora
  • Abstürze im Jahr 2010, wenn ich AnyCPU verwende, sind in x86 in Ordnung. Abstürze in Visual Studio 2010 SP1 mit Platform Target = AnyCPU, aber mit Platform Target = x86 in Ordnung. Auf dieser Maschine ist auch VS2012RC installiert, sodass 4.5 möglicherweise einen direkten Austausch durchführt. Verwenden Sie AnyCPU und TargetPlatform = 3.5, dann stürzt es nicht ab und sieht aus wie eine Regression im Framework.- colinsmith
  • Kann auf x86, x64 oder AnyCPU in VS2010 mit 4.0 nicht reproduziert werden. - - Fuji
  • Kommt nur für x64 vor (2012rc, Fx4.5) - Henk Holterman
  • VS2012 RC auf Win8 RP. Dieses MDA wird beim Targeting von .NET 4.5 zunächst nicht angezeigt. Bei der Umstellung auf .NET 4.0 wurde der MDA angezeigt. Nach dem Zurückschalten auf .NET 4.5 bleibt der MDA erhalten. - Wayne
Gleno
quelle
Ich hätte nie gedacht, dass Sie einen statischen Konstruktor zusammen mit einem öffentlichen erstellen können. Ich wusste nie, dass es statische Konstruktoren gibt.
Cole Johnson
Ich habe eine Idee: Weil Sie B von einer statischen Klasse zu einer Klasse mit einem statischen Main ändern?
Cole Johnson
@ ChrisSinclair, das glaube ich nicht. Ich meine, ich habe diesen Code auf meinem Laptop getestet und die gleichen Ergebnisse erzielt.
Gleno
@ColeJohnson Ja, die IL stimmt mit allen außer dem einen offensichtlichen Ort überein. Hier im c # -Compiler scheint es keinen Fehler zu geben.
Michael Graczyk
14
Vielen Dank sowohl an das Originalplakat für die Berichterstattung hier als auch an Michael für seine hervorragende Analyse. Meine Kollegen in der CLR haben versucht, den Fehler hier zu reproduzieren, und festgestellt, dass er in der "Release Candidate" -Version der 64-Bit-CLR reproduziert wird, jedoch nicht in der endgültigen "Released To Manufacturing" -Version, die nachträglich eine Reihe von Fehlerkorrekturen aufwies. RC. (Die RTM-Version wird am 15. August 2012 für die Öffentlichkeit verfügbar sein.) Sie glauben daher, dass dies das gleiche Problem ist wie das hier gemeldete: connect.microsoft.com/VisualStudio/feedback/details/737108/…
Eric Lippert

Antworten:

114

Dies ist auch keine vollständige Antwort, aber ich habe ein paar Ideen.

Ich glaube, ich habe eine so gute Erklärung gefunden, wie wir sie finden werden, ohne dass jemand vom .NET JIT-Team antwortet.

AKTUALISIEREN

Ich habe etwas tiefer geschaut und glaube, die Ursache des Problems gefunden zu haben. Dies scheint auf eine Kombination aus einem Fehler in der JIT-Typinitialisierungslogik und einer Änderung im C # -Compiler zurückzuführen zu sein, die auf der Annahme beruht, dass die JIT wie beabsichtigt funktioniert. Ich denke, der JIT-Fehler war in .NET 4.0 vorhanden, wurde aber durch die Änderung im Compiler für .NET 4.5 aufgedeckt.

Ich denke nicht, dass dies beforefieldinithier das einzige Problem ist. Ich denke es ist einfacher als das.

Der Typ System.Stringin mscorlib.dll aus .NET 4.0 enthält einen statischen Konstruktor:

.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

In der .NET 4.5-Version von mscorlib.dll String.cctorfehlt (der statische Konstruktor) auffällig:

..... Kein statischer Konstruktor :( .....

In beiden Versionen ist der StringTyp geschmückt mit beforefieldinit:

.class public auto ansi serializable sealed beforefieldinit System.String

Ich habe versucht, einen Typ zu erstellen, der ähnlich wie IL kompiliert wird (so dass er statische Felder, aber keinen statischen Konstruktor enthält .cctor), aber ich konnte es nicht tun. Alle diese Typen haben eine .cctorMethode in 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(); } 
}

Ich vermute, dass sich zwischen .NET 4.0 und 4.5 zwei Dinge geändert haben:

Erstens: Der EE wurde so geändert, dass er automatisch initialisiert wird String.Empty aus nicht verwaltetem Code . Diese Änderung wurde wahrscheinlich für .NET 4.0 vorgenommen.

Zweitens: Der Compiler wurde so geändert, dass er keinen statischen Konstruktor für Zeichenfolgen ausgibt, da er das weiß String.Empty dieser von der nicht verwalteten Seite zugewiesen wird. Diese Änderung wurde anscheinend für .NET 4.5 vorgenommen.

Es scheint , dass der EE nicht nicht zuordnen String.Emptybald genug entlang einiger Optimierungsweg. Die am Compiler vorgenommene Änderung (oder was auch immer geändert wurde, um sie String.cctorverschwinden zu lassen ) erwartete, dass der EE diese Zuweisung vor String.Emptyder Ausführung eines Benutzercodes vornimmt, aber es scheint, dass der EE diese Zuweisung nicht vornimmt, bevor sie in Methoden mit reifizierten generischen Klassen vom Referenztyp verwendet wird.

Schließlich glaube ich, dass der Fehler auf ein tieferes Problem in der JIT-Typinitialisierungslogik hinweist. Es scheint, dass die Änderung im Compiler ein Sonderfall ist System.String, aber ich bezweifle, dass die JIT hier einen Sonderfall gemacht hatSystem.String .

Original

Zunächst einmal WOW Die BCL-Mitarbeiter sind mit einigen Leistungsoptimierungen sehr kreativ geworden. Viele der StringMethoden werden jetzt mit einem statisch zwischengespeicherten Thread- StringBuilderObjekt ausgeführt.

Ich bin diesem Beispiel eine Weile gefolgt, werde aber StringBuilderauf dem nicht verwendetTrim Codepfad verwendet, daher habe ich beschlossen, dass es sich nicht um ein statisches Thread-Problem handeln kann.

Ich glaube, ich habe eine seltsame Manifestation des gleichen Fehlers gefunden.

Dieser Code schlägt mit einer Zugriffsverletzung fehl:

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

Wenn jedoch uncomment Sie //new A<int>(out s);in Maindann funktioniert der Code nur in Ordnung. Wenn Aes mit einem Referenztyp geändert wird, schlägt das Programm tatsächlich fehl. Wenn Aes jedoch mit einem Werttyp geändert wird, schlägt der Code nicht fehl. Auch wenn Sie den Astatischen Konstruktor auskommentieren, schlägt der Code nie fehl. Nach dem Eingraben in Trimund Formatist klar, dass das Problem darin besteht, dass Lengthes inline ist und dass in diesen obigen Beispielen der StringTyp nicht initialisiert wurde. Insbesondere wird innerhalb des Körpers des AKonstruktors string.Emptynicht korrekt zugewiesen, obwohl innerhalb des Körpers von Main,string.Empty ist.

Es ist für mich erstaunlich, dass die Typinitialisierung von Stringirgendwie davon abhängt, ob sie Amit einem Werttyp bestätigt wird oder nicht . Meine einzige Theorie ist, dass es einen optimierenden JIT-Codepfad für die generische Typinitialisierung gibt, der von allen Typen gemeinsam genutzt wird, und dass dieser Pfad Annahmen über BCL-Referenztypen ("spezielle Typen?") Und deren Status macht. Ein kurzer Blick durch andere BCL-Klassen mit public staticFeldern zeigt, dass im Grunde alle einen statischen Konstruktor implementieren (auch solche mit leeren Konstruktoren und ohne Daten wie System.DBNullund System.Empty. BCL-Werttypen mit public staticFeldern scheinen keinen statischen Konstruktor zu implementieren (System.IntPtr zum Beispiel) Dies scheint darauf hinzudeuten, dass die JIT einige Annahmen zur Initialisierung des BCL-Referenztyps trifft.

Zu Ihrer Information Hier ist der JITed-Code für die beiden Versionen:

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 
    }

Der Rest des Codes ( Main) ist zwischen den beiden Versionen identisch.

BEARBEITEN

Darüber hinaus ist die IL aus den beiden Versionen identisch, mit Ausnahme des Aufrufs von A.ctorin B.Main(), in dem die IL für die erste Version Folgendes enthält:

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

gegen

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

in dieser Sekunde.

Eine andere zu beachtende Sache ist, dass der JITed-Code für A<int>.ctor(out string): der gleiche ist wie in der nicht generischen Version.

Michael Graczyk
quelle
3
Ich habe auf einem sehr ähnlichen Weg nach Antworten gesucht, aber es scheint nirgendwo hin zu führen. Dies scheint ein Problem mit der Zeichenfolgenklasse zu sein und hoffentlich kein allgemeineres Problem. Im Moment warte ich also darauf, dass jemand (Eric) mit dem Quellcode kommt und erklärt, was schief gelaufen ist und ob etwas anderes betroffen ist. Als kleinen Vorteil hat diese Diskussion bereits die Debatte beigelegt, ob man verwenden sollte string.Emptyoder ""... :)
Gleno
Ist die IL zwischen ihnen gleich?
Cole Johnson
49
Gute Analyse! Ich werde es an das BCL-Team weitergeben. Vielen Dank!
Eric Lippert
2
@EricLippert und andere: Ich habe festgestellt, dass Code wie typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);unter .NET 4.0 andere Ergebnisse liefert als .NET 4.5. Bezieht sich diese Änderung auf die oben beschriebene Änderung? Wie kann .NET 4.5 mich technisch ignorieren, wenn ich einen Feldwert ändere? Vielleicht sollte ich eine neue Frage dazu stellen?
Jeppe Stig Nielsen
4
@JeppeStigNielsen: Die Antworten auf Ihre Fragen lauten: "Vielleicht", "Ganz einfach, anscheinend" und "Dies ist eine Frage-und-Antwort-Site. Ja, das ist eine gute Idee, wenn Sie eine bessere Antwort auf Ihre Frage wünschen." als 'vielleicht' ".
Eric Lippert
3

Ich vermute sehr, dass dies durch diese Optimierung (im Zusammenhang mit BeforeFieldInit) in .NET 4.0 verursacht wird.

Wenn ich mich richtig erinnere:

Wenn Sie einen statischen Konstruktor explizit deklarieren, beforefieldinitwird ausgegeben, um der Laufzeit mitzuteilen, dass der statische Konstruktor ausgeführt werden muss, bevor statische Mitglieder darauf zugreifen können .

Meine Vermutung:

Ich würde vermuten , dass sie irgendwie diese Tatsache auf den x64 JITer geschraubt, so dass , wenn eine andere der Art statisches Element aus einer Klasse zugegriffen wird , deren eigene statische Konstruktor hat bereits ausgeführt, ist es irgendwie überspringt läuft (oder führt in der falschen Reihenfolge) die statischer Konstruktor - und verursacht daher einen Absturz. (Sie erhalten wahrscheinlich keine Nullzeigerausnahme weil sie nicht nullinitialisiert ist.)

Ich habe Ihren Code nicht ausgeführt, daher ist dieser Teil möglicherweise falsch. Wenn ich jedoch eine andere Vermutung anstellen müsste, würde ich sagen, dass möglicherweise etwas string.Format(oder Console.WriteLine, was ähnlich ist) intern zugegriffen werden muss, das den Absturz verursacht, z Vielleicht eine Gebietsschema- bezogene Klasse, die eine explizite statische Konstruktion benötigt.

Auch hier habe ich es nicht getestet, aber es ist meine beste Vermutung bei den Daten.

Fühlen Sie sich frei, meine Hypothese zu testen und mich wissen zu lassen, wie es geht.

user541686
quelle
Der Fehler tritt immer noch auf, wenn Bkein statischer Konstruktor vorhanden ist, und er tritt nicht auf, wenn er Amit einem Werttyp geändert wird. Ich finde es etwas komplizierter.
Michael Graczyk
@ MichaelGraczyk: Ich denke, ich kann das erklären (wieder mit Vermutungen). BEin statischer Konstruktor spielt keine Rolle. Da Aes einen statischen Ctor hat, bringt die Laufzeit die Reihenfolge, in der sie ausgeführt wird, durcheinander, wenn sie mit einer Gebietsschema-bezogenen Klasse in einem anderen Namespace verglichen wird. Dieses Feld ist also noch nicht initialisiert. Wenn Sie jedoch Amit einem Werttyp instanziieren , ist dies möglicherweise der zweite Durchlauf der Laufzeit durch die Instanziierung A(die CLR hat sie wahrscheinlich bereits mit einem Referenztyp als Optimierung vorinstanziiert), sodass die Reihenfolge funktioniert, wenn sie ein zweites Mal ausgeführt wird .
user541686
@MichaelGraczyk: Auch wenn dies nicht ganz die Erklärung ist - ich glaube, ich bin ziemlich davon überzeugt, dass die gegebene beforefieldinitOptimierung die Hauptursache ist. Es kann sein, dass sich einige der tatsächlichen Erklärungen von den von mir erwähnten unterscheiden, aber die Grundursache ist wahrscheinlich dieselbe.
user541686
Ich habe mehr in die IL geschaut und ich denke, Sie sind auf etwas. Ich denke nicht, dass die Idee des zweiten Durchgangs hier relevant sein wird, da der Code immer noch fehlschlägt, wenn ich willkürlich viele Anrufe bei mache A<object>.ctor().
Michael Graczyk
@ MichaelGraczyk: Gut zu hören und danke für diesen Test. Ich kann es leider nicht auf meinem eigenen Laptop reproduzieren. (2010 4.0 x64) Können Sie überprüfen, ob es tatsächlich mit der Formatierung von Zeichenfolgen zusammenhängt (dh mit dem Gebietsschema)? Was passiert, wenn Sie diesen Teil entfernen?
user541686
1

Eine Beobachtung, aber DotPeek zeigt den dekompilierten String. Leere also:

/// <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()
  {
  }
}

Wenn ich meine eigene auf Emptydie gleiche Weise deklariere , außer ohne das Attribut, erhalte ich den MDA nicht mehr:

class A<T>
{
    static readonly string Empty;

    static A() { }

    public A()
    {
        string.Format("{0}", Empty);
    }
}
lesscode
quelle
Und mit diesem Attribut? Wir haben bereits etablierte ""Lösungen.
Henk Holterman
Dieses Attribut "Leistungskritisch ..." wirkt sich auf den Attributkonstruktor selbst aus, nicht auf die Methoden, die das Attribut schmückt.
Michael Graczyk
Es ist intern. Wenn ich mein eigenes identisches Attribut definiere, verursacht es immer noch keinen MDA. Nicht, dass ich es erwarten würde - wenn der JITter nach diesem spezifischen Attribut sucht, wird er meins nicht finden.
Lesscode