From 360b579a5d65d2a56db5664fba306fe500b33f7e Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Mon, 29 Jun 2026 21:46:16 +0300 Subject: [PATCH] [NTDLL_APITEST] Add more tests for NtCreateThread --- .../rostests/apitests/ntdll/CMakeLists.txt | 2 + .../rostests/apitests/ntdll/NtCreateThread.c | 315 ++++++++++++++++++ .../apitests/ntdll/amd64/NtCreateThread.S | 45 +++ .../apitests/ntdll/i386/NtCreateThread.S | 49 +++ 4 files changed, 411 insertions(+) create mode 100644 modules/rostests/apitests/ntdll/amd64/NtCreateThread.S create mode 100644 modules/rostests/apitests/ntdll/i386/NtCreateThread.S diff --git a/modules/rostests/apitests/ntdll/CMakeLists.txt b/modules/rostests/apitests/ntdll/CMakeLists.txt index 9aee69ca1a9..3c75135007e 100644 --- a/modules/rostests/apitests/ntdll/CMakeLists.txt +++ b/modules/rostests/apitests/ntdll/CMakeLists.txt @@ -156,11 +156,13 @@ if(ARCH STREQUAL "i386") add_asm_files(ntdll_apitest_asm ../crt/i386/setjmp_helper.s i386/NtContinue.S + i386/NtCreateThread.S ) elseif(ARCH STREQUAL "amd64") add_asm_files(ntdll_apitest_asm ../crt/amd64/setjmp_helper.s amd64/NtContinue.S + amd64/NtCreateThread.S ) endif() diff --git a/modules/rostests/apitests/ntdll/NtCreateThread.c b/modules/rostests/apitests/ntdll/NtCreateThread.c index b31fc1fb79e..6d71d3a1942 100644 --- a/modules/rostests/apitests/ntdll/NtCreateThread.c +++ b/modules/rostests/apitests/ntdll/NtCreateThread.c @@ -3,16 +3,139 @@ * LICENSE: See COPYING in the top level directory * PURPOSE: Test for NtCreateThread * PROGRAMMER: Aleksandar Andrejevic + * PROGRAMMER: Timo Kreuzer */ #include "precomp.h" +PVOID TestThreadProcPtr; +CONTEXT TestThreadStartupContext; +DECLSPEC_ALIGN(16) UCHAR TestThreadStartupStack[PAGE_SIZE]; +BOOLEAN TestThreadFunctionCalled; +extern void ThreadStartupThunk(void*); + +#define RPL_MASK 0x0003 +#define MODE_MASK 0x0001 +#define EFLAGS_INTERRUPT_MASK 0x200L +#define KGDT_R3_CODE 0x0018 +#define KGDT_R3_DATA 0x0020 +#define KGDT_R3_TEB 0x0038 +#define KGDT64_R3_CMCODE 0x0020 +#define KGDT64_R3_DATA 0x0028 +#define KGDT64_R3_CODE 0x0030 +#define KGDT64_R3_CMTEB 0x0050 + +static +VOID +InitializeTestContext(PCONTEXT Context) +{ + RtlFillMemory(Context, sizeof(*Context), 0xAA); + + Context->ContextFlags = CONTEXT_ALL; + +#ifdef _M_AMD64 + + /* Control */ + Context->Rsp = (ULONG64)(TestThreadStartupStack + PAGE_SIZE - 16); + Context->Rip = (ULONG64)ThreadStartupThunk; + + /* Set Eflags. These get sanitized, but 0x100 (trap flag) makes it trap instantly. */ + Context->EFlags = 0xFFFFFFFF; + Context->EFlags &= ~0x100; + + /* Integer (these are copied) */ + Context->Rax = 0xF000000000000000; + Context->Rcx = 0xF000000000000001; + Context->Rdx = 0xF000000000000002; + Context->Rbx = 0xF000000000000003; + Context->Rbp = 0xF000000000000005; + Context->Rsi = 0xF000000000000006; + Context->Rdi = 0xF000000000000007; + Context->R8 = 0xF000000000000008; + Context->R9 = 0xF000000000000009; + Context->R10 = 0xF00000000000000A; + Context->R11 = 0xF00000000000000B; + Context->R12 = 0xF00000000000000C; + Context->R13 = 0xF00000000000000D; + Context->R14 = 0xF00000000000000E; + Context->R15 = 0xF00000000000000F; + +#elif defined(_M_IX86) + + /* Control */ + Context->Esp = (ULONG)(TestThreadStartupStack + PAGE_SIZE - 16); + Context->Eip = (ULONG)ThreadStartupThunk; + + /* Set Eflags. These get sanitized, but 0x100 (trap flag) makes it trap instantly. */ + Context->EFlags = 0xFFFFFFFF; + Context->EFlags &= ~0x100; // Disable trap flag, because it would make the thread trap instantly + Context->EFlags &= ~0x00020000; // Disable V86 flag + + /* Integer (these are copied) */ + Context->Eax = 0xF0000000; + Context->Ecx = 0xF0000001; + Context->Edx = 0xF0000002; + Context->Ebx = 0xF0000003; + Context->Ebp = 0xF0000005; + Context->Esi = 0xF0000006; + Context->Edi = 0xF0000007; + + /* FIXME: Initialize segments to prevent ReactOS from crashing in the kernel */ + Context->SegCs = KGDT_R3_CODE | RPL_MASK; + Context->SegSs = KGDT_R3_DATA | RPL_MASK; + Context->SegDs = KGDT_R3_DATA | RPL_MASK; + Context->SegEs = KGDT_R3_DATA | RPL_MASK; + Context->SegFs = KGDT_R3_TEB | RPL_MASK; + Context->SegGs = 0; + +#else + +#pragma message("Missing CONTEXT setup in NtCreateThread test") + ASSERT(FALSE); + +#endif +} + +VOID +NTAPI +TestThreadProc(IN PVOID StartContext) +{ + TestThreadFunctionCalled = TRUE; + +#if defined(_M_IX86) || defined(_M_AMD64) + CONTEXT Context; + + /* Get the debug registers and floating point */ + Context.ContextFlags = CONTEXT_DEBUG_REGISTERS | CONTEXT_FLOATING_POINT; + GetThreadContext(NtCurrentThread(), &Context); + TestThreadStartupContext.Dr0 = Context.Dr0; + TestThreadStartupContext.Dr1 = Context.Dr1; + TestThreadStartupContext.Dr2 = Context.Dr2; + TestThreadStartupContext.Dr3 = Context.Dr3; + TestThreadStartupContext.Dr6 = Context.Dr6; + TestThreadStartupContext.Dr7 = Context.Dr7; +#if defined(_M_IX86) + TestThreadStartupContext.FloatSave = Context.FloatSave; +#endif +#endif + + /* Terminate current thread */ + NtTerminateThread(NtCurrentThread(), STATUS_SUCCESS); +} + START_TEST(NtCreateThread) { NTSTATUS Status; INITIAL_TEB InitialTeb; HANDLE ThreadHandle; OBJECT_ATTRIBUTES Attributes; + DECLSPEC_ALIGN(16) ULONG_PTR Stack[128]; + PULONG_PTR StackPtr = &Stack[ARRAYSIZE(Stack) - 2]; + CONTEXT Context; + CLIENT_ID ClientId; + + BOOL IsWow64 = FALSE; + IsWow64Process(GetCurrentProcess(), &IsWow64); InitializeObjectAttributes(&Attributes, NULL, 0, NULL, NULL); ZeroMemory(&InitialTeb, sizeof(INITIAL_TEB)); @@ -27,4 +150,196 @@ START_TEST(NtCreateThread) FALSE); ok_hex(Status, STATUS_ACCESS_VIOLATION); + + InitialTeb.PreviousStackBase = NULL; + InitialTeb.PreviousStackLimit = NULL; + InitialTeb.StackBase = StackPtr; + InitialTeb.StackLimit = Stack; + InitialTeb.AllocatedStackBase = Stack; + + TestThreadProcPtr = TestThreadProc; + + RtlFillMemory(&TestThreadStartupContext, sizeof(TestThreadStartupContext), 0xCC); + InitializeTestContext(&Context); + + Status = NtCreateThread(&ThreadHandle, + THREAD_ALL_ACCESS, + NULL, + NtCurrentProcess(), + &ClientId, + &Context, + &InitialTeb, + TRUE); + ok_eq_hex(Status, STATUS_SUCCESS); + + NtResumeThread(ThreadHandle, NULL); + WaitForSingleObject(ThreadHandle, INFINITE); + CloseHandle(ThreadHandle); + + ok_eq_bool(TestThreadFunctionCalled, TRUE); + TestThreadFunctionCalled = FALSE; + +#ifdef _M_AMD64 + + /* Control */ + ok_eq_hex64(TestThreadStartupContext.Rsp, Context.Rsp - 0x28); + ok_eq_hex64(TestThreadStartupContext.SegCs, 0x0033); + ok_eq_hex64(TestThreadStartupContext.EFlags, 0x200ED7); + ok_eq_hex(TestThreadStartupContext.SegSs, 0x0002B); + + /* Integer (copied) */ + ok_eq_hex64(TestThreadStartupContext.Rax, Context.Rax); + ok_eq_hex64(TestThreadStartupContext.Rcx, Context.Rcx); + ok_eq_hex64(TestThreadStartupContext.Rdx, Context.Rdx); + ok_eq_hex64(TestThreadStartupContext.Rbx, Context.Rbx); + ok_eq_hex64(TestThreadStartupContext.Rbp, (GetNTVersion() >= _WIN32_WINNT_WIN10) ? Context.Rbp : 0); + ok_eq_hex64(TestThreadStartupContext.Rsi, Context.Rsi); + ok_eq_hex64(TestThreadStartupContext.Rdi, Context.Rdi); + ok_eq_hex64(TestThreadStartupContext.R8, Context.R8); + ok_eq_hex64(TestThreadStartupContext.R9, Context.R9); + ok_eq_hex64(TestThreadStartupContext.R10, Context.R10); + ok_eq_hex64(TestThreadStartupContext.R11, Context.R11); + ok_eq_hex64(TestThreadStartupContext.R12, Context.R12); + ok_eq_hex64(TestThreadStartupContext.R13, Context.R13); + ok_eq_hex64(TestThreadStartupContext.R14, Context.R14); + ok_eq_hex64(TestThreadStartupContext.R15, Context.R15); + + /* Segments (hardcoded) */ + ok_eq_hex(TestThreadStartupContext.SegDs, KGDT64_R3_DATA | RPL_MASK); + ok_eq_hex(TestThreadStartupContext.SegEs, KGDT64_R3_DATA | RPL_MASK); + ok_eq_hex(TestThreadStartupContext.SegFs, KGDT64_R3_CMTEB | RPL_MASK); + ok_eq_hex(TestThreadStartupContext.SegGs, KGDT64_R3_DATA | RPL_MASK); + + /* Floating point (hardcoded) */ + ok_eq_hex64(TestThreadStartupContext.MxCsr, INITIAL_MXCSR); + ok_eq_hex(TestThreadStartupContext.FltSave.ControlWord, INITIAL_FPCSR); + ok_eq_hex(TestThreadStartupContext.FltSave.StatusWord, 0x0000); + ok_eq_hex(TestThreadStartupContext.FltSave.TagWord, 0x00); + ok_eq_hex(TestThreadStartupContext.FltSave.Reserved1, 0x00); + ok_eq_hex(TestThreadStartupContext.FltSave.ErrorOpcode, 0x0000); + ok_eq_hex(TestThreadStartupContext.FltSave.ErrorOffset, 0x00000000); + ok_eq_hex(TestThreadStartupContext.FltSave.ErrorSelector, 0x0000); + ok_eq_hex(TestThreadStartupContext.FltSave.Reserved2, 0x0000); + ok_eq_hex(TestThreadStartupContext.FltSave.DataOffset, 0x00000000); + ok_eq_hex(TestThreadStartupContext.FltSave.DataSelector, 0x0000); + ok_eq_hex(TestThreadStartupContext.FltSave.Reserved3, 0x0000); + ok_eq_hex(TestThreadStartupContext.FltSave.MxCsr, INITIAL_MXCSR); + ok_eq_hex(TestThreadStartupContext.FltSave.MxCsr_Mask, 0x0002FFFF); + for (ULONG i = 0; i < ARRAYSIZE(TestThreadStartupContext.FltSave.FloatRegisters); i++) + { + ok_eq_hex64(TestThreadStartupContext.FltSave.FloatRegisters[i].Low, 0x0000000000000000ull); + ok_eq_hex64(TestThreadStartupContext.FltSave.FloatRegisters[i].High, 0x0000000000000000ull); + } + PM128A XmmRegisters = &TestThreadStartupContext.Xmm0; + for (ULONG i = 0; i < 16; i++) + { + ok_eq_hex64(XmmRegisters[i].Low, 0x0000000000000000ull); + ok_eq_hex64(XmmRegisters[i].High, 0x0000000000000000ull); + } + for (ULONG i = 0; i < ARRAYSIZE(TestThreadStartupContext.VectorRegister); i++) + { + ok_eq_hex64(TestThreadStartupContext.VectorRegister[i].Low, 0xCCCCCCCCCCCCCCCCull); + ok_eq_hex64(TestThreadStartupContext.VectorRegister[i].High, 0xCCCCCCCCCCCCCCCCull); + } + + /* Debug registers (reset to 0) */ + ok_eq_hex64(TestThreadStartupContext.Dr0, 0x0000000000000000ull); + ok_eq_hex64(TestThreadStartupContext.Dr1, 0x0000000000000000ull); + ok_eq_hex64(TestThreadStartupContext.Dr2, 0x0000000000000000ull); + ok_eq_hex64(TestThreadStartupContext.Dr3, 0x0000000000000000ull); + ok_eq_hex64(TestThreadStartupContext.Dr6, 0x0000000000000000ull); + ok_eq_hex64(TestThreadStartupContext.Dr7, 0x0000000000000000ull); + +#elif defined(_M_IX86) + + /* Control */ + ok_eq_hex(TestThreadStartupContext.Esp, Context.Esp); + ok_eq_hex(TestThreadStartupContext.EFlags, IsWow64 ? 0x00240ED7 : 0x003C4ED7); + + /* Integer (copied) */ + ok_eq_hex(TestThreadStartupContext.Eax, Context.Eax); + ok_eq_hex(TestThreadStartupContext.Ecx, Context.Ecx); + ok_eq_hex(TestThreadStartupContext.Edx, Context.Edx); + ok_eq_hex(TestThreadStartupContext.Ebx, Context.Ebx); + ok_eq_hex(TestThreadStartupContext.Ebp, Context.Ebp); + ok_eq_hex(TestThreadStartupContext.Esi, Context.Esi); + ok_eq_hex(TestThreadStartupContext.Edi, Context.Edi); + + /* Segments (hardcoded, the upper 16 bits are left uninitialized by RtlCaptureContext) */ + ok_eq_hex((USHORT)TestThreadStartupContext.SegCs, IsWow64 ? KGDT64_R3_CMCODE | RPL_MASK : KGDT_R3_CODE | RPL_MASK); + ok_eq_hex((USHORT)TestThreadStartupContext.SegDs, IsWow64 ? KGDT64_R3_DATA | RPL_MASK : KGDT_R3_DATA | RPL_MASK); + ok_eq_hex((USHORT)TestThreadStartupContext.SegEs, IsWow64 ? KGDT64_R3_DATA | RPL_MASK : KGDT_R3_DATA | RPL_MASK); + ok_eq_hex((USHORT)TestThreadStartupContext.SegFs, IsWow64 ? KGDT64_R3_CMTEB | RPL_MASK : KGDT_R3_TEB | RPL_MASK); + ok_eq_hex((USHORT)TestThreadStartupContext.SegGs, IsWow64 ? KGDT64_R3_DATA | RPL_MASK : 0x0000); + ok_eq_hex((USHORT)TestThreadStartupContext.SegSs, IsWow64 ? KGDT64_R3_DATA | RPL_MASK : KGDT_R3_DATA | RPL_MASK); + + /* Floating point (hardcoded) */ + ok_eq_hex(TestThreadStartupContext.FloatSave.ControlWord, IsWow64 ? 0x27F : 0xFFFF0A6A); + ok_eq_hex(TestThreadStartupContext.FloatSave.StatusWord, IsWow64 ? 0 : 0xFFFF2A2A); + ok_eq_hex(TestThreadStartupContext.FloatSave.TagWord, IsWow64 ? 0x0000FFFF : 0xFFFF0000); + ok_eq_hex(TestThreadStartupContext.FloatSave.ErrorOffset, IsWow64 ? 0 : 0xAAAAAAAA); + ok_eq_hex(TestThreadStartupContext.FloatSave.ErrorSelector, IsWow64 ? 0 : 0x02AAAAAA); + ok_eq_hex(TestThreadStartupContext.FloatSave.DataOffset, IsWow64 ? 0 : 0xAAAAAAAA); + ok_eq_hex(TestThreadStartupContext.FloatSave.DataSelector, IsWow64 ? 0 : 0xFFFFAAAA); + + /* Debug registers (reset to 0) */ + ok_eq_hex(TestThreadStartupContext.Dr0, 0x00000000); + ok_eq_hex(TestThreadStartupContext.Dr1, 0x00000000); + ok_eq_hex(TestThreadStartupContext.Dr2, 0x00000000); + ok_eq_hex(TestThreadStartupContext.Dr3, 0x00000000); + ok_eq_hex(TestThreadStartupContext.Dr6, 0x00000000); + ok_eq_hex(TestThreadStartupContext.Dr7, 0x00000000); + +#else + +#pragma message("Missing CONTEXT validation in NtCreateThread test") + ASSERT(FALSE); + +#endif + +#if defined(_M_AMD64) || defined(_M_IX86) + /* Test Eflags set to 0 */ + Context.EFlags = 0; + TestThreadStartupContext.EFlags = 0xCCCCCCCC; + Status = NtCreateThread(&ThreadHandle, + THREAD_ALL_ACCESS, + NULL, + NtCurrentProcess(), + &ClientId, + &Context, + &InitialTeb, + TRUE); + ok_eq_hex(Status, STATUS_SUCCESS); + NtResumeThread(ThreadHandle, NULL); + WaitForSingleObject(ThreadHandle, INFINITE); + CloseHandle(ThreadHandle); + ok_eq_hex(TestThreadStartupContext.EFlags, 0x202); +#endif + +#if !defined(_M_IX86) // FIXME: This crashes on x86 + /* Test different InitialTeb values */ + InitialTeb.PreviousStackBase = Stack; + InitialTeb.PreviousStackLimit = NULL; + Status = NtCreateThread(&ThreadHandle, + THREAD_ALL_ACCESS, + NULL, + NtCurrentProcess(), + &ClientId, + &Context, + &InitialTeb, + FALSE); + ok_eq_hex(Status, (GetNTVersion() >= _WIN32_WINNT_WIN10) ? STATUS_NOT_SUPPORTED : STATUS_SUCCESS); + + InitialTeb.PreviousStackBase = NULL; + InitialTeb.PreviousStackLimit = Stack; + Status = NtCreateThread(&ThreadHandle, + THREAD_ALL_ACCESS, + NULL, + NtCurrentProcess(), + &ClientId, + &Context, + &InitialTeb, + FALSE); + ok_eq_hex(Status, (GetNTVersion() >= _WIN32_WINNT_WIN10) ? STATUS_NOT_SUPPORTED : STATUS_SUCCESS); +#endif } diff --git a/modules/rostests/apitests/ntdll/amd64/NtCreateThread.S b/modules/rostests/apitests/ntdll/amd64/NtCreateThread.S new file mode 100644 index 00000000000..8ed574027d0 --- /dev/null +++ b/modules/rostests/apitests/ntdll/amd64/NtCreateThread.S @@ -0,0 +1,45 @@ +/* + * PROJECT: ReactOS API tests + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Helper functions for NtCreateThread test + * COPYRIGHT: Copyright 2026 Timo Kreuzer + */ + +#include +#include + +.code64 + +EXTERN RtlCaptureContext:PROC +EXTERN TestThreadProcPtr:QWORD +EXTERN TestThreadStartupContext:QWORD + +PUBLIC ThreadStartupThunk +.PROC ThreadStartupThunk + + pushfq + push rcx + sub rsp, 5 * 8 + .ENDPROLOG + + lea rcx, TestThreadStartupContext[rip] + call RtlCaptureContext + + /* Save original rcx */ + mov rcx, [rsp + 5 * 8] + mov TestThreadStartupContext[rip + CxRcx], rcx + + /* Save original eflags */ + mov rcx, [rsp + 6 * 8] + mov TestThreadStartupContext[rip + CxEFlags], rcx + + /* Fix Rsp */ + lea rcx, [rsp + 7 * 8] + mov qword ptr TestThreadStartupContext[rip + CxRsp], rcx + + add rsp, 7 * 8 + jmp qword ptr TestThreadProcPtr[rip] + +.ENDP + +END diff --git a/modules/rostests/apitests/ntdll/i386/NtCreateThread.S b/modules/rostests/apitests/ntdll/i386/NtCreateThread.S new file mode 100644 index 00000000000..71a689fe965 --- /dev/null +++ b/modules/rostests/apitests/ntdll/i386/NtCreateThread.S @@ -0,0 +1,49 @@ +/* + * PROJECT: ReactOS API tests + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Helper functions for NtCreateThread test + * COPYRIGHT: Copyright 2026 Timo Kreuzer + */ + +#include +#include + +.code + +EXTERN _RtlCaptureContext@4:PROC +EXTERN _TestThreadProcPtr:DWORD +EXTERN _TestThreadStartupContext:DWORD + +TempEflags: + .long 0 + +PUBLIC _ThreadStartupThunk +.PROC _ThreadStartupThunk + + push ebp + mov ebp, esp + + /* Save EFlags */ + pushfd + + /* Clear direction flag */ + cld + + /* Capture the context */ + push offset _TestThreadStartupContext + call _RtlCaptureContext@4 + + /* Fix up control registers */ + pop eax + mov [_TestThreadStartupContext + CsEflags], eax + lea eax, [esp + 4] + mov [_TestThreadStartupContext + CsEsp], eax + lea eax, _ThreadStartupThunk + mov [_TestThreadStartupContext + CsEip], eax + + pop ebp + jmp dword ptr [_TestThreadProcPtr] + +.ENDP + +END