web-dev-qa-db-fra.com

Gérer gracieusement les exceptions d'état corrompues

En ce qui concerne cette question , je voudrais forcer CLR à laisser mon application .NET 4.5.2 intercepter les exceptions d'état corrompu, dans le seul but de les enregistrer et de mettre fin à l'application. Quelle est la bonne façon de procéder si j'ai catch (Exception ex) à plusieurs endroits dans l'application?

Donc, après avoir spécifié l'attribut <legacyCorruptedStateExceptionsPolicy>, Si j'ai bien compris, tous les gestionnaires catch (Exception ex) intercepteront des exceptions comme AccessViolationException et continueront avec bonheur.

Oui, je sais que catch (Exception ex) est une mauvaise idée ™, mais si CLR mettait au moins la trace de pile correcte dans le journal des événements, je serais plus qu'heureux d'expliquer au client que son application serveur échouait rapidement à 1h du matin et être déconnecté pour la nuit est une bonne chose. Mais malheureusement, CLR enregistre un exception non liée dans le journal des événements, puis ferme le processus afin que je ne puisse pas savoir ce qui s'est réellement passé.

La question est de savoir comment y parvenir, à l'échelle du processus:

if the exception thrown is a Corrupted State Exception:
    - write the message to the log file
    - end the process 

(Mise à jour)

En d'autres termes, cela fonctionnerait probablement pour la plupart des exceptions dans une application simple:

[HandleProcessCorruptedStateExceptions] 
[SecurityCritical]
static void Main() // main entry point
{
    try 
    {

    }
    catch (Exception ex)
    {
        // this will catch CSEs
    }
}

Mais cela ne fonctionnera pas pour:

  • Exceptions de domaine d'application non gérées (c'est-à-dire levées sur des threads non au premier plan)
  • Applications de service Windows (qui n'ont pas de point d'entrée Main réel)

Il semble donc que <legacyCorruptedStateExceptionsPolicy> Est la seule façon de faire fonctionner ce cas, auquel cas je ne sais pas comment échouer après avoir enregistré le CSE?

24
Lou

À la place d'utiliser <legacyCorruptedStateExceptionsPolicy> il vaudrait mieux utiliser [HandleProcessCorruptedStateExceptions] (et [SecurityCritical]) comme indiqué ici:

https://msdn.Microsoft.com/en-us/magazine/dd419661.aspx

Ensuite, votre méthode Main devrait ressembler à ceci:

[HandleProcessCorruptedStateExceptions, SecurityCritical]
static void Main(string[] args)
{
    try
    {
        ...
    }
    catch (Exception ex)
    {
        // Log the CSE.
    }
}

Mais sachez que cela n'attrape pas les exceptions les plus sérieuses comme StackOverflowException et ExecutionEngineException.

De même, finally des blocs try impliqués ne seront pas exécutés:

https://csharp.2000things.com/2013/08/30/920-a-finally-block-is-not-executed-when-a-corrupted-state-exception-occurs/

Pour d'autres exceptions de domaine d'application non gérées, vous pouvez utiliser:

  • AppDomain.CurrentDomain.UnhandledException
  • Application.Current.DispatcherUnhandledException
  • TaskScheduler.UnobservedTaskException

(Veuillez rechercher les détails lorsqu'un gestionnaire spécifique convient à votre situation. TaskScheduler.UnobservedTaskException par exemple est un peu délicat.)

Si vous n'avez pas accès à la méthode Main, vous pouvez également marquer votre gestionnaire d'exceptions AppDomain pour intercepter le CSE:

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

...

[HandleProcessCorruptedStateExceptions, SecurityCritical]
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    // AccessViolationExceptions will get caught here but you cannot stop
    // the termination of the process if e.IsTerminating is true.
}

La dernière ligne de défense pourrait être un UnhandledExceptionFilter non géré comme celui-ci:

[DllImport("kernel32"), SuppressUnmanagedCodeSecurity]
private static extern int SetUnhandledExceptionFilter(Callback cb);
// This has to be an own non generic delegate because generic delegates cannot be marshalled to unmanaged code.
private delegate uint Callback(IntPtr ptrToExceptionInfo);

