From: Yuxuan Shui yshui@codeweavers.com
1. Check if we can use RtlVirtualUnwind to unwind into the parent frame of a user callback. Most importantly, we want to know if %rbp is recovered when doing so. To do that, we use the .seh_setframe directive in an assembly function, so its %rsp must be recovered from %rbp. If the value of %rbp is incorrect, this should derail the unwinding and can be detected. 2. We check whether non-volatile registers other than %rbp is also recovered. --- dlls/user32/tests/win.c | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index f899008f394..b9866ee294d 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -32,6 +32,7 @@ #include "winuser.h" #include "winreg.h"
+#include "wine/exception.h" #include "wine/test.h"
#ifndef WM_SYSTIMER @@ -43,6 +44,7 @@
void dump_region(HRGN hrgn);
+static BOOL (WINAPI *pDestroyWindow)(HWND); static BOOL (WINAPI *pGetWindowInfo)(HWND,WINDOWINFO*); static UINT (WINAPI *pGetWindowModuleFileNameA)(HWND,LPSTR,UINT); static BOOL (WINAPI *pGetLayeredWindowAttributes)(HWND,COLORREF*,BYTE*,DWORD*); @@ -13750,12 +13752,119 @@ static void test_startupinfo_showwindow( char **argv ) } }
+#if defined(_WIN64) && defined(__WINE_PE_BUILD) +static void *unwind_target = NULL; +static void *target_frame; + +static LRESULT unwinding_wnd_proc(HWND w, UINT msg, WPARAM p2, LPARAM p3) +{ + CONTEXT context; + int frames; + UNWIND_HISTORY_TABLE table; + RUNTIME_FUNCTION *func; + ULONG_PTR frame, base; + void *data; + BOOL found = FALSE; + + RtlCaptureContext(&context); + + switch (msg) + { + case WM_NCDESTROY: + for (frames = 0; frames < 16; frames++) + { + func = RtlLookupFunctionEntry(context.Rip, &base, &table); + if (RtlVirtualUnwind(UNW_FLAG_NHANDLER, base, context.Rip, func, &context, &data, &frame, NULL)) + break; + if (!context.Rip) break; + if (!frame) break; + if (context.Rip == (DWORD64)unwind_target || frame == (DWORD64)target_frame) + { + found = TRUE; + + /* check that non-volatile registers are zeroed before entering user callback. */ + ok(!context.Rbx, "unexpected register value, %%rbx = %#I64x\n", context.Rbx); + ok(!context.R12, "unexpected register value, %%r12 = %#I64x\n", context.R12); + ok(!context.R13, "unexpected register value, %%r13 = %#I64x\n", context.R13); + ok(!context.R14, "unexpected register value, %%r14 = %#I64x\n", context.R14); + ok(!context.R15, "unexpected register value, %%r15 = %#I64x\n", context.R15); + break; + } + } + todo_wine ok(found, "couldn't find target frame in parent frames\n"); + break; + default: break; + } + return DefWindowProcA(w, msg, p2, p3); +} + +/* add one more level of stack frame so problems during unwinding are more likely to show up. */ +asm("\t.globl destroy_window_trampoline\n\t" + ".def destroy_window_trampoline\n\t" + ".scl 2\n\t" + ".type 32\n\t" + ".endef\n\t" + ".seh_proc destroy_window_trampoline\n" + "destroy_window_trampoline:\n\t" + "push %rbp\n\t" + ".seh_pushreg %rbp\n\t" + "subq $40, %rsp\n\t" /* allocate some random amount */ + ".seh_stackalloc 40\n\t" + "leaq 32(%rsp), %rbp\n\t" + ".seh_setframe %rbp, 32\n\t" /* force the unwinder to unwind %rsp from %rbp */ + ".seh_endprologue\n\t" + "call *pDestroyWindow(%rip)\n\t" + "nop\n\t" /* some padding before epilogue */ + "addq $40, %rsp\n\t" + "pop %rbp\n\t" + "ret\n\t" + ".seh_endproc"); + +static void test_user_callback_registers(void) +{ + WNDCLASSA cls = {0}; + HWND hwnd; + ATOM atom; + + cls.style = CS_HREDRAW | CS_VREDRAW; + cls.lpfnWndProc = unwinding_wnd_proc; + cls.hInstance = GetModuleHandleA(0); + cls.lpszClassName = "test_user_callback_registers_class"; + + atom = RegisterClassA(&cls); + ok(!!atom, "RegisterClassA failed, error %#lx.\n", GetLastError()); + + hwnd = CreateWindowExA(WS_EX_TOPMOST, cls.lpszClassName, "", WS_POPUP | WS_VISIBLE, 100, 100, + 100, 100, NULL, NULL, 0, NULL); + ok(!!hwnd, "CreateWindowA failed, error %#lx.\n", GetLastError()); + + asm("mov %[poison], %%rbx\n\t" + "mov %[poison], %%r12\n\t" + "mov %[poison], %%r13\n\t" + "mov %[poison], %%r14\n\t" + "mov %[poison], %%r15\n\t" + "mov %%rsp, %0\n\t" + "lea 1f(%%rip), %%rdx\n\t" + "mov %%rdx, %1\n\t" + "mov %[hwnd], %%rcx\n\t" + "call destroy_window_trampoline\n\t" + "1:nop\n\t" + : "=m"(target_frame), "=m"(unwind_target) + : [poison] "i"(0xdeadbeafULL), [hwnd] "rm"(hwnd), "m"(pDestroyWindow) + : "rax", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "rbx", "r12", "r13", "r14", "r15", "memory"); + + UnregisterClassA(cls.lpszClassName, GetModuleHandleA(0)); +} + +#endif + START_TEST(win) { char **argv; int argc = winetest_get_mainargs( &argv ); HMODULE user32 = GetModuleHandleA( "user32.dll" ); HMODULE gdi32 = GetModuleHandleA("gdi32.dll"); + pDestroyWindow = (void *)GetProcAddress( user32, "DestroyWindow" ); pGetWindowInfo = (void *)GetProcAddress( user32, "GetWindowInfo" ); pGetWindowModuleFileNameA = (void *)GetProcAddress( user32, "GetWindowModuleFileNameA" ); pGetLayeredWindowAttributes = (void *)GetProcAddress( user32, "GetLayeredWindowAttributes" ); @@ -13943,6 +14052,9 @@ START_TEST(win) test_ReleaseCapture(); test_SetProcessLaunchForegroundPolicy(); test_startupinfo_showwindow(argv); +#if defined(_WIN64) && defined(__WINE_PE_BUILD) + test_user_callback_registers(); +#endif
/* add the tests above this line */ if (hhook) UnhookWindowsHookEx(hhook);