diff --git a/vmm/vmm_main.c b/vmm/vmm_main.c new file mode 100644 index 0000000000000000000000000000000000000000..e62c8006165947524dea77bdb66227d41bfc119e --- /dev/null +++ b/vmm/vmm_main.c @@ -0,0 +1,165 @@ +#include <err.h> +#include <stdbool.h> +#include <fcntl.h> +#include <linux/kvm.h> +#include <stdint.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +// -- DEFINE -- + +#define KVM_API_VERSION 12 +#define RAM_SIZE (512 * 1024) + +// -- MAIN -- + +int main(int argc, char* argv[]) +{ + int kvmfd, vmfd, vcpufd; + struct kvm_sregs sregs; + struct kvm_run *run; + size_t mmap_size; + uint8_t *mem; + + // -- 0. Handle Args -- + char *file_path = argv[1]; + + // -- 1. Create a KVM device -- + kvmfd = open("/dev/kvm", O_RDWR | O_CLOEXEC); // get file descriptor + if (kvmfd == -1) err(1, "/dev/kvm"); + + // check stable version of the API + int version = ioctl(kvmfd, KVM_GET_API_VERSION, NULL); + if (version == -1) err(1, "KVM_GET_API_VERSION"); + if (version != KVM_API_VERSION) errx(1, "KVM_GET_API_VERSION %d, expected 12", version); + + // -- 2. Create a VM -- + vmfd = ioctl(kvmfd, KVM_CREATE_VM, (unsigned long)0); + if (vmfd == -1) err(1, "KVM_CREATE_VM"); + + // -- 3. Allocate RAM for the VM -- + mem = mmap(NULL, RAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // memory to hold the code + if (!mem) err(1, "allocating guest memory"); + + // -- 4. Map allocated RAM into the VM’s address space -- + struct kvm_userspace_memory_region region = { + .slot = 0, + .guest_phys_addr = 0, + .memory_size = RAM_SIZE, + .userspace_addr = (uint64_t)mem, + .flags = 0, + }; + + int memreg_err = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); + if (memreg_err == -1) err(1, "KVM_SET_USER_MEMORY_REGION"); + + // -- 5. Load guest OS into VM’s RAM -- + // Open the binary file + int binfd = open(file_path, O_RDONLY); + if (binfd < 0) err(1, "Error opening guestOS binary file"); + + // Get the file size using fstat + struct stat st; + if (fstat(binfd, &st) < 0) perror("Error getting file size"); + + // Check if the file size exceeds the available memory + if (st.st_size > RAM_SIZE) err(1, "Error: GuestOS binary size exceeds 512KB\n"); + + // Read the file into the provided memory (mem) + ssize_t bytes_read = read(binfd, mem, st.st_size); + if (bytes_read < 0) err(1, "Error reading guestOS binary file"); + + // Check if all bytes were read + if (bytes_read != st.st_size) + err(1, "Error: Only %ld of %ld bytes read from file\n", bytes_read, st.st_size); + + // Close the file descriptor + close(binfd); + + // -- 6.1. Create a vCPU -- + vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0); + if (vcpufd == -1) err(1, "KVM_CREATE_VCPU"); + + // map the shared kvm_run structure and following data + mmap_size = ioctl(kvmfd, KVM_GET_VCPU_MMAP_SIZE, NULL); + if (mmap_size == -1) err(1, "KVM_GET_VCPU_MMAP_SIZE"); + if (mmap_size < sizeof(*run)) errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small"); + + // get memory-mapped file + run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0); + if (!run) err(1, "mmap vcpu"); + + // -- 6.2. Initialize vCPU registers -- + int sregs_err = ioctl(vcpufd, KVM_GET_SREGS, &sregs); + if (sregs_err == -1) err(1, "KVM_GET_SREGS"); + + // initialize segment registers to zero, via a read-modify-write of sregs + sregs.cs.base = 0; sregs.cs.selector = 0; + sregs.ds.base = 0; sregs.ds.selector = 0; + sregs.es.base = 0; sregs.es.selector = 0; + sregs.ss.base = 0; sregs.ss.selector = 0; + + sregs_err = ioctl(vcpufd, KVM_SET_SREGS, &sregs); + if (sregs_err == -1) err(1, "KVM_SET_SREGS"); + + // initialize pointer and flags + struct kvm_regs regs = { + .rip = 0, // instruction pointer -> beginning of OS's code + .rsp = RAM_SIZE, // stack pointer -> top of RAM + .rflags = 0x2, // flags required by x86 architecture + }; + + int regs_err = ioctl(vcpufd, KVM_SET_REGS, ®s); + if (regs_err == -1) err(1, "KVM_SET_REGS"); + + // -- 7. Run the vCPU -- + + bool done = false; + while (!done) { + if (ioctl(vcpufd, KVM_RUN, NULL) == -1) err(1, "KVM_RUN"); + + // handle VM exits (blocking call) + switch (run->exit_reason) { + + case KVM_EXIT_HLT: + puts("KVM_EXIT_HLT"); + done = true; + break; + + case KVM_EXIT_IO: + if (run->io.direction == KVM_EXIT_IO_OUT && run->io.size == 1 + && run->io.port == 0x3f8 && run->io.count == 1) + putchar(*(((char *)run) + run->io.data_offset)); + else + errx(1, "unhandled KVM_EXIT_IO"); + break; + + case KVM_EXIT_MMIO: + // !TODO! + done = true; + break; + + case KVM_EXIT_FAIL_ENTRY: + errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx", + (unsigned long long)run->fail_entry.hardware_entry_failure_reason); + done = true; + break; + + case KVM_EXIT_INTERNAL_ERROR: + errx(1, "KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x", run->internal.suberror); + done = true; + break; + + default: + errx(1, "exit_reason = 0x%x", run->exit_reason); + done = true; + break; + } + } +}