Skip to content

Commit 77533c5

Browse files
committed
Fix issues with fork initial state and dirty bits
1 parent b517667 commit 77533c5

5 files changed

Lines changed: 96 additions & 29 deletions

File tree

lib/tinykvm/amd64/paging.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ uint64_t setup_amd64_paging(vMemory& memory,
305305
const uint64_t addr2m = (base_giga_page << 30) | (pdidx << 21);
306306
ptentry = PDE64_PRESENT | PDE64_USER | PDE64_NX | PDE64_PS | addr2m;
307307
if (!read) ptentry &= ~PDE64_PRESENT; // A weird one, but... AMD64.
308-
if (write) ptentry |= PDE64_RW;
308+
if (write) ptentry |= PDE64_RW | PDE64_DIRTY;
309309
else ptentry |= PDE64_G; // Global bit for read-only pages
310310
if (exec) ptentry &= ~PDE64_NX;
311311
// Increment whole 2MB page
@@ -349,6 +349,8 @@ uint64_t setup_amd64_paging(vMemory& memory,
349349
if (!write) {
350350
ptentry &= ~PDE64_RW;
351351
ptentry |= PDE64_G; // Global bit for read-only pages
352+
} else {
353+
ptentry |= PDE64_DIRTY;
352354
}
353355
addr += 0x1000;
354356
}

lib/tinykvm/common.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ namespace tinykvm
106106
to the given file. Depending on `snapshot_mode`,
107107
the file may be created if it does not exist,
108108
and must be of the correct size if it does exist. */
109-
std::string snapshot_file;
109+
std::string snapshot_file {};
110110
enum SnapshotMode {
111111
Disabled = 0,
112112
Open = 1,

lib/tinykvm/machine_utils.cpp

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,27 @@ void Machine::memzero(address_t addr, size_t len)
4141

4242
void Machine::copy_to_guest(address_t addr, const void* vsrc, size_t len, bool zeroes)
4343
{
44-
if (uses_cow_memory() || !memory.safely_within(addr, len))
44+
auto* src = (const uint8_t *)vsrc;
45+
while (len != 0)
4546
{
46-
auto* src = (const uint8_t *)vsrc;
47-
while (len != 0)
48-
{
49-
const size_t offset = addr & PageMask();
50-
const size_t size = std::min(vMemory::PageSize() - offset, len);
51-
const bool full_page = (size == vMemory::PageSize());
52-
WritablePageOptions opts;
53-
opts.allow_dirty = full_page;
54-
opts.zeroes = zeroes;
55-
// Get a writable page, possibly allocating a new one
56-
WritablePage page = writable_page_at(memory, addr & ~PageMask(), memory.expectedUsermodeFlags(), opts);
57-
// Page is always dirty
58-
page.set_dirty();
59-
// Copy data to the page
60-
char* page_data = page.page;
61-
std::memcpy(&page_data[offset], src, size);
47+
const size_t offset = addr & PageMask();
48+
const size_t size = std::min(vMemory::PageSize() - offset, len);
49+
const bool full_page = (size == vMemory::PageSize());
50+
WritablePageOptions opts;
51+
opts.allow_dirty = full_page;
52+
opts.zeroes = zeroes;
53+
// Get a writable page, possibly allocating a new one
54+
WritablePage page = writable_page_at(memory, addr & ~PageMask(), memory.expectedUsermodeFlags(), opts);
55+
// Page is always dirty
56+
page.set_dirty();
57+
// Copy data to the page
58+
char* page_data = page.page;
59+
std::memcpy(&page_data[offset], src, size);
6260

63-
addr += size;
64-
src += size;
65-
len -= size;
66-
}
67-
return;
61+
addr += size;
62+
src += size;
63+
len -= size;
6864
}
69-
/* Original VM uses identity-mapped memory */
70-
auto* dst = memory.safely_at(addr, len);
71-
std::memcpy(dst, vsrc, len);
7265
}
7366

7467
void Machine::copy_from_guest(void* vdst, address_t addr, size_t len) const

lib/tinykvm/memory_bank.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ void MemoryBanks::reset(const MachineOptions& options)
156156
//size_t limit_pages = options.reset_free_work_mem / vMemory::PageSize();
157157

158158
/* Instead of removing the banks, give memory back to kernel */
159-
for (size_t i = 1u; i < m_mem.size(); i++) {
159+
for (size_t i = 0u; i < m_mem.size(); i++) {
160160
/* WARNING: MADV_FREE *does not* immediately free, so use MADV_DONTNEED instead. */
161161
if (m_mem[i].dirty_size() > 0)
162162
madvise(m_mem[i].mem, m_mem[i].dirty_size(), MADV_DONTNEED);

tests/unit/fork.cpp

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
#include <catch2/matchers/catch_matchers_string.hpp>
33

44
#include <tinykvm/machine.hpp>
5+
#include <linux/kvm.h>
56
extern std::vector<uint8_t> build_and_load(const std::string& code);
67
static const uint64_t MAX_MEMORY = 8ul << 20; /* 8MB */
7-
static const uint64_t MAX_COWMEM = 3ul << 20; /* 1MB */
8+
static const uint64_t MAX_COWMEM = 3ul << 20; /* 3MB */
89
static const std::vector<std::string> env {
910
"LC_TYPE=C", "LC_ALL=C", "USER=root"
1011
};
@@ -566,3 +567,74 @@ int func2() {
566567
REQUIRE(fork2.return_value() == 22222);
567568
}
568569
}
570+
571+
TEST_CASE("Fork before main()", "[Fork]")
572+
{
573+
const auto binary = build_and_load(R"M(
574+
#include <stdio.h>
575+
extern void _exit(int);
576+
int main() {
577+
printf("Hello World!\n");
578+
_exit(666);
579+
return 666;
580+
}
581+
static unsigned value = 12345;
582+
void set_value(int v) {
583+
value = v;
584+
}
585+
int func1() {
586+
return value;
587+
}
588+
int func2() {
589+
return 54321;
590+
}
591+
)M");
592+
593+
tinykvm::Machine machine1 { binary, {
594+
.max_mem = 10ull << 20, // We need 10mb because of fragmentation
595+
} };
596+
machine1.setup_linux({"fork"}, env);
597+
machine1.prepare_copy_on_write();
598+
REQUIRE(machine1.is_forkable());
599+
REQUIRE(!machine1.is_forked());
600+
601+
tinykvm::Machine machine2 { binary, {
602+
.max_mem = 10ull << 20,
603+
} };
604+
machine2.prepare_copy_on_write(); // No Linux setup
605+
REQUIRE(machine2.is_forkable());
606+
REQUIRE(!machine2.is_forked());
607+
608+
auto fork1 = tinykvm::Machine { machine1, {
609+
.max_cow_mem = MAX_COWMEM,
610+
.split_hugepages = true
611+
} };
612+
613+
for (int i = 0; i < 100; i++)
614+
{
615+
fork1.run(4.0f);
616+
REQUIRE(fork1.return_value() == 666);
617+
618+
fork1.reset_to(machine1, {
619+
.max_cow_mem = 4ul << 20,
620+
.split_hugepages = true
621+
});
622+
}
623+
624+
auto fork2 = tinykvm::Machine { machine2, {
625+
.max_cow_mem = MAX_COWMEM,
626+
.split_hugepages = true
627+
} };
628+
629+
for (int i = 0; i < 100; i++)
630+
{
631+
fork2.setup_linux({"fork"}, env);
632+
fork2.run(4.0f);
633+
REQUIRE(fork2.return_value() == 666);
634+
635+
fork2.reset_to(machine2, {
636+
.max_cow_mem = 4ul << 20,
637+
.split_hugepages = true
638+
});
639+
}
640+
}

0 commit comments

Comments
 (0)