Quelle est la cause de cette FatalExecutionEngineError dans .NET 4.5 beta?

L’échantillon de code ci-dessous s’est produit naturellement. Tout à coup, mon code a généré une exception FatalExecutionEngineError au son très méchant. J’ai passé 30 bonnes minutes à essayer d’isoler et de minimiser l’échantillon de coupables. Comstackz ceci en utilisant Visual Studio 2012 en tant qu’application de console:

 class A { static A() { } public A() { ssortingng.Format("{0}", ssortingng.Empty); } } class B { static void Main() { new A(); } } 

Devrait produire cette erreur sur .NET Framework 4 et 4.5:

Capture d'écran de FatalExecutionException

Est-ce un bug connu, quelle en est la cause et que puis-je faire pour l’atténuer? Mon travail actuel consiste à ne pas utiliser de ssortingng.Empty . ssortingng.Empty , mais suis-je en ssortingng.Empty aboyer le mauvais arbre? Changer quelque chose à propos de ce code le fait fonctionner comme prévu – par exemple en supprimant le constructeur statique vide de A ou en changeant le paramètre de type object en int .

J’ai essayé ce code sur mon ordinateur portable et il ne s’est pas plaint. Cependant, j’ai essayé mon application principale et celle-ci s’est également écrasée sur l’ordinateur portable. J’ai dû supprimer quelque chose en réduisant le problème, je vais voir si je peux comprendre ce que c’était.

Mon ordinateur portable est tombé en panne avec le même code que ci-dessus, avec Framework 4.0, mais le principal se bloque même avec 4.5. Les deux systèmes utilisent VS’12 avec les dernières mises à jour (juillet?).

Plus d’informations :

  • Code IL (compilé Debug / Any CPU / 4.0 / VS2010 (ce n’est pas que l’IDE devrait être important?)): Http://codepad.org/boZDd98E
  • Pas vu VS 2010 avec 4.0. Ne pas planter avec / sans optimisations, CPU cible différent, débogueur attaché / non attaché, etc. – Tim Medora
  • Crashes en 2010 si j’utilise AnyCPU, c’est bien en x86. Se bloque dans Visual Studio 2010 SP1, à l’aide de Platform Target = AnyCPU, mais avec Platform Target = x86. VS2012RC est également installé sur cette machine, donc peut-être un remplacement sur place. Utilisez AnyCPU et TargetPlatform = 3.5 alors il ne plante pas, il ressemble à une régression dans le Framework.- colinsmith
  • Impossible de reproduire sur x86, x64 ou AnyCPU dans VS2010 avec 4.0. – Fuji
  • Ne se produit que pour x64, (2012rc, Fx4.5) – Henk Holterman
  • VS2012 RC sur Win8 RP. Initialement Ne pas voir cette MDA lors du ciblage de .NET 4.5. Lors du passage au ciblage de .NET 4.0, le MDA est apparu. Ensuite, après le retour à .NET 4.5, le MDA rest. – Wayne

Ce n’est pas non plus une réponse complète, mais j’ai quelques idées.

Je crois que j’ai trouvé une explication aussi bonne que celle que nous trouverons sans que quelqu’un de l’équipe .NET JIT réponde.

METTRE À JOUR

J’ai regardé un peu plus loin et je crois avoir trouvé la source du problème. Cela semble être dû à une combinaison d’un bogue dans la logique d’initialisation de type JIT et à une modification du compilateur C # qui repose sur l’hypothèse que le JIT fonctionne comme prévu. Je pense que le bogue JIT existait dans .NET 4.0, mais a été découvert par la modification du compilateur pour .NET 4.5.

Je ne pense pas que beforefieldinit soit le seul problème ici. Je pense que c’est plus simple que ça.

Le type System.Ssortingng dans mscorlib.dll à partir de .NET 4.0 contient un constructeur statique:

 .method private hidebysig specialname rtspecialname static void .cctor() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "" IL_0005: stsfld ssortingng System.Ssortingng::Empty IL_000a: ret } // end of method Ssortingng::.cctor 

Dans la version .NET 4.5 de mscorlib.dll, Ssortingng.cctor (le constructeur statique) est manifestement absent:

….. Pas de constructeur statique 🙁 …..

Dans les deux versions, le type Ssortingng est orné de beforefieldinit :

 .class public auto ansi serializable sealed beforefieldinit System.Ssortingng 

J’ai essayé de créer un type qui comstackrait en IL de manière similaire (de sorte qu’il ait des champs statiques mais pas de constructeur statique .cctor ), mais je ne pouvais pas le faire. Tous ces types ont une méthode .cctor dans IL:

 public class MySsortingng1 { public static MySsortingng1 Empty = new MySsortingng1(); } public class MySsortingng2 { public static MySsortingng2 Empty = new MySsortingng2(); static MySsortingng2() {} } public class MySsortingng3 { public static MySsortingng3 Empty; static MySsortingng3() { Empty = new MySsortingng3(); } } 

Je suppose que deux choses ont changé entre .NET 4.0 et 4.5:

Tout d’abord: l’EE a été modifié pour qu’il initialise automatiquement Ssortingng.Empty partir du code non géré. Cette modification a probablement été apscope pour .NET 4.0.

Deuxièmement: le compilateur a été modifié afin de ne pas émettre de constructeur statique pour la chaîne, sachant que Ssortingng.Empty serait assigné du côté non géré. Ce changement semble avoir été fait pour .NET 4.5.

