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
InitSysCalls()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
LoadUser32()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
VectoredExceptionHandlerForUserModeCallback(struct _EXCEPTION_POINTERS * ExceptionInfo)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
ValidateSyscall_(_In_ PCCH File,_In_ ULONG Line,_In_ ULONG_PTR SyscallId,_In_ ULONG_PTR Result)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
Test_SyscallNumbers()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
Test_SyscallPerformance()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
START_TEST(SystemCall)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