150 votes

Ce qui ' s la cause de cette FatalExecutionEngineError dans .NET 4.5 beta ?

L'exemple de code ci-dessous est produite naturellement. Tout à coup, mon code ce un très méchant à consonance FatalExecutionEngineError d'exception. J'ai passé un bon 30 minutes à essayer de les isoler et de minimiser le coupable de l'échantillon. Compiler à l'aide de Visual Studio 2012 comme une application console:

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

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

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

Devrait produire cette erreur .NET framework 4 et 4,5:

FatalExecutionException screenshot

Est-ce un bug connu, ce qui est la cause et ce que je peux faire pour l'atténuer? Mon travail actuel est de ne pas utiliser string.Empty, mais suis-je fausse route? Changer quoi que ce soit à propos de ce code pour le faire fonctionner comme vous vous attendriez - par exemple en supprimant le vide statique constructeur de A, ou de changer le type de paramètre à partir d' object de int.

J'ai essayé ce code sur mon portable et il n'a pas à se plaindre. Cependant, je l'ai fait essayer mon application principale et il s'est écrasé sur l'ordinateur portable. Je dois avoir mutilé de suite quelque chose pour réduire le problème, je vais voir si je peux comprendre ce que c'était.

Mon portable s'est écrasé avec le même code que ci-dessus, avec le framework 4.0, mais l'essentiel se bloque même avec 4.5. Les deux systèmes sont à l'aide de VS'12 avec les dernières mises à jour (juillet?).

Plus d'informations:

  • IL Code (compilé en Debug/CPU/4.0/VS2010 (pas que l'IDE devrait avoir de l'importance?)): http://codepad.org/boZDd98E
  • Pas vu VS 2010 avec 4.0. Ne pas s'écraser avec/sans optimisations, différent du PROCESSEUR cible, débogueur attaché/attachée, etc. - Tim Medora
  • Accidents en 2010 si j'utilise AnyCPU, est bien dans la x86. Les accidents dans Visual Studio 2010 SP1, en utilisant la Plate-forme Cible = AnyCPU, mais bien avec Plate-forme Cible=x86. Cette machine a VS2012RC installé en tant que bien de sorte 4.5 éventuellement faire un lieu de remplacement. Utilisation AnyCPU et TargetPlatform = 3.5 alors qu'il ne tombe pas en panne ressemble tellement à une régression dans le Cadre.- colinsmith
  • Ne peut pas 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. D'abord ne Pas voir cette MDA lors du ciblage .NET 4.5. Lors du passage à la cible .NET 4.0 de la MDA est apparu. Ensuite, après être repassé .NET 4.5 de la MDA reste. - Wayne

114voto

Michael Graczyk Points 3469

Ce n'est pas une réponse, mais j'ai quelques idées.

Je crois que j'ai trouvé une bonne explication que nous trouverons sans quelqu'un de la .NET JIT équipe de réponse.

Mise à JOUR

J'ai regardé un peu plus loin, et je crois avoir trouvé la source du problème. Il semble être causée par une combinaison d'un bug dans l'équipe type de l'initialisation de la logique, et un changement dans le compilateur C# qui repose sur l'hypothèse que l'équipe fonctionne comme prévu. Je pense que le JIT bug existait dans .NET 4.0, mais il a été découvert par le changement dans le compilateur .NET 4.5.

Je ne pense pas qu' beforefieldinit est la seule question ici. Je pense que c'est plus simple.

Le type System.String 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     string System.String::Empty
  IL_000a:  ret
} // end of method String::.cctor

Dans l' .NET version 4.5 de mscorlib.dll, String.cctor (le constructeur statique) est aux abonnés absents:

..... Aucun constructeur statique :( .....

Dans les deux versions de l' String type est orné beforefieldinit:

.class public auto ansi serializable sealed beforefieldinit System.String

J'ai essayé de créer un type de dresser le IL de même (alors qu'il a les champs statiques, mais aucun constructeur statique .cctor), mais je ne pouvais pas le faire. Tous ces types ont un .cctor méthode de l'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(); } 
}

Ma conjecture est que les deux choses ont changé entre les deux .NET 4.0 et 4.5:

D'abord: L'EE a été modifié de sorte qu'il serait d'initialiser automatiquement String.Empty de code non managé. Ce changement a été probablement faite pour .NET 4.0.

Deuxième: Le compilateur changé, de sorte qu'il n'émettent pas un constructeur statique pour la chaîne, sachant qu' String.Empty seraient affectés de la non géré side. Ce changement semble avoir été faite pour .NET 4.5.

