From: Rémi Bernon rbernon@codeweavers.com
Showing that only non-volatile registers are reliably saved. Volatile registers are only saved by NtGetContextThread whenever it interrupts a thread in user space, and are otherwise returned from some previous, possibly outdated, state. --- dlls/ntdll/tests/thread.c | 253 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+)
diff --git a/dlls/ntdll/tests/thread.c b/dlls/ntdll/tests/thread.c index 3086247d5f4..9233b49b987 100644 --- a/dlls/ntdll/tests/thread.c +++ b/dlls/ntdll/tests/thread.c @@ -21,6 +21,8 @@
#include "ntdll_test.h"
+#include "intrin.h" + static NTSTATUS (WINAPI *pNtCreateThreadEx)( HANDLE *, ACCESS_MASK, OBJECT_ATTRIBUTES *, HANDLE, PRTL_THREAD_START_ROUTINE, void *, ULONG, ULONG_PTR, SIZE_T, SIZE_T, PS_ATTRIBUTE_LIST * ); @@ -177,10 +179,261 @@ static void test_unique_teb(void) ok( args1.teb != args2.teb, "Multiple threads have TEB %p.\n", args1.teb ); }
+#if defined(__i386__) || defined(__x86_64__) +static LONG test_context_signal; + +static DWORD WINAPI test_context_thread( void *arg ) +{ + LARGE_INTEGER timeout = {.QuadPart = 100 * -10000}; + UINT64 DECLSPEC_ALIGN(16) initial[128]; + UINT64 DECLSPEC_ALIGN(16) fpu_buf[128]; + CONTEXT *context; + M128A *xmm, *ymm; + void *buffer; + DWORD size; + BOOL ret; + + ret = InitializeContext( NULL, CONTEXT_ALL | CONTEXT_FLOATING_POINT | CONTEXT_XSTATE, NULL, &size ); + ok( !ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "InitializeContext failed, error %lu\n", GetLastError() ); + buffer = malloc( size ); + ok( !!buffer, "malloc failed\n" ); + ret = InitializeContext( buffer, CONTEXT_ALL | CONTEXT_FLOATING_POINT | CONTEXT_XSTATE, &context, &size ); + ok( ret, "InitializeContext failed, error %lu\n", GetLastError() ); + ret = SetXStateFeaturesMask( context, XSTATE_MASK_GSSE ); + ok( ret, "SetXStateFeaturesMask failed, error %lu\n", GetLastError() ); + xmm = (M128A *)LocateXStateFeature( context, XSTATE_LEGACY_SSE, NULL ); + ok( !!xmm, "LocateXStateFeature XSTATE_LEGACY_SSE failed, error %lu\n", GetLastError() ); + ymm = (M128A *)LocateXStateFeature( context, XSTATE_AVX, NULL ); + ok( !!ymm, "LocateXStateFeature XSTATE_AVX failed, error %lu\n", GetLastError() ); + + _fxsave( initial ); + + /* NtGetContextThread misses volatile FPU registers */ + + _fxsave( fpu_buf ); + fpu_buf[20] = 1; + fpu_buf[21] = 2; + fpu_buf[32] = 3; + fpu_buf[33] = 4; + _fxrstor( fpu_buf ); + fpu_buf[20] = 0; + fpu_buf[21] = 0; + fpu_buf[32] = 0; + fpu_buf[33] = 0; + _fxsave( fpu_buf ); + + context->ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_XSTATE; + NtGetContextThread( GetCurrentThread(), context ); + + _fxrstor( initial ); + + ok( fpu_buf[20] == 1, "got xmm0:lo %#I64x\n", fpu_buf[20] ); + ok( fpu_buf[21] == 2, "got xmm0:hi %#I64x\n", fpu_buf[21] ); + ok( fpu_buf[32] == 3, "got xmm6:lo %#I64x\n", fpu_buf[32] ); + ok( fpu_buf[33] == 4, "got xmm6:hi %#I64x\n", fpu_buf[33] ); + + todo_wine_if( sizeof(void *) == 8 ) + ok( xmm[0].Low == 0, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + todo_wine_if( sizeof(void *) == 8 ) + ok( xmm[0].High == 0, "got context xmm0:hi %#I64x\n", xmm[0].High ); +#ifdef _WIN64 + ok( xmm[6].Low == 3, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 4, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#else + ok( xmm[6].Low == 0, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 0, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#endif + ok( ymm[0].Low == 0, "got context ymm0:lo %#I64x\n", ymm[0].Low ); + ok( ymm[0].High == 0, "got context ymm0:hi %#I64x\n", ymm[0].High ); + ok( ymm[6].Low == 0, "got context ymm6:lo %#I64x\n", ymm[6].Low ); + ok( ymm[6].High == 0, "got context ymm6:hi %#I64x\n", ymm[6].High ); + + /* NtGetContextThread returns volatile FPU registers from the previous NtSetContextThread call */ + + xmm[0].Low = 5; + xmm[0].High = 6; + xmm[6].Low = 7; + xmm[6].High = 8; + ymm[0].Low = 1000; + ymm[0].High = 1001; + ymm[6].Low = 1002; + ymm[6].High = 1003; + context->ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_XSTATE; + NtSetContextThread( GetCurrentThread(), context ); + + _fxsave( fpu_buf ); + fpu_buf[20] = 9; + fpu_buf[21] = 10; + _fxrstor( fpu_buf ); + fpu_buf[20] = 0; + fpu_buf[21] = 0; + _fxsave( fpu_buf ); + + context->ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_XSTATE; + NtGetContextThread( GetCurrentThread(), context ); + + _fxrstor( initial ); + + ok( fpu_buf[20] == 9, "got xmm0:lo %#I64x\n", fpu_buf[20] ); + ok( fpu_buf[21] == 10, "got xmm0:hi %#I64x\n", fpu_buf[21] ); +#ifdef _WIN64 + ok( fpu_buf[32] == 7, "got xmm6:lo %#I64x\n", fpu_buf[32] ); + ok( fpu_buf[33] == 8, "got xmm6:hi %#I64x\n", fpu_buf[33] ); +#else + todo_wine_if( sizeof(void *) == 4 ) + ok( fpu_buf[32] == 0, "got xmm6:lo %#I64x\n", fpu_buf[32] ); + todo_wine_if( sizeof(void *) == 4 ) + ok( fpu_buf[33] == 0, "got xmm6:hi %#I64x\n", fpu_buf[33] ); +#endif + + todo_wine_if( sizeof(void *) == 8 ) + ok( xmm[0].Low == 5, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + todo_wine_if( sizeof(void *) == 8 ) + ok( xmm[0].High == 6, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 7, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 8, "got context xmm6:hi %#I64x\n", xmm[6].High ); + ok( ymm[0].Low == 1000, "got context ymm0:lo %#I64x\n", ymm[0].Low ); + ok( ymm[0].High == 1001, "got context ymm0:hi %#I64x\n", ymm[0].High ); + ok( ymm[6].Low == 1002, "got context ymm6:lo %#I64x\n", ymm[6].Low ); + ok( ymm[6].High == 1003, "got context ymm6:hi %#I64x\n", ymm[6].High ); + + /* check reading context from the main thread while in user space */ + + _fxsave( fpu_buf ); + fpu_buf[20] = 11; + fpu_buf[21] = 12; + fpu_buf[32] = 13; + fpu_buf[33] = 14; + _fxrstor( fpu_buf ); + + InterlockedIncrement( &test_context_signal ); + while (InterlockedOr( &test_context_signal, 0 )) YieldProcessor(); + + /* check reading context from the main thread while in syscall */ + + _fxsave( fpu_buf ); + fpu_buf[20] = 15; + fpu_buf[21] = 16; + fpu_buf[32] = 17; + fpu_buf[33] = 18; + _fxrstor( fpu_buf ); + + InterlockedIncrement( &test_context_signal ); + NtDelayExecution( TRUE, &timeout ); + + /* NtGetContextThread returns volatile FPU registers from the previous NtSetContextThread call */ + + context->ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_XSTATE; + NtGetContextThread( GetCurrentThread(), context ); + + _fxrstor( initial ); + +#ifdef _WIN64 + todo_wine + ok( xmm[0].Low == 11, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + todo_wine + ok( xmm[0].High == 12, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 17, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 18, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#else + ok( xmm[0].Low == 5, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + ok( xmm[0].High == 6, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 7, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 8, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#endif + ok( ymm[0].Low == 1000, "got context ymm0:lo %#I64x\n", ymm[0].Low ); + ok( ymm[0].High == 1001, "got context ymm0:hi %#I64x\n", ymm[0].High ); + ok( ymm[6].Low == 1002, "got context ymm6:lo %#I64x\n", ymm[6].Low ); + ok( ymm[6].High == 1003, "got context ymm6:hi %#I64x\n", ymm[6].High ); + + free( buffer ); + return 0; +} + +static void test_NtGetThreadContext(void) +{ + HANDLE thread = CreateThread( NULL, 0, test_context_thread, 0, 0, NULL ); + CONTEXT *context; + M128A *xmm, *ymm; + void *buffer; + DWORD size; + BOOL ret; + + ret = InitializeContext( NULL, CONTEXT_ALL | CONTEXT_FLOATING_POINT | CONTEXT_XSTATE, NULL, &size ); + ok( !ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "InitializeContext failed, error %lu\n", GetLastError() ); + buffer = malloc( size ); + ok( !!buffer, "malloc failed\n" ); + ret = InitializeContext( buffer, CONTEXT_ALL | CONTEXT_FLOATING_POINT | CONTEXT_XSTATE, &context, &size ); + ok( ret, "InitializeContext failed, error %lu\n", GetLastError() ); + ret = SetXStateFeaturesMask( context, XSTATE_MASK_GSSE ); + ok( ret, "SetXStateFeaturesMask failed, error %lu\n", GetLastError() ); + xmm = (M128A *)LocateXStateFeature( context, XSTATE_LEGACY_SSE, NULL ); + ok( !!xmm, "LocateXStateFeature XSTATE_LEGACY_SSE failed, error %lu\n", GetLastError() ); + ymm = (M128A *)LocateXStateFeature( context, XSTATE_AVX, NULL ); + ok( !!ymm, "LocateXStateFeature XSTATE_AVX failed, error %lu\n", GetLastError() ); + + while (!InterlockedOr( &test_context_signal, 0 )) YieldProcessor(); + + /* NtGetContextThread context while it's in user space captures volatile FPU registers */ + + context->ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_XSTATE; + NtGetContextThread( thread, context ); +#ifdef _WIN64 + ok( xmm[0].Low == 11, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + ok( xmm[0].High == 12, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 13, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 14, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#else + ok( xmm[0].Low == 0, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + ok( xmm[0].High == 0, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 0, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 0, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#endif + ok( ymm[0].Low == 0, "got context ymm0:lo %#I64x\n", ymm[0].Low ); + ok( ymm[0].High == 0, "got context ymm0:hi %#I64x\n", ymm[0].High ); + ok( ymm[6].Low == 0, "got context ymm6:lo %#I64x\n", ymm[6].Low ); + ok( ymm[6].High == 0, "got context ymm6:hi %#I64x\n", ymm[6].High ); + + InterlockedDecrement( &test_context_signal ); + while (!InterlockedOr( &test_context_signal, 0 )) YieldProcessor(); + Sleep( 10 ); /* leave some time for the thread to enter the syscall */ + + /* NtGetContextThread context while in a syscall returns outdated volatile FPU registers */ + + context->ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_XSTATE; + NtGetContextThread( thread, context ); +#ifdef _WIN64 + todo_wine + ok( xmm[0].Low == 11, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + todo_wine + ok( xmm[0].High == 12, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 17, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 18, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#else + ok( xmm[0].Low == 0, "got context xmm0:lo %#I64x\n", xmm[0].Low ); + ok( xmm[0].High == 0, "got context xmm0:hi %#I64x\n", xmm[0].High ); + ok( xmm[6].Low == 0, "got context xmm6:lo %#I64x\n", xmm[6].Low ); + ok( xmm[6].High == 0, "got context xmm6:hi %#I64x\n", xmm[6].High ); +#endif + ok( ymm[0].Low == 0, "got context ymm0:lo %#I64x\n", ymm[0].Low ); + ok( ymm[0].High == 0, "got context ymm0:hi %#I64x\n", ymm[0].High ); + ok( ymm[6].Low == 0, "got context ymm6:lo %#I64x\n", ymm[6].Low ); + ok( ymm[6].High == 0, "got context ymm6:hi %#I64x\n", ymm[6].High ); + + WaitForSingleObject( thread, 1000 ); + CloseHandle( thread ); + + free( buffer ); +} +#endif /* defined(__i386__) || defined(__x86_64__) */ + START_TEST(thread) { init_function_pointers();
test_dbg_hidden_thread_creation(); test_unique_teb(); +#if defined(__i386__) || defined(__x86_64__) + test_NtGetThreadContext(); +#endif /* defined(__i386__) || defined(__x86_64__) */ }