The Pedigree Project 0.1
syscall-profile.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2
3#include "pedigree/kernel/Atomic.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/compiler.h"
6#include "pedigree/kernel/process/Process.h"
7#include "pedigree/kernel/process/Scheduler.h"
8#include "pedigree/kernel/process/Thread.h"
9#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
10#include "pedigree/kernel/processor/Processor.h"
11#include "pedigree/kernel/processor/SyscallManager.h"
12#include "pedigree/kernel/processor/hosted/FunctionProfile.h"
13#include "pedigree/kernel/processor/hosted/smoke.h"
14#include "pedigree/kernel/processor/state.h"
15#include "pedigree/kernel/time/Time.h"
16#include "pedigree/kernel/utilities/utility.h"
17
18#include <fcntl.h>
19#include <stdlib.h>
20#include <string.h>
21#include <unistd.h>
22
23#include "modules/subsys/posix/PosixProcess.h"
24#include "modules/subsys/posix/PosixSubsystem.h"
25#include "modules/subsys/posix/syscalls/translate.h"
28#include "modules/system/vfs/VFS.h"
29#include "system/kernel/core/processor/hosted/SyscallManager.h"
30#include <sys/uio.h>
31
32bool runHostedVasRegressions();
33bool runHostedVmOperationGuardRegressions();
34bool runHostedPostSyscallRegressions();
35bool runHostedAccountingRegressions();
36
37namespace {
38constexpr size_t QueryCount = 1000000;
39constexpr size_t IoCount = 1000;
40constexpr size_t Repetitions = 3;
41constexpr size_t PayloadSize = 4096;
42constexpr uintptr_t ProfileUid = 123;
43
44HostedSyscallState dispatch(uintptr_t number, uintptr_t p1 = 0, uintptr_t p2 = 0,
45 uintptr_t p3 = 0) {
46 HostedSyscallState state = {};
47 state.service = linuxCompat;
48 state.number = number;
49 state.p1 = p1;
50 state.p2 = p2;
51 state.p3 = p3;
52 HostedSyscallManager::dispatchStateForTest(state);
53 return state;
54}
55
56bool checkedCall(uintptr_t number, uintptr_t expected, uintptr_t p1 = 0, uintptr_t p2 = 0,
57 uintptr_t p3 = 0) {
58 const HostedSyscallState state = dispatch(number, p1, p2, p3);
59 if (state.result != expected || state.error) {
60 ERROR("HOSTED-PROFILE: FAIL syscall=" << Dec << number
61 << " result=" << static_cast<int64_t>(state.result)
62 << " expected=" << expected << " errno=" << state.error);
63 return false;
64 }
65 return true;
66}
67
68void reportPhase(const char* phase, size_t repetition, size_t count, Time::Timestamp elapsed) {
69 NOTICE("HOSTED-PROFILE: phase=" << phase << " rep=" << Dec << repetition + 1 << " count=" << count
70 << " elapsed_ns=" << elapsed);
71}
72
73NEVER_INLINE bool hostedProfileGetuid(size_t repetition, size_t count) {
74 hostedFunctionProfileBegin("getuid", repetition, count);
75 const Time::Timestamp start = Time::getTicks();
76 for (size_t i = 0; i < count; ++i) {
77 if (!checkedCall(PedigreeLinuxAmd64Syscall_getuid, ProfileUid)) {
78 hostedFunctionProfileEnd();
79 return false;
80 }
81 }
82 const Time::Timestamp elapsed = Time::getTicks() - start;
83 const bool captured = hostedFunctionProfileEnd();
84 reportPhase("getuid", repetition, count, elapsed);
85 return captured;
86}
87
88NEVER_INLINE bool hostedProfileLseek(size_t repetition, size_t count, int fd) {
89 hostedFunctionProfileBegin("lseek", repetition, count);
90 const Time::Timestamp start = Time::getTicks();
91 for (size_t i = 0; i < count; ++i) {
92 if (!checkedCall(PedigreeLinuxAmd64Syscall_lseek, 0, fd, 0, SEEK_SET)) {
93 hostedFunctionProfileEnd();
94 return false;
95 }
96 }
97 const Time::Timestamp elapsed = Time::getTicks() - start;
98 const bool captured = hostedFunctionProfileEnd();
99 reportPhase("lseek", repetition, count, elapsed);
100 return captured;
101}
102
103NEVER_INLINE bool hostedProfileWritev(size_t repetition, size_t count, int fd,
104 const iovec* vectors) {
105 hostedFunctionProfileBegin("lseek-writev", repetition, count);
106 const Time::Timestamp start = Time::getTicks();
107 for (size_t i = 0; i < count; ++i) {
108 if (!checkedCall(PedigreeLinuxAmd64Syscall_lseek, 0, fd, 0, SEEK_SET) ||
109 !checkedCall(PedigreeLinuxAmd64Syscall_writev, PayloadSize, fd,
110 reinterpret_cast<uintptr_t>(vectors), 2)) {
111 hostedFunctionProfileEnd();
112 return false;
113 }
114 }
115 const Time::Timestamp elapsed = Time::getTicks() - start;
116 const bool captured = hostedFunctionProfileEnd();
117 reportPhase("lseek-writev", repetition, count, elapsed);
118 return captured;
119}
120
121NEVER_INLINE bool hostedProfileReadv(size_t repetition, size_t count, int fd,
122 const iovec* vectors) {
123 hostedFunctionProfileBegin("lseek-readv", repetition, count);
124 const Time::Timestamp start = Time::getTicks();
125 for (size_t i = 0; i < count; ++i) {
126 if (!checkedCall(PedigreeLinuxAmd64Syscall_lseek, 0, fd, 0, SEEK_SET) ||
127 !checkedCall(PedigreeLinuxAmd64Syscall_readv, PayloadSize, fd,
128 reinterpret_cast<uintptr_t>(vectors), 2)) {
129 hostedFunctionProfileEnd();
130 return false;
131 }
132 }
133 const Time::Timestamp elapsed = Time::getTicks() - start;
134 const bool captured = hostedFunctionProfileEnd();
135 reportPhase("lseek-readv", repetition, count, elapsed);
136 return captured;
137}
138
139bool verifyPayload(int fd, const iovec* vectors, const uint8_t* expected, uint8_t* actual) {
140 ByteSet(actual, 0, PayloadSize);
141 if (!checkedCall(PedigreeLinuxAmd64Syscall_lseek, 0, fd, 0, SEEK_SET) ||
142 !checkedCall(PedigreeLinuxAmd64Syscall_readv, PayloadSize, fd,
143 reinterpret_cast<uintptr_t>(vectors), 2)) {
144 return false;
145 }
146 for (size_t i = 0; i < PayloadSize; ++i) {
147 if (actual[i] != expected[i]) {
148 ERROR("HOSTED-PROFILE: FAIL payload offset=" << Dec << i);
149 return false;
150 }
151 }
152 return true;
153}
154
155bool runMappedWork(uintptr_t address, size_t pageSize, size_t payloadSpan, size_t queryCount,
156 size_t ioCount) {
157 auto* writeVectors = reinterpret_cast<iovec*>(address);
158 auto* readVectors = writeVectors + 2;
159 char* path = reinterpret_cast<char*>(readVectors + 2);
160 constexpr char Path[] = "/syscall-profile";
161 MemoryCopy(path, Path, sizeof(Path));
162 auto* source = reinterpret_cast<uint8_t*>(address + pageSize);
163 auto* destination = source + payloadSpan;
164 writeVectors[0] = {source, PayloadSize / 2};
165 writeVectors[1] = {source + PayloadSize / 2, PayloadSize / 2};
166 readVectors[0] = {destination, PayloadSize / 2};
167 readVectors[1] = {destination + PayloadSize / 2, PayloadSize / 2};
168
169 const HostedSyscallState opened =
170 dispatch(PedigreeLinuxAmd64Syscall_open, reinterpret_cast<uintptr_t>(path),
171 O_CREAT | O_RDWR | O_TRUNC, 0600);
172 if (opened.error || static_cast<int64_t>(opened.result) < 0) {
173 ERROR("HOSTED-PROFILE: FAIL open result=" << Dec << static_cast<int64_t>(opened.result)
174 << " errno=" << opened.error);
175 return false;
176 }
177 const int fd = static_cast<int>(opened.result);
178
179 // Allocate the file's backing and fault in both copy directions before timing.
180 bool passed = checkedCall(PedigreeLinuxAmd64Syscall_writev, PayloadSize, fd,
181 reinterpret_cast<uintptr_t>(writeVectors), 2) &&
182 verifyPayload(fd, readVectors, source, destination);
183 for (size_t repetition = 0; repetition < Repetitions && passed; ++repetition) {
184 for (size_t i = 0; i < PayloadSize; ++i) {
185 source[i] = static_cast<uint8_t>(i * 37 + repetition + 1);
186 }
187 passed = hostedProfileGetuid(repetition, queryCount) &&
188 hostedProfileLseek(repetition, ioCount, fd) &&
189 hostedProfileWritev(repetition, ioCount, fd, writeVectors) &&
190 verifyPayload(fd, readVectors, source, destination);
191 if (passed) {
192 ByteSet(destination, 0, PayloadSize);
193 passed = hostedProfileReadv(repetition, ioCount, fd, readVectors);
194 for (size_t i = 0; i < PayloadSize && passed; ++i) {
195 if (source[i] != destination[i]) {
196 ERROR("HOSTED-PROFILE: FAIL readv payload offset=" << Dec << i);
197 passed = false;
198 }
199 }
200 }
201 }
202 const bool closed = checkedCall(PedigreeLinuxAmd64Syscall_close, 0, fd);
203 return passed && closed;
204}
205
206struct ProfileContext {
207 size_t queryCount;
208 size_t ioCount;
209 bool getuidOnly;
210 bool passed = false;
211 Atomic<size_t> returned{0};
212};
213
214int profileWorker(void* parameter) {
215 auto* context = static_cast<ProfileContext*>(parameter);
216 if (context->getuidOnly) {
217 context->passed = true;
218 for (size_t i = 0; i < 1000 && context->passed; ++i) {
219 context->passed = checkedCall(PedigreeLinuxAmd64Syscall_getuid, ProfileUid);
220 }
221 for (size_t repetition = 0; repetition < Repetitions && context->passed; ++repetition) {
222 context->passed = hostedProfileGetuid(repetition, context->queryCount);
223 }
224 context->returned += 1;
225 return context->passed ? 0 : 1;
226 }
227 Thread* thread = Processor::information().getCurrentThread();
228 Process* process = thread->getParent();
229 const size_t pageSize = PhysicalMemoryManager::getPageSize();
230 const size_t payloadSpan = (PayloadSize + pageSize - 1) & ~(pageSize - 1);
231 const size_t length = pageSize + payloadSpan * 2;
232 uintptr_t address = 0;
233 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
234 ERROR("HOSTED-PROFILE: FAIL user-range");
235 context->returned += 1;
236 return 1;
237 }
238 uintptr_t mappedAddress = address;
240 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
241 if (mapping && mappedAddress == address) {
242 ByteSet(reinterpret_cast<void*>(address), 0, length);
243 thread->setErrno(0);
244 context->passed =
245 runMappedWork(address, pageSize, payloadSpan, context->queryCount, context->ioCount);
246 } else {
247 ERROR("HOSTED-PROFILE: FAIL user-mapping");
248 }
249 MemoryMapManager::instance().remove(mappedAddress, length);
250 process->freeUserRange(Process::UserRegion::Normal, address, length);
251 context->returned += 1;
252 return context->passed ? 0 : 1;
253}
254} // namespace
255
256bool hostedRunSyscallProfile() {
257 const char* phase = getenv("PEDIGREE_HOSTED_PROFILE_PHASE");
258 const bool getuidOnly = phase && !strcmp(phase, "getuid");
259 if (phase && *phase && strcmp(phase, "all") && !getuidOnly) {
260 ERROR("HOSTED-PROFILE: FAIL phase must be all or getuid");
261 return false;
262 }
263 if (!runHostedVasRegressions()) {
264 ERROR("HOSTED-PROFILE: FAIL address-space regressions");
265 return false;
266 }
267 if (!runHostedVmOperationGuardRegressions()) {
268 ERROR("HOSTED-PROFILE: FAIL operation-guard regressions");
269 return false;
270 }
271 if (!runHostedPostSyscallRegressions()) {
272 ERROR("HOSTED-PROFILE: FAIL post-syscall regressions");
273 return false;
274 }
275 Thread* driver = Processor::information().getCurrentThread();
276 if (!driver || !driver->runHostedStateCleanupRegression()) {
277 ERROR("HOSTED-PROFILE: FAIL state-cleanup regressions");
278 return false;
279 }
280 NOTICE("HOSTED-PROFILE: PASS state-cleanup regressions");
281 if (!runHostedAccountingRegressions()) {
282 ERROR("HOSTED-PROFILE: FAIL accounting regressions");
283 return false;
284 }
285 const size_t divisor = hostedSyscallProfileDivisor();
286 size_t queryCount = QueryCount / divisor;
287 size_t ioCount = IoCount / divisor;
288 if (!queryCount)
289 queryCount = 1;
290 if (!ioCount)
291 ioCount = 1;
292#if PEDIGREE_HOSTED_FUNCTION_PROFILE
293 queryCount = hostedFunctionProfileCount(queryCount);
294 ioCount = hostedFunctionProfileCount(ioCount);
295#endif
296 NOTICE("HOSTED-PROFILE: BEGIN scope=kernel-origin user-entry-return=excluded abi=linux");
297 NOTICE("HOSTED-PROFILE: config uid=123 payload_bytes=4096 iov_count=2 repetitions=3 divisor="
298 << Dec << divisor << " phase=" << (getuidOnly ? "getuid" : "all"));
299 Process* kernelProcess = Scheduler::instance().getKernelProcess();
300 if (!kernelProcess) {
301 ERROR("HOSTED-PROFILE: FAIL kernel-process");
302 return false;
303 }
304
305 RamFs filesystem;
306 VFS::HostedRootViewScope fixture;
307 if (!filesystem.initialise(nullptr) || !fixture.open(&filesystem)) {
308 ERROR("HOSTED-PROFILE: FAIL ramfs-root");
309 return false;
310 }
311 auto* process = new PosixProcess(kernelProcess, true, Process::FilesystemContextMode::Deferred);
312 auto* subsystem = new PosixSubsystem;
313 process->setSubsystem(subsystem);
314 subsystem->setAbi(PosixSubsystem::LinuxAbi);
315 process->setUserId(ProfileUid);
316 process->setEffectiveUserId(ProfileUid);
317 const bool contextInstalled = fixture.installContext(*process);
318
319 ProfileContext context{queryCount, ioCount, getuidOnly};
320 auto* worker = new Thread(process, profileWorker, &context, nullptr, false, true, true);
321 worker->setName("hosted syscall profile");
322 process->publish();
323 const bool started = contextInstalled && worker->start();
324 const bool joined = started && worker->joinForCompletion();
325 if (!started) {
326 delete worker;
327 } else if (!joined) {
328 FATAL("HOSTED-PROFILE: FAIL worker lifetime barrier");
329 }
330 const bool passed = started && joined && context.returned == 1 && context.passed;
331 delete process;
332 if (!fixture.close()) {
333 FATAL("HOSTED-PROFILE: FAIL retained filesystem owners");
334 }
335 if (passed) {
336 NOTICE("HOSTED-PROFILE: PASS all");
337 } else {
338 ERROR("HOSTED-PROFILE: FAIL fixture");
339 }
340 return passed;
341}
Memory-mapped file interface.
An in-RAM filesystem.
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
void publish()
Definition Process.cc:832
static ProcessorInformation & information()
Definition RamFs.h:114
virtual bool initialise(Disk *pDisk) override
Definition RamFs.cc:416
static Scheduler & instance()
Definition Scheduler.h:96
void setErrno(size_t err)
Definition Thread.h:478
Process * getParent() const
Definition Thread.h:338
@ Dec
Definition Log.h:144