Есть ли способ получить DisableUserModeCallbackFilter
(или аналогичный) для работы на Windows 10?
Предполагается, что исключения, генерируемые из кода пользовательского режима, распространяются через границы пользователя и ядра, и в нем было исправление в более ранних версиях Windows вплоть до Windows 7, но, похоже, я не могу заставить его работать на более поздних версиях. версии.
Вот тестовая программа, которая, похоже, выдает ошибку в 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
мы возвращаемся не к следующей инструкции после нее, а в том месте, откуда мы входим в ядро, откуда будет вызываться обратный вызов пользовательского режима (обычно из GetMessage
или же PeekMessage
).
в любом случае мы должны вытолкнуть кадр стека ядра — так что вызовите ZwCallbackReturn
, так что случай с исключением раскрутки через KiUserCallbackDispatcher
— это неправильно по замыслу и не должно работать. кого в этом случае называют ZwCallbackReturn
? если он будет вызван из __finally
обработчик в KiUserCallbackDispatcher
— это перерыв раскручивать процедуру ( ZwCallbackReturn
как я говорю, никогда не возвращайся, но как прыжок в длину — перенеси нас в другое место с другим указателем стека и всеми регистрами). даже если вы звоните вручную ZwCallbackReturn
от catch
— где вы будете после этого звонка?
поэтому нужно обработать исключение перед 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
— называется ZwRaiseException
с STATUS_FATAL_USER_CALLBACK_EXCEPTION
а также FirstChance = FALSE
(это означает, что это исключение не передается приложению — только для отладчика в качестве последнего случайного исключения)
если мы установим этот флаг — EXCEPTION_EXECUTE_HANDLER
будет возвращен из фильтра — RtlUnwindEx
будет называться (с TargetIp = KiUserCallbackDispatcherContinue
) — в результате __finally
обработчики будут вызваны и в конце ZwCallbackReturn(0, 0, 0);
в любом случае исключение не будет передано в SEH в функции, которая выше в стеке, чем KiUserCallbackDispatcher
(потому что здесь исключение будет обработано)
поэтому нам нужно обработать исключение в стеке ниже KiUserCallbackDispatcher
или установите флаги на 0x80000
— в этом случае, если мы не обработали исключение раньше KiUserCallbackDispatcher
— наш __finally
блоки (если существуют) будут выполнены раньше 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) никогда не будет вызван
Других решений пока нет …