Изящное обращение с поврежденными исключениями штата

Связанный с этим вопросом, я хотел бы заставить CLR позволить моему .NET 4.5.2 приложению улавливать Corrupted State Exceptions с единственной целью регистрации их и затем завершение приложения. Какой правильный способ сделать это, если у меня есть catch (Exception ex) в нескольких местах вокруг приложения?

Итак, после того, как я укажу атрибут <legacyCorruptedStateExceptionsPolicy>, если я правильно понял, все обработчики catch (Exception ex) поймают исключения, такие как AccessViolationException и с радостью продолжат.

Да, я знаю, что catch (Exception ex) - это Bad Idea ™, но если CLR по крайней мере поместит правильную трассировку стека в журнал событий, я был бы более чем счастлив объяснить клиенту, что его серверное приложение быстро не работает 1AM и быть в автономном режиме на ночь - это хорошо. Но, к сожалению, CLR регистрирует несвязанное исключение в журнале событий и затем закрывает процесс, чтобы я не мог узнать, что на самом деле произошло.

Вопрос в том, как это сделать, процесс широкий:

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

(Обновление)

Другими словами, это, вероятно, будет работать для большинства исключений в простом приложении:

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

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

Но это не сработает для:

  • Необработанные исключения домена приложения (т.е. брошенные на потоки без переднего плана)
  • Приложения Windows Service (у которых нет фактической точки входа Main)

Итак, кажется, что <legacyCorruptedStateExceptionsPolicy> - единственный способ сделать эту работу, и в этом случае я не знаю, как сбой после регистрации CSE?

Ответ 1

Вместо использования <legacyCorruptedStateExceptionsPolicy> было бы лучше использовать [HandleProcessCorruptedStateExceptions][SecurityCritical]), как указано здесь:

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

После этого ваш метод Main должен выглядеть примерно так:

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

Но имейте в виду, что это не захватывает более серьезные исключения, такие как StackOverflowException и ExecutionEngineException.

Также, finally задействованные блоки try не будут выполняться:

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

Для других необработанных исключений appdomain вы можете использовать:

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

(Пожалуйста, выполните поиск деталей, когда конкретный обработчик подходит для вашей ситуации. Например, TaskScheduler.UnobservedTaskException немного сложнее.)

Если у вас нет доступа к методу Main, вы также можете пометить обработчик исключений AppDomain, чтобы поймать 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.
}

Последняя линия защиты может быть неуправляемым UnhandledExceptionFilter следующим образом:

[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);

А потом где-то в начале вашего процесса:

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

Более подробную информацию о возможных кодах возврата можно найти здесь:

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

"Специальность" UnhandledExceptionFilter заключается в том, что он не вызывается, если подключен отладчик. (По крайней мере, не в моем случае с WPF-приложением.) Знайте об этом.

Если вы установите все соответствующие ExceptionHandlers сверху, вы должны регистрировать все исключения, которые могут быть зарегистрированы. Для более серьезных исключений (например, StackOverflowException и ExecutionEngineException) вы должны найти другой путь, потому что весь процесс невозможен после того, как они произошли. Возможным способом может быть другой процесс, который следит за основным процессом и регистрирует любые фатальные ошибки.

Дополнительные подсказки:

Ответ 2

Благодаря @haindl за то, что вы также можете украсить методы обработчика с помощью атрибута [HandleProcessCorruptedStateExceptions] 1 поэтому я сделал небольшое тестовое приложение, чтобы подтвердить, действительно ли все работает так, как предполагается к.

1 Примечание.. Большинство ответов утверждают, что я должен также включать атрибут [SecurityCritical], хотя в приведенных ниже тестах не было изменений поведения ([HandleProcessCorruptedStateExceptions], казалось, работал просто отлично). Тем не менее, я оставлю оба атрибута ниже, поскольку я предполагаю, что все эти люди знали, что они говорят. Это школьный пример шаблона "Скопирован из StackOverflow" в действии.

Идея состоит в том, чтобы удалить параметр <legacyCorruptedStateExceptionsPolicy> из app.config, т.е. разрешить нашим внешним (начальным) обработчикам (ов) захватить исключение, зарегистрировать его, а затем потерпит неудачу. Добавление этого параметра позволит вашему приложению продолжить, если вы поймаете исключение в каком-либо внутреннем обработчике, и это не то, что вы хотите: идея состоит в том, чтобы получить точную информацию об исключениях, а затем умерщвиться.

Я использовал следующий метод, чтобы выбросить исключение:

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. Исключение исключений из 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. Захват всех исключений, включая фоновые темы/задачи:

// 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 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);
}

Я бы рекомендовал использовать только последний подход и удалить [HandleProcessCorruptedStateExceptions] из всех других мест, чтобы исключить попадание в неподходящее место. То есть если у вас есть блок try/catch где-то и вызывается AccessViolationException, вы хотите, чтобы CLR пропустил блок catch и распространился на UnhandledException до окончания приложения.