1 /* 2 * PROJECT: ReactOS API Tests 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Tests for system calls 5 * COPYRIGHT: Copyright 2024 Timo Kreuzer <timo.kreuzer@reactos.org> 6 */ 7 8 #include "precomp.h" 9 10 #define EFLAGS_TF 0x100L 11 #define EFLAGS_INTERRUPT_MASK 0x200L 12 13 ULONG g_NoopSyscallNumber = 0; 14 ULONG g_HandlerCalled = 0; 15 ULONG g_RandomSeed = 0x63c28b49; 16 17 VOID 18 DoSyscallAndCaptureContext( 19 _In_ ULONG SyscallNumber, 20 _Out_ PCONTEXT PreContext, 21 _Out_ PCONTEXT PostContext); 22 23 extern const UCHAR SyscallReturn; 24 25 ULONG_PTR 26 DoSyscallWithUnalignedStack( 27 _In_ ULONG64 SyscallNumber); 28 29 #ifdef _M_IX86 30 __declspec(dllimport) 31 VOID 32 NTAPI 33 KiFastSystemCallRet(VOID); 34 #endif 35 36 static 37 BOOLEAN 38 InitSysCalls() 39 { 40 /* Scan instructions in NtFlushWriteBuffer to find the syscall number 41 for NtFlushWriteBuffer, which is a noop syscall on x86/x64 */ 42 PUCHAR Instructions = (PUCHAR)NtFlushWriteBuffer; 43 for (ULONG i = 0; i < 32; i++) 44 { 45 if (Instructions[i] == 0xB8) 46 { 47 g_NoopSyscallNumber = *(PULONG)&Instructions[i + 1]; 48 return TRUE; 49 } 50 } 51 52 return FALSE; 53 } 54 55 static 56 VOID 57 LoadUser32() 58 { 59 HMODULE hUser32 = LoadLibraryW(L"user32.dll"); 60 ok(hUser32 != NULL, "Failed to load user32.dll\n"); 61 } 62 63 static 64 LONG 65 WINAPI 66 VectoredExceptionHandlerForUserModeCallback( 67 struct _EXCEPTION_POINTERS *ExceptionInfo) 68 { 69 g_HandlerCalled++; 70 71 /* Return from the callback */ 72 NtCallbackReturn(NULL, 0, 0xdeadbeef); 73 74 /* If that failed, we were not in a callback, keep searching */ 75 return EXCEPTION_CONTINUE_SEARCH; 76 } 77 78 VOID 79 ValidateSyscall_( 80 _In_ PCCH File, 81 _In_ ULONG Line, 82 _In_ ULONG_PTR SyscallId, 83 _In_ ULONG_PTR Result) 84 { 85 CONTEXT PreContext, PostContext; 86 87 #ifdef _M_IX86 88 DoSyscallAndCaptureContext(SyscallId, &PreContext, &PostContext); 89 90 /* Non-volatile registers and rsp are unchanged */ 91 ok_eq_hex_(File, Line, PostContext.Esp, PreContext.Esp); 92 ok_eq_hex_(File, Line, PostContext.Ebx, PreContext.Ebx); 93 ok_eq_hex_(File, Line, PostContext.Esi, PreContext.Esi); 94 ok_eq_hex_(File, Line, PostContext.Edi, PreContext.Edi); 95 ok_eq_hex_(File, Line, PostContext.Ebp, PreContext.Ebp); 96 97 /* Special cases */ 98 ok_eq_hex_(File, Line, PostContext.Ecx, PreContext.Esp - 0x4C); 99 ok_eq_hex_(File, Line, PostContext.Edx, (ULONG)KiFastSystemCallRet); 100 ok_eq_hex_(File, Line, PostContext.Eax, Result); 101 102 #elif defined(_M_AMD64) 103 /* Initiaize the pre-contex with random numbers */ 104 PULONG64 IntegerRegs = &PreContext.Rax; 105 PM128A XmmRegs = &PreContext.Xmm0; 106 for (ULONG Index = 0; Index < 16; Index++) 107 { 108 IntegerRegs[Index] = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed); 109 XmmRegs[Index].Low = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed); 110 XmmRegs[Index].High = (ULONG64)RtlRandom(&g_RandomSeed) << 32 | RtlRandom(&g_RandomSeed); 111 } 112 PreContext.EFlags = RtlRandom(&g_RandomSeed); 113 PreContext.EFlags &= ~(EFLAGS_TF | 0x20 | 0x40000); 114 PreContext.EFlags |= EFLAGS_INTERRUPT_MASK; 115 116 PreContext.SegDs = 0; //0x0028; 117 PreContext.SegEs = 0; //0x002B; 118 PreContext.SegFs = 0; //0x0053; 119 PreContext.SegGs = 0; //0x002B; 120 PreContext.SegSs = 0; // 0x002B; 121 122 DoSyscallAndCaptureContext(SyscallId, &PreContext, &PostContext); 123 124 /* Non-volatile registers and rsp are unchanged */ 125 ok_eq_hex64_(File, Line, PostContext.Rsp, PreContext.Rsp); 126 ok_eq_hex64_(File, Line, PostContext.Rbx, PreContext.Rbx); 127 ok_eq_hex64_(File, Line, PostContext.Rsi, PreContext.Rsi); 128 ok_eq_hex64_(File, Line, PostContext.Rdi, PreContext.Rdi); 129 ok_eq_hex64_(File, Line, PostContext.Rbp, PreContext.Rbp); 130 ok_eq_hex64_(File, Line, PostContext.R12, PreContext.R12); 131 ok_eq_hex64_(File, Line, PostContext.R13, PreContext.R13); 132 ok_eq_hex64_(File, Line, PostContext.R14, PreContext.R14); 133 ok_eq_hex64_(File, Line, PostContext.R15, PreContext.R15); 134 ok_eq_hex64_(File, Line, PostContext.Xmm6.Low, PreContext.Xmm6.Low); 135 ok_eq_hex64_(File, Line, PostContext.Xmm6.High, PreContext.Xmm6.High); 136 ok_eq_hex64_(File, Line, PostContext.Xmm7.Low, PreContext.Xmm7.Low); 137 ok_eq_hex64_(File, Line, PostContext.Xmm7.High, PreContext.Xmm7.High); 138 ok_eq_hex64_(File, Line, PostContext.Xmm8.Low, PreContext.Xmm8.Low); 139 ok_eq_hex64_(File, Line, PostContext.Xmm8.High, PreContext.Xmm8.High); 140 ok_eq_hex64_(File, Line, PostContext.Xmm9.Low, PreContext.Xmm9.Low); 141 ok_eq_hex64_(File, Line, PostContext.Xmm9.High, PreContext.Xmm9.High); 142 ok_eq_hex64_(File, Line, PostContext.Xmm10.Low, PreContext.Xmm10.Low); 143 ok_eq_hex64_(File, Line, PostContext.Xmm10.High, PreContext.Xmm10.High); 144 ok_eq_hex64_(File, Line, PostContext.Xmm11.Low, PreContext.Xmm11.Low); 145 ok_eq_hex64_(File, Line, PostContext.Xmm11.High, PreContext.Xmm11.High); 146 ok_eq_hex64_(File, Line, PostContext.Xmm12.Low, PreContext.Xmm12.Low); 147 ok_eq_hex64_(File, Line, PostContext.Xmm12.High, PreContext.Xmm12.High); 148 ok_eq_hex64_(File, Line, PostContext.Xmm13.Low, PreContext.Xmm13.Low); 149 ok_eq_hex64_(File, Line, PostContext.Xmm13.High, PreContext.Xmm13.High); 150 ok_eq_hex64_(File, Line, PostContext.Xmm14.Low, PreContext.Xmm14.Low); 151 ok_eq_hex64_(File, Line, PostContext.Xmm14.High, PreContext.Xmm14.High); 152 ok_eq_hex64_(File, Line, PostContext.Xmm15.Low, PreContext.Xmm15.Low); 153 ok_eq_hex64_(File, Line, PostContext.Xmm15.High, PreContext.Xmm15.High); 154 155 /* Parity flag is flaky */ 156 ok_eq_hex64_(File, Line, PostContext.EFlags & ~0x4, PreContext.EFlags & ~0x9F5); 157 158 ok_eq_hex64_(File, Line, PostContext.SegCs, 0x0033); 159 ok_eq_hex64_(File, Line, PostContext.SegSs, 0x002B); 160 ok_(File, Line)(PostContext.SegDs == PreContext.SegDs || PostContext.SegDs == 0x002B, 161 "Expected 0x002B, got 0x%04X\n", PostContext.SegDs); 162 ok_(File, Line)(PostContext.SegEs == PreContext.SegEs || PostContext.SegEs == 0x002B, 163 "Expected 0x002B, got 0x%04X\n", PostContext.SegEs); 164 ok_(File, Line)(PostContext.SegFs == PreContext.SegFs || PostContext.SegFs == 0x0053, 165 "Expected 0x002B, got 0x%04X\n", PostContext.SegFs); 166 ok_(File, Line)(PostContext.SegGs == PreContext.SegGs || PostContext.SegGs == 0x002B, 167 "Expected 0x002B, got 0x%04X\n", PostContext.SegGs); 168 ok_eq_hex64_(File, Line, PostContext.SegSs, 0x002B); 169 170 /* These volatile registers are zeroed */ 171 ok_eq_hex64_(File, Line, PostContext.Rdx, 0); 172 ok_eq_hex64_(File, Line, PostContext.R10, 0); 173 ok_eq_hex64_(File, Line, PostContext.Xmm0.Low, 0); 174 ok_eq_hex64_(File, Line, PostContext.Xmm0.High, 0); 175 ok_eq_hex64_(File, Line, PostContext.Xmm1.Low, 0); 176 ok_eq_hex64_(File, Line, PostContext.Xmm1.High, 0); 177 ok_eq_hex64_(File, Line, PostContext.Xmm2.Low, 0); 178 ok_eq_hex64_(File, Line, PostContext.Xmm2.High, 0); 179 ok_eq_hex64_(File, Line, PostContext.Xmm3.Low, 0); 180 ok_eq_hex64_(File, Line, PostContext.Xmm3.High, 0); 181 ok_eq_hex64_(File, Line, PostContext.Xmm4.Low, 0); 182 ok_eq_hex64_(File, Line, PostContext.Xmm4.High, 0); 183 ok_eq_hex64_(File, Line, PostContext.Xmm5.Low, 0); 184 ok_eq_hex64_(File, Line, PostContext.Xmm5.High, 0); 185 186 /* Special cases */ 187 ok_eq_hex64_(File, Line, PostContext.Rax, Result); 188 ok_eq_hex64_(File, Line, PostContext.Rcx, (ULONG64)&SyscallReturn); 189 ok_eq_hex64_(File, Line, PostContext.R8, PreContext.Rsp); 190 ok_eq_hex64_(File, Line, PostContext.R9, PreContext.Rbp); 191 ok_eq_hex64_(File, Line, PostContext.R11, PostContext.EFlags); 192 193 // TODO:Debug regs, mxcsr, floating point, etc. 194 #else 195 #error Unsupported architecture 196 #endif 197 } 198 199 #define ValidateSyscall(SyscallId, Result) ValidateSyscall_(__FILE__, __LINE__, SyscallId, Result) 200 201 static 202 VOID 203 Test_SyscallNumbers() 204 { 205 BOOL Wow64Process; 206 207 if (IsWow64Process(NtCurrentProcess(), &Wow64Process) && Wow64Process) 208 { 209 skip("Skipping syscall tests on WOW64\n"); 210 return; 211 } 212 213 /* Test valid syscall number */ 214 ValidateSyscall(g_NoopSyscallNumber, STATUS_SUCCESS); 215 216 /* Test invalid syscall number */ 217 ValidateSyscall(0x0FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE); 218 219 /* Add a vectored exception handler to catch the exception we will get 220 when KiUserCallbackDispatcher is called and user32.dll is not loaded 221 We cannot use SEH here, because the exception is outside of the try block */ 222 PVOID hHandler = AddVectoredExceptionHandler(TRUE, VectoredExceptionHandlerForUserModeCallback); 223 ok(hHandler != NULL, "Failed to add vectored exception handler\n"); 224 225 /* Test win32k syscall number without user32.dll loaded */ 226 #ifdef _M_AMD64 227 ValidateSyscall(0x1000, STATUS_SUCCESS); 228 #else 229 ValidateSyscall(0x1000, (ULONG)STATUS_INVALID_SYSTEM_SERVICE); 230 #endif 231 ok_eq_ulong(g_HandlerCalled, 1UL); 232 233 /* Test invalid win32k syscall number without user32.dll loaded */ 234 #ifdef _M_IX86 235 ValidateSyscall(0x1FFF, 0xffffffbf); 236 #else 237 ValidateSyscall(0x1FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE); 238 #endif 239 240 ok_eq_ulong(g_HandlerCalled, 2UL); 241 242 RemoveVectoredExceptionHandler(hHandler); 243 244 LoadUser32(); 245 246 /* Test invalid win32k syscall number */ 247 #ifdef _M_IX86 248 ValidateSyscall(0x1FFF, 0xffffffbf); 249 #else 250 ValidateSyscall(0x1FFF, (ULONG)STATUS_INVALID_SYSTEM_SERVICE); 251 #endif 252 253 /* Test invalid syscall table number */ 254 ValidateSyscall(0x2000 + g_NoopSyscallNumber, STATUS_SUCCESS); 255 ValidateSyscall(0x3000 + g_NoopSyscallNumber, STATUS_SUCCESS); 256 257 #if 0 // This only happens, when running the test from VS, but not from the command line 258 /* For some unknown reason the result gets sign extended in this case */ 259 ULONG64 Result = DoSyscallWithUnalignedStack(0x2000); 260 ok_eq_hex64(Result, (LONG)STATUS_ACCESS_VIOLATION); 261 #endif 262 263 /* Test invalid upper bits in syscall number */ 264 ValidateSyscall(0xFFFFFFFFFFF70000ULL + g_NoopSyscallNumber, STATUS_SUCCESS); 265 } 266 267 static 268 VOID 269 Test_SyscallPerformance() 270 { 271 ULONG64 Start, End, Cycles; 272 ULONG64 TotalCycles = 0, Min = -1, Max = 0; 273 ULONG64 Count = 100000; 274 ULONG Outliers = 0; 275 ULONG_PTR OldAffinityMask; 276 double AvgCycles; 277 278 OldAffinityMask = SetThreadAffinityMask(GetCurrentThread(), 1); 279 280 for (ULONG64 i = 0; i < Count; i++) 281 { 282 Start = __rdtsc(); 283 NtFlushWriteBuffer(); 284 End = __rdtsc(); 285 Cycles = End - Start; 286 if (Cycles > 2000) 287 { 288 Outliers++; 289 continue; 290 } 291 TotalCycles += Cycles; 292 Min = min(Min, Cycles); 293 Max = max(Max, Cycles); 294 } 295 296 AvgCycles = (double)TotalCycles / (Count - Outliers); 297 298 trace("NtFlushWriteBuffer: avg %.2f cycles, min %I64u, max %I64u, Outliers %lu\n", 299 AvgCycles, Min, Max, Outliers); 300 301 SetThreadAffinityMask(GetCurrentThread(), OldAffinityMask); 302 } 303 304 START_TEST(SystemCall) 305 { 306 if (!InitSysCalls()) 307 { 308 skip("Failed to initialize.\n"); 309 return; 310 } 311 312 Test_SyscallNumbers(); 313 Test_SyscallPerformance(); 314 } 315