The Pedigree Project 0.1
pagefault-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/process/PerProcessorScheduler.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/PageFaultHandler.h"
15#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/VirtualAddressSpace.h"
18#include "pedigree/kernel/time/Time.h"
19#include "pedigree/kernel/utilities/utility.h"
20
21namespace {
22bool check(bool condition, const char* detail, const char* test = "pagefault-handler-lifetime") {
23 if (condition) {
24 return true;
25 }
26
27 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
28 return false;
29}
30
31struct RegistryDispatchContext;
32RegistryDispatchContext* g_RegistryDispatchContext = nullptr;
33void dispatchWhileWriterLocked();
34void dispatchWhileDeferredScopeLocked();
35
36class RegistryDispatchHandler : public MemoryTrapHandler {
37 public:
38 explicit RegistryDispatchHandler(RegistryDispatchContext& context) : m_Context(context) {}
39
40 bool trap(InterruptState&, uintptr_t, bool, bool) override;
41
42 private:
43 RegistryDispatchContext& m_Context;
44};
45
46struct RegistryDispatchContext {
47 explicit RegistryDispatchContext(PageFaultHandler* registry)
48 : registry(registry),
49 handler(*this),
50 calls(0),
51 writerRequested(0),
52 writerHookCalls(0),
53 writerDispatches(0),
54 deferredScopeRequested(0),
55 deferredScopeHookCalls(0),
56 deferredScopeDispatches(0),
57 prePinHookCalls(0),
58 unregisterSucceeded(0) {}
59
60 PageFaultHandler* registry;
61 RegistryDispatchHandler handler;
62 Atomic<size_t> calls;
63 Atomic<size_t> writerRequested;
64 Atomic<size_t> writerHookCalls;
65 Atomic<size_t> writerDispatches;
66 Atomic<size_t> deferredScopeRequested;
67 Atomic<size_t> deferredScopeHookCalls;
68 Atomic<size_t> deferredScopeDispatches;
69 Atomic<size_t> prePinHookCalls;
70 Atomic<size_t> unregisterSucceeded;
71};
72
73bool RegistryDispatchHandler::trap(InterruptState&, uintptr_t, bool, bool) {
74 m_Context.calls += 1;
75 if (m_Context.writerRequested.compareAndSwap(1, 2)) {
76 m_Context.registry->withMutationLockForTest(dispatchWhileWriterLocked);
77 }
78 if (m_Context.deferredScopeRequested.compareAndSwap(1, 2)) {
79 Processor::information().getCurrentThread()->withDeferredScopeLockForTest(
80 dispatchWhileDeferredScopeLocked);
81 }
82 return true;
83}
84
85void dispatchWhileWriterLocked() {
86 RegistryDispatchContext* context = g_RegistryDispatchContext;
87 if (!context) {
88 return;
89 }
90
91 context->writerHookCalls += 1;
92 if (context->registry->dispatchHandlerForTest(&context->handler)) {
93 context->writerDispatches += 1;
94 }
95}
96
97void dispatchWhileDeferredScopeLocked() {
98 RegistryDispatchContext* context = g_RegistryDispatchContext;
99 if (!context) {
100 return;
101 }
102
103 context->deferredScopeHookCalls += 1;
104 if (context->registry->dispatchHandlerForTest(&context->handler)) {
105 context->deferredScopeDispatches += 1;
106 }
107}
108
109bool lockIndependentDispatch() {
110 constexpr const char* Test = "pagefault-lock-reentry";
112 RegistryDispatchContext context(registry);
113 const bool registered = registry->registerHandler(&context.handler);
114
115 g_RegistryDispatchContext = &context;
116 context.writerRequested = 1;
117 const bool writerDispatched = registry->dispatchHandlerForTest(&context.handler);
118 context.deferredScopeRequested = 1;
119 const bool deferredScopeDispatched = registry->dispatchHandlerForTest(&context.handler);
120 g_RegistryDispatchContext = nullptr;
121
122 const bool cleaned = registered && registry->unregisterHandler(&context.handler);
123 bool passed = true;
124 passed &= check(registered, "the test handler could not be registered", Test);
125 passed &= check(writerDispatched && context.writerHookCalls == 1 && context.writerDispatches == 1,
126 "dispatch or callback return waited for the registry writer lock", Test);
127 passed &= check(deferredScopeDispatched && context.deferredScopeHookCalls == 1 &&
128 context.deferredScopeDispatches == 1 && context.calls == 4,
129 "dispatch entered the interrupted Thread's cleanup publication lock", Test);
130 passed &= check(registry->activeDispatchCountForTest(&context.handler) == 0,
131 "nested dispatch leaked a callback hazard", Test);
132 passed &= check(cleaned, "the test handler could not be removed", Test);
133 if (passed) {
134 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-lock-reentry");
135 }
136 return passed;
137}
138
139void unregisterBeforePin(MemoryTrapHandler* handler) {
140 RegistryDispatchContext* context = g_RegistryDispatchContext;
141 if (!context || handler != &context->handler || !context->prePinHookCalls.compareAndSwap(0, 1)) {
142 return;
143 }
144
145 if (context->registry->unregisterHandler(&context->handler)) {
146 context->unregisterSucceeded += 1;
147 }
148}
149
150bool admissionCloseRevalidation() {
151 constexpr const char* Test = "pagefault-admission-close-race";
153 RegistryDispatchContext context(registry);
154 const bool registered = registry->registerHandler(&context.handler);
155 const size_t callsBeforeDispatch = context.calls;
156
157 g_RegistryDispatchContext = &context;
158 PageFaultHandler::setHandlerPrePinHook(unregisterBeforePin);
159 const bool staleDispatch = registry->dispatchHandlerForTest(&context.handler);
160 PageFaultHandler::setHandlerPrePinHook(nullptr);
161 g_RegistryDispatchContext = nullptr;
162
163 const bool reused = registry->registerHandler(&context.handler);
164 const bool cleaned = reused && registry->unregisterHandler(&context.handler);
165 bool passed = true;
166 passed &= check(registered, "the test handler could not be registered", Test);
167 passed &= check(context.prePinHookCalls == 1 && context.unregisterSucceeded == 1,
168 "unregister did not retire the pre-pin publication", Test);
169 passed &= check(!staleDispatch && context.calls == callsBeforeDispatch,
170 "a stale pre-pin snapshot entered the retired callback", Test);
171 passed &= check(registry->activeDispatchCountForTest(&context.handler) == 0,
172 "rejected admission leaked a callback hazard", Test);
173 passed &= check(reused && cleaned, "the revalidated slot could not be reused and removed", Test);
174 if (passed) {
175 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-admission-close-race");
176 }
177 return passed;
178}
179
180struct AtomicReopenContext;
181AtomicReopenContext* g_AtomicReopenContext = nullptr;
182
183class AtomicReopenHandler : public MemoryTrapHandler {
184 public:
185 explicit AtomicReopenHandler(AtomicReopenContext& context) : m_Context(context) {}
186
187 bool trap(InterruptState&, uintptr_t, bool, bool) override;
188
189 private:
190 AtomicReopenContext& m_Context;
191};
192
193struct AtomicReopenContext {
194 explicit AtomicReopenContext(PageFaultHandler* registry)
195 : registry(registry),
196 handler(*this),
197 remover(nullptr),
198 phase(0),
199 handlerCalls(0),
200 pinHookCalls(0),
201 drainHookCalls(0),
202 selfRemovalRejected(0),
203 atomicRemovalReturned(0),
204 atomicRemovalRejected(0),
205 failures(0) {}
206
207 PageFaultHandler* registry;
208 AtomicReopenHandler handler;
209 Thread* remover;
210 Atomic<size_t> phase;
211 Atomic<size_t> handlerCalls;
212 Atomic<size_t> pinHookCalls;
213 Atomic<size_t> drainHookCalls;
214 Atomic<size_t> selfRemovalRejected;
215 Atomic<size_t> atomicRemovalReturned;
216 Atomic<size_t> atomicRemovalRejected;
217 Atomic<size_t> failures;
218};
219
220bool AtomicReopenHandler::trap(InterruptState&, uintptr_t, bool, bool) {
221 m_Context.handlerCalls += 1;
222 if (m_Context.phase != static_cast<size_t>(2)) {
223 m_Context.failures += 1;
224 return true;
225 }
226
227 if (!m_Context.registry->unregisterHandler(this)) {
228 m_Context.selfRemovalRejected += 1;
229 }
230 m_Context.phase = 3;
231 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
232 while (!m_Context.atomicRemovalReturned && Time::getTicks() < deadline) {
234 }
235 if (!m_Context.atomicRemovalReturned) {
236 m_Context.failures += 1;
237 }
238 return true;
239}
240
241void atomicReopenPinHook(MemoryTrapHandler* handler) {
242 AtomicReopenContext* context = g_AtomicReopenContext;
243 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
244 return;
245 }
246
247 context->pinHookCalls += 1;
248 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
249 while (context->phase != static_cast<size_t>(2) && Time::getTicks() < deadline) {
251 }
252 if (context->phase != static_cast<size_t>(2)) {
253 context->failures += 1;
254 }
255}
256
257void atomicReopenDrainHook(MemoryTrapHandler* handler) {
258 AtomicReopenContext* context = g_AtomicReopenContext;
259 if (!context || handler != &context->handler) {
260 return;
261 }
262
263 context->drainHookCalls += 1;
264 const bool interruptsWereEnabled = Processor::getInterrupts();
266 context->phase = 2;
267 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
268 while (context->phase != static_cast<size_t>(3) && Time::getTicks() < deadline) {
270 }
271 if (context->phase != static_cast<size_t>(3)) {
272 context->failures += 1;
273 }
274 Processor::setInterrupts(interruptsWereEnabled);
275}
276
277int atomicReopenRemover(void* parameter) {
278 AtomicReopenContext* context = reinterpret_cast<AtomicReopenContext*>(parameter);
279 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
280 while (context->phase != static_cast<size_t>(1) && Time::getTicks() < deadline) {
282 }
283 if (context->phase != static_cast<size_t>(1)) {
284 context->failures += 1;
285 return 1;
286 }
287
288 const bool interruptsWereEnabled = Processor::getInterrupts();
290 if (!context->registry->unregisterHandler(&context->handler)) {
291 context->atomicRemovalRejected += 1;
292 }
293 Processor::setInterrupts(interruptsWereEnabled);
294 context->atomicRemovalReturned += 1;
295 return 0;
296}
297
298bool atomicReopenPreservesSelfRemoval() {
299 constexpr const char* Test = "pagefault-atomic-reopen-self-removal";
301 AtomicReopenContext context(registry);
302 context.remover = new Thread(Scheduler::instance().getKernelProcess(), atomicReopenRemover,
303 &context, nullptr, false, true);
304 context.remover->setName("hosted atomic pagefault remover");
305
306 g_AtomicReopenContext = &context;
307 PageFaultHandler::setHandlerPinHook(atomicReopenPinHook);
308 PageFaultHandler::setAtomicDrainHook(atomicReopenDrainHook);
309 const bool registered = registry->registerHandler(&context.handler);
310 const bool dispatched = registry->dispatchHandlerForTest(&context.handler);
311 const bool removerJoined = context.remover->join();
312 PageFaultHandler::setAtomicDrainHook(nullptr);
313 PageFaultHandler::setHandlerPinHook(nullptr);
314 g_AtomicReopenContext = nullptr;
315
316 const bool lateDispatchRejected = !registry->dispatchHandlerForTest(&context.handler);
317 const bool hazardsDrained = registry->activeDispatchCountForTest(&context.handler) == 0;
318 const bool reused = registry->registerHandler(&context.handler);
319 const bool cleaned = reused && registry->unregisterHandler(&context.handler);
320
321 bool passed = true;
322 passed &= check(registered && dispatched && removerJoined,
323 "the atomic-removal workers did not complete", Test);
324 passed &= check(context.failures == 0 && context.pinHookCalls == 1 &&
325 context.drainHookCalls == 1 && context.handlerCalls == 1,
326 "the atomic reopen interleaving was not reached", Test);
327 passed &= check(context.selfRemovalRejected == 1 && context.atomicRemovalReturned == 1 &&
328 context.atomicRemovalRejected == 1,
329 "atomic reopen erased or replaced callback self-removal", Test);
330 passed &= check(lateDispatchRejected && hazardsDrained,
331 "the self-removal publication remained enabled after callback return", Test);
332 passed &= check(reused && cleaned, "the retired slot was not reusable", Test);
333 if (passed) {
334 NOTICE(
335 "HOSTED-WAIT-TEST: PASS "
336 "pagefault-atomic-reopen-self-removal");
337 }
338 return passed;
339}
340
341struct NestedDispatchContext;
342
343class OuterNestedHandler : public MemoryTrapHandler {
344 public:
345 explicit OuterNestedHandler(NestedDispatchContext& context) : m_Context(context) {}
346
347 bool trap(InterruptState&, uintptr_t, bool, bool) override;
348
349 private:
350 NestedDispatchContext& m_Context;
351};
352
353class InnerNestedHandler : public MemoryTrapHandler {
354 public:
355 explicit InnerNestedHandler(NestedDispatchContext& context) : m_Context(context) {}
356
357 bool trap(InterruptState&, uintptr_t, bool, bool) override;
358
359 private:
360 NestedDispatchContext& m_Context;
361};
362
363struct NestedDispatchContext {
364 explicit NestedDispatchContext(PageFaultHandler* registry)
365 : registry(registry),
366 outer(*this),
367 inner(*this),
368 orderCount(0),
369 innerDispatches(0),
370 selfRemovalRejected(0) {
371 for (size_t& entry : order) {
372 entry = 0;
373 }
374 }
375
376 void record(size_t value) {
377 if (orderCount < 4) {
378 order[orderCount++] = value;
379 }
380 }
381
382 PageFaultHandler* registry;
383 OuterNestedHandler outer;
384 InnerNestedHandler inner;
385 size_t order[4];
386 size_t orderCount;
387 size_t innerDispatches;
388 size_t selfRemovalRejected;
389};
390
391bool OuterNestedHandler::trap(InterruptState&, uintptr_t, bool, bool) {
392 m_Context.record(1);
393 if (m_Context.registry->dispatchHandlerForTest(&m_Context.inner)) {
394 ++m_Context.innerDispatches;
395 }
396 m_Context.record(4);
397 return true;
398}
399
400bool InnerNestedHandler::trap(InterruptState&, uintptr_t, bool, bool) {
401 m_Context.record(2);
402 if (!m_Context.registry->unregisterHandler(this)) {
403 ++m_Context.selfRemovalRejected;
404 }
405 m_Context.record(3);
406 return true;
407}
408
409bool nestedDispatchRemoval() {
410 constexpr const char* Test = "pagefault-nested-dispatch";
412 NestedDispatchContext context(registry);
413 const bool outerRegistered = registry->registerHandler(&context.outer);
414 const bool innerRegistered = registry->registerHandler(&context.inner);
415 const bool dispatched = registry->dispatchHandlerForTest(&context.outer);
416 const bool innerLateDispatchRejected = !registry->dispatchHandlerForTest(&context.inner);
417 const bool hazardsDrained = registry->activeDispatchCountForTest(&context.outer) == 0 &&
418 registry->activeDispatchCountForTest(&context.inner) == 0;
419 const bool outerCleaned = outerRegistered && registry->unregisterHandler(&context.outer);
420 const bool innerReused = registry->registerHandler(&context.inner);
421 const bool innerCleaned = innerReused && registry->unregisterHandler(&context.inner);
422
423 bool passed = true;
424 passed &= check(outerRegistered && innerRegistered && dispatched,
425 "the nested handlers could not be registered and dispatched", Test);
426 passed &= check(context.innerDispatches == 1 && context.selfRemovalRejected == 1 &&
427 context.orderCount == 4 && context.order[0] == 1 && context.order[1] == 2 &&
428 context.order[2] == 3 && context.order[3] == 4,
429 "nested dispatch did not unwind in strict LIFO order", Test);
430 passed &= check(innerLateDispatchRejected && hazardsDrained,
431 "nested self-removal did not retire after the final hazard", Test);
432 passed &= check(outerCleaned && innerReused && innerCleaned,
433 "nested handlers did not leave reusable registry slots", Test);
434 if (passed) {
435 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-nested-dispatch");
436 }
437 return passed;
438}
439
440struct HandlerLifetimeContext;
441HandlerLifetimeContext* g_HandlerLifetimeContext = nullptr;
442
443class LifetimeHandler : public MemoryTrapHandler {
444 public:
445 explicit LifetimeHandler(HandlerLifetimeContext& context) : m_Context(context) {}
446
447 bool trap(InterruptState&, uintptr_t, bool, bool) override;
448
449 private:
450 HandlerLifetimeContext& m_Context;
451};
452
453struct HandlerLifetimeContext {
454 explicit HandlerLifetimeContext(PageFaultHandler* registry)
455 : registry(registry),
456 handler(*this),
457 remover(nullptr),
458 competitor(nullptr),
459 phase(0),
460 competitorStart(0),
461 hookCalls(0),
462 hookObservedDrain(0),
463 handlerCalls(0),
464 callbacksAfterReturn(0),
465 unregisterReturned(0),
466 unregisterSucceeded(0),
467 competitorReturned(0),
468 competitorRejected(0),
469 selfRemovalDuringDrain(0),
470 failures(0) {}
471
472 PageFaultHandler* registry;
473 LifetimeHandler handler;
474 Thread* remover;
475 Thread* competitor;
476 Atomic<size_t> phase;
477 Atomic<size_t> competitorStart;
478 Atomic<size_t> hookCalls;
479 Atomic<size_t> hookObservedDrain;
480 Atomic<size_t> handlerCalls;
481 Atomic<size_t> callbacksAfterReturn;
482 Atomic<size_t> unregisterReturned;
483 Atomic<size_t> unregisterSucceeded;
484 Atomic<size_t> competitorReturned;
485 Atomic<size_t> competitorRejected;
486 Atomic<size_t> selfRemovalDuringDrain;
487 Atomic<size_t> failures;
488};
489
490bool LifetimeHandler::trap(InterruptState&, uintptr_t, bool, bool) {
491 m_Context.handlerCalls += 1;
492 if (m_Context.unregisterReturned) {
493 m_Context.callbacksAfterReturn += 1;
494 }
495 if (m_Context.phase == static_cast<size_t>(3) && !m_Context.registry->unregisterHandler(this)) {
496 m_Context.selfRemovalDuringDrain += 1;
497 }
498 return true;
499}
500
501bool hasCallbackDrainWait(Thread* thread, MemoryTrapHandler* handler) {
502 Thread::WaitDebugInfo wait = {};
503 uintptr_t debugAddress = 0;
504 return thread && thread->getWaitDebugInfo(wait) && wait.queue && wait.channelOwner &&
505 wait.channelValue && wait.queued && wait.reason == WaitQueue::WakeReason::Waiting &&
506 thread->getDebugState(debugAddress) == Thread::CallbackDrain &&
507 debugAddress == reinterpret_cast<uintptr_t>(handler);
508}
509
510class SelfRemovingHandler : public MemoryTrapHandler {
511 public:
512 explicit SelfRemovingHandler(PageFaultHandler* registry)
513 : m_Registry(registry), calls(0), rejectionSeen(0) {}
514
515 bool trap(InterruptState&, uintptr_t, bool, bool) override {
516 calls += 1;
517 if (!m_Registry->unregisterHandler(this)) {
518 rejectionSeen += 1;
519 }
520 return true;
521 }
522
523 PageFaultHandler* m_Registry;
524 Atomic<size_t> calls;
525 Atomic<size_t> rejectionSeen;
526};
527
528void handlerPinHook(MemoryTrapHandler* handler) {
529 HandlerLifetimeContext* context = g_HandlerLifetimeContext;
530 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
531 return;
532 }
533
534 context->hookCalls += 1;
535 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
536 while (Time::getTicks() < deadline) {
537 if (context->phase == static_cast<size_t>(2) &&
538 hasCallbackDrainWait(context->remover, &context->handler)) {
539 break;
540 }
542 }
543
544 if (context->phase == static_cast<size_t>(2) &&
545 hasCallbackDrainWait(context->remover, &context->handler) && !context->unregisterReturned) {
546 context->hookObservedDrain += 1;
547 context->competitorStart = 1;
548 while (!context->competitorReturned && Time::getTicks() < deadline) {
550 }
551 if (!context->competitorReturned) {
552 context->failures += 1;
553 }
554 context->remover->setUnwindState(Thread::TerminateThread);
555 } else {
556 context->failures += 1;
557 }
558 context->phase = 3;
559}
560
561int unregisterPinnedHandler(void* parameter) {
562 HandlerLifetimeContext* context = reinterpret_cast<HandlerLifetimeContext*>(parameter);
563 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
564 while (context->phase != static_cast<size_t>(1) && Time::getTicks() < deadline) {
566 }
567
568 if (context->phase != static_cast<size_t>(1)) {
569 context->failures += 1;
570 return 1;
571 }
572
573 context->phase = 2;
574 if (context->registry->unregisterHandler(&context->handler)) {
575 context->unregisterSucceeded += 1;
576 }
577 context->unregisterReturned += 1;
578 context->phase = 4;
579 return 0;
580}
581
582int competingUnregister(void* parameter) {
583 HandlerLifetimeContext* context = reinterpret_cast<HandlerLifetimeContext*>(parameter);
584 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
585 while (!context->competitorStart && Time::getTicks() < deadline) {
587 }
588
589 if (!context->competitorStart) {
590 context->failures += 1;
591 return 1;
592 }
593
594 if (!context->registry->unregisterHandler(&context->handler)) {
595 context->competitorRejected += 1;
596 }
597 context->competitorReturned += 1;
598 return 0;
599}
600
601bool handlerLifetimeBarrier() {
603 HandlerLifetimeContext context(registry);
604 context.remover = new Thread(Scheduler::instance().getKernelProcess(), unregisterPinnedHandler,
605 &context, nullptr, false, true);
606 context.remover->setName("hosted pagefault-handler remover");
607 context.competitor = new Thread(Scheduler::instance().getKernelProcess(), competingUnregister,
608 &context, nullptr, false, true);
609 context.competitor->setName("hosted pagefault-handler competitor");
610
611 g_HandlerLifetimeContext = &context;
612 PageFaultHandler::setHandlerPinHook(handlerPinHook);
613 const bool registered = registry->registerHandler(&context.handler);
614 const bool duplicateRejected = !registry->registerHandler(&context.handler);
615 const bool dispatched = registry->dispatchHandlerForTest(&context.handler);
616 const bool joined = context.remover->join();
617 const bool competitorJoined = context.competitor->join();
618 PageFaultHandler::setHandlerPinHook(nullptr);
619 g_HandlerLifetimeContext = nullptr;
620
621 if (!context.unregisterSucceeded) {
622 registry->unregisterHandler(&context.handler);
623 }
624
625 const size_t callsAtUnregisterReturn = context.handlerCalls;
626 const bool lateDispatchRejected = !registry->dispatchHandlerForTest(&context.handler);
627 const bool unregisterBoundaryHeld = lateDispatchRejected &&
628 context.handlerCalls == callsAtUnregisterReturn &&
629 context.callbacksAfterReturn == 0;
630
631 const bool reregistered = registry->registerHandler(&context.handler);
632 const bool redispatched = registry->dispatchHandlerForTest(&context.handler);
633 const bool recleaned = reregistered && registry->unregisterHandler(&context.handler);
634
635 SelfRemovingHandler selfRemoving(registry);
636 const bool selfRegistered = registry->registerHandler(&selfRemoving);
637 const bool selfDispatched = registry->dispatchHandlerForTest(&selfRemoving);
638 const bool selfLateDispatchRejected = !registry->dispatchHandlerForTest(&selfRemoving);
639 const bool selfReregistered = registry->registerHandler(&selfRemoving);
640 const bool selfCleanup = selfReregistered && registry->unregisterHandler(&selfRemoving);
641
642 bool passed = true;
643 passed &= check(registered && duplicateRejected && dispatched,
644 "registration, duplicate rejection, or initial dispatch failed");
645 passed &= check(joined && competitorJoined && context.failures == 0,
646 "the concurrent unregister workers did not finish cleanly");
647 passed &= check(context.hookCalls == 1 && context.hookObservedDrain == 1,
648 "unregister did not wait for the callback pin");
649 passed &= check(context.unregisterSucceeded == 1 && context.unregisterReturned == 1 &&
650 context.competitorReturned == 1 && context.competitorRejected == 1 &&
651 context.selfRemovalDuringDrain == 1,
652 "draining self-removal was lost or a second drainer was admitted");
653 passed &= check(unregisterBoundaryHeld, "a callback began after unregisterHandler returned");
654 passed &= check(reregistered && redispatched && recleaned &&
655 context.handlerCalls == callsAtUnregisterReturn + 1,
656 "a drained handler could not be registered and dispatched again");
657 passed &= check(selfRegistered && selfDispatched && selfRemoving.rejectionSeen == 1 &&
658 selfRemoving.calls == 1 && selfLateDispatchRejected,
659 "self-unregister did not reject its own drain and retire on return");
660 passed &= check(selfReregistered && selfCleanup,
661 "deferred self-removal did not release its registry slot");
662
663 if (passed) {
664 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-handler-waitqueue-drain");
665 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-handler-lifetime");
666 }
667 return passed;
668}
669
670struct CopyOnWriteRaceContext {
671 CopyOnWriteRaceContext(VirtualAddressSpace* addressSpace, void* address)
672 : addressSpace(addressSpace),
673 address(address),
674 arrivals(0),
675 release(0),
676 successes(0),
677 failures(0) {}
678
679 VirtualAddressSpace* addressSpace;
680 void* address;
681 Atomic<size_t> arrivals;
682 Atomic<size_t> release;
683 Atomic<size_t> successes;
684 Atomic<size_t> failures;
685};
686
687CopyOnWriteRaceContext* g_CopyOnWriteRaceContext = nullptr;
688
689void copyOnWritePreCommit(void* address) {
690 CopyOnWriteRaceContext* context = g_CopyOnWriteRaceContext;
691 if (!context || context->address != address) {
692 return;
693 }
694
695 context->arrivals += 1;
696 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
697 while (!context->release && Time::getTicks() < deadline) {
699 }
700 if (!context->release) {
701 context->failures += 1;
702 }
703}
704
705int resolveCopyOnWrite(void* parameter) {
706 CopyOnWriteRaceContext* context = reinterpret_cast<CopyOnWriteRaceContext*>(parameter);
707 if (context->addressSpace->handleCopyOnWriteFault(context->address, true)) {
708 context->successes += 1;
709 return 0;
710 }
711
712 context->failures += 1;
713 return 1;
714}
715
716bool concurrentCopyOnWriteResolution() {
717 constexpr const char* Test = "pagefault-cow-atomic-publish";
718 VirtualAddressSpace& addressSpace = Processor::information().getVirtualAddressSpace();
720 const size_t pageSize = PhysicalMemoryManager::getPageSize();
721
722 void* address = reinterpret_cast<void*>(addressSpace.getDynamicStart() + (16 * pageSize));
723 while (addressSpace.isMapped(address)) {
724 address = adjust_pointer(address, pageSize);
725 }
726
727 const physical_uintptr_t original = physicalMemory.allocatePage();
728 physicalMemory.pin(original);
729 physicalMemory.pin(original);
730 if (!addressSpace.map(original, address, VirtualAddressSpace::Write)) {
731 physicalMemory.freePage(original);
732 physicalMemory.freePage(original);
733 return check(false, "the copy-on-write fixture could not be mapped", Test);
734 }
735
736 uint8_t* bytes = reinterpret_cast<uint8_t*>(address);
737 for (size_t i = 0; i < pageSize; ++i) {
738 bytes[i] = static_cast<uint8_t>((i * 37U + 11U) & 0xFFU);
739 }
740 addressSpace.setFlags(address, VirtualAddressSpace::CopyOnWrite);
741
742 CopyOnWriteRaceContext context(&addressSpace, address);
743 Thread* first = new Thread(Scheduler::instance().getKernelProcess(), resolveCopyOnWrite, &context,
744 nullptr, false, true, true);
745 Thread* second = new Thread(Scheduler::instance().getKernelProcess(), resolveCopyOnWrite,
746 &context, nullptr, false, true, true);
747 first->setName("hosted first copy-on-write resolver");
748 second->setName("hosted second copy-on-write resolver");
749
750 g_CopyOnWriteRaceContext = &context;
751 VirtualAddressSpace::setCopyOnWritePreCommitHookForTest(copyOnWritePreCommit);
752 const bool firstStarted = first->start();
753 const bool secondStarted = second->start();
754
755 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
756 while (context.arrivals != static_cast<size_t>(2) && Time::getTicks() < deadline) {
758 }
759
760 physical_uintptr_t preCommitPhysical = 0;
761 size_t preCommitFlags = 0;
762 addressSpace.getMapping(address, preCommitPhysical, preCommitFlags);
763 const size_t preCommitReferences = PhysicalMemoryManager::pageReferenceCountForTest(original);
764 context.release = 1;
765
766 const bool firstJoined = firstStarted && first->joinForCompletion();
767 const bool secondJoined = secondStarted && second->joinForCompletion();
768 VirtualAddressSpace::setCopyOnWritePreCommitHookForTest(nullptr);
769 g_CopyOnWriteRaceContext = nullptr;
770
771 physical_uintptr_t finalPhysical = 0;
772 size_t finalFlags = 0;
773 addressSpace.getMapping(address, finalPhysical, finalFlags);
774 bool contentPreserved = true;
775 for (size_t i = 0; i < pageSize; ++i) {
776 const uint8_t expected = static_cast<uint8_t>((i * 37U + 11U) & 0xFFU);
777 if (bytes[i] != expected) {
778 contentPreserved = false;
779 break;
780 }
781 }
782
783 const size_t oldReferencesAfter = PhysicalMemoryManager::pageReferenceCountForTest(original);
784 const size_t replacementReferences =
785 PhysicalMemoryManager::pageReferenceCountForTest(finalPhysical);
786
787 bool passed = true;
788 passed &= check(firstStarted && secondStarted && context.arrivals == 2,
789 "both resolvers did not reach the pre-commit window", Test);
790 passed &=
791 check(preCommitPhysical == original && (preCommitFlags & VirtualAddressSpace::CopyOnWrite) &&
792 !(preCommitFlags & VirtualAddressSpace::Write),
793 "a replacement became visible before race revalidation", Test);
794 passed &= check(preCommitReferences == 2,
795 "the pre-commit window changed the shared-page references", Test);
796 passed &= check(firstJoined && secondJoined && context.successes == 2 && context.failures == 0,
797 "the winner or stale-fault resolver did not complete", Test);
798 passed &= check(finalPhysical != original && (finalFlags & VirtualAddressSpace::Write) &&
799 !(finalFlags & VirtualAddressSpace::CopyOnWrite),
800 "the winning replacement was not published writable", Test);
801 passed &= check(contentPreserved, "the replacement exposed incomplete page contents", Test);
802 passed &= check(oldReferencesAfter == 1 && replacementReferences == 1,
803 "the race did not retire exactly one shared-page reference", Test);
804
806 const bool userPrivilegeRejected = !addressSpace.handleCopyOnWriteFault(address, true);
807 physical_uintptr_t privilegedPhysical = 0;
808 size_t privilegedFlags = 0;
809 addressSpace.getMapping(address, privilegedPhysical, privilegedFlags);
810 passed &= check(userPrivilegeRejected && privilegedPhysical == finalPhysical &&
811 (privilegedFlags & VirtualAddressSpace::KernelMode) &&
812 (privilegedFlags & VirtualAddressSpace::Write) &&
813 !(privilegedFlags & VirtualAddressSpace::CopyOnWrite),
814 "a user fault accepted a supervisor-only writable mapping as stale", Test);
815
816 addressSpace.unmap(address);
817 physicalMemory.freePage(finalPhysical);
818 physicalMemory.freePage(original);
819 const size_t finalOldReferences = PhysicalMemoryManager::pageReferenceCountForTest(original);
820 const size_t finalReplacementReferences =
821 PhysicalMemoryManager::pageReferenceCountForTest(finalPhysical);
822 passed &= check(finalOldReferences == 0 && finalReplacementReferences == 0,
823 "the test mappings did not release both physical pages", Test);
824
825 if (passed) {
826 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-cow-atomic-publish");
827 }
828 return passed;
829}
830
831bool hostedAddressSpaceCloneSemantics() {
832 constexpr const char* Test = "pagefault-hosted-clone-cow";
833 VirtualAddressSpace& originalSpace = Processor::information().getVirtualAddressSpace();
835 const size_t pageSize = PhysicalMemoryManager::getPageSize();
836 void* address = reinterpret_cast<void*>(originalSpace.getDynamicStart() + (24 * pageSize));
837 while (originalSpace.isMapped(address)) {
838 address = adjust_pointer(address, pageSize);
839 }
840
841 Process* kernelProcess = Scheduler::instance().getKernelProcess();
842 Process* sourceProcess = new Process(kernelProcess, true);
843 VirtualAddressSpace* source = sourceProcess->getAddressSpace();
844 VirtualAddressSpace* child = nullptr;
845 Process* childProcess = nullptr;
846 const physical_uintptr_t original = physicalMemory.allocatePage();
847 bool passed = true;
848
849 if (!source->map(original, address, VirtualAddressSpace::Write)) {
850 physicalMemory.freePage(original);
851 delete sourceProcess;
852 return check(false, "the private-clone fixture could not be mapped", Test);
853 }
854
856 volatile uint8_t* bytes = reinterpret_cast<volatile uint8_t*>(address);
857 bytes[0] = 0x31;
858 bytes[pageSize - 1] = 0x7A;
859 childProcess = new Process(sourceProcess, true);
860 child = childProcess->getAddressSpace();
861
862 physical_uintptr_t sourceBeforePhysical = 0;
863 physical_uintptr_t childBeforePhysical = 0;
864 size_t sourceBeforeFlags = 0;
865 size_t childBeforeFlags = 0;
866 source->getMapping(address, sourceBeforePhysical, sourceBeforeFlags);
867 child->getMapping(address, childBeforePhysical, childBeforeFlags);
868 const size_t sharedReferences = PhysicalMemoryManager::pageReferenceCountForTest(original);
869
870 passed &= check(sourceBeforePhysical == original && childBeforePhysical == original &&
871 (sourceBeforeFlags & VirtualAddressSpace::CopyOnWrite) &&
872 !(sourceBeforeFlags & VirtualAddressSpace::Write) &&
873 (childBeforeFlags & VirtualAddressSpace::CopyOnWrite) &&
874 !(childBeforeFlags & VirtualAddressSpace::Write),
875 "a private clone did not downgrade both mappings", Test);
876 passed &= check(sharedReferences == 2, "the private clone did not retain both page owners", Test);
877
878 const bool sourceResolved = source->handleCopyOnWriteFault(address, false);
879 if (sourceResolved) {
880 bytes[0] = 0xA5;
881 }
882 physical_uintptr_t sourceAfterPhysical = 0;
883 size_t sourceAfterFlags = 0;
884 source->getMapping(address, sourceAfterPhysical, sourceAfterFlags);
885 passed &= check(sourceResolved && sourceAfterPhysical != original &&
886 (sourceAfterFlags & VirtualAddressSpace::Write) &&
887 !(sourceAfterFlags & VirtualAddressSpace::CopyOnWrite) && bytes[0] == 0xA5 &&
888 bytes[pageSize - 1] == 0x7A &&
889 PhysicalMemoryManager::pageReferenceCountForTest(original) == 1,
890 "the parent write did not split from the child page", Test);
891
893 passed &= check(bytes[0] == 0x31 && bytes[pageSize - 1] == 0x7A,
894 "the parent write changed the child view", Test);
895 const bool childResolved = child->handleCopyOnWriteFault(address, false);
896 if (childResolved) {
897 bytes[0] = 0x5C;
898 }
899
900 physical_uintptr_t childAfterPhysical = 0;
901 size_t childAfterFlags = 0;
902 child->getMapping(address, childAfterPhysical, childAfterFlags);
903 passed &= check(childResolved && childAfterPhysical != original &&
904 childAfterPhysical != sourceAfterPhysical &&
905 (childAfterFlags & VirtualAddressSpace::Write) &&
906 !(childAfterFlags & VirtualAddressSpace::CopyOnWrite) && bytes[0] == 0x5C &&
907 bytes[pageSize - 1] == 0x7A &&
908 PhysicalMemoryManager::pageReferenceCountForTest(original) == 0,
909 "the child write did not publish an independent page", Test);
910
912 passed &= check(bytes[0] == 0xA5 && bytes[pageSize - 1] == 0x7A,
913 "the child write changed the parent view", Test);
914
915 Processor::switchAddressSpace(originalSpace);
916 delete childProcess;
917 delete sourceProcess;
918 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(original) == 0 &&
919 PhysicalMemoryManager::pageReferenceCountForTest(sourceAfterPhysical) == 0 &&
920 PhysicalMemoryManager::pageReferenceCountForTest(childAfterPhysical) == 0,
921 "the private clone leaked a physical page", Test);
922
923 constexpr const char* WritableAliasTest = "pagefault-hosted-clone-writable-alias";
924 sourceProcess = new Process(kernelProcess, true);
925 source = sourceProcess->getAddressSpace();
926 child = nullptr;
927 childProcess = nullptr;
928 const physical_uintptr_t sharedPhysical = physicalMemory.allocatePage();
929 if (!source->map(sharedPhysical, address, VirtualAddressSpace::Write)) {
930 physicalMemory.freePage(sharedPhysical);
931 delete sourceProcess;
932 return check(false, "the writable-alias fixture could not be mapped", WritableAliasTest) &&
933 passed;
934 }
935
937 bytes[0] = 0x12;
938 childProcess = new Process(sourceProcess, false);
939 child = childProcess->getAddressSpace();
940
941 physical_uintptr_t sourceSharedPhysical = 0;
942 physical_uintptr_t childSharedPhysical = 0;
943 size_t sourceSharedFlags = 0;
944 size_t childSharedFlags = 0;
945 source->getMapping(address, sourceSharedPhysical, sourceSharedFlags);
946 child->getMapping(address, childSharedPhysical, childSharedFlags);
947 passed &=
948 check(sourceSharedPhysical == sharedPhysical && childSharedPhysical == sharedPhysical &&
949 (sourceSharedFlags & VirtualAddressSpace::Write) &&
950 !(sourceSharedFlags & VirtualAddressSpace::CopyOnWrite) &&
951 (childSharedFlags & VirtualAddressSpace::Write) &&
952 !(childSharedFlags & VirtualAddressSpace::CopyOnWrite) &&
953 PhysicalMemoryManager::pageReferenceCountForTest(sharedPhysical) == 2,
954 "clone(false) changed a fresh writable mapping's alias semantics", WritableAliasTest);
955
956 bytes[0] = 0x48;
958 passed &= check(bytes[0] == 0x48, "the child did not observe the parent's aliased write",
959 WritableAliasTest);
960 bytes[0] = 0x84;
962 passed &= check(bytes[0] == 0x84, "the parent did not observe the child's aliased write",
963 WritableAliasTest);
964
965 Processor::switchAddressSpace(originalSpace);
966 delete childProcess;
967 delete sourceProcess;
968 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(sharedPhysical) == 0,
969 "the writable alias leaked its physical page", WritableAliasTest);
970
971 if (passed) {
972 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-hosted-clone-cow");
973 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-hosted-clone-writable-alias");
974 }
975 return passed;
976}
977
978struct AbandonedDispatchContext;
979AbandonedDispatchContext* g_AbandonedDispatchContext = nullptr;
980
981class AbandonedDispatchHandler : public MemoryTrapHandler {
982 public:
983 explicit AbandonedDispatchHandler(AbandonedDispatchContext& context) : m_Context(context) {}
984
985 bool trap(InterruptState&, uintptr_t, bool, bool) override;
986
987 private:
988 AbandonedDispatchContext& m_Context;
989};
990
991struct AbandonedDispatchContext {
992 explicit AbandonedDispatchContext(PageFaultHandler* registry)
993 : registry(registry),
994 handler(*this),
995 remover(nullptr),
996 dispatcher(nullptr),
997 phase(0),
998 hookCalls(0),
999 hookObservedDrain(0),
1000 handlerCalls(0),
1001 dispatchReturned(0),
1002 unregisterReturned(0),
1003 unregisterSucceeded(0),
1004 failures(0) {}
1005
1006 PageFaultHandler* registry;
1007 AbandonedDispatchHandler handler;
1008 Thread* remover;
1009 Thread* dispatcher;
1010 Atomic<size_t> phase;
1011 Atomic<size_t> hookCalls;
1012 Atomic<size_t> hookObservedDrain;
1013 Atomic<size_t> handlerCalls;
1014 Atomic<size_t> dispatchReturned;
1015 Atomic<size_t> unregisterReturned;
1016 Atomic<size_t> unregisterSucceeded;
1017 Atomic<size_t> failures;
1018};
1019
1020bool AbandonedDispatchHandler::trap(InterruptState&, uintptr_t, bool, bool) {
1021 m_Context.handlerCalls += 1;
1022 return true;
1023}
1024
1025void abandonedDispatchPinHook(MemoryTrapHandler* handler) {
1026 AbandonedDispatchContext* context = g_AbandonedDispatchContext;
1027 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
1028 return;
1029 }
1030
1031 context->hookCalls += 1;
1032 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1033 while (Time::getTicks() < deadline) {
1034 if (context->phase == static_cast<size_t>(2) &&
1035 hasCallbackDrainWait(context->remover, &context->handler)) {
1036 context->hookObservedDrain += 1;
1037 Processor::information().getCurrentThread()->getScheduler()->abandonCurrentThreadStack(
1038 PerProcessorScheduler::StackDiscardReason::HostedRegression);
1039 }
1041 }
1042
1043 // Reaching the deadline means the unregister path never published its
1044 // drain wait. Returning would exercise the ordinary cleanup path instead
1045 // of the abandoned-stack path this regression is intended to prove.
1046 context->failures += 1;
1047 Processor::information().getCurrentThread()->getScheduler()->abandonCurrentThreadStack(
1048 PerProcessorScheduler::StackDiscardReason::HostedRegression);
1049}
1050
1051int unregisterAbandonedDispatch(void* parameter) {
1052 AbandonedDispatchContext* context = reinterpret_cast<AbandonedDispatchContext*>(parameter);
1053 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1054 while (context->phase != static_cast<size_t>(1) && Time::getTicks() < deadline) {
1056 }
1057
1058 if (context->phase != static_cast<size_t>(1)) {
1059 context->failures += 1;
1060 return 1;
1061 }
1062
1063 context->phase = 2;
1064 if (context->registry->unregisterHandler(&context->handler)) {
1065 context->unregisterSucceeded += 1;
1066 }
1067 context->unregisterReturned += 1;
1068 return 0;
1069}
1070
1071int dispatchThenAbandon(void* parameter) {
1072 AbandonedDispatchContext* context = reinterpret_cast<AbandonedDispatchContext*>(parameter);
1073 context->registry->dispatchHandlerForTest(&context->handler);
1074 context->dispatchReturned += 1;
1075 return 1;
1076}
1077
1078bool abandonedDispatchCleanup() {
1080 AbandonedDispatchContext context(registry);
1081 context.remover = new Thread(Scheduler::instance().getKernelProcess(),
1082 unregisterAbandonedDispatch, &context, nullptr, false, true);
1083 context.remover->setName("hosted abandoned pagefault remover");
1084 context.dispatcher = new Thread(Scheduler::instance().getKernelProcess(), dispatchThenAbandon,
1085 &context, nullptr, false, true, true);
1086 context.dispatcher->setName("hosted abandoned pagefault dispatch");
1087
1088 g_AbandonedDispatchContext = &context;
1089 PageFaultHandler::setHandlerPinHook(abandonedDispatchPinHook);
1090 const bool registered = registry->registerHandler(&context.handler);
1091 const bool started = context.dispatcher->start();
1092 const bool dispatcherJoined = context.dispatcher->join();
1093 const bool removerJoined = context.remover->join();
1094 PageFaultHandler::setHandlerPinHook(nullptr);
1095 g_AbandonedDispatchContext = nullptr;
1096
1097 if (!context.unregisterSucceeded) {
1098 registry->unregisterHandler(&context.handler);
1099 }
1100 const size_t activeDispatches = registry->activeDispatchCountForTest(&context.handler);
1101 const bool lateDispatchRejected = !registry->dispatchHandlerForTest(&context.handler);
1102
1103 const bool passed =
1104 check(registered && started && dispatcherJoined && removerJoined,
1105 "the abandoned-dispatch workers did not complete") &&
1106 check(context.failures == 0 && context.hookCalls == 1 && context.hookObservedDrain == 1,
1107 "the forced abandonment window was not reached") &&
1108 check(context.handlerCalls == 0 && context.dispatchReturned == 0,
1109 "the abandoned fault stack returned through normal dispatch") &&
1110 check(activeDispatches == 0, "stack abandonment leaked an active callback hazard") &&
1111 check(context.unregisterSucceeded == 1 && context.unregisterReturned == 1 &&
1112 lateDispatchRejected,
1113 "state cleanup did not release the pinned callback exactly once");
1114 if (passed) {
1115 NOTICE("HOSTED-WAIT-TEST: PASS pagefault-abandoned-dispatch");
1116 }
1117 return passed;
1118}
1119} // namespace
1120
1121bool runHostedPageFaultRegressions() {
1122 bool passed = lockIndependentDispatch();
1123 passed &= admissionCloseRevalidation();
1124 passed &= atomicReopenPreservesSelfRemoval();
1125 passed &= nestedDispatchRemoval();
1126 passed &= handlerLifetimeBarrier();
1127 passed &= concurrentCopyOnWriteResolution();
1128 passed &= hostedAddressSpaceCloneSemantics();
1129 passed &= abandonedDispatchCleanup();
1130 return passed;
1131}
virtual bool trap(InterruptState &state, uintptr_t address, bool bIsWrite, bool bWasPresent)=0
EXPORTED_PUBLIC bool unregisterHandler(MemoryTrapHandler *pHandler)
EXPORTED_PUBLIC bool registerHandler(MemoryTrapHandler *pHandler)
static PageFaultHandler & instance()
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
VirtualAddressSpace * getAddressSpace()
Definition Process.h:478
static bool getInterrupts()
static ProcessorInformation & information()
static void switchAddressSpace(VirtualAddressSpace &AddressSpace)
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool joinForCompletion()
Definition Thread.cc:2771
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
bool start()
Definition Thread.cc:794
virtual void setFlags(void *virtualAddress, size_t newFlags)=0
virtual bool map(physical_uintptr_t physicalAddress, void *virtualAddress, size_t flags)=0
virtual bool isMapped(void *virtualAddress)=0
virtual bool handleCopyOnWriteFault(void *virtualAddress, bool userMode)=0
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
virtual void unmap(void *virtualAddress)=0