when window procedure called - kernel push additional stack frame in kernel stack and in user mode called special "function" (faster even label then normal function) KiUserCallbackDispatcher
, which call window procedure and finally return to kernel by special api call ZwCallbackReturn
pop kernel stack frame. note that after call ZwCallbackReturn
we return not to next instruction after it, but in place from where we enter to kernel, from where user mode callback will be called (usually from GetMessage
or PeekMessage
).
anyway we must pop kernel stack frame - so call ZwCallbackReturn
. so case with unwind exception across KiUserCallbackDispatcher
- is wrong by design and must not work. who in this case call ZwCallbackReturn
? if it will be called from __finally
handler in KiUserCallbackDispatcher
- this break unwind procedure ( ZwCallbackReturn
how i say never return, but like long jump - move us to another place with another stack pointer and all registers). even if you manually call ZwCallbackReturn
from catch
- where you will be after this call ?
so need handle exception before KiUserCallbackDispatcher
SEH handler or how minimum have __finally
blocks, if need de-allocate some resources.
KiUserCallbackDispatcher
used SEH
handler for call ZwCallbackReturn
even if exception will be in callback. however behavior of this SEH handler can be affected by undocumented flag in RTL_USER_PROCESS_PARAMETERS.Flags
:
pseudo-code:
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;
}
if this flag (0x80000
) not set (by default) - LdrpLogFatalUserCallbackException
called. it internally call UnhandledExceptionFilter
and if it not return EXCEPTION_CONTINUE_EXECUTION
- called ZwRaiseException
with STATUS_FATAL_USER_CALLBACK_EXCEPTION
and FirstChance = FALSE
(this mean this exception not passed to application - only for debugger as last chance exception)
if we set this flag - EXCEPTION_EXECUTE_HANDLER
will be returned from filter - RtlUnwindEx
will be called (with TargetIp = KiUserCallbackDispatcherContinue
) - as result __finally
handlers will be called and at the end ZwCallbackReturn(0, 0, 0);
in any case exception will be not passed to SEH in function which is higher in stack, than KiUserCallbackDispatcher
(because here exception will be handled)
so we need handle exception in stack below KiUserCallbackDispatcher
or set flags to 0x80000
- in this case, if we not handle exception before KiUserCallbackDispatcher
- our __finally
blocks (if exist) will be executed before ZwCallbackReturn
which finish callback.
SetProcessUserModeExceptionPolicy
not exported in recent windows versions (begin from win8), but yearly code of this api was next(exactly code):
#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;
}
test:
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);
}
}
}
try comment or un-comment RtlGetCurrentPeb()->ProcessParameters->Flags |= 0x80000;
line (or NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Flags & 0x80000
which is the same) and compare effect.
anyway top SEH filter (before call wndproc) never will be called