Изящное обращение с поврежденными исключениями штата
Связанный с этим вопросом, я хотел бы заставить 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
до окончания приложения.