The Pedigree Project 0.1
ptrace-frame-regressions.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include <config.h>
3
4#if PEDIGREE_PTRACE_TESTS && THREADS
5#include "pedigree/kernel/Log.h"
6#include "pedigree/kernel/process/Thread.h"
7#include "pedigree/kernel/processor/Processor.h"
8#include "pedigree/kernel/processor/UserReturnFrame.h"
9#include "pedigree/kernel/processor/state.h"
10#include "pedigree/kernel/utilities/utility.h"
11
12namespace {
13bool check(bool condition, const char* detail) {
14 if (!condition)
15 ERROR("PTRACE-FRAME-CORE: FAIL " << detail);
16 return condition;
17}
18
19bool equalBytes(const void* left, const void* right, size_t count) {
20 const auto* a = static_cast<const unsigned char*>(left);
21 const auto* b = static_cast<const unsigned char*>(right);
22 for (size_t i = 0; i < count; ++i)
23 if (a[i] != b[i])
24 return false;
25 return true;
26}
27
28bool scopes(Thread& thread) {
29 SyscallState state;
30 ByteSet(&state, 0, sizeof(state));
31 UserReturnFrame outer(thread, state);
32 bool passed = check(!thread.currentUserReturnFrame(), "initial frame slot");
33 {
34 Thread::UserReturnFrameScope outerScope(thread, outer);
35 passed &= check(thread.currentUserReturnFrame() == &outer, "outer frame publication");
36 UserReturnFrame inner(thread, state);
37 {
38 Thread::UserReturnFrameScope innerScope(thread, inner);
39 passed &= check(thread.currentUserReturnFrame() == &inner, "same-level frame override");
40 }
41 passed &= check(thread.currentUserReturnFrame() == &outer, "same-level frame restoration");
42 const size_t level = thread.getStateLevel();
43 if (!thread.pushState())
44 return false;
45 passed &= check(!thread.currentUserReturnFrame(), "nested kernel state excludes outer frame");
46 {
47 Thread::UserReturnFrameScope nestedScope(thread, inner);
48 passed &= check(thread.currentUserReturnFrame() == &inner, "nested frame publication");
49 thread.abandonCurrentState(false);
50 passed &= check(thread.getStateLevel() == level && thread.currentUserReturnFrame() == &outer,
51 "abandoned nested frame restored before storage retirement");
52 }
53 passed &= check(thread.currentUserReturnFrame() == &outer, "discarded scope destructor inert");
54 }
55 passed &= check(!thread.currentUserReturnFrame(), "final frame slot cleared");
56 if (passed)
57 NOTICE("PTRACE-FRAME-CORE: PASS scope lifetime");
58 return passed;
59}
60
61bool legacyAdmission(Thread& thread) {
62 const bool pin = thread.tryPinLegacyUserCallbacks();
63 bool passed = check(pin, "legacy callback pin admission");
64 if (!pin)
65 return false;
66 const bool unexpectedRequirement = thread.tryRequireSignalFrames();
67 passed &= check(!unexpectedRequirement, "legacy callback excludes tracing frame requirement");
68 if (unexpectedRequirement)
69 thread.clearSignalFrameRequirement();
70 thread.unpinLegacyUserCallbacks();
71 const bool required = thread.tryRequireSignalFrames();
72 passed &= check(required, "frame requirement after last callback pin");
73 if (required) {
74 const bool unexpectedPin = thread.tryPinLegacyUserCallbacks();
75 passed &= check(!unexpectedPin && thread.requiresSignalFrames(),
76 "frame requirement excludes callback");
77 if (unexpectedPin)
78 thread.unpinLegacyUserCallbacks();
79 thread.clearSignalFrameRequirement();
80 }
81 if (passed)
82 NOTICE("PTRACE-FRAME-CORE: PASS legacy admission exclusion");
83 return passed;
84}
85
86#if X64 && !HOSTED
87extern "C" void pedigree_capture_user_entry(X64UserEntryMetadata*);
88extern "C" void pedigree_restore_user_entry(const X64UserEntryMetadata*);
89
90bool metadataRoundTrip() {
91 X64UserEntryMetadata saved = {}, requested = {}, observed = {};
92 bool preservedKernelBase;
93 {
94 const bool interruptsWereEnabled = Processor::getInterrupts();
96 uint32_t beforeLow, beforeHigh, afterLow, afterHigh;
97 asm volatile("rdmsr" : "=a"(beforeLow), "=d"(beforeHigh) : "c"(0xc0000102));
98 pedigree_capture_user_entry(&saved);
99 requested.ds = requested.es = 0x23;
100 requested.fsBase = 0x12345000;
101 requested.gsBase = 0x23456000;
102 pedigree_restore_user_entry(&requested);
103 pedigree_capture_user_entry(&observed);
104 // No logging, allocation or scheduling occurs with these test bases live.
105 pedigree_restore_user_entry(&saved);
106 asm volatile("rdmsr" : "=a"(afterLow), "=d"(afterHigh) : "c"(0xc0000102));
107 preservedKernelBase = beforeLow == afterLow && beforeHigh == afterHigh;
108 Processor::setInterrupts(interruptsWereEnabled);
109 }
110 const bool passed =
111 check(equalBytes(&requested, &observed, sizeof(requested)) && preservedKernelBase,
112 "actual selectors/bases restore without changing kernel GS stack");
113 if (passed)
114 NOTICE("PTRACE-FRAME-CORE: PASS machine metadata round trip");
115 return passed;
116}
117
118bool payload(Thread& thread) {
119 SyscallState state;
120 ByteSet(&state, 0, sizeof(state));
121 for (size_t i = 0; i < 13; ++i)
122 state.setRegister(i, 0x1000 + i);
123 state.setInstructionPointer(0x401234);
124 state.setStackPointer(0x800000);
125 state.setFlags(0x202);
126 X64UserEntryMetadata metadata = {0x20, 0x21, 0x22, 0x23, 0x11110000, 0x22220000, 234};
127 state.setUserEntryMetadata(metadata);
128 UserReturnFrame frame(thread, state);
130 ByteSet(&output, 0xa5, sizeof(output));
131 const UserRegisterSnapshot canary = output;
132 bool passed = check(!frame.snapshot(output) && equalBytes(&output, &canary, sizeof(output)),
133 "unscoped snapshot leaves output unchanged");
134 {
135 Thread::UserReturnFrameScope scope(thread, frame);
136 passed &= check(frame.snapshot(output), "complete syscall snapshot");
137 }
138 const uint64_t expected[27] = {
139 0x100c, 0x100b, 0x100a, 0x1009, 0x1005, 0x1001, 0x202, 0x1008, 0x1007,
140 0x1006, 0x1000, 0x401234, 0x1002, 0x1004, 0x1003, 234, 0x401234, 0x2b,
141 0x202, 0x800000, 0x23, 0x11110000, 0x22220000, 0x20, 0x21, 0x22, 0x23};
142 passed &= check(output.architecture == UserRegisterSnapshot::Architecture::Amd64 &&
143 equalBytes(&output.amd64, expected, sizeof(expected)),
144 "all 27 syscall ABI slots");
145 const uint64_t syscallSlots[16] = {0x100c, 0x100b, 0x100a, 0x1009, 0x1008, 0x1007,
146 0x1006, 0x1005, 0x1004, 0x1003, 0x1002, 0x1001,
147 0x1000, 0x202, 0x401234, 0x800000};
148 passed &= check(equalBytes(&state, &metadata, sizeof(metadata)) &&
149 equalBytes(reinterpret_cast<const unsigned char*>(&state) + 32, syscallSlots,
150 sizeof(syscallSlots)),
151 "syscall storage matches assembly slots after metadata prefix");
152 const UserRegisterSnapshot saved = output;
153 state.setRegister(0, 0xdead);
154 passed &=
155 check(equalBytes(&output, &saved, sizeof(output)), "snapshot independent of live frame");
156 UserReturnFrame invalid(thread, state, UserReturnFrame::Origin::Interrupt);
157 {
158 Thread::UserReturnFrameScope scope(thread, invalid);
159 passed &= check(!invalid.snapshot(output) && equalBytes(&output, &saved, sizeof(output)),
160 "incompatible frame origin preserves output");
161 }
162
163 ProcessorState registers;
164 registers.rax = 0x2000;
165 registers.rbx = 0x2001;
166 registers.rcx = 0x2002;
167 registers.rdx = 0x2003;
168 registers.rdi = 0x2004;
169 registers.rsi = 0x2005;
170 registers.rbp = 0x2006;
171 registers.r8 = 0x2007;
172 registers.r9 = 0x2008;
173 registers.r10 = 0x2009;
174 registers.r11 = 0x200a;
175 registers.r12 = 0x200b;
176 registers.r13 = 0x200c;
177 registers.r14 = 0x200d;
178 registers.r15 = 0x200e;
179 registers.rip = 0x402345;
180 alignas(16) unsigned char storage[sizeof(InterruptState)];
181 registers.rsp = reinterpret_cast<uintptr_t>(storage + sizeof(storage));
182 metadata.origRax = ~uint64_t(0);
183 InterruptState* irq = InterruptState::construct(registers, true, metadata);
184 irq->setStackPointer(0x900000);
185 irq->setFlags(0x246);
186 UserReturnFrame irqFrame(thread, *irq);
187 {
188 Thread::UserReturnFrameScope scope(thread, irqFrame);
189 passed &= check(irqFrame.snapshot(output), "complete interrupt snapshot");
190 }
191 const uint64_t irqExpected[27] = {
192 0x200e, 0x200d, 0x200c, 0x200b, 0x2006, 0x2001, 0x200a, 0x2009, 0x2008,
193 0x2007, 0x2000, 0x2002, 0x2003, 0x2005, 0x2004, ~uint64_t(0), 0x402345, 0x1b,
194 0x246, 0x900000, 0x23, 0x11110000, 0x22220000, 0x20, 0x21, 0x22, 0x23};
195 passed &= check(equalBytes(&output.amd64, irqExpected, sizeof(irqExpected)),
196 "all 27 interrupt ABI slots");
197 const uint64_t irqSlots[22] = {0x200e, 0x200d, 0x200c, 0x200b, 0x200a, 0x2009, 0x2008, 0x2007,
198 0x2006, 0x2005, 0x2004, 0x2003, 0x2002, 0x2001, 0x2000, 0,
199 0, 0x402345, 0x1b, 0x246, 0x900000, 0x23};
200 passed &= check(
201 equalBytes(irq, &metadata, sizeof(metadata)) &&
202 equalBytes(reinterpret_cast<const unsigned char*>(irq) + 32, irqSlots, sizeof(irqSlots)),
203 "interrupt storage matches assembly slots after metadata prefix");
204 const UserRegisterSnapshot irqSaved = output;
205 irq = InterruptState::construct(registers, false, metadata);
206 UserReturnFrame kernel(thread, *irq);
207 {
208 Thread::UserReturnFrameScope scope(thread, kernel);
209 passed &= check(!kernel.snapshot(output) && equalBytes(&output, &irqSaved, sizeof(output)),
210 "kernel frame rejected without partial snapshot");
211 }
212 if (passed)
213 NOTICE("PTRACE-FRAME-CORE: PASS complete register snapshots");
214 return passed;
215}
216#endif
217} // namespace
218
219EXPORTED_PUBLIC bool runPtraceFrameRegressions() {
220 NOTICE("PTRACE-FRAME-CORE: BEGIN");
221 Thread* current = Processor::information().getCurrentThread();
222 if (!check(current && Processor::getInterrupts(), "fixture context"))
223 return false;
224 bool passed = scopes(*current) && legacyAdmission(*current);
225#if X64 && !HOSTED
226 passed = passed && metadataRoundTrip() && payload(*current);
227#else
228 SyscallState state;
229 UserReturnFrame frame(*current, state);
231 const auto before = output;
232 Thread::UserReturnFrameScope scope(*current, frame);
233 passed &= check(!frame.snapshot(output) && equalBytes(&before, &output, sizeof(output)),
234 "unsupported architecture snapshot unchanged");
235 NOTICE("PTRACE-FRAME-CORE: SKIP amd64 machine metadata (hosted backend)");
236#endif
237 if (passed)
238 NOTICE("PTRACE-FRAME-CORE: END PASS");
239 return passed;
240}
241#endif
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
bool tryPinLegacyUserCallbacks()
SchedulerState * pushState()
Definition Thread.cc:884
size_t getStateLevel() const
Definition Thread.h:314
void abandonCurrentState(bool clean=false)
Definition Thread.cc:999