43 votes

Dépannage de .NET "Fatal Execution Engine Error" (erreur fatale du moteur d'exécution)

Résumé :

Je reçois périodiquement une erreur fatale du moteur d'exécution .NET sur une application que je ne parviens pas à déboguer. La boîte de dialogue qui s'affiche propose uniquement de fermer le programme ou d'envoyer des informations sur l'erreur à Microsoft. J'ai essayé de consulter les informations plus détaillées, mais je ne sais pas comment les utiliser.

Erreur :

L'erreur est visible dans l'Observateur d'événements sous Applications et se présente comme suit :

.NET Runtime version 2.0.50727.3607 - Erreur fatale du moteur d'exécution (7A09795E) (80131506)

L'ordinateur qui l'exécute est Windows XP Professional SP 3. (Intel Core2Quad Q6600 2.4GHz avec 2.0 GB de RAM) D'autres projets basés sur .NET qui n'ont pas de téléchargement multithread (voir ci-dessous) semblent fonctionner parfaitement.

Application :

L'application est écrite en C#/.NET 3.5 avec VS2008, et installée via un projet d'installation.

L'application est multithread et télécharge des données à partir de plusieurs serveurs web à l'aide des éléments suivants System.Net.HttpWebRequest et ses méthodes. J'ai déterminé que l'erreur .NET a quelque chose à voir avec le threading ou HttpWebRequest, mais je n'ai pas pu m'approcher davantage car cette erreur particulière semble impossible à déboguer.

J'ai essayé de gérer les erreurs à plusieurs niveaux, y compris ce qui suit dans Program.cs :

// handle UI thread exceptions
Application.ThreadException += Application_ThreadException;

// handle non-UI thread exceptions
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

// force all windows forms errors to go through our handler
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

Autres notes et ce que j'ai essayé...

  • J'ai installé Visual Studio 2008 sur la machine cible et j'ai essayé de l'exécuter en mode débogage, mais l'erreur se produit toujours, sans indication de l'endroit du code source où elle s'est produite.
  • Lorsque vous exécutez le programme à partir de sa version installée (Release), l'erreur se produit plus fréquemment, généralement quelques minutes après le lancement de l'application. Lorsque le programme est exécuté en mode débogage dans VS2008, il peut fonctionner pendant des heures ou des jours avant de générer l'erreur.
  • Réinstallation de .NET 3.5 et vérification de l'application de toutes les mises à jour.
  • J'ai cassé des objets de mon bureau par frustration.
  • Réécriture de certaines parties du code qui traitent du threading et du téléchargement pour tenter d'attraper et d'enregistrer les exceptions, bien que l'enregistrement semble aggraver le problème (et ne fournisse jamais aucune donnée).

Question :

Quelles mesures puis-je prendre pour résoudre ou déboguer ce type d'erreur ? Les vidages de mémoire et autres semblent être la prochaine étape, mais je ne suis pas expérimenté pour les interpréter. Peut-être y a-t-il quelque chose de plus que je puisse faire dans le code pour essayer de détecter les erreurs... Ce serait bien si l'erreur "Fatal Execution Engine Error" était plus informative, mais les recherches sur Internet m'ont seulement appris qu'il s'agit d'une erreur courante pour de nombreux éléments liés à .NET.

0 votes

Avez-vous essayé de l'exécuter sous .NET 4.0 ? Bien que ce ne soit pas une solution, c'est un autre point de données.

0 votes

@Eamon : merci, c'est une bonne idée que je vais essayer.

1 votes

Pouvez-vous limiter le problème à certains composants/classes ? Si oui, vous pouvez peut-être obtenir au moins le code problématique en ajoutant les sorties de la position réelle à un journal de débogage.

45voto

Hans Passant Points 475940

Eh bien, vous avez un gros problème. Cette exception est levée par le CLR lorsqu'il détecte que l'intégrité du tas de déchets collectés est compromise. La corruption du tas, le fléau de tout programmeur qui a déjà écrit du code dans un langage non géré comme C ou C++.

Ces langues font très Il est facile de corrompre le tas, il suffit d'écrire au-delà de la fin d'un tableau alloué sur le tas. Ou d'utiliser la mémoire après qu'elle ait été libérée. Ou avoir une mauvaise valeur pour un pointeur. Le genre de bugz que le code géré a été inventé pour résoudre.

Mais vous utilisez du code géré, à en juger par votre question. Enfin, la plupart du temps, votre Le code est géré. Mais vous exécutez lots de code non géré. Tout le code de bas niveau qui fait réellement fonctionner une HttpWebRequest n'est pas géré. Le CLR l'est aussi, il a été écrit en C++ et est donc techniquement tout aussi susceptible de corrompre le tas. Mais après plus de quatre mille révisions et des millions de programmes qui l'ont utilisé, les chances qu'il souffre encore des poux du tas sont les suivantes très petit.

Il n'en va pas de même pour tous les autres codes non gérés qui veulent un morceau de HttpWebRequest. Le code que vous ne connaissez pas parce que vous ne l'avez pas écrit et qu'il n'est pas documenté par Microsoft. Votre pare-feu. Votre scanner de virus. Le moniteur d'utilisation d'Internet de votre entreprise. Dieu sait quel "accélérateur de téléchargement".

Isolez le problème, supposez que ce n'est ni votre code ni celui de Microsoft qui cause le problème. Supposez d'abord que c'est l'environnement et débarrassez-vous du crapware.

Pour une histoire épique de FEEE environnementale, lire ce fil .

0 votes

Une excellente analyse de la situation dans son ensemble et un aperçu de la situation actuelle. raisons que cette erreur peut se produire. Néanmoins, il serait bon d'avoir des directives ou des méthodes permettant de reproduire l'erreur, de la contourner ou de l'éviter complètement.

2 votes

Désolé, il n'y en a pas. L'exception est soulevée longtemps après que le mal ait été fait. Partez du principe que la cause est environnementale, changez l'environnement.

0 votes

Par exemple, je pense utiliser une bibliothèque tierce telle que IPWorks de /n software, mais je ne sais pas si elle utilise les mêmes blocs de code non gérés ou si elle les évite effectivement. ( nsoftware.com/ipworks/v8/default.aspx )

9voto

ouflak Points 1206

Comme les suggestions précédentes sont de nature assez générique, j'ai pensé qu'il pourrait être utile de poster ma propre bataille contre cette exception avec des exemples de code spécifiques, les changements d'arrière-plan que j'ai mis en œuvre pour que cette exception se produise, et comment je l'ai résolue.

D'abord, la version TL;DR : J'utilisais une dll interne écrite en C++ (non gérée). J'ai transmis un tableau d'une taille spécifique à partir de mon exécutable .NET. Le code non géré a tenté d'écrire dans un emplacement de tableau qui n'était pas alloué par le code géré. Cela a provoqué une corruption de la mémoire qui a ensuite été configurée pour être collectée par le garbage collector. Lorsque le ramasseur d'ordures se prépare à collecter la mémoire, il vérifie d'abord l'état de la mémoire (et des limites). Quand il trouve la corruption, BOOM .

Maintenant, la version détaillée :

J'utilise une dll non gérée développée en interne, écrite en C++. Mon propre développement d'interface graphique est en C# .Net 4.0. J'appelle une variété de ces méthodes non gérées. Cette dll agit effectivement comme ma source de données. Un exemple de définition externe de la dll :

    [DllImport(@"C:\Program Files\MyCompany\dataSource.dll",
        EntryPoint = "get_sel_list",
        CallingConvention = CallingConvention.Winapi)]
    private static extern int ExternGetSelectionList(
        uint parameterNumber,
        uint[] list,
        uint[] limits,
        ref int size);

J'intègre ensuite les méthodes dans ma propre interface pour les utiliser dans l'ensemble de mon projet :

    /// <summary>
    /// Get the data for a ComboBox (Drop down selection).
    /// </summary>
    /// <param name="parameterNumber"> The parameter number</param>
    /// <param name="messageList"> Message number </param>
    /// <param name="valueLimits"> The limits </param>
    /// <param name="size"> The maximum size of the memory buffer to 
    /// allocate for the data </param>
    /// <returns> 0 - If successful, something else otherwise. </returns>
    public int GetSelectionList(uint parameterNumber, 
           ref uint[] messageList, 
           ref uint[] valueLimits, 
           int size)
    {
        int returnValue = -1;
        returnValue = ExternGetSelectionList(parameterNumber,
                                         messageList, 
                                         valueLimits, 
                                         ref size);
        return returnValue;
    }

Un exemple d'appel de cette méthode :

            uint[] messageList = new uint[3];
            uint[] valueLimits = new uint[3];
            int dataReferenceParameter = 1;

            // BUFFERSIZE = 255.
            MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
                          dataReferenceParameter, 
                          ref messageList, 
                          ref valueLimits, 
                          BUFFERSIZE);