Et puis quelque part au début de votre processus:

SetUnhandledExceptionFilter(ptrToExceptionInfo =>
{
    var errorCode = "0x" + Marshal.GetExceptionCode().ToString("x2");
    ...
    return 1;
});

Vous pouvez trouver plus d'informations sur les codes retour possibles ici:

https://msdn.Microsoft.com/en-us/library/ms680634 (VS.85) .aspx

Une "spécialité" du UnhandledExceptionFilter est qu'il n'est pas appelé si un débogueur est attaché. (Du moins pas dans mon cas d'avoir une application WPF.) Alors soyez conscient de cela.

Si vous définissez tous les gestionnaires d'exception appropriés ci-dessus, vous devez consigner toutes les exceptions pouvant être consignées. Pour les exceptions les plus sérieuses (comme StackOverflowException et ExecutionEngineException), vous devez trouver un autre moyen car l'ensemble du processus est inutilisable après leur apparition. Un moyen possible pourrait peut-être être un autre processus qui surveille le processus principal et enregistre les erreurs fatales.

Conseils supplémentaires:

26
haindl

Merci à @haindl d'avoir souligné que vous pouvez également décorer des méthodes de gestionnaire avec le [HandleProcessCorruptedStateExceptions]1 attribut, j'ai donc fait une petite application de test juste pour confirmer si les choses fonctionnent vraiment comme elles sont censées le faire.

1 Remarque: La plupart des réponses indiquent que je devrais également inclure le [SecurityCritical], bien que dans les tests ci-dessous l'omission n'ait pas changé le comportement (le [HandleProcessCorruptedStateExceptions] seul semblait très bien fonctionner). Cependant, je vais laisser les deux attributs ci-dessous car je suppose que tous ces gens savaient ce qu'ils disaient. C'est un exemple d'école de modèle "Copié à partir de StackOverflow" en action.

L'idée est évidemment de supprimer le <legacyCorruptedStateExceptionsPolicy> réglage à partir de app.config, c'est-à-dire autoriser uniquement nos gestionnaires externes (d'entrée de gamme) à intercepter l'exception, à la consigner, puis à échouer. L'ajout du paramètre permettra à votre application de continuer, si vous interceptez l'exception dans un gestionnaire interne, et ce n'est pas ce que vous voulez : l'idée est juste pour obtenir les informations d'exception précises, puis mourir misérablement.

J'ai utilisé méthode suivante pour lever l'exception:

static void DoSomeAccessViolation()
{
    // if you have any questions about why this throws,
    // the answer is "42", of course

    var ptr = new IntPtr(42);
    Marshal.StructureToPtr(42, ptr, true);
}

1. Attraper les exceptions de Main:

[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
static void Main(string[] args)
{
    try
    {
        DoSomeAccessViolation();
    }
    catch (Exception ex)
    {
        // this will catch all CSEs in the main thread
        Log(ex);
    }
}

2. Attraper toutes les exceptions, y compris les tâches/threads d'arrière-plan:

// no need to add attributes here
static void Main(string[] args)
{
    AppDomain.CurrentDomain.UnhandledException += UnhandledException;

    // throw on a background thread
    var t = new Task(DoSomeAccessViolation);
    t.Start();
    t.Wait();
}

// but it's important that this method is marked
[SecurityCritical]
[HandleProcessCorruptedStateExceptions]
private static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    // this will catch all unhandled exceptions, including CSEs
    Log(e.ExceptionObject as Exception);
}

Je recommanderais d'utiliser uniquement cette dernière approche et de supprimer le [HandleProcessCorruptedStateExceptions] de tous les autres endroits pour vous assurer que l'exception ne se trouve pas au mauvais endroit. C'est à dire. si tu as un try/catch bloquer quelque part et un AccessViolationException est lancé, vous voulez que CLR saute le bloc catch et se propage dans le UnhandledException avant de terminer l'application.

11
Lou