Skip to content

Commit

Permalink
[UMKM_APITEST] Add a test for syscall handling
Browse files Browse the repository at this point in the history
This is intentionally not part of ntdll_apitest, because that links to advapi32, which links to rpcrt4, which (wrongly!) links to ws2_32, which (wrongly!) links to user32, which breaks the test.
  • Loading branch information
tkreuzer committed Apr 6, 2024
1 parent a771729 commit ea28951
Show file tree
Hide file tree
Showing 8 changed files with 628 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/rostests/apitests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ add_subdirectory(shell32)
add_subdirectory(shlwapi)
add_subdirectory(spoolss)
add_subdirectory(psapi)
add_subdirectory(umkm)
add_subdirectory(user32)
add_subdirectory(user32_dynamic)
add_subdirectory(userenv)
Expand Down
3 changes: 3 additions & 0 deletions modules/rostests/apitests/include/apitest.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#define ok_hr_(file, line, status, expected) ok_hex_(file, line, status, expected)

#define ok_eq_print(value, expected, spec) ok((value) == (expected), #value " = " spec ", expected " spec "\n", value, expected)
#define ok_eq_print_(file, line, value, expected, spec) ok_(file,line)((value) == (expected), #value " = " spec ", expected " spec "\n", value, expected)
#define ok_eq_pointer(value, expected) ok_eq_print(value, expected, "%p")
#define ok_eq_int(value, expected) ok_eq_print(value, expected, "%d")
#define ok_eq_uint(value, expected) ok_eq_print(value, expected, "%u")
Expand Down Expand Up @@ -83,6 +84,8 @@
#define ok_eq_wstr(value, expected) ok(!wcscmp(value, expected), #value " = \"%ls\", expected \"%ls\"\n", value, expected)
#define ok_eq_tag(value, expected) ok_eq_print(value, expected, "0x%08lx")

#define ok_eq_hex_(file, line, value, expected) ok_eq_print_(file, line, value, expected, "0x%08lx")
#define ok_eq_hex64_(file, line, value, expected) ok_eq_print_(file, line, value, expected, "%I64x")
#define ok_eq_hex64(value, expected) ok_eq_print(value, expected, "%I64x")
#define ok_eq_xmm(value, expected) ok((value).Low == (expected).Low, #value " = %I64x'%08I64x, expected %I64x'%08I64x\n", (value).Low, (value).High, (expected).Low, (expected).High)

Expand Down
33 changes: 33 additions & 0 deletions modules/rostests/apitests/umkm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

include_directories(${REACTOS_SOURCE_DIR}/ntoskrnl/include)

list(APPEND SOURCE
SystemCall.c
precomp.h)

if(ARCH STREQUAL "i386")
add_asm_files(umkm_apitest_asm
i386/SystemCall_asm.s
)
elseif(ARCH STREQUAL "amd64")
add_asm_files(umkm_apitest_asm
amd64/SystemCall_asm.s
)
endif()

list(APPEND PCH_SKIP_SOURCE
testlist.c)

add_executable(umkm_apitest
${SOURCE}
${umkm_apitest_asm}
${PCH_SKIP_SOURCE})

target_link_libraries(umkm_apitest wine uuid ${PSEH_LIB})
set_module_type(umkm_apitest win32cui)
add_importlibs(umkm_apitest msvcrt kernel32 ntdll)
add_pch(umkm_apitest precomp.h "${PCH_SKIP_SOURCE}")
add_dependencies(umkm_apitest load_notifications)

add_rostests_file(TARGET umkm_apitest)

314 changes: 314 additions & 0 deletions modules/rostests/apitests/umkm/SystemCall.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/*
* PROJECT: ReactOS API Tests
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
* PURPOSE: Tests for system calls
* COPYRIGHT: Copyright 2024 Timo Kreuzer <[email protected]>
*/

#include "precomp.h"

#define EFLAGS_TF 0x100L
#define EFLAGS_INTERRUPT_MASK 0x200L

ULONG g_NoopSyscallNumber = 0;
ULONG g_HandlerCalled = 0;
ULONG g_RandomSeed = 0x63c28b49;

