c++ - 有没有办法让 DisableUserModeCallbackFilter 在 Windows 10 中工作?

标签 c++ winapi exception kernel-mode stack-unwinding

有没有办法得到 DisableUserModeCallbackFilter (或类似)在 Windows 10 上工作?

它应该允许从用户模式代码引发的异常跨用户/内核边界传播,并且它在 Windows 7 之前的早期 Windows 版本上有一个修补程序,但我似乎无法让它在最近的版本上运行版本。

这是一个在 Windows 10 x64 上似乎出错的测试程序,但在 Windows XP x86 上却没有:

#include <tchar.h>
#include <stdio.h>
#include <Windows.h>

#pragma comment(lib, "user32")

WNDPROC oldproc = NULL;

LRESULT CALLBACK newproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    _ftprintf(stderr, _T("OMG\n"));
    fflush(stderr);
    throw 0;
    return oldproc(hwnd, uMsg, wParam, lParam);
}

int _tmain(int argc, TCHAR *argv[])
{
    HWND hWnd = CreateWindowEx(0, TEXT("STATIC"), TEXT("Name"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, NULL, NULL);
    oldproc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)newproc);
    try {
        UpdateWindow(hWnd);
    } catch (int ex) {
        _ftprintf(stderr, _T("Error: %d\n"), ex);
        fflush(stderr);
    }
}

最佳答案

当调用窗口过程时 - 内核在内核堆栈和用户模式下推送额外的堆栈帧称为特殊“函数”(偶数标签比普通函数更快)KiUserCallbackDispatcher ,它调用窗口过程,最后通过特殊的api调用返回内核ZwCallbackReturn弹出内核堆栈帧。请注意,通话后 ZwCallbackReturn我们不会返回到它之后的下一条指令,而是从我们进入内核的地方返回,从那里将调用用户模式回调(通常来自 GetMessagePeekMessage )。
无论如何,我们必须弹出内核堆栈帧 - 所以调用 ZwCallbackReturn .所以在 KiUserCallbackDispatcher 中出现展开异常的情况- 设计错误,不能工作。在这种情况下谁调用 ZwCallbackReturn ?如果它会从 __finally 调用KiUserCallbackDispatcher 中的处理程序- 这个中断展开过程(ZwCallbackReturn 我怎么说永不返回,但就像跳远一样 - 用另一个堆栈指针和所有寄存器将我们移动到另一个地方)。即使您手动调用ZwCallbackReturn来自 catch - 打完电话后你会在哪里?
所以需要在 KiUserCallbackDispatcher 之前处理异常SEH 处理程序或最小有多少 __finally block ,如果需要取消分配一些资源。KiUserCallbackDispatcher使用 SEH调用处理程序 ZwCallbackReturn即使异常将在回调中。然而,这个 SEH 处理程序的行为可能会受到 RTL_USER_PROCESS_PARAMETERS.Flags 中未记录的标志的影响。 :
伪代码:

void KiUserCallbackDispatcher(...)
{
    __try {

        //...
    } __except(KiUserCallbackExceptionFilter(GetExceptionInformation())) {
KiUserCallbackDispatcherContinue:
        ZwCallbackReturn(0, 0, 0);
    }
}

void LdrpLogFatalUserCallbackException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord);

int KiUserCallbackExceptionFilter(PEXCEPTION_POINTERS pep)
{
    if ( NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags & 0x80000)
    {
        return EXCEPTION_EXECUTE_HANDLER;
    }

    LdrpLogFatalUserCallbackException(pep->ExceptionRecord, pep->ContextRecord);

    return EXCEPTION_CONTINUE_EXECUTION;
}
如果此标志 ( 0x80000 ) 未设置 (默认) - LdrpLogFatalUserCallbackException叫。它在内部调用 UnhandledExceptionFilter如果它不返回 EXCEPTION_CONTINUE_EXECUTION - 称为 ZwRaiseException STATUS_FATAL_USER_CALLBACK_EXCEPTIONFirstChance = FALSE (这意味着此异常未传递给应用程序 - 仅用于调试器作为最后机会异常)
如果我们设置这个标志 - EXCEPTION_EXECUTE_HANDLER将从过滤器返回 - RtlUnwindEx 将被调用(使用 TargetIp = KiUserCallbackDispatcherContinue ) - 结果为 __finally处理程序将被调用,最后 ZwCallbackReturn(0, 0, 0);在任何情况下,异常都不会在 的函数中传递给 SEH更高 在堆栈中,比 KiUserCallbackDispatcher (因为这里会处理异常)
所以我们需要在 KiUserCallbackDispatcher 下面的堆栈中处理异常或将标志设置为 0x80000 - 在这种情况下,如果我们在 KiUserCallbackDispatcher 之前不处理异常- 我们的__finally block (如果存在)将在 ZwCallbackReturn 之前执行完成回调。
SetProcessUserModeExceptionPolicy 没有在最近的 Windows 版本中导出(从 win8 开始),但是这个 api 的年度代码是下一个(确切的代码):
#define PROCESS_CALLBACK_FILTER_ENABLED     0x1

BOOL WINAPI SetProcessUserModeExceptionPolicy(DWORD dwFlags)
{
    PLONG pFlags = (PLONG)&NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags;
    if (dwFlags & PROCESS_CALLBACK_FILTER_ENABLED)
    {
        _bittestandset(pFlags, 19); // |= 0x80000
    }
    else
    {
        _bittestandreset(pFlags, 19); // &= ~0x80000
    }

    return TRUE;
}
测试:
WNDPROC oldproc;

LRESULT CALLBACK newproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)oldproc);
    __try {
        *(int*)0=0;
        //RaiseException(STATUS_ACCESS_VIOLATION, 0, 0, 0);

    } __finally {
        DbgPrint("in finally\n");
        CallWindowProc(oldproc, hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

void test()
{
    RtlGetCurrentPeb()->ProcessParameters->Flags |= 0x80000;

    if (HWND hwnd = CreateWindowExW(0, WC_EDIT, L"***", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, NULL, NULL))
    {
        oldproc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newproc);
        __try {
            ShowWindow(hwnd, SW_SHOW);
        }__except(EXCEPTION_EXECUTE_HANDLER){
            DbgPrint("no sense. never will be called\n");
        }

        MSG msg;
        while (0 < GetMessage(&msg, hwnd, 0, 0))
        {
            DispatchMessage(&msg);
        }
    }
}
尝试评论或取消评论RtlGetCurrentPeb()->ProcessParameters->Flags |= 0x80000;行(或 NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags & 0x80000 相同)并比较效果。
无论如何,顶级 SEH 过滤器(在调用 wndproc 之前)永远不会被调用

关于c++ - 有没有办法让 DisableUserModeCallbackFilter 在 Windows 10 中工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49372987/

相关文章:

c++ - UDP_TABLE_CLASS 值在哪里定义?

c++ - 使用 CreateWindowEx() 创建的窗口中的默认按钮

java - Android Realm Java 0.82.1 ArrayIndexOutOfBoundsException

c++ - 防止 clang-format 在 -> 运算符处换行

c++ - 断言失败 Eigen Debug模式

c++ - 在 Unicode 应用程序中使用 ANSI 函数是否有任何限制

c++ - 如何将 boost::exception 与 boost::unit_test 结合起来,并有一个很好的消息

exception - SSRS 报告生成器 - 无法预览报告

c++ - 如何找出内存访问粒度?

c++ - Windows 上的写时复制文件映射