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
61#if X64 && !HOSTED
62extern "C" void pedigree_capture_user_entry(X64UserEntryMetadata*);
63extern "C" void pedigree_restore_user_entry(const X64UserEntryMetadata*);
64
65bool metadataRoundTrip() {
66 X64UserEntryMetadata saved = {}, requested = {}, observed = {};
67 bool preservedKernelBase;
68 {
69 const bool interruptsWereEnabled = Processor::getInterrupts();
71 uint32_t beforeLow, beforeHigh, afterLow, afterHigh;
72 asm volatile("rdmsr" : "=a"(beforeLow), "=d"(beforeHigh) : "c"(0xc0000102));
73 pedigree_capture_user_entry(&saved);
74 requested.ds = requested.es = 0x23;
75 requested.fsBase = 0x12345000;
76 requested.gsBase = 0x23456000;
77 pedigree_restore_user_entry(&requested);
78 pedigree_capture_user_entry(&observed);
79 // No logging, allocation or scheduling occurs with these test bases live.
80 pedigree_restore_user_entry(&saved);
81 asm volatile("rdmsr" : "=a"(afterLow), "=d"(afterHigh) : "c"(0xc0000102));
82 preservedKernelBase = beforeLow == afterLow && beforeHigh == afterHigh;
83 Processor::setInterrupts(interruptsWereEnabled);
84 }
85 const bool passed =
86 check(equalBytes(&requested, &observed, sizeof(requested)) && preservedKernelBase,
87 "actual selectors/bases restore without changing kernel GS stack");
88 if (passed)
89 NOTICE("PTRACE-FRAME-CORE: PASS machine metadata round trip");
90 return passed;
91}
92
93bool payload(Thread& thread) {
94 SyscallState state;
95 ByteSet(&state, 0, sizeof(state));
96 for (size_t i = 0; i < 13; ++i)
97 state.setRegister(i, 0x1000 + i);
98 state.setInstructionPointer(0x401234);
99 state.setStackPointer(0x800000);
100 state.setFlags(0x202);
101 X64UserEntryMetadata metadata = {0x20, 0x21, 0x22, 0x23, 0x11110000, 0x22220000, 234};
102 state.setUserEntryMetadata(metadata);
103 UserReturnFrame frame(thread, state);
105 ByteSet(&output, 0xa5, sizeof(output));
106 const UserRegisterSnapshot canary = output;
107 bool passed = check(!frame.snapshot(output) && equalBytes(&output, &canary, sizeof(output)),
108 "unscoped snapshot leaves output unchanged");
109 {
110 Thread::UserReturnFrameScope scope(thread, frame);
111 passed &= check(frame.snapshot(output), "complete syscall snapshot");
112 }
113 const uint64_t expected[27] = {
114 0x100c, 0x100b, 0x100a, 0x1009, 0x1005, 0x1001, 0x202, 0x1008, 0x1007,
115 0x1006, 0x1000, 0x401234, 0x1002, 0x1004, 0x1003, 234, 0x401234, 0x2b,
116 0x202, 0x800000, 0x23, 0x11110000, 0x22220000, 0x20, 0x21, 0x22, 0x23};
117 passed &= check(output.architecture == UserRegisterSnapshot::Architecture::Amd64 &&
118 equalBytes(&output.amd64, expected, sizeof(expected)),
119 "all 27 syscall ABI slots");
120 const uint64_t syscallSlots[16] = {0x100c, 0x100b, 0x100a, 0x1009, 0x1008, 0x1007,
121 0x1006, 0x1005, 0x1004, 0x1003, 0x1002, 0x1001,
122 0x1000, 0x202, 0x401234, 0x800000};
123 passed &= check(equalBytes(&state, &metadata, sizeof(metadata)) &&
124 equalBytes(reinterpret_cast<const unsigned char*>(&state) + 32, syscallSlots,
125 sizeof(syscallSlots)),
126 "syscall storage matches assembly slots after metadata prefix");
127 const UserRegisterSnapshot saved = output;
128 state.setRegister(0, 0xdead);
129 passed &=
130 check(equalBytes(&output, &saved, sizeof(output)), "snapshot independent of live frame");
131 UserReturnFrame invalid(thread, state, UserReturnFrame::Origin::Interrupt);
132 {
133 Thread::UserReturnFrameScope scope(thread, invalid);
134 passed &= check(!invalid.snapshot(output) && equalBytes(&output, &saved, sizeof(output)),
135 "incompatible frame origin preserves output");
136 }
137
138 ProcessorState registers;
139 registers.rax = 0x2000;
140 registers.rbx = 0x2001;
141 registers.rcx = 0x2002;
142 registers.rdx = 0x2003;
143 registers.rdi = 0x2004;
144 registers.rsi = 0x2005;
145 registers.rbp = 0x2006;
146 registers.r8 = 0x2007;
147 registers.r9 = 0x2008;
148 registers.r10 = 0x2009;
149 registers.r11 = 0x200a;
150 registers.r12 = 0x200b;
151 registers.r13 = 0x200c;
152 registers.r14 = 0x200d;
153 registers.r15 = 0x200e;
154 registers.rip = 0x402345;
155 alignas(16) unsigned char storage[sizeof(InterruptState)];
156 registers.rsp = reinterpret_cast<uintptr_t>(storage + sizeof(storage));
157 metadata.origRax = ~uint64_t(0);
158 InterruptState* irq = InterruptState::construct(registers, true, metadata);
159 irq->setStackPointer(0x900000);
160 irq->setFlags(0x246);
161 UserReturnFrame irqFrame(thread, *irq);
162 {
163 Thread::UserReturnFrameScope scope(thread, irqFrame);
164 passed &= check(irqFrame.snapshot(output), "complete interrupt snapshot");
165 }
166 const uint64_t irqExpected[27] = {
167 0x200e, 0x200d, 0x200c, 0x200b, 0x2006, 0x2001, 0x200a, 0x2009, 0x2008,
168 0x2007, 0x2000, 0x2002, 0x2003, 0x2005, 0x2004, ~uint64_t(0), 0x402345, 0x1b,
169 0x246, 0x900000, 0x23, 0x11110000, 0x22220000, 0x20, 0x21, 0x22, 0x23};
170 passed &= check(equalBytes(&output.amd64, irqExpected, sizeof(irqExpected)),
171 "all 27 interrupt ABI slots");
172 const uint64_t irqSlots[22] = {0x200e, 0x200d, 0x200c, 0x200b, 0x200a, 0x2009, 0x2008, 0x2007,
173 0x2006, 0x2005, 0x2004, 0x2003, 0x2002, 0x2001, 0x2000, 0,
174 0, 0x402345, 0x1b, 0x246, 0x900000, 0x23};
175 passed &= check(
176 equalBytes(irq, &metadata, sizeof(metadata)) &&
177 equalBytes(reinterpret_cast<const unsigned char*>(irq) + 32, irqSlots, sizeof(irqSlots)),
178 "interrupt storage matches assembly slots after metadata prefix");
179 const UserRegisterSnapshot irqSaved = output;
180 irq = InterruptState::construct(registers, false, metadata);
181 UserReturnFrame kernel(thread, *irq);
182 {
183 Thread::UserReturnFrameScope scope(thread, kernel);
184 passed &= check(!kernel.snapshot(output) && equalBytes(&output, &irqSaved, sizeof(output)),
185 "kernel frame rejected without partial snapshot");
186 }
187 if (passed)
188 NOTICE("PTRACE-FRAME-CORE: PASS complete register snapshots");
189 return passed;
190}
191#endif
192} // namespace
193
194EXPORTED_PUBLIC bool runPtraceFrameRegressions() {
195 NOTICE("PTRACE-FRAME-CORE: BEGIN");
196 Thread* current = Processor::information().getCurrentThread();
197 if (!check(current && Processor::getInterrupts(), "fixture context"))
198 return false;
199 bool passed = scopes(*current);
200#if X64 && !HOSTED
201 passed = passed && metadataRoundTrip() && payload(*current);
202#else
203 SyscallState state;
204 UserReturnFrame frame(*current, state);
206 const auto before = output;
207 Thread::UserReturnFrameScope scope(*current, frame);
208 passed &= check(!frame.snapshot(output) && equalBytes(&before, &output, sizeof(output)),
209 "unsupported architecture snapshot unchanged");
210 NOTICE("PTRACE-FRAME-CORE: SKIP amd64 machine metadata (hosted backend)");
211#endif
212 if (passed)
213 NOTICE("PTRACE-FRAME-CORE: END PASS");
214 return passed;
215}
216#endif
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
SchedulerState * pushState()
Definition Thread.cc:841
size_t getStateLevel() const
Definition Thread.h:316
void abandonCurrentState(bool clean=false)
Definition Thread.cc:956