优雅地处理损坏的状态异常

Lou*_*Lou 23 .net c# access-violation fail-fast corrupted-state-exception

此问题相关,我想强制CLR让我的.NET 4.5.2应用程序捕获损坏的状态异常,其唯一目的是记录它们然后终止应用程序.如果我catch (Exception ex)在应用程序周围的几个地方,这样做的正确方法是什么?

因此,在我指定<legacyCorruptedStateExceptionsPolicy>属性后,如果我理解正确,所有catch (Exception ex)处理程序将捕获异常,AccessViolationException并愉快地继续.

是的,我知道catch (Exception ex)是一个坏主意™,但如果CLR至少将正确的堆栈跟踪放入事件日志中,我将非常乐意向客户解释他的服务器应用程序在凌晨1点快速失败并且离线晚上是件好事.但不幸的是,CLR将一个不相关的异常记录到事件日志中,然后关闭该过程,以便我无法找出实际发生的情况.

问题是,如何实现这一目标,流程广泛:

if the exception thrown is a Corrupted State Exception:
    - write the message to the log file
    - end the process 
Run Code Online (Sandbox Code Playgroud)

(更新)

换句话说,这可能适用于简单应用中的大多数例外:

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

    }
    catch (Exception ex)
    {
        // this will catch CSEs
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,它不适用于:

  • 未处理的应用程序域异常(即在非前台线程上抛出)
  • Windows服务应用程序(没有实际Main入口点)

所以这似乎<legacyCorruptedStateExceptionsPolicy>是使这项工作成功的唯一方法,在这种情况下,我不知道在记录CSE后如何失败?

hai*_*ndl 25

而不是使用<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.
    }
}
Run Code Online (Sandbox Code Playgroud)

但请注意,这并没有捕获更严重的例外情况,例如StackOverflowExceptionExecutionEngineException.

同时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.
}
Run Code Online (Sandbox Code Playgroud)

最后一道防线可能是一个非托管的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);
Run Code Online (Sandbox Code Playgroud)

然后在流程开始的某个地方:

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

您可以在此处找到有关可能的返回代码的更多信息:

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

它的一个"特性" UnhandledExceptionFilter是,如果连接了调试器,则不会调用它.(至少在我的情况下没有WPF应用程序.)所以要注意这一点.

如果从上面设置所有适当的ExceptionHandler,则应记录可记录的所有异常.对于更严重的异常(例如StackOverflowExceptionExecutionEngineException),你必须找到另一种方法,因为整个过程在它们发生后都无法使用.一种可能的方法可能是另一个监视主进程并记录任何致命错误的进程.

其他提示:


Lou*_*Lou 11

感谢@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);
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

我建议只使用后一种方法,并[HandleProcessCorruptedStateExceptions]所有其他地方删除,以确保异常不会被错误的地方捕获.即如果你在try/catch某个地方有一个块并AccessViolationException抛出一个,你希望CLR跳过该catch块并传播到UnhandledException结束应用之前.


归档时间:

查看次数:

6837 次

最近记录:

8 年,11 月 前