Il semble que l'EE n'a pas attribuer String.Empty assez vite le long de certaines d'optimisation des chemins. La modification apportée à l'un compilateur (ou quel que soit modifiée pour rendre String.cctor disparaissent) devrait EE de procéder à cette affectation avant tout code d'utilisateur exécute, mais il semble que l'EE ne permet pas de faire ce travail avant d' String.Empty est utilisé dans les méthodes de type de référence réifiée des classes génériques.

Enfin, je pense que le bug est le signe d'un problème plus profond dans l'équipe type de l'initialisation de la logique. Il semble que le changement dans le compilateur est un cas particulier pour System.String, mais je doute que l'équipe a fait un cas particulier ici pour System.String.

D'origine

Tout d'abord, WOW La BCL gens ont obtenu de très créatif avec quelques optimisations de performances. Beaucoup de de la String méthodes sont désormais effectuées à l'aide d'un Fil statique de cache StringBuilder objet.

Je l'ai suivi dans cette voie pendant un certain temps, mais StringBuilder n'est pas utilisé sur la Trim chemin de code, j'ai donc décidé qu'il ne pouvait pas être un Thread problème statique.

Je crois que j'ai trouvé une étrange manifestation de la même bug.

Ce code ne fonctionne pas avec une violation d'accès:

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

Toutefois, si vous supprimez //new A<int>(out s); en Main alors que le code fonctionne très bien. En fait, si l' A est réifiée avec n'importe quel type de référence, le programme ne parvient pas, mais si A est réifiée avec n'importe quel type de valeur que le code n'échoue pas. Aussi, si vous commentez As'constructeur statique, le code n'échoue jamais. En creusant, en Trim et Format, il est clair que le problème est qu' Length est inline, et que dans ces échantillons au-dessus de l' String type n'a pas été initialisé. En particulier, à l'intérieur du corps de l' As'constructeur, string.Empty n'est pas correctement affecté, bien qu'à l'intérieur du corps de l' Main, string.Empty est attribuée correctement.

Il est étonnant pour moi que le type d'initialisation de String d'une certaine manière dépend de si oui ou non A est réifiée avec un type de valeur. Ma théorie est qu'il y a de l'optimisation JIT chemin de code pour le type générique-initialisation qui est partagé entre tous les types, et que ce chemin fait des hypothèses sur la BCL types de référence ("types particuliers?") et de leur état. Un rapide coup d'oeil si d'autres BCL classes avec public static champs montre que, fondamentalement, tous d'entre eux de mettre en œuvre un constructeur statique (même ceux avec les constructeurs et les données, comme System.DBNull et System.Empty. BCL types de valeur avec public static champs ne semblent pas mettre en œuvre un constructeur statique (System.IntPtr, par exemple). Cela semble indiquer que le JIT fait quelques hypothèses sur la BCL type de référence de l'initialisation.

Pour info Voici le JITed code pour les deux versions:

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 
    }

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

MODIFIER

En outre, le IL de les deux versions est identique sauf pour l'appel à la A.ctor en B.Main(), où la IL pour la première version contient:

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

rapport

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

dans la seconde.

Une autre chose à noter est que le JITed code pour A<int>.ctor(out string): c'est le même que dans le non-générique.

3voto

Mehrdad Points 70493

Je soupçonne fortement que cela est causé par l'optimisation (liés à l' BeforeFieldInit) .NET 4.0.

Si je me souviens bien:

Lorsque vous déclarez un constructeur statique explicitement, beforefieldinit est émise, en indiquant à l'exécution que le constructeur statique doit être exécuté avant que tout membre statique d'accès.

Ma conjecture:

Je suppose qu'ils en quelque sorte vissé vers le haut de ce fait, sur l'x64 JITer, de sorte que quand un différent type de membre statique est accessible à partir d'une classe dont le propre constructeur statique a déjà été exécuté, c'est en quelque sorte saute en cours d'exécution (ou exécute dans le mauvais ordre) le constructeur statique -- et, par conséquent, provoque un blocage. (Vous n'obtenez pas une exception de pointeur null, probablement parce qu'il n'est pas null-initialisé.)

J'ai pas exécuter votre code, cette partie peut-être tort, mais si je devais faire une autre supposition, je dirais qu'il pourrait être quelque chose d' string.Format (ou Console.WriteLine, ce qui est similaire) doit accéder à l'interne qui est à l'origine du crash, comme peut-être un des paramètres régionauxliés à la classe qui doit explicite statique de la construction.

Encore une fois, je ne l'ai pas testé, mais c'est ma meilleure supposition les données.

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

1voto

Wayne Points 3098

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

Si je déclare mon propre `` la même façon sauf sans l’attribut, je n’obtiens plus le MDA :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X