2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/errors.h"
4#include "pedigree/kernel/process/Process.h"
5#include "pedigree/kernel/process/Scheduler.h"
6#include "pedigree/kernel/process/Semaphore.h"
7#include "pedigree/kernel/process/Thread.h"
8#include "pedigree/kernel/processor/MemoryRegion.h"
9#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
10#include "pedigree/kernel/processor/Processor.h"
11#include "pedigree/kernel/processor/VirtualAddressSpace.h"
12#include "pedigree/kernel/utilities/utility.h"
14#include "modules/system/vfs/File.h"
18using Status = MemoryMapManager::UserPageCopyStatus;
19using Resident = VirtualAddressSpace::ResidentCopyStatus;
20constexpr auto ReadWrite = MemoryMappedObject::Read | MemoryMappedObject::Write;
21bool check(
bool condition,
const char* detail) {
23 ERROR(
"PROCESS-MEMORY-BACKEND: FAIL " << detail);
26bool filled(
const uint8_t* bytes,
size_t count, uint8_t value) {
27 for (
size_t i = 0; i < count; ++i)
28 if (bytes[i] != value)
33class ProbeFile final :
public File {
37 :
File(
String(
"process-memory-probe"), 0, 0, 0, 1, nullptr,
39 storage(
"Process Memory Probe") {
42 ~ProbeFile()
override {
43 shutdownFillCacheWriteback();
55 bool isDirectPhysicalMapping()
const override {
65 bool sync(
size_t offset,
bool async)
override {
70 return __atomic_load_n(&physicalPageLoans(), __ATOMIC_ACQUIRE);
72 bool backingEquals(
size_t offset, uint8_t value)
const {
73 return static_cast<const uint8_t*
>(storage.virtualAddress())[offset] == value;
75 bool failRead =
false, noMemory =
false, direct =
false;
78 uintptr_t
readBlock(uint64_t offset)
override {
79 if (failRead || offset >= getSize()) {
81 return FILE_BAD_BLOCK;
83 return reinterpret_cast<uintptr_t
>(storage.virtualAddress()) + offset;
85 bool pinBlock(uint64_t offset)
override {
86 return !failRead && offset < getSize();
89 void writeBlocks(uint64_t offset, uintptr_t source,
size_t length)
override {
90 if (offset >= getSize())
92 const size_t remaining = getSize() - offset;
93 MemoryCopy(
static_cast<uint8_t*
>(storage.virtualAddress()) + offset,
94 reinterpret_cast<const void*
>(source), length < remaining ? length : remaining);
104 ProbeFile* file =
nullptr;
105 ProbeFile* failure =
nullptr;
106 uintptr_t anonymous = 0, privateFile = 0, sharedFile = 0, readonly = 0, execute = 0;
107 uintptr_t inaccessible = 0, failedFile = 0, raw = 0;
108 bool setup =
false, cleaned =
false;
111int ownerWorker(
void* parameter) {
112 auto& context = *
static_cast<Context*
>(parameter);
116 ProbeFile file, failure;
118 context.space = &space;
119 context.file = &file;
120 context.failure = &failure;
123 manager.bindMemoryLockPolicy(space);
124 context.setup = file.initialise() && failure.initialise() &&
125 manager.mapAnon(context.anonymous, page, ReadWrite) &&
126 manager.mapAnon(context.readonly, page, MemoryMappedObject::Read) &&
127 manager.mapAnon(context.execute, page, MemoryMappedObject::Exec) &&
128 manager.mapAnon(context.inaccessible, page, MemoryMappedObject::None) &&
129 manager.mapFile(&file, context.privateFile, page, ReadWrite, 0,
true) &&
130 manager.mapFile(&file, context.sharedFile, 4 * page, ReadWrite, 0,
false) &&
131 manager.mapFile(&failure, context.failedFile, page, ReadWrite, 0,
false);
133 raw = space.allocateStack(3 * page);
134 context.setup = raw && raw->regionId();
136 context.raw =
reinterpret_cast<uintptr_t
>(raw->getBase());
139 context.ready.release();
140 if (!context.release.acquire(1, 30))
141 FATAL(
"PROCESS-MEMORY-BACKEND: owner release timed out");
146 space.freeStack(raw);
147 context.cleaned = check(!file.loans() && !failure.loans(),
"final file loan retirement");
148 space.rawUserMemory().clear();
149 space.setUserMemoryPolicy(
nullptr);
154bool managedCopies(Context& context) {
156 auto& space = *context.space;
160 auto copy = [&](uintptr_t address,
bool write) {
161 return manager.copyUserPage(space, address, bytes,
sizeof(bytes), write);
163 if (!check(!space.isMapped(
reinterpret_cast<void*
>(context.anonymous)) &&
164 !space.isMapped(
reinterpret_cast<void*
>(context.privateFile)),
165 "initial lazy mappings"))
167 ByteSet(bytes, 0x71,
sizeof(bytes));
168 if (!check(copy(context.anonymous + 19,
false) == Status::Success && filled(bytes, 64, 0),
169 "remote anonymous zero read"))
171 ByteSet(bytes, 0x71,
sizeof(bytes));
172 if (!check(copy(context.anonymous + 19,
true) == Status::Success,
173 "remote anonymous write preparation"))
175 physical_uintptr_t physical = 0;
177 space.getMapping(
reinterpret_cast<void*
>(context.anonymous), physical, flags);
181 copy(context.anonymous + 19,
false) == Status::Success && filled(bytes, 64, 0x71),
182 "anonymous bytes and latest PTE dirty state"))
185 if (!check(copy(context.privateFile + 19,
false) == Status::Success && filled(bytes, 64, 0x49),
186 "lazy private file read"))
188 physical_uintptr_t borrowed = 0;
189 space.getMapping(
reinterpret_cast<void*
>(context.privateFile), borrowed, flags);
191 context.file->loans() == 1,
192 "read prematurely resolved private ownership"))
194 ByteSet(bytes, 0x72,
sizeof(bytes));
195 if (!check(copy(context.privateFile + 19,
true) == Status::Success,
196 "private file write preparation"))
198 space.getMapping(
reinterpret_cast<void*
>(context.privateFile), physical, flags);
201 context.file->backingEquals(19, 0x49),
202 "private file write changed backing or retained its loan"))
205 ByteSet(bytes, 0x73,
sizeof(bytes));
206 if (!check(copy(context.sharedFile + 19,
true) == Status::Success && context.file->loans() == 1 &&
207 context.file->sync(0,
false) && context.file->backingEquals(19, 0x73),
208 "shared file writeback did not use its original loan"))
210 ByteSet(bytes, 0x7a,
sizeof(bytes));
211 if (!check(copy(context.sharedFile + 2 * page + 200,
false) == Status::Success &&
212 filled(bytes, 64, 0),
213 "partial EOF page zero tail"))
215 ByteSet(bytes, 0x7a,
sizeof(bytes));
217 copy(context.sharedFile + 3 * page,
false) == Status::Inaccessible &&
218 copy(context.readonly,
true) == Status::Inaccessible &&
219 copy(context.execute,
false) == Status::Inaccessible &&
220 copy(context.inaccessible,
false) == Status::Inaccessible &&
221 copy(context.anonymous + page - 32,
false) == Status::Inaccessible &&
222 manager.copyUserPage(space, context.anonymous, bytes, 0,
false) == Status::Inaccessible &&
223 filled(bytes, 64, 0x7a) && !space.isMapped(
reinterpret_cast<void*
>(context.readonly)) &&
224 !space.isMapped(
reinterpret_cast<void*
>(context.execute)),
225 "denied fragments changed bytes or populated a denied page");
228bool failedPreparation(Context& context) {
230 auto& space = *context.space;
231 auto& file = *context.failure;
233 ByteSet(bytes, 0x7b,
sizeof(bytes));
236 const size_t previous = thread->getErrno();
237 thread->setErrno(Error::BadFileDescriptor);
238 file.noMemory =
true;
240 check(manager.copyUserPage(space, context.failedFile, bytes, 16,
true) == Status::NoMemory &&
241 thread->getErrno() == Error::BadFileDescriptor &&
242 !space.isMapped(
reinterpret_cast<void*
>(context.failedFile)) && !file.loans() &&
243 filled(bytes, 16, 0x7b),
244 "backing allocation rejection changed page, bytes or caller errno");
245 file.noMemory =
false;
246 file.failRead =
true;
248 check(manager.copyUserPage(space, context.failedFile, bytes, 16,
false) == Status::IoError &&
249 thread->getErrno() == Error::BadFileDescriptor &&
250 !space.isMapped(
reinterpret_cast<void*
>(context.failedFile)) && !file.loans() &&
251 filled(bytes, 16, 0x7b),
252 "backing I/O failure changed page, bytes or caller errno");
253 file.failRead =
false;
256 manager.copyUserPage(space, context.failedFile, bytes, 16,
false) == Status::Unsupported &&
257 !file.loans() && filled(bytes, 16, 0x7b),
258 "direct physical backing admitted");
261 check(manager.copyUserPage(space, context.failedFile, bytes, 16,
false) == Status::Success &&
262 filled(bytes, 16, 0x49),
263 "backing did not recover after preparation rejection");
264 thread->setErrno(previous);
268bool rawCopies(Context& context) {
270 auto& space = *context.space;
272 const size_t page = memory.getPageSize();
273 const uintptr_t base = context.raw;
275 ByteSet(bytes, 0x51,
sizeof(bytes));
277 auto& raw = space.rawUserMemory();
278 if (!check(raw.covers(base + 1, 3 * page - 2) && !raw.covers(base - 1, 2) &&
279 !raw.covers(base, 0) && !raw.covers(~uintptr_t(0) - 1, 4) &&
280 manager.copyUserPage(space, base, bytes, 16,
true) == Status::Success,
281 "raw coverage and initial bytes"))
284 if (!check(raw.prepareReplacement(base + 2 * page, page, hole) == MemoryLockStatus::Success,
285 "raw hole preparation"))
287 hole.get()->commit();
288 if (!check(raw.covers(base, 2 * page) && !raw.covers(base, 3 * page) &&
289 !raw.covers(base + 2 * page, 1) &&
290 manager.copyUserPage(space, base + 2 * page, bytes, 16,
false) ==
291 Status::Inaccessible,
292 "raw replacement hole was treated as continuous heap"))
295 physical_uintptr_t original = 0, retired = 0;
296 size_t flags = 0, retiredFlags = 0;
297 space.getMapping(
reinterpret_cast<void*
>(base), original, flags);
298 if (!check(space.detachMapping(
reinterpret_cast<void*
>(base + page), retired, retiredFlags),
299 "CoW alias destination retirement"))
301 memory.freePage(retired);
304 memory.pin(original);
305 memory.pin(original);
306 if (!space.map(original,
reinterpret_cast<void*
>(base + page),
308 memory.freePage(original);
309 return check(
false,
"CoW alias publication");
311 space.setFlags(
reinterpret_cast<void*
>(base),
313 if (!check(space.copyResidentUserPage(base, bytes, 16,
true) == Resident::Inaccessible,
314 "resident primitive wrote an unresolved CoW page"))
316 ByteSet(bytes, 0x52,
sizeof(bytes));
317 if (!check(manager.copyUserPage(space, base, bytes, 16,
true) == Status::Success,
318 "raw CoW preparation"))
320 physical_uintptr_t replacement = 0;
321 space.getMapping(
reinterpret_cast<void*
>(base), replacement, flags);
324 manager.copyUserPage(space, base + page, bytes, 16,
false) == Status::Success &&
325 filled(bytes, 16, 0x51),
326 "CoW replacement changed the retained alias"))
329 ByteSet(bytes, 0x53,
sizeof(bytes));
330 if (!check(space.copyResidentUserPage(base, bytes, 16,
true) == Resident::Inaccessible &&
331 manager.copyUserPage(space, base, bytes, 16,
true) == Status::Inaccessible &&
332 manager.copyUserPage(space, base, bytes, 16,
false) == Status::Success &&
333 filled(bytes, 16, 0x52),
334 "write protection changed resident bytes"))
337 const auto runtime = memory.allocatePage();
338 if (!check(runtime != 0,
"runtime page allocation"))
340 void* runtimeAddress =
reinterpret_cast<void*
>(base + 2 * page);
341 if (!space.map(runtime, runtimeAddress,
343 memory.freePage(runtime);
344 return check(
false,
"runtime page publication");
347 check(manager.copyUserPage(space, base + 2 * page, bytes, 16,
true) == Status::Success,
348 "classified runtime user RAM rejected");
351 manager.copyUserPage(space, base + 2 * page, bytes, 16,
false) == Status::Inaccessible &&
352 space.copyResidentUserPage(base + 2 * page, bytes, 16,
false) == Resident::Success,
353 "unclassified resident user RAM was admitted by the manager");
354 if (!space.detachMapping(runtimeAddress, retired, retiredFlags))
355 FATAL(
"PROCESS-MEMORY-BACKEND: runtime page retirement failed");
356 memory.freePage(retired);
361EXPORTED_PUBLIC
bool processMemoryBackendRegression() {
362 NOTICE(
"PROCESS-MEMORY-BACKEND: BEGIN");
365 if (!check(process !=
nullptr,
"owner process allocation"))
368 Thread*
worker =
new Thread(process, ownerWorker, &context,
nullptr,
false,
true,
true);
370 const bool ready = started && context.ready.acquire(1, 10);
371 bool passed = check(ready && context.setup && context.space != callerSpace,
"owner setup");
373 passed = managedCopies(context) && failedPreparation(context) && rawCopies(context);
375 "remote operation switched the caller's address space");
376 context.release.release();
379 const bool joined =
worker &&
worker->joinForCompletion();
381 FATAL(
"PROCESS-MEMORY-BACKEND: owner could not be joined safely");
383 passed = check(joined && context.cleaned,
"owner teardown") && passed;
385 NOTICE(
"PROCESS-MEMORY-BACKEND: END PASS");
Memory-mapped file interface.
virtual uintptr_t readBlock(uint64_t location)
virtual void unpinBlock(uint64_t location)
virtual void writeBlocks(uint64_t location, uintptr_t addr, size_t length)
bool syncFillCache(size_t offset, bool async, bool &present)
void enableFillCacheWriteback()
virtual bool prepareSharedMapping(size_t offset, size_t length)
virtual size_t getBlockSize() const
virtual MUST_USE_RESULT bool pinBlock(uint64_t location)
static MemoryMapManager & instance()
Special memory entity in the kernel's virtual address space.
static PhysicalMemoryManager & instance()
static constexpr size_t getPageSize() PURE
static ProcessorInformation & information()
static Scheduler & instance()
@ TerminateThread
Exit only this thread during Process exit.
static const size_t CopyOnWrite
static const size_t Borrowed
static const size_t Accessed
static const size_t RuntimeMapping
static const size_t Shared
static const size_t KernelMode
static const size_t Write
static const size_t WriteProtected
static const size_t Dirty