Il semble que l’EE n’atsortingbue pas assez rapidement Ssortingng.Empty sur certains chemins d’optimisation. La modification apscope au compilateur (ou tout ce qui a changé pour faire disparaître Ssortingng.cctor ) attendait que l’EE effectue cette affectation avant l’exécution du code utilisateur, mais il semble que l’EE Ssortingng.Empty pas cette atsortingbution avant Ssortingng.Empty est utilisé dans les méthodes de référence. type reified génériques classes.

Enfin, je pense que le bogue indique un problème plus profond dans la logique d’initialisation de type JIT. Il semble que le changement dans le compilateur soit un cas particulier pour System.Ssortingng , mais je doute que le JIT ait créé un cas particulier pour System.Ssortingng .

Original

Tout d’abord, WOW Les gens de la BCL ont été très créatifs en optimisant leurs performances. De nombreuses méthodes Ssortingng sont désormais effectuées à l’aide d’un object Ssortingng Thread statique mis en cache Thread.

J’ai suivi cette piste pendant un moment, mais SsortingngBuilder n’est pas utilisé sur le chemin du code Trim , j’ai donc décidé que cela ne pouvait pas être un problème statique du thread.

Je pense cependant avoir trouvé une manifestation étrange du même bug.

Ce code échoue avec une violation d’access:

 class A { static A() { } public A(out ssortingng s) { s = ssortingng.Empty; } } class B { static void Main() { ssortingng s; new A(out s); //new A(out s); System.Console.WriteLine(s.Length); } } 

Cependant, si vous décommentez //new A(out s); dans Main alors le code fonctionne très bien. En fait, si A est réifié avec n’importe quel type de référence, le programme échoue, mais si A est réifié avec n’importe quel type de valeur, le code n’échoue pas. De plus, si vous mettez en commentaire le constructeur statique de A, le code n’échoue jamais. Après avoir creusé dans Trim and Format , il est clair que le problème est que la Length est en cours de mise en ligne, et que dans ces exemples au-dessus du type Ssortingng n’a pas été initialisé. En particulier, dans le corps du constructeur de A, ssortingng.Empty n’est pas correctement atsortingbué, bien que dans le corps de Main , ssortingng.Empty soit affecté correctement.

Il est étonnant pour moi que l’initialisation de type de Ssortingng dépend d’une manière ou d’ A réification de A avec un type de valeur. Ma seule théorie est qu’il existe un chemin de code JIT d’optimisation pour l’initialisation de type générique qui est partagé entre tous les types, et que ce chemin fait des hypothèses sur les types de référence BCL (“types spéciaux”) et leur état. Un coup d’œil rapide sur les autres classes BCL avec public static champs public static montre que tous implémentent tous un constructeur statique (même ceux avec des constructeurs vides et aucune donnée, comme System.DBNull et System.Empty . Les types de valeur BCL avec public static champs public static ne semblent pas pour implémenter un constructeur statique ( System.IntPtr , par exemple) Cela semble indiquer que JIT émet des hypothèses sur l’initialisation du type de référence BCL.

FYI Voici le code JIT pour les deux versions:

A.ctor(out ssortingng) :

  public A(out ssortingng 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 = ssortingng.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.ctor(out ssortingng) :

  public A(out ssortingng s) { 00000000 sub rsp,28h 00000004 mov rax,rdx s = ssortingng.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 } 

Le rest du code ( Main ) est identique entre les deux versions.

MODIFIER

De plus, l’IL des deux versions est identique à l’exception de l’appel à A.ctor dans B.Main() , où l’IL de la première version contient:

 newobj instance void class A`1::.ctor(ssortingng&) 

contre

 ... A`1... 

dans la seconde.

Une autre chose à noter est que le code JIT pour A.ctor(out ssortingng) : est le même que dans la version non générique.

Je soupçonne fortement que cela est dû à cette optimisation (liée à BeforeFieldInit ) dans .NET 4.0.

Si je me souviens bien:

Lorsque vous déclarez explicitement un constructeur statique, beforefieldinit est émis, indiquant à l’exécution que le constructeur statique doit être exécuté avant tout access statique à un membre .

Ma conjecture:

J’imagine qu’ils ont en quelque sorte foiré ce fait sur JITer x64, de sorte que lorsque l’ on accède à un membre statique d’ un type différent à partir d’une classe dont le constructeur statique a déjà exécuté, il ignore l’ exécution (ou s’exécute dans le mauvais ordre) constructeur statique – et provoque donc un crash. (Vous n’obtenez pas une exception de pointeur null, probablement parce que ce n’est pas une initialisation par null.)

Je n’ai pas exécuté votre code, donc cette partie peut être incorrecte – mais si je devais faire une autre conjecture, je dirais que cela pourrait être quelque chose de ssortingng.Format (ou Console.WriteLine , qui est similaire) doit accéder en interne c’est provoquant le crash, par exemple une classe liée aux parameters régionaux nécessitant une construction statique explicite.

Encore une fois, je ne l’ai pas testé, mais c’est ma meilleure idée des données.

N’hésitez pas à tester mon hypothèse et laissez-moi savoir comment ça se passe.

Une observation, mais DotPeek montre la chaîne décompilée. Vide donc:

 ///  /// Represents the empty ssortingng. This field is read-only. ///  /// 1 [__DynamicallyInvokable] public static readonly ssortingng Empty; internal sealed class __DynamicallyInvokableAtsortingbute : Atsortingbute { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public __DynamicallyInvokableAtsortingbute() { } } 

Si je déclare mon propre Empty la même manière, sauf sans l’atsortingbut, je ne reçois plus le MDA:

 class A { static readonly ssortingng Empty; static A() { } public A() { ssortingng.Format("{0}", Empty); } }