The Pedigree Project 0.1
page-content-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/linker/Elf.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Semaphore.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/processor/ProcessorInformation.h"
17#include "pedigree/kernel/processor/VirtualAddressSpace.h"
18#include "pedigree/kernel/utilities/utility.h"
19
20#include "DynamicLinker.h"
21#include "modules/system/vfs/File.h"
23
24namespace {
25bool fail(const char* test, const char* detail) {
26 ERROR("HOSTED-PAGE-CONTENT-TEST: FAIL " << test << ": " << detail);
27 return false;
28}
29
30size_t g_FailedAllocationCalls = 0;
31
32physical_uintptr_t failDemandPageAllocation() {
33 ++g_FailedAllocationCalls;
34 return 0;
35}
36
37class DemandPageElf final : public Elf {
38 public:
39 DemandPageElf(size_t fileSize, size_t memorySize) {
40 m_nProgramHeaders = 1;
41 m_pProgramHeaders = new ElfProgramHeader_t[1];
42 ByteSet(m_pProgramHeaders, 0, sizeof(ElfProgramHeader_t));
43 m_pProgramHeaders[0].type = PT_LOAD;
44 m_pProgramHeaders[0].filesz = fileSize;
45 m_pProgramHeaders[0].memsz = memorySize;
46 }
47
48 void addRelocations(uintptr_t relativeOffset, uintptr_t relativeAddend, uintptr_t narrowOffset,
49 uintptr_t narrowSymbol, uintptr_t narrowAddend) {
50 m_pRelaTable = new ElfRela_t[2];
51 ByteSet(m_pRelaTable, 0, sizeof(ElfRela_t) * 2);
52 m_pRelaTable[0].offset = relativeOffset;
53 m_pRelaTable[0].info = 8; // R_X86_64_RELATIVE
54 m_pRelaTable[0].addend = relativeAddend;
55 m_pRelaTable[1].offset = narrowOffset;
56 m_pRelaTable[1].info = (static_cast<Elf_Xword>(1) << 32) | 10; // R_X86_64_32
57 m_pRelaTable[1].addend = narrowAddend;
58 m_nRelaTableSize = sizeof(ElfRela_t) * 2;
59
60 m_pDynamicSymbolTable = new ElfSymbol_t[2];
61 ByteSet(m_pDynamicSymbolTable, 0, sizeof(ElfSymbol_t) * 2);
62 m_pDynamicSymbolTable[1].info = ST_INFO(STB_LOCAL, STT_SECTION);
63 m_pDynamicSymbolTable[1].shndx = 1;
64 m_nDynamicSymbolTableSize = sizeof(ElfSymbol_t) * 2;
65
66 m_pSectionHeaders = new ElfSectionHeader_t[2];
67 ByteSet(m_pSectionHeaders, 0, sizeof(ElfSectionHeader_t) * 2);
68 m_pSectionHeaders[1].addr = narrowSymbol;
69 m_nSectionHeaders = 2;
70 }
71};
72
73Atomic<size_t> g_DemandPageAllocationCalls(0);
74Atomic<size_t> g_DemandPageReadyCalls(0);
75Atomic<size_t> g_DemandPageFreeCalls(0);
76Atomic<size_t> g_DemandPageFreeMask(0);
77Atomic<bool> g_DemandPageReadyWaitFailed(false);
78physical_uintptr_t g_DemandPageAllocations[2] = {};
79Semaphore* g_DemandPageFirstReady = nullptr;
80Semaphore* g_DemandPageResumeFirst = nullptr;
81
82physical_uintptr_t allocateControlledDemandPage() {
83 const size_t call = g_DemandPageAllocationCalls += 1;
84 return call <= 2 ? g_DemandPageAllocations[call - 1] : 0;
85}
86
87void pauseFirstReadyDemandPage(uintptr_t) {
88 const size_t call = g_DemandPageReadyCalls += 1;
89 if (call == 1 && g_DemandPageFirstReady && g_DemandPageResumeFirst) {
90 g_DemandPageFirstReady->release();
91 if (!g_DemandPageResumeFirst->acquireForCompletion()) {
92 g_DemandPageReadyWaitFailed = true;
93 }
94 }
95}
96
97void observeFreedDemandPage(physical_uintptr_t page) {
98 g_DemandPageFreeCalls += 1;
99 for (size_t i = 0; i < 2; ++i) {
100 if (page == g_DemandPageAllocations[i]) {
101 g_DemandPageFreeMask |= static_cast<size_t>(1) << i;
102 }
103 }
104}
105
106struct DemandPageLoadContext {
107 DemandPageLoadContext(Elf* elf, uintptr_t buffer, size_t size, uintptr_t address)
108 : elf(elf), buffer(buffer), size(size), address(address), completed(false), result(false) {}
109
110 Elf* elf;
111 uintptr_t buffer;
112 size_t size;
113 uintptr_t address;
114 bool completed;
115 bool result;
116};
117
118int demandPageLoadWorker(void* parameter) {
119 DemandPageLoadContext* context = reinterpret_cast<DemandPageLoadContext*>(parameter);
120 context->result = DynamicLinker::loadDemandPageForTest(
121 context->elf, context->buffer, context->size, context->address, nullptr, context->address);
122 context->completed = true;
123 return context->result ? 0 : 1;
124}
125
126class SentinelFile final : public File {
127 public:
128 SentinelFile(size_t pageSize, size_t dataSize)
129 : File(), m_PageSize(pageSize), m_DataSize(dataSize), m_Reads(0), m_ReadShapeValid(true) {
130 setSize(dataSize);
131 }
132
133 size_t reads() const {
134 return m_Reads;
135 }
136
137 bool readShapeValid() const {
138 return m_ReadShapeValid;
139 }
140
141 protected:
142 bool isBytewise() const override {
143 return true;
144 }
145
146 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
147 ++m_Reads;
148 if (location != 0 || size != m_DataSize || !buffer) {
149 m_ReadShapeValid = false;
150 return 0;
151 }
152
153 uint8_t* bytes = reinterpret_cast<uint8_t*>(buffer);
154 ByteSet(bytes, 0xA5, m_PageSize);
155 for (size_t i = 0; i < size; ++i) {
156 bytes[i] = static_cast<uint8_t>(i + 1);
157 }
158 return size;
159 }
160
161 private:
162 size_t m_PageSize;
163 size_t m_DataSize;
164 size_t m_Reads;
165 bool m_ReadShapeValid;
166};
167
168class BlockingSentinelFile final : public File {
169 public:
170 explicit BlockingSentinelFile(size_t dataSize)
171 : File(),
172 m_DataSize(dataSize),
173 m_ReadEntered(0),
174 m_ResumeRead(0),
175 m_Reads(0),
176 m_ReadShapeValid(true) {
177 setSize(dataSize);
178 }
179
180 bool waitUntilRead() {
181 return m_ReadEntered.acquireForCompletion(1, 2);
182 }
183
184 void resumeRead() {
185 m_ResumeRead.release();
186 }
187
188 size_t reads() const {
189 return m_Reads;
190 }
191
192 bool readShapeValid() const {
193 return m_ReadShapeValid;
194 }
195
196 protected:
197 bool isBytewise() const override {
198 return true;
199 }
200
201 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
202 ++m_Reads;
203 if (location != 0 || size != m_DataSize || !buffer) {
204 m_ReadShapeValid = false;
205 return 0;
206 }
207
208 m_ReadEntered.release();
209 if (!m_ResumeRead.acquireForCompletion()) {
210 m_ReadShapeValid = false;
211 return 0;
212 }
213
214 uint8_t* bytes = reinterpret_cast<uint8_t*>(buffer);
215 for (size_t i = 0; i < size; ++i) {
216 bytes[i] = static_cast<uint8_t>(i + 1);
217 }
218 return size;
219 }
220
221 private:
222 size_t m_DataSize;
223 Semaphore m_ReadEntered;
224 Semaphore m_ResumeRead;
225 size_t m_Reads;
226 bool m_ReadShapeValid;
227};
228
229struct PublishAfterInitialiseContext {
230 PublishAfterInitialiseContext(MemoryMappedFile* mapping, uintptr_t address)
231 : mapping(mapping), address(address), completed(false), result(false) {}
232
233 MemoryMappedFile* mapping;
234 uintptr_t address;
235 bool completed;
236 bool result;
237};
238
239int publishAfterInitialiseWorker(void* parameter) {
240 PublishAfterInitialiseContext* context =
241 reinterpret_cast<PublishAfterInitialiseContext*>(parameter);
242 context->result = context->mapping->trap(Processor::information().getVirtualAddressSpace(),
243 context->address, false);
244 context->completed = true;
245 return context->result ? 0 : 1;
246}
247
248bool memoryMappedFileEofZeroFill() {
249 constexpr size_t DataSize = 37;
250 const size_t pageSize = PhysicalMemoryManager::getPageSize();
251 if (pageSize <= DataSize) {
252 return fail("mmap-eof-zero-fill", "target page is too small for the fixture");
253 }
254
255 Thread* thread = Processor::information().getCurrentThread();
256 Process* process = thread ? thread->getParent() : nullptr;
257 if (!process) {
258 return fail("mmap-eof-zero-fill", "no current process");
259 }
260
261 uintptr_t address = 0;
262 if (!process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
263 return fail("mmap-eof-zero-fill", "could not reserve a target page");
264 }
265
266 bool trapped = false;
267 bool dataIntact = false;
268 bool tailZero = false;
269 bool readValid = false;
270 {
271 SentinelFile file(pageSize, DataSize);
272 MemoryMappedFile mapping(address, DataSize, 0, &file, true, MemoryMappedObject::Read);
273 trapped = mapping.trap(Processor::information().getVirtualAddressSpace(), address, false);
274 if (trapped) {
275 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(address);
276 dataIntact = true;
277 for (size_t i = 0; i < DataSize; ++i) {
278 if (bytes[i] != static_cast<uint8_t>(i + 1)) {
279 dataIntact = false;
280 break;
281 }
282 }
283
284 tailZero = true;
285 for (size_t i = DataSize; i < pageSize; ++i) {
286 if (bytes[i] != 0) {
287 tailZero = false;
288 break;
289 }
290 }
291 }
292 readValid = file.reads() == 1 && file.readShapeValid();
293 }
294
295 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
296 if (!trapped || !readValid || !dataIntact || !tailZero) {
297 return fail("mmap-eof-zero-fill", "the copied EOF page retained non-file data");
298 }
299
300 NOTICE("HOSTED-PAGE-CONTENT-TEST: PASS mmap-eof-zero-fill");
301 return true;
302}
303
304bool memoryMappedFilePublishesAfterInitialise() {
305 constexpr size_t DataSize = 37;
306 const size_t pageSize = PhysicalMemoryManager::getPageSize();
307 if (pageSize <= DataSize) {
308 return fail("mmap-publish-after-init", "target page is too small for the fixture");
309 }
310
311 Thread* current = Processor::information().getCurrentThread();
312 Process* process = current ? current->getParent() : nullptr;
313 if (!process) {
314 return fail("mmap-publish-after-init", "no current process");
315 }
316
317 uintptr_t address = 0;
318 if (!process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
319 return fail("mmap-publish-after-init", "could not reserve a target page");
320 }
321
322 VirtualAddressSpace& va = Processor::information().getVirtualAddressSpace();
323 bool started = false;
324 bool readEntered = false;
325 bool absentWhileInitialising = false;
326 bool joined = false;
327 bool completed = false;
328 bool trapped = false;
329 bool mapped = false;
330 bool dataIntact = false;
331 bool tailZero = false;
332 bool readValid = false;
333 {
334 BlockingSentinelFile file(DataSize);
335 MemoryMappedFile mapping(address, DataSize, 0, &file, true, MemoryMappedObject::Read);
336 PublishAfterInitialiseContext context(&mapping, address);
337 Thread* worker =
338 new Thread(process, publishAfterInitialiseWorker, &context, nullptr, false, true, true);
339 worker->setName("hosted mmap publish-after-init");
340 started = worker->start();
341 readEntered = started && file.waitUntilRead();
342 absentWhileInitialising = readEntered && !va.isMapped(reinterpret_cast<void*>(address));
343 file.resumeRead();
344 joined = started && worker->joinForCompletion();
345 if (!started) {
346 delete worker;
347 }
348
349 completed = context.completed;
350 trapped = context.result;
351 mapped = va.isMapped(reinterpret_cast<void*>(address));
352 dataIntact = mapped;
353 tailZero = mapped;
354 if (mapped) {
355 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(address);
356 for (size_t i = 0; i < DataSize; ++i) {
357 if (bytes[i] != static_cast<uint8_t>(i + 1)) {
358 dataIntact = false;
359 break;
360 }
361 }
362 for (size_t i = DataSize; i < pageSize; ++i) {
363 if (bytes[i]) {
364 tailZero = false;
365 break;
366 }
367 }
368 }
369 readValid = file.reads() == 1 && file.readShapeValid();
370 }
371
372 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
373 if (!started || !readEntered || !absentWhileInitialising || !joined || !completed || !trapped ||
374 !mapped || !dataIntact || !tailZero || !readValid) {
375 return fail("mmap-publish-after-init", "the user mapping was visible before page population");
376 }
377
378 NOTICE("HOSTED-PAGE-CONTENT-TEST: PASS mmap-publish-after-init");
379 return true;
380}
381
382bool dynamicDemandPagePublishesOnce() {
383 constexpr size_t FileSize = 64;
384 constexpr uintptr_t RelocationOffset = 16;
385 constexpr uintptr_t RelocationAddend = 0x1234;
386 constexpr uintptr_t NarrowRelocationSymbol = 0x10203040;
387 constexpr uintptr_t NarrowRelocationAddend = 0x55;
388
389 const size_t pageSize = PhysicalMemoryManager::getPageSize();
390 if (pageSize <= FileSize) {
391 return fail("dynamic-demand-page-publish", "target page is too small for the fixture");
392 }
393 const uintptr_t NarrowRelocationOffset = pageSize - sizeof(uint32_t);
394
395 Thread* current = Processor::information().getCurrentThread();
396 Process* process = current ? current->getParent() : nullptr;
397 if (!process) {
398 return fail("dynamic-demand-page-publish", "no current process");
399 }
400
401 uintptr_t address = 0;
402 if (!process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
403 return fail("dynamic-demand-page-publish", "could not reserve a target page");
404 }
405
407 g_DemandPageAllocations[0] = memory.allocatePage();
408 g_DemandPageAllocations[1] = memory.allocatePage();
409 if (!g_DemandPageAllocations[0] || !g_DemandPageAllocations[1]) {
410 if (g_DemandPageAllocations[0]) {
411 memory.freePage(g_DemandPageAllocations[0]);
412 }
413 if (g_DemandPageAllocations[1]) {
414 memory.freePage(g_DemandPageAllocations[1]);
415 }
416 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
417 return fail("dynamic-demand-page-publish", "could not allocate controlled pages");
418 }
419
420 uint8_t fileData[FileSize];
421 for (size_t i = 0; i < FileSize; ++i) {
422 fileData[i] = static_cast<uint8_t>((i * 5) + 1);
423 }
424
425 DemandPageElf elf(FileSize, pageSize);
426 elf.addRelocations(RelocationOffset, RelocationAddend, NarrowRelocationOffset,
427 NarrowRelocationSymbol, NarrowRelocationAddend);
428
429 Semaphore firstReady(0);
430 Semaphore resumeFirst(0);
431 g_DemandPageFirstReady = &firstReady;
432 g_DemandPageResumeFirst = &resumeFirst;
433 g_DemandPageAllocationCalls = 0;
434 g_DemandPageReadyCalls = 0;
435 g_DemandPageFreeCalls = 0;
436 g_DemandPageFreeMask = 0;
437 g_DemandPageReadyWaitFailed = false;
438 DynamicLinker::setDemandPageAllocationHookForTest(allocateControlledDemandPage);
439 DynamicLinker::setDemandPageReadyHookForTest(pauseFirstReadyDemandPage);
440 DynamicLinker::setDemandPageFreeHookForTest(observeFreedDemandPage);
441
442 DemandPageLoadContext firstContext(&elf, reinterpret_cast<uintptr_t>(fileData), sizeof(fileData),
443 address);
444 Thread* first =
445 new Thread(process, demandPageLoadWorker, &firstContext, nullptr, false, true, true);
446 first->setName("hosted linker first page publisher");
447 const bool firstStarted = first->start();
448 const bool firstReachedReady = firstStarted && firstReady.acquireForCompletion(1, 2);
449
450 VirtualAddressSpace& va = Processor::information().getVirtualAddressSpace();
451 const bool absentWhileFirstReady =
452 firstReachedReady && !va.isMapped(reinterpret_cast<void*>(address));
453
454 DemandPageLoadContext secondContext(&elf, reinterpret_cast<uintptr_t>(fileData), sizeof(fileData),
455 address);
456 Thread* second = nullptr;
457 bool secondStarted = false;
458 bool secondJoined = false;
459 physical_uintptr_t pageAfterSecond = 0;
460 if (firstReachedReady) {
461 second = new Thread(process, demandPageLoadWorker, &secondContext, nullptr, false, true, true);
462 second->setName("hosted linker second page publisher");
463 secondStarted = second->start();
464 secondJoined = secondStarted && second->joinForCompletion();
465 if (!secondStarted) {
466 delete second;
467 }
468 if (va.isMapped(reinterpret_cast<void*>(address))) {
469 size_t flags = 0;
470 va.getMapping(reinterpret_cast<void*>(address), pageAfterSecond, flags);
471 }
472 }
473
474 resumeFirst.release();
475 const bool firstJoined = firstStarted && first->joinForCompletion();
476 if (!firstStarted) {
477 delete first;
478 }
479
480 DynamicLinker::setDemandPageReadyHookForTest(nullptr);
481 DynamicLinker::setDemandPageAllocationHookForTest(nullptr);
482 DynamicLinker::setDemandPageFreeHookForTest(nullptr);
483 g_DemandPageFirstReady = nullptr;
484 g_DemandPageResumeFirst = nullptr;
485
486 const bool mapped = va.isMapped(reinterpret_cast<void*>(address));
487 physical_uintptr_t finalPage = 0;
488 size_t finalFlags = 0;
489 if (mapped) {
490 va.getMapping(reinterpret_cast<void*>(address), finalPage, finalFlags);
491 }
492
493 bool dataIntact = mapped;
494 bool tailZero = mapped;
495 bool relocationCorrect = mapped;
496 bool narrowRelocationCorrect = mapped;
497 if (mapped) {
498 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(address);
499 for (size_t i = 0; i < FileSize; ++i) {
500 if (i >= RelocationOffset && i < RelocationOffset + sizeof(uint64_t)) {
501 continue;
502 }
503 if (i >= NarrowRelocationOffset && i < NarrowRelocationOffset + sizeof(uint32_t)) {
504 continue;
505 }
506 if (bytes[i] != fileData[i]) {
507 dataIntact = false;
508 break;
509 }
510 }
511 for (size_t i = FileSize; i < pageSize; ++i) {
512 if (i >= NarrowRelocationOffset && i < NarrowRelocationOffset + sizeof(uint32_t)) {
513 continue;
514 }
515 if (bytes[i]) {
516 tailZero = false;
517 break;
518 }
519 }
520 relocationCorrect = *reinterpret_cast<const uint64_t*>(address + RelocationOffset) ==
521 address + RelocationAddend;
522 narrowRelocationCorrect =
523 *reinterpret_cast<const uint32_t*>(address + NarrowRelocationOffset) ==
524 NarrowRelocationSymbol + NarrowRelocationAddend;
525 }
526
527 if (mapped) {
528 va.unmap(reinterpret_cast<void*>(address));
529 memory.freePage(finalPage);
530 }
531 for (size_t i = 0; i < 2; ++i) {
532 const bool wasPublished = mapped && finalPage == g_DemandPageAllocations[i];
533 const bool wasFreed = (g_DemandPageFreeMask & (static_cast<size_t>(1) << i)) != 0;
534 if (!wasPublished && !wasFreed) {
535 memory.freePage(g_DemandPageAllocations[i]);
536 }
537 }
538 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
539
540 const bool passed =
541 firstStarted && firstReachedReady && absentWhileFirstReady && secondStarted && secondJoined &&
542 secondContext.completed && secondContext.result && firstJoined && firstContext.completed &&
543 firstContext.result && g_DemandPageAllocationCalls == 2 && g_DemandPageReadyCalls == 2 &&
544 mapped && pageAfterSecond == g_DemandPageAllocations[1] && finalPage == pageAfterSecond &&
545 g_DemandPageFreeCalls == 1 && g_DemandPageFreeMask == 1 && !g_DemandPageReadyWaitFailed &&
546 dataIntact && tailZero && relocationCorrect && narrowRelocationCorrect;
547 if (!passed) {
548 return fail(
549 "dynamic-demand-page-publish",
550 "a staging page was published early, overwritten by a loser, or relocated via its alias");
551 }
552
553 NOTICE("HOSTED-PAGE-CONTENT-TEST: PASS dynamic-demand-page-publish");
554 return true;
555}
556
557bool memoryMapFaultReplay() {
558 const size_t pageSize = PhysicalMemoryManager::getPageSize();
559 uintptr_t address = 0;
561 MemoryMappedObject* mapping =
562 manager.mapAnon(address, pageSize, MemoryMappedObject::Read | MemoryMappedObject::Write);
563 if (!mapping) {
564 return fail("mmap-fault-replay", "could not create an anonymous mapping");
565 }
566
567 VirtualAddressSpace& va = Processor::information().getVirtualAddressSpace();
568 const bool initialRead = manager.trapForHostedTest(address, false, false);
569 const bool initialMapped = va.isMapped(reinterpret_cast<void*>(address));
570
571 physical_uintptr_t readPage = 0;
572 size_t readFlags = 0;
573 if (initialRead && initialMapped) {
574 va.getMapping(reinterpret_cast<void*>(address), readPage, readFlags);
575 }
576
577 const bool missingReplay = manager.trapForHostedTest(address, false, false);
578 const bool replayedReadMapped = va.isMapped(reinterpret_cast<void*>(address));
579 physical_uintptr_t replayedReadPage = 0;
580 size_t replayedReadFlags = 0;
581 if (missingReplay && replayedReadMapped) {
582 va.getMapping(reinterpret_cast<void*>(address), replayedReadPage, replayedReadFlags);
583 }
584
585 const bool copyOnWrite = manager.trapForHostedTest(address, true, true);
586 const bool writeMapped = va.isMapped(reinterpret_cast<void*>(address));
587 physical_uintptr_t writePage = 0;
588 size_t writeFlags = 0;
589 if (copyOnWrite && writeMapped) {
590 va.getMapping(reinterpret_cast<void*>(address), writePage, writeFlags);
591 }
592
593 const bool writeReplay = manager.trapForHostedTest(address, true, true);
594 const bool replayedWriteMapped = va.isMapped(reinterpret_cast<void*>(address));
595 physical_uintptr_t replayedWritePage = 0;
596 size_t replayedWriteFlags = 0;
597 if (writeReplay && replayedWriteMapped) {
598 va.getMapping(reinterpret_cast<void*>(address), replayedWritePage, replayedWriteFlags);
599 }
600
601 manager.removeAndRelease(address, pageSize);
602
603 const bool passed = initialRead && initialMapped && missingReplay && replayedReadMapped &&
604 copyOnWrite && writeMapped && writeReplay && replayedWriteMapped &&
605 readPage == replayedReadPage && readFlags == replayedReadFlags &&
606 writePage != readPage && (writeFlags & VirtualAddressSpace::Write) &&
607 writePage == replayedWritePage && writeFlags == replayedWriteFlags;
608 if (!passed) {
609 return fail("mmap-fault-replay", "a replay repeated or skipped demand-page work");
610 }
611
612 NOTICE("HOSTED-PAGE-CONTENT-TEST: PASS mmap-fault-replay");
613 return true;
614}
615
616bool memoryMapDeferredWriteReplay() {
617 constexpr const char* Test = "mmap-deferred-write-replay";
618 const size_t pageSize = PhysicalMemoryManager::getPageSize();
619 uintptr_t address = 0;
621 MemoryMappedObject* mapping =
622 manager.mapAnon(address, pageSize, MemoryMappedObject::Read | MemoryMappedObject::Write);
623 if (!mapping) {
624 return fail(Test, "could not create an anonymous mapping");
625 }
626
627 VirtualAddressSpace& va = Processor::information().getVirtualAddressSpace();
628 const bool initialRead = manager.trapForHostedTest(address, false, false);
629 physical_uintptr_t readPage = 0;
630 size_t readFlags = 0;
631 if (initialRead && va.isMapped(reinterpret_cast<void*>(address))) {
632 va.getMapping(reinterpret_cast<void*>(address), readPage, readFlags);
633 }
634
635 const bool deferredWrite = manager.trapForHostedTest(address, true, false);
636 physical_uintptr_t writePage = 0;
637 size_t writeFlags = 0;
638 if (deferredWrite && va.isMapped(reinterpret_cast<void*>(address))) {
639 va.getMapping(reinterpret_cast<void*>(address), writePage, writeFlags);
640 }
641
642 manager.removeAndRelease(address, pageSize);
643
644 const bool passed = initialRead && (readFlags & VirtualAddressSpace::Shared) &&
645 !(readFlags & VirtualAddressSpace::Write) && deferredWrite &&
646 writePage != readPage && (writeFlags & VirtualAddressSpace::Write) &&
647 !(writeFlags & VirtualAddressSpace::Shared);
648 if (!passed) {
649 return fail(Test, "a deferred write accepted the read-only shared zero page");
650 }
651
652 NOTICE("HOSTED-PAGE-CONTENT-TEST: PASS mmap-deferred-write-replay");
653 return true;
654}
655} // namespace
656
657bool runHostedPageContentRegressions() {
658 constexpr size_t FileSize = 48;
659 constexpr size_t MemorySize = 96;
660 const size_t pageSize = PhysicalMemoryManager::getPageSize();
661 if (pageSize <= MemorySize) {
662 return fail("dynamic-demand-page-zero-fill", "target page is too small for the fixture");
663 }
664
665 Thread* thread = Processor::information().getCurrentThread();
666 Process* process = thread ? thread->getParent() : nullptr;
667 if (!process) {
668 return fail("dynamic-demand-page-zero-fill", "no current process");
669 }
670
671 uintptr_t address = 0;
672 if (!process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
673 return fail("dynamic-demand-page-zero-fill", "could not reserve a target page");
674 }
675
676 VirtualAddressSpace& va = Processor::information().getVirtualAddressSpace();
678
679 uint8_t fileData[FileSize];
680 for (size_t i = 0; i < FileSize; ++i) {
681 fileData[i] = static_cast<uint8_t>((i * 7) + 3);
682 }
683 DemandPageElf elf(FileSize, MemorySize);
684
685 g_FailedAllocationCalls = 0;
686 DynamicLinker::setDemandPageAllocationHookForTest(failDemandPageAllocation);
687 const bool allocationFailureRejected = !DynamicLinker::loadDemandPageForTest(
688 &elf, reinterpret_cast<uintptr_t>(fileData), sizeof(fileData), address, nullptr, address);
689 DynamicLinker::setDemandPageAllocationHookForTest(nullptr);
690 const bool allocationFailureClean = !va.isMapped(reinterpret_cast<void*>(address));
691 if (!allocationFailureRejected || g_FailedAllocationCalls != 1 || !allocationFailureClean) {
692 if (!allocationFailureClean) {
693 va.unmap(reinterpret_cast<void*>(address));
694 }
695 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
696 return fail("dynamic-demand-page-zero-fill", "an allocation failure left demand-page state");
697 }
698
699 const physical_uintptr_t dirtyPage = memory.allocatePage();
700 if (!va.map(dirtyPage, reinterpret_cast<void*>(address), VirtualAddressSpace::Write)) {
701 memory.freePage(dirtyPage);
702 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
703 return fail("dynamic-demand-page-zero-fill", "could not map the sentinel page");
704 }
705
706 ByteSet(reinterpret_cast<void*>(address), 0xA5, pageSize);
707 va.unmap(reinterpret_cast<void*>(address));
708 memory.freePage(dirtyPage);
709
710 const bool loaded = DynamicLinker::loadDemandPageForTest(
711 &elf, reinterpret_cast<uintptr_t>(fileData), sizeof(fileData), address, nullptr, address);
712
713 physical_uintptr_t loadedPage = 0;
714 size_t flags = 0;
715 const bool mapped = va.isMapped(reinterpret_cast<void*>(address));
716 if (mapped) {
717 va.getMapping(reinterpret_cast<void*>(address), loadedPage, flags);
718 }
719
720 bool fileDataIntact = loaded && mapped;
721 bool remainderZero = loaded && mapped;
722 if (loaded && mapped) {
723 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(address);
724 for (size_t i = 0; i < FileSize; ++i) {
725 if (bytes[i] != fileData[i]) {
726 fileDataIntact = false;
727 break;
728 }
729 }
730 for (size_t i = FileSize; i < pageSize; ++i) {
731 if (bytes[i] != 0) {
732 remainderZero = false;
733 break;
734 }
735 }
736 }
737
738 if (mapped) {
739 va.unmap(reinterpret_cast<void*>(address));
740 memory.freePage(loadedPage);
741 }
742 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
743
744 const bool reusedSentinel = mapped && loadedPage == dirtyPage;
745 const bool demandPassed = loaded && reusedSentinel && fileDataIntact && remainderZero;
746 if (!demandPassed) {
747 return fail("dynamic-demand-page-zero-fill", "the demand-loaded page retained prior data");
748 }
749
750 NOTICE("HOSTED-PAGE-CONTENT-TEST: PASS dynamic-demand-page-zero-fill");
751 return dynamicDemandPagePublishesOnce() && memoryMappedFileEofZeroFill() &&
752 memoryMappedFilePublishesAfterInitialise() && memoryMapFaultReplay() &&
753 memoryMapDeferredWriteReplay();
754}
Memory-mapped file interface.
Definition Elf.h:201
Definition File.h:75
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1369
virtual bool isBytewise() const
Definition File.cc:1365
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
static MemoryMapManager & instance()
size_t removeAndRelease(uintptr_t base, size_t length, VmStatus *status=nullptr)
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
Process * getParent()
Definition Process.h:620
static ProcessorInformation & information()
void release(size_t n=1)
Definition Semaphore.cc:549
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:372
bool joinForCompletion()
Definition Thread.cc:2750
Process * getParent() const
Definition Thread.h:340
bool start()
Definition Thread.cc:751
virtual bool map(physical_uintptr_t physicalAddress, void *virtualAddress, size_t flags)=0
virtual bool isMapped(void *virtualAddress)=0
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
virtual void unmap(void *virtualAddress)=0