Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/ntdll/unix/signal_i386.c | 58 ++++++++++++++++++++++++++++++-- dlls/ntdll/unix/signal_x86_64.c | 59 +++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 4 deletions(-)
diff --git a/dlls/ntdll/unix/signal_i386.c b/dlls/ntdll/unix/signal_i386.c index 5d4d1469472..45482b819cc 100644 --- a/dlls/ntdll/unix/signal_i386.c +++ b/dlls/ntdll/unix/signal_i386.c @@ -683,6 +683,56 @@ static inline void save_fpux( CONTEXT *context ) }
+/*********************************************************************** + * save_xstate + * + * Save the XState context + */ +static inline NTSTATUS save_xstate( CONTEXT *context ) +{ + CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); + DECLSPEC_ALIGN(64) struct + { + XSAVE_FORMAT xsave; + XSTATE xstate; + } + xsave_area; + XSTATE *xs; + + if (!(user_shared_data->XState.EnabledFeatures && (xs = xstate_from_context( context )))) + return STATUS_SUCCESS; + + if (context_ex->XState.Length < offsetof(XSTATE, YmmContext) + || context_ex->XState.Length > sizeof(XSTATE)) + return STATUS_INVALID_PARAMETER; + + if (user_shared_data->XState.CompactionEnabled) + { + /* xsavec doesn't use anything from the save area. */ + __asm__ volatile( "xsavec %0" : "=m"(xsave_area) + : "a" ((unsigned int)(xs->CompactionMask & (1 << XSTATE_AVX))), "d" (0) ); + } + else + { + /* xsave preserves those bits in the mask which are not in EDX:EAX, so zero it. */ + xsave_area.xstate.Mask = xsave_area.xstate.CompactionMask = 0; + __asm__ volatile( "xsave %0" : "=m"(xsave_area) + : "a" ((unsigned int)(xs->Mask & (1 << XSTATE_AVX))), "d" (0) ); + } + + memcpy(xs, &xsave_area.xstate, offsetof(XSTATE, YmmContext)); + if (xs->Mask & (1 << XSTATE_AVX)) + { + if (context_ex->XState.Length < sizeof(XSTATE)) + return STATUS_BUFFER_OVERFLOW; + + memcpy(&xs->YmmContext, &xsave_area.xstate.YmmContext, sizeof(xs->YmmContext)); + } + + return STATUS_SUCCESS; +} + + /*********************************************************************** * restore_fpu * @@ -1189,11 +1239,15 @@ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) */ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) { - NTSTATUS ret; + NTSTATUS ret, xsave_status; struct syscall_frame *frame = x86_thread_data()->syscall_frame; DWORD needed_flags = context->ContextFlags & ~CONTEXT_i386; BOOL self = (handle == GetCurrentThread());
+ /* Save xstate before any calls which can potentially change volatile ymm registers. + * E. g., debug output will clobber ymm registers. */ + xsave_status = self ? save_xstate( context ) : STATUS_SUCCESS; /* FIXME: other thread. */ + /* debug registers require a server call */ if (needed_flags & CONTEXT_DEBUG_REGISTERS) self = FALSE;
@@ -1265,7 +1319,7 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) TRACE( "%p: dr0=%08x dr1=%08x dr2=%08x dr3=%08x dr6=%08x dr7=%08x\n", handle, context->Dr0, context->Dr1, context->Dr2, context->Dr3, context->Dr6, context->Dr7 );
- return STATUS_SUCCESS; + return xsave_status; }
diff --git a/dlls/ntdll/unix/signal_x86_64.c b/dlls/ntdll/unix/signal_x86_64.c index c77d65f8678..a0ad49815a4 100644 --- a/dlls/ntdll/unix/signal_x86_64.c +++ b/dlls/ntdll/unix/signal_x86_64.c @@ -1481,6 +1481,56 @@ static void save_context( struct xcontext *xcontext, const ucontext_t *sigcontex }
+/*********************************************************************** + * save_xstate + * + * Save the XState context + */ +static inline NTSTATUS save_xstate( CONTEXT *context ) +{ + CONTEXT_EX *context_ex = (CONTEXT_EX *)(context + 1); + DECLSPEC_ALIGN(64) struct + { + XSAVE_FORMAT xsave; + XSTATE xstate; + } + xsave_area; + XSTATE *xs; + + if (!(user_shared_data->XState.EnabledFeatures && (xs = xstate_from_context( context )))) + return STATUS_SUCCESS; + + if (context_ex->XState.Length < offsetof(XSTATE, YmmContext) + || context_ex->XState.Length > sizeof(XSTATE)) + return STATUS_INVALID_PARAMETER; + + if (user_shared_data->XState.CompactionEnabled) + { + /* xsavec doesn't use anything from the save area. */ + __asm__ volatile( "xsavec %0" : "=m"(xsave_area) + : "a" ((unsigned int)(xs->CompactionMask & (1 << XSTATE_AVX))), "d" (0) ); + } + else + { + /* xsave preserves those bits in the mask which are not in EDX:EAX, so zero it. */ + xsave_area.xstate.Mask = xsave_area.xstate.CompactionMask = 0; + __asm__ volatile( "xsave %0" : "=m"(xsave_area) + : "a" ((unsigned int)(xs->Mask & (1 << XSTATE_AVX))), "d" (0) ); + } + + memcpy(xs, &xsave_area.xstate, offsetof(XSTATE, YmmContext)); + if (xs->Mask & (1 << XSTATE_AVX)) + { + if (context_ex->XState.Length < sizeof(XSTATE)) + return STATUS_BUFFER_OVERFLOW; + + memcpy(&xs->YmmContext, &xsave_area.xstate.YmmContext, sizeof(xs->YmmContext)); + } + + return STATUS_SUCCESS; +} + + /*********************************************************************** * restore_context * @@ -1780,13 +1830,17 @@ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) */ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) { - NTSTATUS ret; + NTSTATUS ret, xsave_status; DWORD needed_flags; struct syscall_frame *frame = amd64_thread_data()->syscall_frame; BOOL self = (handle == GetCurrentThread());
if (!context) return STATUS_INVALID_PARAMETER;
+ /* Save xstate before any calls which can potentially change volatile ymm registers. + * E. g., debug output will clobber ymm registers. */ + xsave_status = self ? save_xstate( context ) : STATUS_SUCCESS; /* FIXME: other thread. */ + needed_flags = context->ContextFlags & ~CONTEXT_AMD64;
/* debug registers require a server call */ @@ -1859,7 +1913,8 @@ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) amd64_thread_data()->dr7 = context->Dr7; } } - return STATUS_SUCCESS; + + return xsave_status; }
extern void CDECL raise_func_trampoline( void *dispatcher );
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/ntdll/tests/exception.c | 268 ++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 1 deletion(-)
diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index 301b86e8d3c..aac5df531a0 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -6189,6 +6189,116 @@ static void test_extended_context(void) 0xc5, 0xfc, 0x11, 0x00, /* vmovups %ymm0,(%ax) */ 0xc3, /* ret */ }; + + struct call_func_offsets + { + unsigned int func_addr; + unsigned int func_param1; + unsigned int func_param2; + unsigned int ymm0_save; + }; +#ifdef __x86_64__ + static BYTE call_func_code_set_ymm0[] = + { + 0x55, /* pushq %rbp */ + 0x48, 0xb8, /* mov imm,%rax */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x48, 0xb9, /* mov imm,%rcx */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x48, 0xba, /* mov imm,%rdx */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x48, 0xbd, /* mov imm,%rbp */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0xc5, 0xfc, 0x10, 0x45, 0x00, /* vmovups (%rbp),%ymm0 */ + 0xff, 0xd0, /* call *rax */ + 0xc5, 0xfc, 0x11, 0x45, 0x00, /* vmovups %ymm0,(%rbp) */ + 0x5d, /* popq %rbp */ + 0xc3, /* ret */ + }; + static BYTE call_func_code_reset_ymm_state[] = + { + 0x55, /* pushq %rbp */ + 0x48, 0xb8, /* mov imm,%rax */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x48, 0xb9, /* mov imm,%rcx */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x48, 0xba, /* mov imm,%rdx */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x48, 0xbd, /* mov imm,%rbp */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0xc5, 0xf8, 0x77, /* vzeroupper */ + 0x0f, 0x57, 0xc0, /* xorps %xmm0,%xmm0 */ + 0xff, 0xd0, /* call *rax */ + 0xc5, 0xfc, 0x11, 0x45, 0x00, /* vmovups %ymm0,(%rbp) */ + 0x5d, /* popq %rbp */ + 0xc3, /* ret */ + }; + static const struct call_func_offsets call_func_offsets = {3, 13, 23, 33}; +#else + static BYTE call_func_code_set_ymm0[] = + { + 0x55, /* pushl %ebp */ + 0xb8, /* mov imm,%eax */ + 0x00, 0x00, 0x00, 0x00, + + 0xb9, /* mov imm,%ecx */ + 0x00, 0x00, 0x00, 0x00, + + 0xba, /* mov imm,%edx */ + 0x00, 0x00, 0x00, 0x00, + + 0xbd, /* mov imm,%ebp */ + 0x00, 0x00, 0x00, 0x00, + + 0x81, 0xfa, 0xef, 0xbe, 0xad, 0xde, + /* cmpl $0xdeadbeef, %edx */ + 0x74, 0x01, /* je 1f */ + 0x52, /* pushl %edx */ + 0x51, /* 1: pushl %ecx */ + 0xc5, 0xfc, 0x10, 0x45, 0x00, /* vmovups (%ebp),%ymm0 */ + 0xff, 0xd0, /* call *eax */ + 0xc5, 0xfc, 0x11, 0x45, 0x00, /* vmovups %ymm0,(%ebp) */ + 0x5d, /* popl %ebp */ + 0xc3, /* ret */ + }; + static BYTE call_func_code_reset_ymm_state[] = + { + 0x55, /* pushl %ebp */ + 0xb8, /* mov imm,%eax */ + 0x00, 0x00, 0x00, 0x00, + + 0xb9, /* mov imm,%ecx */ + 0x00, 0x00, 0x00, 0x00, + + 0xba, /* mov imm,%edx */ + 0x00, 0x00, 0x00, 0x00, + + 0xbd, /* mov imm,%ebp */ + 0x00, 0x00, 0x00, 0x00, + + 0x81, 0xfa, 0xef, 0xbe, 0xad, 0xde, + /* cmpl $0xdeadbeef, %edx */ + 0x74, 0x01, /* je 1f */ + 0x52, /* pushl %edx */ + 0x51, /* 1: pushl %ecx */ + 0xc5, 0xf8, 0x77, /* vzeroupper */ + 0x0f, 0x57, 0xc0, /* xorps %xmm0,%xmm0 */ + 0xff, 0xd0, /* call *eax */ + 0xc5, 0xfc, 0x11, 0x45, 0x00, /* vmovups %ymm0,(%ebp) */ + 0x5d, /* popl %ebp */ + 0xc3, /* ret */ + }; + static const struct call_func_offsets call_func_offsets = {2, 7, 12, 17}; +#endif + static const struct { ULONG flag; @@ -6235,8 +6345,9 @@ static void test_extended_context(void) DECLSPEC_ALIGN(64) BYTE context_buffer[2048]; unsigned int i, j, address_offset, test; ULONG ret, ret2, length, length2, align; + ULONG flags, flags_fpx, expected_flags; + ULONG (WINAPI* func)(void) = code_mem; CONTEXT_EX *context_ex; - ULONG flags, flags_fpx; CONTEXT *context; unsigned data[8]; ULONG64 mask; @@ -6831,6 +6942,161 @@ static void test_extended_context(void) return; }
+ /* Test RtlCaptureContext (doesn't support xstates). */ + length = sizeof(context_buffer); + memset(context_buffer, 0xcc, sizeof(context_buffer)); + bret = pInitializeContext(context_buffer, CONTEXT_XSTATE, &context, &length); + ok(bret, "Got unexpected bret %#x.\n", bret); + context_ex = (CONTEXT_EX *)(context + 1); + xs = (XSTATE *)((BYTE *)context_ex + context_ex->XState.Offset); + + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_addr) = RtlCaptureContext; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_param1) = context; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_param2) = (void *)0xdeadbeef; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.ymm0_save) = data; + memcpy(code_mem, call_func_code_set_ymm0, sizeof(call_func_code_set_ymm0)); + + memcpy(data, test_extended_context_data, sizeof(data)); + func(); + ok(context->ContextFlags == (CONTEXT_FULL | CONTEXT_SEGMENTS), "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + for (i = 0; i < 8; ++i) + ok(data[i] == test_extended_context_data[i], "Got unexpected data %#x, i %u.\n", data[i], i); + + /* Test GetThreadContext (current thread, ymm0 set). */ + length = sizeof(context_buffer); + memset(context_buffer, 0xcc, sizeof(context_buffer)); + bret = pInitializeContext(context_buffer, CONTEXT_FULL | CONTEXT_XSTATE | CONTEXT_FLOATING_POINT, + &context, &length); + ok(bret, "Got unexpected bret %#x.\n", bret); + memset(&xs->YmmContext, 0xcc, sizeof(xs->YmmContext)); + + expected_flags = CONTEXT_FULL | CONTEXT_XSTATE | CONTEXT_FLOATING_POINT; +#ifdef __i386__ + expected_flags |= CONTEXT_EXTENDED_REGISTERS; +#endif + pSetXStateFeaturesMask(context, ~(ULONG64)0); + ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_addr) = GetThreadContext; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_param1) = (void *)GetCurrentThread(); + *(void **)(call_func_code_set_ymm0 + call_func_offsets.func_param2) = context; + *(void **)(call_func_code_set_ymm0 + call_func_offsets.ymm0_save) = data; + memcpy(code_mem, call_func_code_set_ymm0, sizeof(call_func_code_set_ymm0)); + xs->CompactionMask = 2; + if (!compaction_enabled) + xs->Mask = 0; + context_ex->XState.Length = sizeof(XSTATE); + + bret = func(); + ok(bret, "Got unexpected bret %#x, GetLastError() %u.\n", bret, GetLastError()); + + ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + expected_compaction = compaction_enabled ? (ULONG64)1 << 63 : 0; + + ok(!xs->Mask, "Got unexpected Mask %s.\n", wine_dbgstr_longlong(xs->Mask)); + ok(xs->CompactionMask == expected_compaction, "Got unexpected CompactionMask %s.\n", + wine_dbgstr_longlong(xs->CompactionMask)); + + for (i = 4; i < 8; ++i) + ok(data[i] == test_extended_context_data[i], "Got unexpected data %#x, i %u.\n", data[i], i); + + for (i = 0; i < 4; ++i) + ok(((ULONG *)&xs->YmmContext)[i] == 0xcccccccc, + "Got unexpected data %#x, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); + + expected_compaction = compaction_enabled ? ((ULONG64)1 << 63) | 4 : 0; + + xs->CompactionMask = 4; + xs->Mask = compaction_enabled ? 0 : 4; + context_ex->XState.Length = sizeof(XSTATE) + 64; + bret = func(); + ok(!bret && GetLastError() == ERROR_INVALID_PARAMETER, + "Got unexpected bret %#x, GetLastError() %u.\n", bret, GetLastError()); + ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + ok(xs->Mask == (compaction_enabled ? 0 : 4), "Got unexpected Mask %s.\n", wine_dbgstr_longlong(xs->Mask)); + ok(xs->CompactionMask == 4, "Got unexpected CompactionMask %s.\n", + wine_dbgstr_longlong(xs->CompactionMask)); + for (i = 0; i < 4; ++i) + ok(((ULONG *)&xs->YmmContext)[i] == 0xcccccccc, + "Got unexpected data %#x, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); + + xs->CompactionMask = 4; + xs->Mask = compaction_enabled ? 0 : 4; + context_ex->XState.Length = offsetof(XSTATE, YmmContext); + bret = func(); + ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + ok(!bret && GetLastError() == ERROR_MORE_DATA, + "Got unexpected bret %#x, GetLastError() %u.\n", bret, GetLastError()); + ok(xs->Mask == 4, "Got unexpected Mask %s.\n", wine_dbgstr_longlong(xs->Mask)); + ok(xs->CompactionMask == expected_compaction, "Got unexpected CompactionMask %s.\n", + wine_dbgstr_longlong(xs->CompactionMask)); + for (i = 0; i < 4; ++i) + ok(((ULONG *)&xs->YmmContext)[i] == 0xcccccccc, + "Got unexpected data %#x, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); + + context_ex->XState.Length = sizeof(XSTATE); + xs->CompactionMask = 4; + xs->Mask = compaction_enabled ? 0 : 4; + bret = func(); + ok(bret, "Got unexpected bret %#x, GetLastError() %u.\n", bret, GetLastError()); + + ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + + ok(xs->Mask == 4, "Got unexpected Mask %s.\n", wine_dbgstr_longlong(xs->Mask)); + ok(xs->CompactionMask == expected_compaction, "Got unexpected CompactionMask %s.\n", + wine_dbgstr_longlong(xs->CompactionMask)); + + for (i = 4; i < 8; ++i) + ok(data[i] == test_extended_context_data[i], "Got unexpected data %#x, i %u.\n", data[i], i); + + for (i = 0; i < 4; ++i) + ok(((ULONG *)&xs->YmmContext)[i] == test_extended_context_data[i + 4], + "Got unexpected data %#x, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); + + /* Test GetThreadContext (current thread, ymm state cleared). */ + length = sizeof(context_buffer); + memset(context_buffer, 0xcc, sizeof(context_buffer)); + bret = pInitializeContext(context_buffer, CONTEXT_FULL | CONTEXT_XSTATE | CONTEXT_FLOATING_POINT, + &context, &length); + memset(&xs->YmmContext, 0xcc, sizeof(xs->YmmContext)); + ok(bret, "Got unexpected bret %#x.\n", bret); + pSetXStateFeaturesMask(context, ~(ULONG64)0); + *(void **)(call_func_code_reset_ymm_state + call_func_offsets.func_addr) = GetThreadContext; + *(void **)(call_func_code_reset_ymm_state + call_func_offsets.func_param1) = (void *)GetCurrentThread(); + *(void **)(call_func_code_reset_ymm_state + call_func_offsets.func_param2) = context; + *(void **)(call_func_code_reset_ymm_state + call_func_offsets.ymm0_save) = data; + memcpy(code_mem, call_func_code_reset_ymm_state, sizeof(call_func_code_reset_ymm_state)); + + bret = func(); + ok(bret, "Got unexpected bret %#x, GetLastError() %u.\n", bret, GetLastError()); + + expected_flags = CONTEXT_FULL | CONTEXT_XSTATE | CONTEXT_FLOATING_POINT; +#ifdef __i386__ + expected_flags |= CONTEXT_EXTENDED_REGISTERS; +#endif + ok(context->ContextFlags == expected_flags, "Got unexpected ContextFlags %#x.\n", + context->ContextFlags); + + expected_compaction = compaction_enabled ? ((ULONG64)1 << 63) | 4 : 0; + + xs = (XSTATE *)((BYTE *)context_ex + context_ex->XState.Offset); + ok(!xs->Mask, "Got unexpected Mask %s.\n", wine_dbgstr_longlong(xs->Mask)); + ok(xs->CompactionMask == expected_compaction, "Got unexpected CompactionMask %s.\n", + wine_dbgstr_longlong(xs->CompactionMask)); + + for (i = 4; i < 8; ++i) + ok(!data[i], "Got unexpected data %#x, i %u.\n", data[i], i); + + for (i = 0; i < 4; ++i) + ok(((ULONG *)&xs->YmmContext)[i] == 0xcccccccc + || broken(((ULONG *)&xs->YmmContext)[i] == test_extended_context_data[i + 4]), + "Got unexpected data %#x, i %u.\n", ((ULONG *)&xs->YmmContext)[i], i); + /* Test fault exception context. */ memset(data, 0xff, sizeof(data)); test_extended_context_modified_state = FALSE;