c# - .NET 中非托管线程的异常

标签 c# .net winapi unmanaged unhandled-exception

如何在我的应用程序终止时使用终止前的回调来处理这种情况?

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

场景

我想通过向我们的 .net 应用程序中的服务发送消息和错误报告来响应所有应用程序终止的情况。

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

  • AppDomain.UnhandledException(最重要的)
  • 应用程序线程异常
  • Dispatcher.UnhandledException

它们被标记为SecuirtyCriticalHandleProcessCorruptedStateExceptionslegacyCorruptedStateExceptionsPolicy 在 app.config 中设置为 true

我在野外的两个例子

  • 运行 widows10 的 VirtualBox 在某处初始化 WPF 时会抛出一些 vboxd3d.dll(关闭 vbox 3d accel“修复它”)
  • Win8 机器在系统上下文菜单中有“在显卡 A/B 上运行”的可疑选项,在 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 调度程序和 WinForms 应用程序(为什么不)异常处理程序。

更新

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

最佳答案

题中代码的问题是这样的:

SetUnhandledExceptionFilter(Win32Handler);

由于委托(delegate)是自动创建的,因此等价于:

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

问题是,GC 可以在它成为最终引用后的任何时候收集它,以及创建的 native > 管理的 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(); 
    }
}

其他答案也是很好的资源;现在不确定将什么标记为答案。

关于c# - .NET 中非托管线程的异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30899188/

相关文章:

c - 为什么 Microsoft 将 WINAPI、CALLBACK 和 APIENTRY 定义为都引用 __stdcall?

c++ - 如何将(阿拉伯语)字形与 SDL-TTF v2.20 结合起来?

c# - 从文件扩展名获取 ImageFormat

c# - C# 中 "this"赋值的示例是什么?

javascript - 定义 Kendo UI 默认值和功能

C# - 使用自定义键定义哈希集

c# - 如何将 WriteAsync 与 XPSDocument 一起使用?

c# - 无需反射即可根据名称动态调用方法的有效方法

c# - 使用 .Net 调用 Servicenow Web 服务

c++ - 是否可以修改显示的原点? (win32)