Solución:
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
Siguiendo eso, tu Main
El método debería verse así:
[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 StackOverflowException
y ExecutionEngineException
.
También finally
de involucrados try
los bloques no se ejecutarán:
#920 – A finally Block Is Not Executed When a Corrupted State Exception Occurs
Para otras excepciones de dominio de aplicación no controladas, puede usar:
AppDomain.CurrentDomain.UnhandledException
Application.Current.DispatcherUnhandledException
TaskScheduler.UnobservedTaskException
(Realice 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 Main
, también puede marcar el controlador de excepciones de AppDomain para detectar 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 retorno aquí:
https://msdn.microsoft.com/en-us/library/ms680634(VS.85).aspx
Una “especialidad” del UnhandledExceptionFilter
es que no se llama si se adjunta un depurador. (Al menos no en mi caso de tener una aplicación WPF). Así que tenlo en cuenta.
Si configura todos los ExceptionHandlers apropiados desde arriba, debe registrar todas las excepciones que se pueden registrar. Para las excepciones más graves (como StackOverflowException
y ExecutionEngineException
) tienes que encontrar otra manera porque todo el proceso es inutilizable después de que sucedieron. Una forma posible podría ser quizás otro proceso que observe el proceso principal y registre cualquier error fatal.
Sugerencias adicionales:
- En el
AppDomain.CurrentDomain.UnhandledException
puedes lanzar con seguridad ele.ExceptionObject
paraException
sin tener que preocuparse, al menos si no tiene ningún código IL que arroje otros objetos que no seanException
: ¿Por qué UnhandledExceptionEventArgs.ExceptionObject es un objeto y no una excepción? - Si desea suprimir el cuadro de diálogo Informe de errores de Windows, puede echar un vistazo aquí: ¿Cómo terminar un programa cuando falla? (que debería fallar una prueba unitaria en lugar de quedarse atascado para siempre)
- Si tiene una aplicación WPF con varios despachadores, también puede usar un
Dispatcher.UnhandledException
para los otros despachadores.
Gracias a @haindl por señalar que también puedes decorar métodos de controlador con el [HandleProcessCorruptedStateExceptions]
1 atributo, así que hice una pequeña aplicación de prueba solo para confirmar si las cosas realmente funcionan como se supone que deben hacerlo.
1 Nota: La mayoría de las respuestas afirman que también debería incluir el [SecurityCritical]
atributo, aunque en las pruebas siguientes omitirlo no cambió el comportamiento (el [HandleProcessCorruptedStateExceptions]
solo parecía funcionar bien). Sin embargo, dejaré ambos atributos a continuación, ya que supongo que todas estas personas sabían lo que estaban diciendo. Ese es un ejemplo escolar del patrón “Copiado de StackOverflow” en acción.
La idea es, obviamente, retirar los <legacyCorruptedStateExceptionsPolicy>
ajuste de app.config
, es decir, solo permitir que nuestros manejadores más externos (de nivel de entrada) capturen 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 es no es lo que quieres: la idea es solo obtener la información precisa de la excepción y luego morir miserablemente.
Usé 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. Captura de 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. Detectar todas las excepciones, incluidos los hilos / tareas 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 enfoque y eliminar el [HandleProcessCorruptedStateExceptions]
de todos los demás lugares para asegurarse de que la excepción no quede atrapada en el lugar incorrecto. Es decir, si tienes un try/catch
bloque en algún lugar y un AccessViolationException
se lanza, desea que CLR omita el catch
bloquear y propagar a la UnhandledException
antes de finalizar la aplicación.
¿Se acabó la fiesta? no tan rapido
Microsoft: “Utilice dominios de aplicaciones para aislar las tareas que podrían provocar la caída de un proceso”.
El programa a continuación protegerá su aplicación / hilo principal de fallas irrecuperables sin riesgos asociados con el uso de HandleProcessCorruptedStateExceptions
y <legacyCorruptedStateExceptionsPolicy>
public class BoundaryLessExecHelper : MarshalByRefObject
{
public void DoSomething(MethodParams parms, Action action)
{
if (action != null)
action();
parms.BeenThere = true; // example of return value
}
}
public struct MethodParams
{
public bool BeenThere { get; set; }
}
class Program
{
static void InvokeCse()
{
IntPtr ptr = new IntPtr(123);
System.Runtime.InteropServices.Marshal.StructureToPtr(123, ptr, true);
}
// This is a plain code that will prove that CSE is thrown and not handled
// this method is not a solution. Solution is below
private static void ExecInThisDomain()
{
try
{
var o = new BoundaryLessExecHelper();
var p = new MethodParams() { BeenThere = false };
Console.WriteLine("Before call");
o.DoSomething(p, CausesAccessViolation);
Console.WriteLine("After call. param been there? : " + p.BeenThere.ToString()); //never stops here
}
catch (Exception exc)
{
Console.WriteLine($"CSE: {exc.ToString()}");
}
Console.ReadLine();
}
// This is a solution for CSE not to break your app.
private static void ExecInAnotherDomain()
{
AppDomain dom = null;
try
{
dom = AppDomain.CreateDomain("newDomain");
var p = new MethodParams() { BeenThere = false };
var o = (BoundaryLessExecHelper)dom.CreateInstanceAndUnwrap(typeof(BoundaryLessExecHelper).Assembly.FullName, typeof(BoundaryLessExecHelper).FullName);
Console.WriteLine("Before call");
o.DoSomething(p, CausesAccessViolation);
Console.WriteLine("After call. param been there? : " + p.BeenThere.ToString()); // never gets to here
}
catch (Exception exc)
{
Console.WriteLine($"CSE: {exc.ToString()}");
}
finally
{
AppDomain.Unload(dom);
}
Console.ReadLine();
}
static void Main(string[] args)
{
ExecInAnotherDomain(); // this will not break app
ExecInThisDomain(); // this will
}
}