c# - Manejando con gracia las excepciones de estado corrupto
.net access-violation (2)
En relación con esta pregunta , me gustaría obligar a CLR a permitir que mi aplicación .NET 4.5.2 capture las Excepciones de estado corrupto, con el único propósito de registrarlas y luego terminar la aplicación. ¿Cuál es la forma correcta de hacer esto, si tengo catch (Exception ex)
en varios lugares alrededor de la aplicación?
Entonces, después de especificar el atributo <legacyCorruptedStateExceptionsPolicy>
, si lo entendí correctamente, todos los controladores catch (Exception ex)
detectarán excepciones como AccessViolationException
y continuarán felizmente.
Sí, sé que catch (Exception ex)
es una mala idea ™, pero si CLR al menos colocara el seguimiento correcto de la pila en el registro de eventos, estaría más que feliz de explicarle al cliente que su aplicación de servidor falla rápidamente a la 1AM y estar desconectado por la noche es algo bueno. Pero desafortunadamente, CLR registra una excepción no relacionada en el registro de eventos y luego cierra el proceso para que no pueda averiguar qué sucedió realmente.
La pregunta es, cómo hacer que esto suceda, todo el proceso:
if the exception thrown is a Corrupted State Exception:
- write the message to the log file
- end the process
(Actualizar)
En otras palabras, esto probablemente funcionaría para la mayoría de las excepciones en una aplicación simple:
[HandleProcessCorruptedStateExceptions]
[SecurityCritical]
static void Main() // main entry point
{
try
{
}
catch (Exception ex)
{
// this will catch CSEs
}
}
Pero, no funcionará para:
- Excepciones de dominio de aplicaciones no manejadas (es decir, lanzadas en subprocesos que no son de primer plano)
- Aplicaciones de servicio de Windows (que no tienen un punto de entrada
Main
real)
Entonces, ¿parece que <legacyCorruptedStateExceptionsPolicy>
es la única forma de hacer que esto funcione, en cuyo caso no sé cómo fallar después de registrar el CSE?
En lugar de usar <legacyCorruptedStateExceptionsPolicy>
sería mejor usar [HandleProcessCorruptedStateExceptions]
(y [SecurityCritical]
) como se indica aquí:
https://msdn.microsoft.com/en-us/magazine/dd419661.aspx
Después de eso, su método Main
debe verse algo como esto:
[HandleProcessCorruptedStateExceptions, SecurityCritical]
static void Main(string[] args)
{
try
{
...
}
catch (Exception ex)
{
// Log the CSE.
}
}
Pero tenga en cuenta que esto no detecta las excepciones más serias como Exception
y ExecutionEngineException
.
También finally
no se ejecutarán los bloques de try
involucrados:
Para otras excepciones de aplicaciones no manejadas, puede usar:
-
AppDomain.CurrentDomain.UnhandledException
-
Application.Current.DispatcherUnhandledException
-
TaskScheduler.UnobservedTaskException
(Por favor, haga una búsqueda de los detalles cuando un controlador específico sea apropiado para su situación. TaskScheduler.UnobservedTaskException
por ejemplo, es un poco complicado).
Si no tiene acceso al método Main
, también puede marcar su controlador de excepciones de dominio de aplicación para capturar el 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 última línea de defensa podría ser un UnhandledExceptionFilter no administrado como este:
[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);
Y luego en algún lugar al comienzo de su proceso:
SetUnhandledExceptionFilter(ptrToExceptionInfo =>
{
var errorCode = "0x" + Marshal.GetExceptionCode().ToString("x2");
...
return 1;
});
Puede encontrar más información sobre los posibles códigos de devolución aquí:
https://msdn.microsoft.com/en-us/library/ms680634(VS.85).aspx
Una "especialidad" de UnhandledExceptionFilter
es que no se llama si hay un depurador adjunto. (Al menos no en mi caso de tener una aplicación WPF). Así que sé consciente de eso.
Si configura todos los ExceptionHandlers apropiados de arriba, debe registrar todas las excepciones que se pueden registrar. Para las excepciones más serias (como Exception
y ExecutionEngineException
), tiene que encontrar otra forma porque todo el proceso es inutilizable después de que sucedió. Una posible forma podría ser otro proceso que vigile el proceso principal y registre cualquier error fatal.
Consejos adicionales:
- En la
Exception
AppDomain.CurrentDomain.UnhandledException
puede convertir de forma segurae.ExceptionObject
enException
sin tener que preocuparse, al menos si no tiene ningún código IL que arroje otro objeto queException
: Why ¿Excepción? - Si desea suprimir el cuadro de diálogo Informe de errores de Windows, puede consultar aquí: ¿Cómo finalizar un programa cuando se bloquea? (que solo debería fallar una prueba de unidad en lugar de quedarse atascado para siempre)
- Si tiene una aplicación WPF con múltiples despachadores, también puede usar
Dispatcher.UnhandledException
para los otros despachadores.
Gracias a @haindl por señalar que también puede decorar los métodos de manejo con el [HandleProcessCorruptedStateExceptions]
1 , por lo que hice una pequeña aplicación de prueba para confirmar si las cosas realmente funcionan como se supone.
1 Nota: La mayoría de las respuestas indican que también debería incluir el atributo [SecurityCritical]
, aunque en las pruebas a continuación, omitirlo no cambió el comportamiento (las [HandleProcessCorruptedStateExceptions]
sí solas funcionaron bien). Sin embargo, dejaré ambos atributos a continuación, ya que presumo que todas estas personas sabían lo que decían. Ese es un ejemplo escolar del patrón "Copiado desde " en acción.
La idea es, obviamente, eliminar la <legacyCorruptedStateExceptionsPolicy>
de app.config
, es decir, solo permitir que nuestros controladores más externos (de nivel de entrada) app.config
la excepción, la registren y luego fallen. Agregar la configuración permitirá que su aplicación continúe, si detecta la excepción en algún controlador interno, y esto no es lo que desea : la idea es obtener la información precisa de la excepción y luego morir de forma miserable.
Utilicé el siguiente método para lanzar la excepción:
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. Atrapar excepciones 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. Captura de todas las excepciones, incluidas las tareas / subprocesos en segundo plano:
// 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);
}
Recomendaría usar solo el último método y eliminar las [HandleProcessCorruptedStateExceptions]
de todos los demás lugares para asegurarse de que la excepción no [HandleProcessCorruptedStateExceptions]
atrapada en el lugar equivocado. Es decir, si tiene un bloque try/catch
algún lugar y se lanza una AccessViolationException
, quiere que CLR omita el bloque catch
y se propague a la UnhandledException
antes de finalizar la aplicación.