VOID
DoSyscallAndCaptureContext(
_In_ ULONG SyscallNumber,
_Out_ PCONTEXT PreContext,
_Out_ PCONTEXT PostContext);

extern const UCHAR SyscallReturn;

ULONG_PTR
DoSyscallWithUnalignedStack(
_In_ ULONG64 SyscallNumber);

#ifdef _M_IX86
__declspec(dllimport)
VOID
NTAPI
KiFastSystemCallRet(VOID);
#endif

static
BOOLEAN
InitSysCalls()
{
/* Scan instructions in NtFlushWriteBuffer to find the syscall number
for NtFlushWriteBuffer, which is a noop syscall on x86/x64 */
PUCHAR Instructions = (PUCHAR)NtFlushWriteBuffer;
for (ULONG i = 0; i < 32; i++)
{
if (Instructions[i] == 0xB8)
{
g_NoopSyscallNumber = *(PULONG)&Instructions[i + 1];
return TRUE;
}
}

return FALSE;
}

static
VOID
LoadUser32()
{
HMODULE hUser32 = LoadLibraryW(L"user32.dll");
ok(hUser32 != NULL, "Failed to load user32.dll\n");
}

static
LONG
WINAPI
VectoredExceptionHandlerForUserModeCallback(
struct _EXCEPTION_POINTERS *ExceptionInfo)
{
g_HandlerCalled++;

/* Return from the callback */
NtCallbackReturn(NULL, 0, 0xdeadbeef);

/* If that failed, we were not in a callback, keep searching */
return EXCEPTION_CONTINUE_SEARCH;
}

VOID
ValidateSyscall_(
_In_ PCCH File,
_In_ ULONG Line,
_In_ ULONG_PTR SyscallId,
_In_ ULONG_PTR Result)
{
CONTEXT PreContext, PostContext;

#ifdef _M_IX86
DoSyscallAndCaptureContext(SyscallId, &PreContext, &PostContext);

/* Non-volatile registers and rsp are unchanged */
ok_eq_hex_(File, Line, PostContext.Esp, PreContext.Esp);
ok_eq_hex_(File, Line, PostContext.Ebx, PreContext.Ebx);
ok_eq_hex_(File, Line, PostContext.Esi, PreContext.Esi);
ok_eq_hex_(File, Line, PostContext.Edi, PreContext.Edi);
ok_eq_hex_(File, Line, PostContext.Ebp, PreContext.Ebp);

/* Special cases */
ok_eq_hex_(File, Line, PostContext.Ecx, PreContext.Esp - 0x4C);
ok_eq_hex_(File, Line, PostContext.Edx, (ULONG)KiFastSystemCallRet);
ok_eq_hex_(File, Line, PostContext.Eax, Result);

#elif defined(_M_AMD64)
/* Initiaize the pre-contex with random numbers */
PULONG64 IntegerRegs = &PreContext.Rax;
PM128A XmmRegs = &PreContext.Xmm0;
for (ULONG Index = 0; Index < 16; Index++)
{
IntegerRegs[Index] = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed);
XmmRegs[Index].Low = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed);
XmmRegs[Index].High = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed);
}
PreContext.EFlags = RtlRandom(&g_RandomSeed);
PreContext.EFlags &= ~(EFLAGS_TF | 0x20 | 0x40000);
PreContext.EFlags |= EFLAGS_INTERRUPT_MASK;

PreContext.SegDs = 0; //0x0028;
PreContext.SegEs = 0; //0x002B;
PreContext.SegFs = 0; //0x0053;
PreContext.SegGs = 0; //0x002B;
PreContext.SegSs = 0; // 0x002B;

DoSyscallAndCaptureContext(SyscallId, &PreContext, &PostContext);

