2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/errors.h"
4#include "pedigree/kernel/process/Scheduler.h"
5#include "pedigree/kernel/process/Thread.h"
6#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
7#include "pedigree/kernel/processor/Processor.h"
8#include "pedigree/kernel/processor/VirtualAddressSpace.h"
9#include "pedigree/kernel/utilities/Pointers.h"
10#include "pedigree/kernel/utilities/utility.h"
12#include "PosixProcess.h"
13#include "PosixSubsystem.h"
15#include "process-vm-syscalls.h"
19constexpr auto ReadWrite = MemoryMappedObject::Read | MemoryMappedObject::Write;
20bool check(
bool condition,
const char* detail) {
22 ERROR(
"PROCESS-VM-REGRESSION: FAIL " << detail);
25bool fillPage(uintptr_t address, uint8_t* scratch,
size_t page, uint8_t value) {
26 ByteSet(scratch, value, page);
29bool pageEquals(uintptr_t address, uint8_t* scratch,
size_t page, uint8_t value) {
31 ERROR(
"PROCESS-VM-REGRESSION: page read failed address=" <<
Hex << address);
34 for (
size_t n = 0; n < page; ++n) {
35 if (scratch[n] != value) {
36 ERROR(
"PROCESS-VM-REGRESSION: page mismatch address="
37 <<
Hex << address <<
" byte=" <<
Dec << n <<
" expected="
38 <<
static_cast<unsigned>(value) <<
" actual=" <<
static_cast<unsigned>(scratch[n]));
45 iovec local, remote[2];
50 uintptr_t replacement;
52 size_t page, calls = 0;
55void replaceAfterFragment(
size_t copied,
void* parameter) {
56 auto& cutoff = *
static_cast<Cutoff*
>(parameter);
58 setProcessVmAfterFragmentHookForTest(
nullptr,
nullptr,
nullptr);
59 if (!check(copied == cutoff.page,
"hook did not follow exactly one committed page"))
63 cutoff.subsystem.invalidateUserImage();
64 uintptr_t replacement = cutoff.replacement;
65 if (!check(manager.mapAnon(replacement, cutoff.page, ReadWrite,
66 MemoryMapManager::Placement::FixedReplace,
nullptr) &&
67 replacement == cutoff.replacement &&
68 fillPage(replacement, cutoff.scratch, cutoff.page, 0x52),
69 "replacement page publication"))
72 check(cutoff.subsystem.publishUserImage(cutoff.space),
"replacement image publication");
75bool generationCutoff(
bool write) {
77 auto* process =
static_cast<PosixProcess*
>(thread->getParent());
78 auto& subsystem = *
static_cast<PosixSubsystem*
>(process->getSubsystem());
83 if (!check(
static_cast<bool>(scratch),
"scratch allocation"))
85 uintptr_t local = 0, remote[2] = {}, metadata = 0;
89 const bool mapped = manager.mapAnon(local, 2 * page, ReadWrite) &&
90 manager.mapAnon(remote[0], page, ReadWrite) &&
91 manager.mapAnon(remote[1], page, ReadWrite) &&
92 manager.mapAnon(metadata, page, ReadWrite);
93 if (!check(mapped,
"owned user vector and independent payload mappings")) {
98 auto* vectors =
reinterpret_cast<UserVectors*
>(metadata);
99 const uint8_t localValue = write ? 0x61 : 0x70;
100 const UserVectors initial{
101 {
reinterpret_cast<void*
>(local), 2 * page},
102 {{
reinterpret_cast<void*
>(remote[0]), page}, {
reinterpret_cast<void*
>(remote[1]), page}}};
104 if (!check(fillPage(local, scratch.get(), page, localValue) &&
105 fillPage(local + page, scratch.get(), page, localValue) &&
106 fillPage(remote[0], scratch.get(), page, 0x41) &&
107 fillPage(remote[1], scratch.get(), page, 0x41) &&
109 subsystem.publishUserImage(space),
110 "owned image setup"))
113 Cutoff cutoff{subsystem, space, remote[1], scratch.get(), page};
114 setProcessVmAfterFragmentHookForTest(thread, replaceAfterFragment, &cutoff);
115 thread->setErrno(Error::IoError);
116 const ssize_t result = write
117 ? posix_process_vm_writev(process->
getUserspaceId(), &vectors->local,
118 1, vectors->remote, 2, 0)
119 : posix_process_vm_readv(process->getUserspaceId(), &vectors->local,
120 1, vectors->remote, 2, 0);
121 const size_t error = thread->getErrno();
122 setProcessVmAfterFragmentHookForTest(
nullptr,
nullptr,
nullptr);
123 if (!check(result ==
static_cast<ssize_t
>(page) && !error && cutoff.calls == 1 &&
125 "generation change did not stop actual transfer at its committed prefix"))
127 if (!check(pageEquals(local, scratch.get(), page, write ? 0x61 : 0x41) &&
128 pageEquals(local + page, scratch.get(), page, localValue) &&
129 pageEquals(remote[0], scratch.get(), page, write ? 0x61 : 0x41) &&
130 pageEquals(remote[1], scratch.get(), page, 0x52),
131 "old transfer touched replacement or uncommitted local suffix"))
134 const UserVectors fresh{{
reinterpret_cast<void*
>(local + page), page},
135 {{
reinterpret_cast<void*
>(remote[1]), page}, {
nullptr, 0}}};
137 "fresh user vector import setup"))
139 const ssize_t freshResult =
140 write ? posix_process_vm_writev(process->
getUserspaceId(), &vectors->local, 1,
141 vectors->remote, 1, 0)
142 : posix_process_vm_readv(process->getUserspaceId(), &vectors->local, 1,
143 vectors->remote, 1, 0);
144 const size_t freshError = thread->getErrno();
147 pageEquals(write ? remote[1] : local + page, scratch.get(), page, write ? 0x61 : 0x52);
148 const bool freshPassed =
149 freshResult ==
static_cast<ssize_t
>(page) && !freshError && content && sameSpace;
151 ERROR(
"PROCESS-VM-REGRESSION: fresh-" << (write ?
"write" :
"read") <<
" result=" <<
Dec
152 << freshResult <<
" errno=" << freshError
153 <<
" expected=" << page <<
" same-space=" << sameSpace);
154 return check(freshPassed,
"fresh syscall could not transfer through the replacement image");
156 setProcessVmAfterFragmentHookForTest(
nullptr,
nullptr,
nullptr);
159 subsystem.invalidateUserImage();
163 NOTICE(
"PROCESS-VM-REGRESSION: PASS generation-" << (write ?
"write" :
"read"));
166int regressionWorker(
void* parameter) {
167 bool& passed = *
static_cast<bool*
>(parameter);
168 passed = generationCutoff(
false) && generationCutoff(
true);
173EXPORTED_PUBLIC
bool processVmRegression() {
174 NOTICE(
"PROCESS-VM-REGRESSION: BEGIN");
177 return check(
false,
"isolated process allocation");
181 return check(
false,
"isolated subsystem allocation");
183 process->setSubsystem(subsystem);
184 subsystem->setAbi(PosixSubsystem::LinuxAbi);
186 Thread*
worker =
new Thread(process, regressionWorker, &passed,
nullptr,
false,
true,
true);
189 return check(
false,
"isolated worker allocation");
192 const bool started =
worker->start();
195 const bool joined =
worker->joinForCompletion();
197 FATAL(
"PROCESS-VM-REGRESSION: worker could not be joined safely");
199 passed = check(started && joined && passed,
"isolated transfer worker") && passed;
201 NOTICE(
"PROCESS-VM-REGRESSION: END PASS");
Memory-mapped file interface.
static MemoryMapManager & instance()
static constexpr size_t getPageSize() PURE
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
size_t getUserspaceId() const
VirtualAddressSpace * getAddressSpace()
static ProcessorInformation & information()
static Scheduler & instance()
@ TerminateThread
Exit only this thread during Process exit.