13

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

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

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

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

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

(Update)

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

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

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

Но, это не будет работать:

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

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

ответ

9

Вместо того, чтобы использовать <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) вам нужно найти другой путь, потому что весь процесс неприменим после того, как они произошли. Возможным способом может быть другой процесс, который следит за основным процессом и регистрирует любые фатальные ошибки.

Дополнительные советы:

+0

Спасибо, так это значит, что я могу украсить метод обработчика 'AppDomain.CurrentDomain.UnhandledException' с помощью' [HandleProcessCorruptedStateExceptions] ', и он наверняка поймает CSE? – Lou

+1

Да, вы определенно можете. Я отредактирую свой ответ, чтобы подчеркнуть этот аспект. – haindl

5

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

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

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

Я использовал following method бросить исключение:

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

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

Смежные вопросы