/* Non-volatile registers and rsp are unchanged */
ok_eq_hex64_(File, Line, PostContext.Rsp, PreContext.Rsp);
ok_eq_hex64_(File, Line, PostContext.Rbx, PreContext.Rbx);
ok_eq_hex64_(File, Line, PostContext.Rsi, PreContext.Rsi);
ok_eq_hex64_(File, Line, PostContext.Rdi, PreContext.Rdi);
ok_eq_hex64_(File, Line, PostContext.Rbp, PreContext.Rbp);
ok_eq_hex64_(File, Line, PostContext.R12, PreContext.R12);
ok_eq_hex64_(File, Line, PostContext.R13, PreContext.R13);
ok_eq_hex64_(File, Line, PostContext.R14, PreContext.R14);
ok_eq_hex64_(File, Line, PostContext.R15, PreContext.R15);
ok_eq_hex64_(File, Line, PostContext.Xmm6.Low, PreContext.Xmm6.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm6.High, PreContext.Xmm6.High);
ok_eq_hex64_(File, Line, PostContext.Xmm7.Low, PreContext.Xmm7.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm7.High, PreContext.Xmm7.High);
ok_eq_hex64_(File, Line, PostContext.Xmm8.Low, PreContext.Xmm8.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm8.High, PreContext.Xmm8.High);
ok_eq_hex64_(File, Line, PostContext.Xmm9.Low, PreContext.Xmm9.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm9.High, PreContext.Xmm9.High);
ok_eq_hex64_(File, Line, PostContext.Xmm10.Low, PreContext.Xmm10.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm10.High, PreContext.Xmm10.High);
ok_eq_hex64_(File, Line, PostContext.Xmm11.Low, PreContext.Xmm11.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm11.High, PreContext.Xmm11.High);
ok_eq_hex64_(File, Line, PostContext.Xmm12.Low, PreContext.Xmm12.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm12.High, PreContext.Xmm12.High);
ok_eq_hex64_(File, Line, PostContext.Xmm13.Low, PreContext.Xmm13.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm13.High, PreContext.Xmm13.High);
ok_eq_hex64_(File, Line, PostContext.Xmm14.Low, PreContext.Xmm14.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm14.High, PreContext.Xmm14.High);
ok_eq_hex64_(File, Line, PostContext.Xmm15.Low, PreContext.Xmm15.Low);
ok_eq_hex64_(File, Line, PostContext.Xmm15.High, PreContext.Xmm15.High);

/* Parity flag is flaky */
ok_eq_hex64_(File, Line, PostContext.EFlags & ~0x4, PreContext.EFlags & ~0x9F5);

ok_eq_hex64_(File, Line, PostContext.SegCs, 0x0033);
ok_eq_hex64_(File, Line, PostContext.SegSs, 0x002B);
ok_(File, Line)(PostContext.SegDs == PreContext.SegDs || PostContext.SegDs == 0x002B,
"Expected 0x002B, got 0x%04X\n", PostContext.SegDs);
ok_(File, Line)(PostContext.SegEs == PreContext.SegEs || PostContext.SegEs == 0x002B,
"Expected 0x002B, got 0x%04X\n", PostContext.SegEs);
ok_(File, Line)(PostContext.SegFs == PreContext.SegFs || PostContext.SegFs == 0x0053,
"Expected 0x002B, got 0x%04X\n", PostContext.SegFs);
ok_(File, Line)(PostContext.SegGs == PreContext.SegGs || PostContext.SegGs == 0x002B,
"Expected 0x002B, got 0x%04X\n", PostContext.SegGs);
ok_eq_hex64_(File, Line, PostContext.SegSs, 0x002B);

/* These volatile registers are zeroed */
ok_eq_hex64_(File, Line, PostContext.Rdx, 0);
ok_eq_hex64_(File, Line, PostContext.R10, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm0.Low, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm0.High, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm1.Low, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm1.High, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm2.Low, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm2.High, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm3.Low, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm3.High, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm4.Low, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm4.High, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm5.Low, 0);
ok_eq_hex64_(File, Line, PostContext.Xmm5.High, 0);

/* Special cases */
ok_eq_hex64_(File, Line, PostContext.Rax, Result);
ok_eq_hex64_(File, Line, PostContext.Rcx, (ULONG64)&SyscallReturn);
ok_eq_hex64_(File, Line, PostContext.R8, PreContext.Rsp);
ok_eq_hex64_(File, Line, PostContext.R9, PreContext.Rbp);
ok_eq_hex64_(File, Line, PostContext.R11, PostContext.EFlags);

