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:
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?
À 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:
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:
AppDomain.CurrentDomain.UnhandledException
vous pouvez lancer le e.ExceptionObject
à Exception
sans avoir à vous inquiéter - au moins si vous n'avez pas de code IL qui lance d'autres objets que Exception
: Pourquoi UnhandledExceptionEventArgs.ExceptionObject est-il un objet et non une exception?Dispatcher.UnhandledException
pour les autres répartiteurs.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.