The Pedigree Project 0.1
VirtualAddressSpace-remap-regressions.cc
1#include "pedigree/kernel/BootstrapInfo.h"
2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/process/Process.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/utilities/Pointers.h"
9
10#include "VirtualAddressSpace.h"
11#include "utils.h"
12
13#if PEDIGREE_VM_REMAP_TESTS
14namespace {
15constexpr size_t PageSize = 4096;
16using Status = VirtualAddressSpace::RemapStatus;
17
18bool retainedTablesRegression() {
20 auto& memory = PhysicalMemoryManager::instance();
21 const auto physical = memory.allocatePage();
22 if (!space || !physical) {
23 if (physical)
24 memory.freePage(physical);
25 return false;
26 }
27 void* address = reinterpret_cast<void*>(space.get()->getDynamicStart());
28 bool ownsPhysical = true, passed = true;
29 const size_t expectedTables[] = {3, 0, 3};
30 for (size_t expected : expectedTables) {
31 size_t tables = 0;
32 if (!space.get()->tryMapUserPage(physical, address,
34 &tables)) {
35 passed = false;
36 break;
37 }
38 ownsPhysical = false;
39 if (!space.get()->tryDetachUserPage(address, physical)) {
40 passed = false;
41 break;
42 }
43 ownsPhysical = true;
44 if (tables != expected ||
45 !space.get()->map(physical, address,
47 passed = false;
48 break;
49 }
50 physical_uintptr_t detached = 0;
51 size_t flags = 0;
52 if (!space.get()->detachMapping(address, detached, flags) || detached != physical ||
53 space.get()->isMapped(address)) {
54 passed = false;
55 break;
56 }
57 if (!expected) {
58 // A global free-page count is stable only without another CPU or thread.
59 const bool singleProcessor = Processor::getCount() == 1;
60 const bool interrupts = Processor::getInterrupts();
62 const size_t before = singleProcessor ? memory.freePageCount() : 0;
63 space.get()->revertToKernelAddressSpace();
64 const size_t after = singleProcessor ? memory.freePageCount() : 0;
65 Processor::setInterrupts(interrupts);
66 if (singleProcessor && after != before + 3) {
67 passed = false;
68 break;
69 }
70 }
71 }
72 space.reset();
73 if (ownsPhysical)
74 memory.freePage(physical);
75 return passed;
76}
77
78bool hugePageTableReplacementRegression() {
79 constexpr size_t HugePageSize = 2 * 1024 * 1024;
80 physical_uintptr_t hugePhysical = 0;
81 bool found = false;
82 if (!g_pBootstrapInfo)
83 return false;
84 for (void* entry = g_pBootstrapInfo->getMemoryMap(); entry;
85 entry = g_pBootstrapInfo->nextMemoryMapEntry(entry)) {
86 const uint64_t base = g_pBootstrapInfo->getMemoryMapEntryAddress(entry);
87 const uint64_t length = g_pBootstrapInfo->getMemoryMapEntryLength(entry);
88 if (g_pBootstrapInfo->getMemoryMapEntryType(entry) != 1 || length < HugePageSize ||
89 base > ~uint64_t{0} - (HugePageSize - 1) || length > ~uint64_t{0} - base)
90 continue;
91 const uint64_t aligned = (base + HugePageSize - 1) & ~(uint64_t{HugePageSize} - 1);
92 if (aligned - base <= length - HugePageSize) {
93 hugePhysical = aligned;
94 found = true;
95 break;
96 }
97 }
98 if (!found)
99 return false;
100
102 auto& memory = PhysicalMemoryManager::instance();
103 const auto physical = memory.allocatePage();
104 if (!space || !physical) {
105 if (physical)
106 memory.freePage(physical);
107 return false;
108 }
109 const uintptr_t base = space.get()->getDynamicStart();
110 void* address = reinterpret_cast<void*>(base);
112 bool passed = !(base & (HugePageSize - 1)) && !(hugePhysical & (HugePageSize - 1)) &&
113 space.get()->map(physical, address, flags);
114 if (passed) {
115 // This private address space is never activated. Both mappings borrow RAM,
116 // so destroying it cannot release the backing range or the test's data page.
117 const bool singleProcessor = Processor::getCount() == 1;
118 const bool interrupts = Processor::getInterrupts();
120 const size_t before = singleProcessor ? memory.freePageCount() : 0;
121 passed = space.get()->mapHuge(hugePhysical, address, HugePageSize / PageSize, flags);
122 const size_t after = singleProcessor ? memory.freePageCount() : 0;
123 Processor::setInterrupts(interrupts);
124 passed = passed && (!singleProcessor || after == before + 1) &&
125 space.get()->isMapped(address) &&
126 space.get()->isMapped(reinterpret_cast<void*>(base + HugePageSize - PageSize)) &&
127 !space.get()->isMapped(reinterpret_cast<void*>(base - PageSize)) &&
128 !space.get()->isMapped(reinterpret_cast<void*>(base + HugePageSize));
129 }
130 space.reset();
131 memory.freePage(physical);
132 return passed;
133}
134
135bool reserveAligned(Process& process, size_t length, uintptr_t& result) {
136 for (;;) {
138 uintptr_t raw = 0;
139 if (!process.snapshotUserReservations(snapshot) ||
140 !snapshot.dynamic.allocate(length + PageSize - 1, raw)) {
141 return false;
142 }
143 const uintptr_t aligned = (raw + PageSize - 1) & ~(PageSize - 1);
144 const size_t prefix = aligned - raw;
145 const size_t suffix = PageSize - 1 - prefix;
146 if ((prefix && !snapshot.dynamic.tryFree(raw, prefix)) ||
147 (suffix && !snapshot.dynamic.tryFree(aligned + length, suffix))) {
148 return false;
149 }
150 if (process.commitUserReservations(snapshot.generation, snapshot)) {
151 result = aligned;
152 return true;
153 }
154 }
155}
156
157struct Admission {
158 Process* process;
160 bool called = false;
161};
162
163bool admit(void* context) {
164 auto& admission = *static_cast<Admission*>(context);
165 admission.called = true;
166 return admission.process->commitUserReservations(admission.snapshot->generation,
167 *admission.snapshot);
168}
169
170class RemapFixture {
171 public:
172 explicit RemapFixture(Process& process)
173 : process(process),
174 space(),
175 source(0),
176 destination(0),
177 extra(0),
178 originalCow(0),
179 sourceReserved(false),
180 destinationReserved(false),
181 committed(false),
182 plan() {}
183 ~RemapFixture() {
184 if (committed) {
185 for (size_t i = 0; i < plan.get()->detachedPageCount(); ++i) {
186 const auto& page = plan.get()->detachedPages()[i];
187 if (page.mapped) {
189 }
190 }
191 }
192 if (space) {
193 for (size_t i = 0; i < 5; ++i) {
194 const uintptr_t address = i < 3 ? source + i * PageSize : destination + (i - 3) * PageSize;
195 physical_uintptr_t physical = 0;
196 size_t flags = 0;
197 if (address &&
198 space.get()->detachMapping(reinterpret_cast<void*>(address), physical, flags)) {
200 }
201 }
202 }
203 plan.reset();
204 space.reset();
205 if (originalCow) {
207 }
208 if (extra) {
209 process.freeUserRange(Process::UserRegion::Dynamic, extra, PageSize);
210 }
211 if (sourceReserved) {
212 process.freeUserRange(Process::UserRegion::Dynamic, source, 3 * PageSize);
213 }
214 if (destinationReserved) {
215 process.freeUserRange(Process::UserRegion::Dynamic, destination, 2 * PageSize);
216 }
217 }
218
219 bool initialise() {
220 if (!reserveAligned(process, 3 * PageSize, source)) {
221 return false;
222 }
223 sourceReserved = true;
224 if (!reserveAligned(process, 2 * PageSize, destination)) {
225 return false;
226 }
227 destinationReserved = true;
229 if (!space) {
230 return false;
231 }
232 for (size_t i = 0; i < 5; ++i) {
233 const auto physical = PhysicalMemoryManager::instance().allocatePage();
234 if (!physical) {
235 return false;
236 }
237 const uintptr_t address = i < 3 ? source + i * PageSize : destination + (i - 3) * PageSize;
238 size_t flags = VirtualAddressSpace::Write;
239 if (i == 0) {
241 } else if (i == 1) {
243 }
244 ByteSet(physicalAddress(reinterpret_cast<void*>(physical)), 0x31 + i, PageSize);
245 if (!space.get()->map(physical, reinterpret_cast<void*>(address), flags)) {
247 return false;
248 }
249 physicalPages[i] = physical;
250 space.get()->getMapping(reinterpret_cast<void*>(address), physicalPages[i], pageFlags[i]);
251 if (!i) {
252 // The first pin counts the source mapping; the second retains the fixture's reference.
255 originalCow = physical;
256 }
257 }
258 return true;
259 }
260
261 bool unchanged() {
262 for (size_t i = 0; i < 5; ++i) {
263 const uintptr_t address = i < 3 ? source + i * PageSize : destination + (i - 3) * PageSize;
264 if (!space.get()->isMapped(reinterpret_cast<void*>(address))) {
265 return false;
266 }
267 physical_uintptr_t physical = 0;
268 size_t flags = 0;
269 space.get()->getMapping(reinterpret_cast<void*>(address), physical, flags);
270 if (physical != physicalPages[i] || flags != pageFlags[i]) {
271 return false;
272 }
273 const auto* bytes = physicalAddress(reinterpret_cast<unsigned char*>(physical));
274 for (size_t offset = 0; offset < PageSize; ++offset) {
275 if (bytes[offset] != (offset ? 0x31 + i : firstBytes[i])) {
276 return false;
277 }
278 }
279 }
281 return process.snapshotUserReservations(snapshot) &&
282 !snapshot.dynamic.allocateSpecific(source, 3 * PageSize) &&
283 !snapshot.dynamic.allocateSpecific(destination, 2 * PageSize);
284 }
285
286 Process& process;
288 uintptr_t source, destination, extra;
289 physical_uintptr_t originalCow;
290 physical_uintptr_t physicalPages[5] = {};
291 size_t pageFlags[5] = {};
292 unsigned char firstBytes[5] = {0x31, 0x32, 0x33, 0x34, 0x35};
293 bool sourceReserved, destinationReserved, committed;
295};
296} // namespace
297
298extern "C" EXPORTED_PUBLIC bool x64RemapCoreRegression() {
299 Thread* thread = Processor::information().getCurrentThread();
300 if (!thread || !thread->getParent()) {
301 return false;
302 }
303 if (!retainedTablesRegression()) {
304 ERROR("VM-REMAP-CORE: FAIL retained-tables");
305 return false;
306 }
307 NOTICE("VM-REMAP-CORE: PASS retained-tables");
308 if (!hugePageTableReplacementRegression()) {
309 ERROR("VM-REMAP-CORE: FAIL huge-table-replacement");
310 return false;
311 }
312 NOTICE("VM-REMAP-CORE: PASS huge-table-replacement");
313 RemapFixture fixture(*thread->getParent());
314 if (!fixture.initialise()) {
315 return false;
316 }
317 VirtualAddressSpace::RemapRange victim{fixture.destination, 2 * PageSize};
319 fixture.source, fixture.destination, 3 * PageSize, 2 * PageSize, true, &victim, 1};
320 size_t failedStages = 0;
321 bool prepared = false;
322 for (ssize_t stage = 0; stage < 32; ++stage) {
323 fixture.space.get()->setRemapPreparationFailureForTest(stage);
324 const auto status = fixture.space.get()->prepareRemap(request, fixture.plan);
325 if (!fixture.unchanged()) {
326 return false;
327 }
328 if (status == Status::Success) {
329 prepared = true;
330 break;
331 }
332 if (status != Status::NoMemory || fixture.plan) {
333 return false;
334 }
335 ++failedStages;
336 }
337 if (!prepared || failedStages < 5) {
338 return false;
339 }
340
342 if (!fixture.process.snapshotUserReservations(reservations) ||
343 !reservations.dynamic.tryFree(fixture.source, 3 * PageSize) ||
344 !reserveAligned(fixture.process, PageSize, fixture.extra)) {
345 return false;
346 }
347 Admission stale{&fixture.process, &reservations};
348 if (fixture.plan.get()->commit(admit, &stale) != Status::Retry || !stale.called ||
349 fixture.plan.get()->detachedPageCount() || !fixture.unchanged()) {
350 return false;
351 }
352
353 fixture.plan.reset();
354 if (fixture.space.get()->prepareRemap(request, fixture.plan) != Status::Success ||
355 !fixture.space.get()->handleCopyOnWriteFault(reinterpret_cast<void*>(fixture.source), true)) {
356 return false;
357 }
358 physical_uintptr_t currentCow = 0;
359 size_t currentFlags = 0;
360 fixture.space.get()->getMapping(reinterpret_cast<void*>(fixture.source), currentCow,
361 currentFlags);
362 if (currentCow == fixture.originalCow) {
363 return false;
364 }
365 *physicalAddress(reinterpret_cast<unsigned char*>(currentCow)) = 0x7B;
366 fixture.physicalPages[0] = currentCow;
367 fixture.pageFlags[0] = currentFlags;
368 fixture.firstBytes[0] = 0x7B;
369 for (size_t attempt = 0; attempt < 32; ++attempt) {
370 if (!fixture.process.snapshotUserReservations(reservations) ||
371 !reservations.dynamic.tryFree(fixture.source, 3 * PageSize)) {
372 return false;
373 }
374 Admission fresh{&fixture.process, &reservations};
375 const auto status = fixture.plan.get()->commit(admit, &fresh);
376 if (status == Status::Success) {
377 fixture.committed = true;
378 fixture.sourceReserved = false;
379 if (!fresh.called) {
380 return false;
381 }
382 break;
383 }
384 if (status != Status::Retry || !fresh.called || fixture.plan.get()->detachedPageCount() ||
385 fixture.plan.get()->detachedPages() || !fixture.unchanged()) {
386 return false;
387 }
388 }
389 if (!fixture.committed || fixture.plan.get()->detachedPageCount() != 3) {
390 return false;
391 }
392 for (size_t i = 0; i < 3; ++i) {
393 const auto& retired = fixture.plan.get()->detachedPages()[i];
394 const size_t original = i ? i + 2 : 2;
395 if (!retired.mapped || retired.physical != fixture.physicalPages[original] ||
396 retired.flags != fixture.pageFlags[original] ||
397 fixture.space.get()->isMapped(reinterpret_cast<void*>(fixture.source + i * PageSize))) {
398 return false;
399 }
400 }
401 physical_uintptr_t moved = 0;
402 size_t movedFlags = 0;
403 fixture.space.get()->getMapping(reinterpret_cast<void*>(fixture.destination), moved, movedFlags);
404 if (moved != currentCow || movedFlags != currentFlags ||
405 *physicalAddress(reinterpret_cast<unsigned char*>(moved)) != 0x7B) {
406 return false;
407 }
408 fixture.space.get()->getMapping(reinterpret_cast<void*>(fixture.destination + PageSize), moved,
409 movedFlags);
410 if (moved != fixture.physicalPages[1] || movedFlags != fixture.pageFlags[1]) {
411 return false;
412 }
414 return fixture.process.snapshotUserReservations(after) &&
415 after.dynamic.allocateSpecific(fixture.source, 3 * PageSize) &&
416 !after.dynamic.allocateSpecific(fixture.destination, 2 * PageSize);
417}
418#endif
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
virtual void pin(physical_uintptr_t page)=0
bool commitUserReservations(uint64_t expectedGeneration, UserReservationSnapshot &replacement)
static bool getInterrupts()
static ProcessorInformation & information()
static size_t getCount()
static void setInterrupts(bool bEnable)
bool allocateSpecific(T address, T length)
Definition RangeList.h:363
Process * getParent() const
Definition Thread.h:340
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
static VirtualAddressSpace * create()
uintptr_t physicalAddress(physical_uintptr_t address) PURE
Definition utils.h:39