Dans l'interface graphique, on navigue à travers différentes pages contenant une variété de graphiques et d'entrées utilisateur. La méthode précédente m'a permis d'obtenir les données pour remplir ComboBoxes . Un exemple de ma configuration de navigation et de mon appel à l'époque avant cette exception :

Dans ma fenêtre d'hôte, je configure une propriété :

    /// <summary>
    /// Gets or sets the User interface page
    /// </summary>
    internal UserInterfacePage UserInterfacePageProperty
    {
        get
        {
            if (this.userInterfacePage == null)
            {
                this.userInterfacePage = new UserInterfacePage();
            }

            return this.userInterfacePage;
        }

        set { this.userInterfacePage = value; }
    }

Puis, au besoin, je navigue vers la page :

MainNavigationWindow.MainNavigationProperty.Navigate(
        MainNavigation.MainNavigationProperty.UserInterfacePageProperty);

Tout a fonctionné assez bien, même si j'ai eu de sérieux problèmes de reptation. Lorsque l'on navigue en utilisant l'objet ( Méthode NavigationService.Navigate (Objet) ), le paramètre par défaut de l IsKeepAlive la propriété est true . Mais le problème est plus grave que cela. Même si vous définissez le IsKeepAlive dans le constructeur de cette page spécifiquement pour false il est toujours laissé seul par le ramasseur d'ordures comme s'il était true . Pour beaucoup de mes pages, ce n'était pas un problème. Elles avaient de petites empreintes mémorielles et pas grand-chose à faire. Mais beaucoup d'autres de ces pages comportaient de grands graphiques très détaillés à des fins d'illustration. Il n'a pas fallu longtemps pour que l'utilisation normale de cette interface par les opérateurs de notre équipement provoque d'énormes allocations de mémoire qui ne s'effacent jamais et finissent par bloquer tous les processus de la machine. Après que le rush du développement initial se soit calmé, passant d'un tsunami à un raz-de-marée, j'ai finalement décidé de m'attaquer aux fuites de mémoire une fois pour toutes. Je ne vais pas entrer dans les détails de toutes les astuces que j'ai mises en œuvre pour nettoyer la mémoire ( Référence faible aux images, en désactivant les gestionnaires d'événements lors de Unload(), en utilisant une minuterie personnalisée mettant en œuvre la fonction IWeakEventListener interface, etc...). Le changement clé que j'ai fait était de naviguer vers les pages en utilisant l'Uri au lieu de l'objet ( Méthode NavigationService.Navigate (Uri) ). Il existe deux différences importantes dans l'utilisation de ce type de navigation :

  1. IsKeepAlive est réglé sur false par défaut.
  2. Le ramasseur d'ordures va maintenant essayer de nettoyer l'objet de navigation comme si IsKeepAlive a été fixé à false .

Maintenant, ma navigation ressemble à ça :

MainNavigation.MainNavigationProperty.Navigate(
    new Uri("/Pages/UserInterfacePage.xaml", UriKind.Relative));

Autre chose à noter ici : Cela n'affecte pas seulement la manière dont les objets sont nettoyés par le ramasseur d'ordures, mais aussi la manière dont ils sont initialement alloué en mémoire comme j'allais bientôt le découvrir.

Tout semble avoir bien fonctionné. Ma mémoire se nettoyait rapidement pour revenir à un état proche de l'état initial lorsque je naviguais dans les pages à forte intensité graphique, jusqu'à ce que j'atteigne cette page particulière avec cet appel particulier à la dll dataSource pour remplir quelques comboBoxes. Ensuite, j'ai eu ce désagrément FatalEngineExecutionError . Après des jours de recherche et de découverte de vagues suggestions, ou de solutions très spécifiques qui ne s'appliquaient pas à moi, et après avoir utilisé toutes les armes de débogage de mon arsenal personnel de programmation, j'ai finalement décidé que la seule façon de résoudre ce problème était de reconstruire une copie exacte de cette page particulière, élément par élément, méthode par méthode, ligne par ligne, jusqu'à ce que je trouve enfin le code qui génère cette exception. C'était aussi fastidieux et pénible que je le laisse entendre, mais j'ai fini par le retrouver.

Il s'est avéré que le problème venait de la façon dont la dll non gérée allouait de la mémoire pour écrire des données dans les tableaux que j'envoyais pour les remplir. Cette méthode particulière regardait en fait le numéro du paramètre et, à partir de cette information, allouait un tableau d'une taille particulière en fonction de la quantité de données qu'elle s'attendait à écrire dans le tableau que j'envoyais. Le code qui s'est planté :

            uint[] messageList = new uint[2];
            uint[] valueLimits = new uint[2];
            int dataReferenceParameter = 1;

            // BUFFERSIZE = 255.
            MainNavigationWindow.MainNavigationProperty.DataSourceWrapper.GetSelectionList(
                           dataReferenceParameter, 
                           ref messageList, 
                           ref valueLimits, 
                           BUFFERSIZE);

Ce code peut sembler identique à l'exemple ci-dessus, mais il comporte une petite différence. La taille du tableau que j'alloue est 2 no 3 . Je l'ai fait parce que je savais que cette ComboBox particulière n'aurait que deux éléments de sélection, contrairement aux autres ComboBox de la page qui avaient toutes trois éléments de sélection. Cependant, le code non géré n'a pas vu les choses comme je les voyais. Il a pris le tableau que je lui ai remis et a essayé d'écrire une taille 3 dans ma taille 2 allocation, et c'était tout. * bang ! * * crash ! * J'ai changé la taille de l'allocation à 3, et l'erreur a disparu.

Ce code particulier avait déjà fonctionné sans cette erreur pendant au moins un an. Mais le simple fait de naviguer sur cette page via un fichier de type Uri par opposition à un Object a provoqué l'apparition du crash. Cela implique que l'objet initial doit être alloué différemment à cause de la méthode de navigation que j'ai utilisée. Étant donné qu'avec mon ancienne méthode de navigation, la mémoire était simplement empilée et laissée à ma disposition pour l'éternité, il ne semblait pas important qu'elle soit un peu corrompue à un ou deux petits endroits. Dès que le ramasseur d'ordures devait faire quelque chose avec cette mémoire (comme la nettoyer), il détectait la corruption de la mémoire et lançait l'exception. Ironiquement, ma fuite de mémoire majeure couvrait une erreur de mémoire fatale !

Il est évident que nous allons revoir cette interface pour éviter que des hypothèses aussi simples ne provoquent de tels crashs à l'avenir. J'espère que cela aidera d'autres personnes à comprendre ce qui se passe dans leur propre code.

4 votes

Merci pour l'effort et l'enquête détaillée. Elle peut très bien être utile à d'autres.

3voto

Eamon Nerbonne Points 21663

Une présentation qui pourrait être un bon tutoriel pour savoir par où commencer avec ce genre de problème est la suivante : Débogage de production hardcore en .NET par Ingo Rammer .

Je fais un peu de codage C++/CLI, et la corruption du tas n'entraîne généralement pas cette erreur ; habituellement, la corruption du tas entraîne soit une corruption des données et une exception normale subséquente, soit une erreur de protection de la mémoire - ce qui ne veut probablement rien dire.

En plus d'essayer .net 4.0 (qui charge le code non géré différemment), vous devriez comparer les éditions x86 et x64 du CLR - si possible - la version x64 a un espace d'adressage plus grand et donc un comportement de malloc (+fragmentation) complètement différent et donc vous pourriez avoir de la chance et avoir une erreur différente (plus débuggable) ici (si elle se produit du tout).

De plus, avez-vous activé le débogage de code non géré dans le débogueur (une option du projet), lorsque vous exécutez avec Visual Studio activé ? Et les assistants de débogage gérés sont-ils activés ?

0 votes

Je n'ai pas encore activé le débogage du code non géré, c'est quelque chose que je vais essayer grâce à votre suggestion. En ce qui concerne les Managed Debug Assistants, je n'en avais pas connaissance jusqu'à présent, c'est donc quelque chose que je vais immédiatement examiner. Pour l'instant, il y a certains MDA qui sont vérifiés et d'autres qui sont effacés (sur les exceptions lancées). Je vais devoir rechercher lesquels activer et comment en tirer des informations.

1 votes

Je voulais vous attribuer la prime à la question parce que vous avez fourni un certain nombre de bonnes options à essayer. Merci d'être intervenu.

2voto

youen Points 31

Dans mon cas, j'avais installé un gestionnaire d'exception avec AppDomain.CurrentDomain.FirstChanceException . Ce gestionnaire enregistrait quelques exceptions, et tout allait bien pendant quelques années (en fait, ce code de débogage n'aurait pas dû rester en production).

Mais suite à une erreur de configuration, le logger a commencé à échouer, et le handler lui-même a été lancé, ce qui a apparemment entraîné une FatalExecutionEngineError semblant venir de nulle part.

Ainsi, toute personne rencontrant cette erreur pourrait passer quelques secondes à chercher des occurrences de FirstChanceException n'importe où dans le code et peut-être épargner quelques heures de grattage de tête :)

-1voto

Si vous utilisez thread.sleep(), cela peut être la raison. Le code non géré ne peut être mis en sommeil qu'à partir de la fonction sleep() de kernell.32.

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