Skip to content

Commit f0367e6

Browse files
committed
Add main VM CoW-mode test, with memory limits
1 parent fa54dd9 commit f0367e6

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

lib/tinykvm/threads.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ void Thread::exit()
6666
if (this->clear_tid) {
6767
THPRINT("Clearing thread value for tid=%d at 0x%lX\n",
6868
this->tid, this->clear_tid);
69-
*(uint32_t*) mt.machine.rw_memory_at(this->clear_tid, 4) = 0;
69+
// clear the thread id in the parent
70+
const uint32_t value = 0;
71+
mt.machine.copy_to_guest(this->clear_tid, &value, sizeof(value));
7072
}
7173
auto& thr = this->mt;
7274
// delete this thread
@@ -130,11 +132,13 @@ Thread& MultiThreading::create(
130132
}
131133
if (flags & CLONE_CHILD_SETTID) {
132134
THPRINT("CHILD_SETTID at 0x%lX\n", ctid);
133-
*(uint32_t*) machine.rw_memory_at(ctid, 4) = tid;
135+
// set the thread id in the child
136+
machine.copy_to_guest(ctid, &tid, sizeof(tid));
134137
}
135138
if (flags & CLONE_PARENT_SETTID) {
136139
THPRINT("PARENT_SETTID at 0x%lX\n", ptid);
137-
*(uint32_t*) machine.rw_memory_at(ptid, 4) = tid;
140+
// set the thread id in the parent
141+
machine.copy_to_guest(ptid, &tid, sizeof(tid));
138142
}
139143
if (flags & CLONE_CHILD_CLEARTID) {
140144
THPRINT("CHILD_CLEARTID at 0x%lX\n", ctid);
@@ -304,11 +308,12 @@ void Machine::setup_multithreading()
304308
const auto futex_op = regs.rsi;
305309
const uint32_t val = regs.rdx;
306310
THPRINT("Futex on: 0x%llX val=%d\n", regs.rdi, val);
307-
auto* fx = cpu.machine().template rw_memory_at<uint32_t>(addr, 4);
308311

309312
if ((futex_op & 0xF) == FUTEX_WAIT || (futex_op & 0xF) == FUTEX_WAIT_BITSET) {
310-
THPRINT("FUTEX: Waiting for unlock... uaddr=%u val=%u\n", *fx, val);
311-
if (*fx == val) {
313+
uint32_t futexVal;
314+
cpu.machine().copy_from_guest(&futexVal, addr, sizeof(futexVal));
315+
THPRINT("FUTEX: Waiting for unlock... uaddr=%u val=%u\n", futexVal, val);
316+
if (futexVal == val) {
312317
if (cpu.machine().threads().suspend_and_yield()) {
313318
return;
314319
}

tests/unit/fork.cpp

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,93 @@ extern void crash() {
359359
}());
360360
}
361361
}
362+
363+
TEST_CASE("Fork and run main()", "[Fork]")
364+
{
365+
const auto binary = build_and_load(R"M(
366+
int main() {
367+
return 666;
368+
}
369+
static unsigned value = 12345;
370+
void set_value(int v) {
371+
value = v;
372+
}
373+
int func1() {
374+
return value;
375+
}
376+
int func2() {
377+
return 54321;
378+
}
379+
)M");
380+
381+
tinykvm::Machine machine { binary, { .max_mem = MAX_MEMORY } };
382+
383+
// We need to create a Linux environment for runtimes to work well
384+
machine.setup_linux({"fork"}, env);
385+
REQUIRE(machine.banked_memory_pages() == 0);
386+
387+
// Make machine forkable (with *NO* working memory)
388+
machine.prepare_copy_on_write(4ULL << 20);
389+
REQUIRE(machine.is_forkable());
390+
REQUIRE(!machine.is_forked());
391+
REQUIRE(machine.return_value() == 0); // Initial register value
392+
393+
// Run for at most 4 seconds before giving up
394+
machine.run(4.0f);
395+
REQUIRE(machine.return_value() == 666); // Main() return value
396+
397+
// We only gave it 4MB working memory, so lets mmap allocate that and verify
398+
// that if we write more than that, we get an exception thrown
399+
REQUIRE_THROWS([&] () {
400+
const size_t size = 5ULL << 20;
401+
uint64_t addr = machine.mmap_allocate(5ULL << 20);
402+
char buffer[4096];
403+
for (int i = 0; i < 4096; i++)
404+
buffer[i] = 'a';
405+
for (size_t i = 0; i < size; i += 4096)
406+
{
407+
machine.copy_to_guest(addr + i, buffer, 4096);
408+
}
409+
// Unreachable
410+
abort();
411+
}());
412+
413+
// There are banked pages now
414+
const auto banked_pages_before = machine.banked_memory_pages();
415+
REQUIRE(banked_pages_before > 500);
416+
417+
// Create fork
418+
auto fork1 = tinykvm::Machine { machine, {
419+
.max_mem = MAX_MEMORY, .max_cow_mem = MAX_COWMEM
420+
} };
421+
REQUIRE(fork1.return_value() == 666); // Main() return value
422+
423+
fork1.vmcall("func1");
424+
REQUIRE(fork1.return_value() == 12345);
425+
426+
fork1.vmcall("func2");
427+
REQUIRE(fork1.return_value() == 54321);
428+
429+
// This is problematic, but we will try to fix this later
430+
// Forked VM is supposed to diverge from the main VM, regardless of mode
431+
machine.vmcall("set_value", 99999);
432+
fork1.vmcall("func1");
433+
REQUIRE(fork1.return_value() == 99999);
434+
435+
REQUIRE(machine.banked_memory_pages() == banked_pages_before);
436+
REQUIRE(fork1.banked_memory_pages() > 0);
437+
438+
for (int i = 0; i < 20; i++)
439+
{
440+
fork1.reset_to(machine, {
441+
.max_mem = MAX_MEMORY,
442+
.max_cow_mem = MAX_COWMEM,
443+
});
444+
445+
fork1.vmcall("func1");
446+
REQUIRE(fork1.return_value() == 99999);
447+
448+
fork1.vmcall("func2");
449+
REQUIRE(fork1.return_value() == 54321);
450+
}
451+
}

0 commit comments

Comments
 (0)