.NET中非托管线程的例外情况

时间:2015-06-17 18:11:24

标签: c# .net winapi unmanaged unhandled-exception

如何处理我的应用终止时的情况,使用终止前的回调?

.NET处理程序在以下场景中不起作用,SetUnhandledExceptionHandler是正确的选择吗?它似乎有以下讨论的缺点。

方案

我想回复所有应用终止案例,并在我们的.net应用中向我们的服务发送消息和错误报告。

但是,我有一个WPF应用程序,其中我们的两个测试人员获得绕过的未处理的异常:

  • AppDomain.UnhandledException(最重要的是)
  • Application.ThreadException
  • Dispatcher.UnhandledException

它们标记为 SecuirtyCritical HandleProcessCorruptedStateExceptions 。 app.config中 legacyCorruptedStateExceptionsPolicy 设置为 true

我在野外的两个例子

  • 运行widows10的VirtualBox在某处初始化WPF时抛出一些vboxd3d.dll(关闭vbox 3d accel“修复它”)
  • 在系统上下文菜单中带有“在显卡A / B上运行”的可疑选项的Win8计算机,在WPF启动期间崩溃到某处(:/)但仅在应用了反破解工具时崩溃。

无论哪种方式,实时应用都必须在终止前对这些类型的失败做出响应

我可以使用非托管异常重现这一点,该异常发生在.net中的PInvoked方法的非托管线程中:

Test.dll的

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

DWORD WINAPI myThread(LPVOID lpParameter)
{
    long testfail = *(long*)(-9022);
    return 1;
}

extern "C" __declspec(dllexport) void test()
{
    DWORD tid;
    HANDLE myHandle = CreateThread(0, 0, myThread, NULL, 0, &tid);
    WaitForSingleObject(myHandle, INFINITE);
}

APP.EXE

class TestApp
{
    [DllImport("kernel32.dll")]
    static extern FilterDelegate SetUnhandledExceptionFilter(FilterDelegate lpTopLevelExceptionFilter);

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    delegate int FilterDelegate(IntPtr exception_pointers);

    static int Win32Handler(IntPtr nope)
    {
        MessageBox.Show("Native uncaught SEH exception"); // show + report or whatever
        Environment.Exit(-1); // exit and avoid WER etc
        return 1; // thats EXCEPTION_EXECUTE_HANDLER, although this wont be called due to the previous line
    }

    [DllImport("test.dll")]
    static extern void test();

    [STAThread]
    public static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        SetUnhandledExceptionFilter(Win32Handler);
        test(); // This is caught by Win32Handler, not CurrentDomain_UnhandledException
    }
    [SecurityCritical, HandleProcessCorruptedStateExceptions ]
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Exception ex = e.ExceptionObject as Exception;
        MessageBox.Show(ex.ToString()); // show + report or whatever
        Environment.Exit(-1); // exit and avoid WER etc
    }
}

这可以处理裸WPF测试应用程序中vboxd3d.dll的失败,当然还有WCF Dispatcher和WinForms Application(为什么不会)注册的异常处理程序。

更新

  • 在我试图使用它的生产代码中,处理程序似乎被其他调用者覆盖了,我可以通过每100ms调用一次这个方法来解决这个问题,当然这是愚蠢的。
    • 在出现vbox3d.dll问题的计算机上,执行上述操作会将该异常替换为clr.dll中的异常。
    • 在崩溃时,传入kernel32的托管函数指针不再有效。使用本机帮助器dll设置处理程序,它调用内部本机函数似乎正在工作。托管函数是一个静态方法 - 我不确定在这里应用pinning,也许clr正在终止...
    • 确实正在收集管理代表。没有“覆盖”处理程序。我已经添加了作为答案..不确定接受什么或SO惯例在这里......

3 个答案:

答案 0 :(得分:2)

问题中代码的问题是:

SetUnhandledExceptionFilter(Win32Handler);

由于委托是自动创建的,因此请注意:

FilterDelegate del = new FilterDelegate(Win32Handler);
SetUnhandledExceptionFilter(del);

问题是,GC可以在最终引用之后的任何时刻收集它,以及创建的本机>托管thunk。所以:

SetUnhandledExceptionFilter(Win32Handler);
GC.Collect();
native_crash_on_unmanaged_thread();

总是会导致令人讨厌的崩溃,其中传递给kernel32.dll的处理程序不再是有效的函数指针。这可以通过不允许GC收集来解决:

public class Program
{
    static FilterDelegate mdel;
    public static void Main(string[] args)
    {
        FilterDelegate del = new FilterDelegate(Win32Handler);
        SetUnhandledExceptionFilter(del);
        GC.KeepAlive(del);  // do not collect "del" in this scope (main)
        // You could also use mdel, which I dont believe is collected either
        GC.Collect();
        native_crash_on_unmanaged_thread(); 
    }
}

其他答案也是一个很好的资源;不知道现在要把什么标记为答案。

答案 1 :(得分:0)

我们不得不处理不可预知的非托管图书馆。

如果你正在调用非托管代码,那么你可能会遇到问题。我发现围绕非托管代码使用C ++ / CLI包装器更容易,在某些情况下,在进入C ++ / CLI之前,我已经在库周围编写了另一组非托管C ++包装器。

你可能在想,"为什么你会写两套包装纸呢?"

首先,如果你隔离了非托管代码,它可以更容易地捕获异常并使它们更加可口。

第二个是纯粹实用的 - 如果你有一个使用stl的库(不是dll),你会发现该链接将神奇地给出所有代码,托管和非托管,stl函数的CLI实现。防止这种情况的最简单方法是完全隔离使用stl的代码,这意味着每次通过非托管代码中的stl访问数据结构时,最终会在托管代码和非托管代码之间进行多次转换,并且您的性能将会提升。你可能会想到自己,"我是一个严谨的程序员 - 我会非常小心地把#pragma managed和/或#pragma unmanaged包装好的地方放在正确的地方我和我的#39 ; m all set。"不,不,不,不。这不仅是困难和不可靠的,当你(如果没有)不能正确地做到这一点时,你就不会有一个很好的方法来检测它。

和往常一样,你应该确保你写的任何包装都是厚实而不是健谈。

以下是处理不稳定库的典型非托管代码:

try {
    // a bunch of set up code that you don't need to
    // see reduced to this:
    SomeImageType *outImage = GetImage();
    // I was having problems with the heap getting mangled
    // so heapcheck() is conditional macro that calls [_heapchk()][1]
    heapcheck();
    return outImage;
}
catch (std::bad_alloc &) {
    throw MyLib::MyLibNoMemory();
}
catch (MyLib::MyLibFailure &err)
{
    throw err;
}
catch (const char* msg)
{
    // seriously, some code throws a string.
    throw msg;
}
catch (...) {
    throw MyLib::MyLibFailure(MyKib::MyFailureReason::kUnknown2);
}

答案 2 :(得分:-1)

无法正常处理无法正常处理的异常,无论您多么努力地从内部保护它,该过程都可能意外死亡。但是,您可以从外部监控它。

另一个进程监控您的主进程。如果主进程在没有记录错误或正常报告事件的情况下突然消失,则第二个进程可以执行此操作。第二个过程可以简单得多,根本没有非托管呼叫,因此它突然消失的可能性要小得多。

作为最后的手段,当您的流程开始时,请检查他们是否已正常关闭。如果没有,则可以报告错误关闭。如果整个机器死亡,这将非常有用。