【问题标题】:Is there a way to get DisableUserModeCallbackFilter to work in Windows 10?有没有办法让 DisableUserModeCallbackFilter 在 Windows 10 中工作?
【发布时间】:2018-03-19 21:55:15
【问题描述】:

有没有办法让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);
    }
}

【问题讨论】:

  • 跨越用户/内核边界 - 你是关于?像往常一样传递给用户模式的异常(如果之前没有由调试器处理)。 KiUserCallbackDispatcherHandler 中的问题(这是来自 ntdll.dll 的用户模式例程)。它的行为取决于RtlGetCurrentPeb()-&gt;ProcessParameters-&gt;Flags - 0x80000 标志。 SetProcessUserModeExceptionPolicy 设置或重置此标志现在它不会导出,但您可以自己设置此标志 (RtlGetCurrentPeb()-&gt;ProcessParameters-&gt;Flags |= 0x80000;),这不会是致命错误,但无论如何您的处理程序都不会被调用
  • 这是让 Win7/Server2008 表现更好的 hack,默认行为相当可怕。他们在 Win8 中找到了一个更好的解决方案,重新提升 0xC000041D 非常有效。只是不要试图打败一个好的解决方案。以 x64 为目标也是一个很好的解决方案。
  • @HansPassant:很困惑,但是,定位 x64 在这里没有任何区别吗?无论哪种方式,顶部的异常处理程序都不会被调用。

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


【解决方案1】:

当调用窗口过程时 - 内核在内核堆栈和用户模式下推送额外的堆栈帧称为特殊“函数”(更快的偶数标签比普通函数)KiUserCallbackDispatcher,它调用窗口过程并最终通过特殊 api 调用返回内核ZwCallbackReturn 弹出内核堆栈帧。请注意,在调用ZwCallbackReturn 之后,我们不会返回到它之后的下一条指令,而是从我们进入内核的地方返回,从那里调用用户模式回调(通常来自GetMessagePeekMessage)。

无论如何我们必须弹出内核堆栈帧 - 所以调用ZwCallbackReturn。因此,KiUserCallbackDispatcher 出现 unwind 异常的情况 - 设计错误,不能正常工作。在这种情况下谁打电话给ZwCallbackReturn?如果它将从KiUserCallbackDispatcher 中的__finally 处理程序调用- 这个中断展开过程(ZwCallbackReturn 我怎么说永远不会返回,但就像跳远- 将我们移动到另一个带有另一个堆栈指针和所有寄存器的地方)。即使您从catch 手动调用ZwCallbackReturn - 调用后您会在哪里?

所以需要在KiUserCallbackDispatcher SEH 处理程序之前处理异常,或者如果需要取消分配一些资源,__finally 块的最小值是多少。

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 - 调用ZwRaiseExceptionSTATUS_FATAL_USER_CALLBACK_EXCEPTIONFirstChance = FALSE(这意味着这个异常没有传递给应用程序 - 仅用于调试器作为最后机会异常)

如果我们设置这个标志 - EXCEPTION_EXECUTE_HANDLER 将从过滤器返回 - RtlUnwindEx 将被调用(TargetIp = KiUserCallbackDispatcherContinue) - 结果__finally 处理程序将被调用并在最后ZwCallbackReturn(0, 0, 0);

在任何情况下,堆栈中高于的函数都不会将异常传递给 SEH,而不是 KiUserCallbackDispatcher(因为这里将处理异常)

所以我们需要在KiUserCallbackDispatcher下面的堆栈中处理异常或将标志设置为0x80000 - 在这种情况下,如果我们在KiUserCallbackDispatcher之前不处理异常 - 我们的__finally块(如果存在)将在@之前执行987654362@完成回调。

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()-&gt;ProcessParameters-&gt;Flags |= 0x80000;行(或NtCurrentTeb()-&gt;ProcessEnvironmentBlock-&gt;ProcessParameters-&gt;Flags &amp; 0x80000,相同)并比较效果。

无论如何,顶级 SEH 过滤器(在调用 wndproc 之前)永远不会被调用

【讨论】:

  • 与往常一样,这些都不能保证有效,甚至没有记录。这需要在这个答案中明确指出,目前的说法是,好像这是受支持的场景。有一个原因,为什么微软越来越难以跨外部堆栈框架传递异常。 C++ 代码应该尽可能使用 C++11 工具(如std::rethrow_exception)来跨外部堆栈帧传输异常对象。
  • @IInspectable - 这是什么问题以及答案。开始时的问题涉及几乎未记录的主题。如果想要在文档限制内 - 根本没有答案。或回答 - 不可能。无论如何不可能在_tmain 中真正捕获异常。但部分可能更改异常处理程序的行为。我在这里尝试做的所有事情-有些人解释了它的内部工作原理。这与 c++ 异常(即 shell over windows 异常)无关。 main - 回调中的异常当然像往常一样传递给用户模式,但在 KiUserCallbackDispatcher SEH 中处理
  • 没有“几乎无证”,就像没有“几乎怀孕”这样的东西。 OP 已链接到官方 KB 条目,并使用那里记录的 is 内容。您的答案提出了一个解决方案,该解决方案仅基于未记录的实施细节。我在使用 C++ 异常(如 OP)时提出了一个替代方案,因为它已经提供了按值传递异常对象的工具。如果您想为 SEH 异常实施类似的方案,我相信这也是可能的。但是 OP 正在使用 C++ 异常。
  • 无论如何-“记录”SetProcessUserModeExceptionPolicy 的代码完全是-取自win7。如果有人想要 - 可以获取此代码并测试,回调中的异常路由在设置标志后将如何变化。或直接致电RtlGetCurrentPeb()-&gt;ProcessParameters-&gt;Flags |= 0x80000;。最大程度地利用这一点 - 避免致命错误,如果需要,执行__finally 处理程序,免费提供一些资源
  • @IInspectable - SetProcessUserModeExceptionPolicy 在任何系统中都不可用 - 我在回答中写了这个。但我从win7粘贴它的实际代码 - 非常简单地自己实现它。或RtlGetCurrentPeb()-&gt;ProcessParameters-&gt;Flags |= 0x80000 - 一样。如果你能阅读。并且操作不只询问c++ 异常。问题是关于一般的例外情况。我的回答是信息性的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-13
  • 2015-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
相关资源
最近更新 更多