// TODO:Debug regs, mxcsr, floating point, etc.
#else
#error Unsupported architecture
#endif
}

#define ValidateSyscall(SyscallId, Result) ValidateSyscall_(__FILE__, __LINE__, SyscallId, Result)

static
VOID
Test_SyscallNumbers()
{
BOOL Wow64Process;

if (IsWow64Process(NtCurrentProcess(), &Wow64Process) && Wow64Process)
{
skip("Skipping syscall tests on WOW64\n");
return;
}

/* Test valid syscall number */
ValidateSyscall(g_NoopSyscallNumber, STATUS_SUCCESS);

/* Test invalid syscall number */
ValidateSyscall(0x0FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);

/* Add a vectored exception handler to catch the exception we will get
when KiUserCallbackDispatcher is called and user32.dll is not loaded
We cannot use SEH here, because the exception is outside of the try block */
PVOID hHandler = AddVectoredExceptionHandler(TRUE, VectoredExceptionHandlerForUserModeCallback);
ok(hHandler != NULL, "Failed to add vectored exception handler\n");

/* Test win32k syscall number without user32.dll loaded */
#ifdef _M_AMD64
ValidateSyscall(0x1000, STATUS_SUCCESS);
#else
ValidateSyscall(0x1000, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
#endif
ok_eq_ulong(g_HandlerCalled, 1UL);

/* Test invalid win32k syscall number without user32.dll loaded */
#ifdef _M_IX86
ValidateSyscall(0x1FFF, 0xffffffbf);
#else
ValidateSyscall(0x1FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
#endif

ok_eq_ulong(g_HandlerCalled, 2UL);

RemoveVectoredExceptionHandler(hHandler);

LoadUser32();

/* Test invalid win32k syscall number */
#ifdef _M_IX86
ValidateSyscall(0x1FFF, 0xffffffbf);
#else
ValidateSyscall(0x1FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE);
#endif

/* Test invalid syscall table number */
ValidateSyscall(0x2000 + g_NoopSyscallNumber, STATUS_SUCCESS);
ValidateSyscall(0x3000 + g_NoopSyscallNumber, STATUS_SUCCESS);

#if 0 // This only happens, when running the test from VS, but not from the command line
/* For some unknown reason the result gets sign extended in this case */
ULONG64 Result = DoSyscallWithUnalignedStack(0x2000);
ok_eq_hex64(Result, (LONG)STATUS_ACCESS_VIOLATION);
#endif

/* Test invalid upper bits in syscall number */
ValidateSyscall(0xFFFFFFFFFFF70000ULL + g_NoopSyscallNumber, STATUS_SUCCESS);
}

static
VOID
Test_SyscallPerformance()
{
ULONG64 Start, End, Cycles;
ULONG64 TotalCycles = 0, Min = -1, Max = 0;
ULONG64 Count = 100000;
ULONG Outliers = 0;
ULONG_PTR OldAffinityMask;
double AvgCycles;

OldAffinityMask = SetThreadAffinityMask(GetCurrentThread(), 1);

for (ULONG64 i = 0; i < Count; i++)
{
Start = __rdtsc();
NtFlushWriteBuffer();
End = __rdtsc();
Cycles = End - Start;
if (Cycles > 2000)
{
Outliers++;
continue;
}
TotalCycles += Cycles;
Min = min(Min, Cycles);
Max = max(Max, Cycles);
}

AvgCycles = (double)TotalCycles / (Count - Outliers);

trace("NtFlushWriteBuffer: avg %.2f cycles, min %I64u, max %I64u, Outliers %lu\n",
AvgCycles, Min, Max, Outliers);

SetThreadAffinityMask(GetCurrentThread(), OldAffinityMask);
}

START_TEST(SystemCall)
{
if (!InitSysCalls())
{
skip("Failed to initialize.\n");
return;
}

Test_SyscallNumbers();
Test_SyscallPerformance();
}
Loading

0 comments on commit ea28951

Please sign in to comment.