From: Yuxuan Shui yshui@codeweavers.com
Check which non-volatile registers are set to their saved values before a user callback is entered. --- dlls/user32/tests/win.c | 129 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index f899008f394..358a529e245 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 @@ -13750,6 +13751,131 @@ static void test_startupinfo_showwindow( char **argv ) } }
+#ifdef __x86_64__ +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 set properly before entering user callback. */ + ok(context.Rbx != 0xdeadbeef, "unexpected register value, %%rbx = %#I64x\n", context.Rbx); + ok(context.R12 != 0xdeadbeef, "unexpected register value, %%r12 = %#I64x\n", context.R12); + ok(context.R13 != 0xdeadbeef, "unexpected register value, %%r13 = %#I64x\n", context.R13); + ok(context.R14 != 0xdeadbeef, "unexpected register value, %%r14 = %#I64x\n", context.R14); + ok(context.R15 != 0xdeadbeef, "unexpected register value, %%r15 = %#I64x\n", context.R15); + todo_wine ok(context.Rbp == 0xdeadbeef, "unexpected register value, %%rbp = %#I64x\n", context.Rbp); + + /* native seems to zero these registers, unclear whether wine should do the same. */ + todo_wine { + 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; + } + } + ok(found, "couldn't find target frame in parent frames\n"); + break; + default: break; + } + return DefWindowProcA(w, msg, p2, p3); +} + +static void test_user_callback_registers(void) +{ + WNDCLASSA cls = {0}; + HWND hwnd; + ATOM atom; + UINT64 patch; + BYTE *code_mem = VirtualAlloc( NULL, 65536, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + void (*pdestroy_window_trampoline)(HWND) = (void (*)(HWND))code_mem; + + /* setup register context so later we can check what changed and what hasn't. */ + static const BYTE trampoline[] = + { + 0x55, /* 00: push %rbp */ + 0x41, 0x57, /* 01: push %r15 */ + 0x41, 0x56, /* 03: push %r14 */ + 0x41, 0x55, /* 05: push %r13 */ + 0x41, 0x54, /* 07: push %r12 */ + 0x53, /* 09: push %rbx */ + 0x48, 0x83, 0xec, 0x28, /* 0a: sub 0x28,%rsp */ + 0xbd, 0xef, 0xbe, 0xad, 0xde, /* 0e: mov $0xdeadbeef,%rbp */ + 0xbb, 0xef, 0xbe, 0xad, 0xde, /* 13: mov $0xdeadbeef,%rbx */ + 0x41, 0xbc, 0xef, 0xbe, 0xad, 0xde, /* 18: mov $0xdeadbeef,%r12 */ + 0x41, 0xbd, 0xef, 0xbe, 0xad, 0xde, /* 1e: mov $0xdeadbeef,%r13 */ + 0x41, 0xbe, 0xef, 0xbe, 0xad, 0xde, /* 24: mov $0xdeadbeef,%r14 */ + 0x41, 0xbf, 0xef, 0xbe, 0xad, 0xde, /* 2a: mov $0xdeadbeef,%r15 */ + 0x48, 0xb8, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, /* 30: mov ?,%rax # &target_frame */ + 0x48, 0x89, 0x20, /* 3a: mov %rsp,(%rax) */ + 0x48, 0xb8, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, /* 3d: mov ?,%rax # DestroyWindow */ + 0xff, 0xd0, /* 47: call *%rax */ + 0x90, /* 49: nop */ + 0x48, 0x83, 0xc4, 0x28, /* 4a: add $0x28,%rsp */ + 0x5b, /* 4e: pop %rbx */ + 0x41, 0x5c, /* 4f: pop %r12 */ + 0x41, 0x5d, /* 51: pop %r13 */ + 0x41, 0x5e, /* 53: pop %r14 */ + 0x41, 0x5f, /* 55: pop %r15 */ + 0x5d, /* 57: pop %rbp */ + 0xc3, /* 58: ret */ + }; + + memcpy(code_mem, trampoline, ARRAYSIZE(trampoline)); + + patch = (ULONG_PTR)&target_frame; + memcpy(code_mem + 0x30 + 2, &patch, 8); + patch = (ULONG_PTR)DestroyWindow; + memcpy(code_mem + 0x3d + 2, &patch, 8); + + unwind_target = code_mem + 0x49; + + 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()); + + pdestroy_window_trampoline(hwnd); + + VirtualFree(code_mem, 0, MEM_RELEASE); + UnregisterClassA(cls.lpszClassName, GetModuleHandleA(0)); +} + +#endif + START_TEST(win) { char **argv; @@ -13943,6 +14069,9 @@ START_TEST(win) test_ReleaseCapture(); test_SetProcessLaunchForegroundPolicy(); test_startupinfo_showwindow(argv); +#ifdef __x86_64__ + test_user_callback_registers(); +#endif
/* add the tests above this line */ if (hhook) UnhookWindowsHookEx(hhook);