reactos/modules/rostests/apitests/umkm/SystemCall.c
Timo Kreuzer ea28951607 [UMKM_APITEST] Add a test for syscall handling
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.
2024-04-06 08:14:52 +02:00

314 lines
11 KiB
C

/*
* 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 <timo.kreuzer@reactos.org>
*/
#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();
}