diff options
author | Marvin Borner | 2021-08-29 21:18:03 +0200 |
---|---|---|
committer | Marvin Borner | 2021-08-29 21:18:03 +0200 |
commit | 75bb42663294b388377178b7257c6f8c0f787032 (patch) | |
tree | 0371bf7570ee85ada4ab6dfc98e6ffe9098cef16 | |
parent | eef250dd4a0ae185a8d9d668f020b2ab5c2f4849 (diff) |
KVM stuff
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | bios.bin | bin | 131072 -> 0 bytes | |||
-rw-r--r-- | inc/cpu.h | 8 | ||||
-rw-r--r-- | inc/kvm.h | 9 | ||||
-rw-r--r-- | src/cpu.c | 83 | ||||
-rw-r--r-- | src/kvm.c | 344 | ||||
-rw-r--r-- | src/log.c | 11 | ||||
-rw-r--r-- | src/main.c | 63 | ||||
-rw-r--r-- | test.asm | 12 |
10 files changed, 451 insertions, 103 deletions
@@ -1,3 +1,6 @@ build/ .idea/ + +compile_commands.json .clang-format +tags @@ -1,23 +1,28 @@ -SOURCEDIR = src -INCDIR = inc -BUILDDIR = build +SOURCEDIR = $(PWD)/src +INCDIR = $(PWD)/inc +BUILDDIR = $(PWD)/build SOURCES = $(wildcard $(SOURCEDIR)/*.c) OBJS = $(patsubst $(SOURCEDIR)/%.c, $(BUILDDIR)/%.o, $(SOURCES)) CC = gcc WARNINGS = -Wall -Wextra -Wshadow -Wpointer-arith -Wwrite-strings -Wredundant-decls -Wnested-externs -Wformat=2 -Wmissing-declarations -Wstrict-prototypes -Wmissing-prototypes -Wcast-qual -Wswitch-default -Wswitch-enum -Wlogical-op -Wunreachable-code -Wundef -Wold-style-definition -Wvla -pedantic -DEBUG = -fsanitize=undefined -fsanitize=address -ggdb3 -g3 -g -s -Og +DEBUG = -fsanitize=undefined -fsanitize=address -fstack-protector-strong -ggdb3 -g3 -g -s -Og CFLAGS = -Ofast $(WARNINGS) -I$(INCDIR) $(DEBUG) all: $(OBJS) - @$(CC) -o ./$(BUILDDIR)/out $^ $(CFLAGS) + @$(CC) -o $(BUILDDIR)/out $(CFLAGS) $^ clean: @$(RM) -rf $(BUILDDIR) -run: clean all - @./$(BUILDDIR)/out +run: clean all sync + @nasm test.asm -o $(BUILDDIR)/test + @$(BUILDDIR)/out + +sync: + @make --always-make --dry-run | grep -wE 'gcc|g\+\+' | grep -w '\-c' | jq -nR '[inputs|{directory:".", command:., file: match(" [^ ]+$$").string[1:]}]' >compile_commands.json + @ctags -R --exclude=.git --exclude=build . $(BUILDDIR)/%.o: $(SOURCEDIR)/%.c @mkdir -p $(BUILDDIR) - @$(CC) -c -o $@ $< $(CFLAGS) + @$(CC) -c -o $@ $(CFLAGS) $< diff --git a/bios.bin b/bios.bin Binary files differdeleted file mode 100644 index 4f440f1..0000000 --- a/bios.bin +++ /dev/null diff --git a/inc/cpu.h b/inc/cpu.h deleted file mode 100644 index fcbb57f..0000000 --- a/inc/cpu.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef CPU_H -#define CPU_H - -int cpu_init(void); -void cpu_run(void); -void cpu_destroy(void); - -#endif diff --git a/inc/kvm.h b/inc/kvm.h new file mode 100644 index 0000000..92dbe98 --- /dev/null +++ b/inc/kvm.h @@ -0,0 +1,9 @@ +#ifndef KVM_H +#define KVM_H + +int kvm_init(void); +int kvm_load(long addr, long data, int size); +void kvm_exec(void); +void kvm_destroy(void); + +#endif diff --git a/src/cpu.c b/src/cpu.c deleted file mode 100644 index ce4410a..0000000 --- a/src/cpu.c +++ /dev/null @@ -1,83 +0,0 @@ -#include <errno.h> -#include <fcntl.h> -#include <linux/kvm.h> -#include <stdio.h> -#include <string.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <unistd.h> - -#include <cpu.h> -#include <log.h> - -static int kvm, vm, vcpu; -static struct kvm_run *kvm_run; - -int cpu_init(void) -{ - kvm = open("/dev/kvm", O_RDWR); - if (kvm < 0) { - errln("KVM is required: %s", strerror(errno)); - return -1; - } - - int version = ioctl(kvm, KVM_GET_API_VERSION, 0); - if (version != KVM_API_VERSION) { - close(kvm); - errln("Invalid KVM version: %d", version); - return -1; - } - - vm = ioctl(kvm, KVM_CREATE_VM, 0); - if (vm < 0) { - close(kvm); - errln("Couldn't create VM: %s", strerror(errno)); - return -1; - } - - vcpu = ioctl(vm, KVM_CREATE_VCPU, 0); - if (vcpu < 0) { - close(vm); - close(kvm); - errln("Couldn't create VCPU: %s", strerror(errno)); - return -1; - } - - int mmap_sz = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, 0); - if (mmap_sz <= 0 || mmap_sz & 0xfff) { - close(vcpu); - close(vm); - close(kvm); - errln("Invalid mmap_sz: %d", mmap_sz); - return -1; - } - - kvm_run = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu, 0); - if (kvm_run == MAP_FAILED) { - close(vcpu); - close(vm); - close(kvm); - errln("Couldn't map kvm_run"); - return -1; - } - - logln("Done"); - return 0; -} - -void cpu_destroy(void) -{ - close(vcpu); - close(vm); - close(kvm); - logln("Done"); -} - -void cpu_run(void) -{ - int run = ioctl(vcpu, KVM_RUN, 0); - if (run < 0) { - errln("CPU can't be run: %s", strerror(errno)); - return; - } -} diff --git a/src/kvm.c b/src/kvm.c new file mode 100644 index 0000000..f6ecf45 --- /dev/null +++ b/src/kvm.c @@ -0,0 +1,344 @@ +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/kvm.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <kvm.h> +#include <log.h> + +static int kvm = 0, vm = 0; +static struct kvm_run *kvm_run; +static void *memory; +static size_t memory_size; + +struct { + pthread_t thread; + enum { OFFLINE = 0, RUNNING, ONLINE } state; + int vcpu; +} cpu[1] = { 0 }; + +static int kvm_slot = 0; +static int kvm_register(int flags, long guest_addr, long host_addr, size_t size) +{ + struct kvm_userspace_memory_region region; + region.slot = kvm_slot++; + region.flags = flags; + region.guest_phys_addr = guest_addr; + region.memory_size = size; + region.userspace_addr = host_addr; + + if (ioctl(vm, KVM_SET_USER_MEMORY_REGION, ®ion) < 0) { + errln("Couldn't set memory region: %s", strerror(errno)); + return -1; + } + return 0; +} + +static void kvm_signal(int number) +{ + (void)number; + logln("KVM signal"); +} + +static int kvm_init_cpu(int index) +{ + /** + * VCPU + */ + + cpu[index].vcpu = ioctl(vm, KVM_CREATE_VCPU, index); + if (cpu[index].vcpu < 0) { + errln("Couldn't create VCPU%d: %s", index, strerror(errno)); + return -1; + } + + /** + * KVM_RUN + */ + + int mmap_size = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, 0); + if (mmap_size <= 0 || mmap_size & 0xfff) { + errln("Invalid mmap_size: %d", mmap_size); + return -1; + } + + kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, cpu[index].vcpu, 0); + if (kvm_run == MAP_FAILED) { + errln("Couldn't map kvm_run"); + return -1; + } + + /** + * Registers + */ + + struct kvm_regs regs = { 0 }; + struct kvm_sregs sregs = { 0 }; + + // Get registers + + if (ioctl(cpu[index].vcpu, KVM_GET_REGS, ®s) < 0) { + errln("Couldn't get REGS of VCPU%d", index); + return -1; + } + + if (ioctl(cpu[index].vcpu, KVM_GET_SREGS, &sregs) < 0) { + errln("Couldn't get SREGS of VCPU%d", index); + return -1; + } + + // Modify registers accordingly + + regs.rflags = 2; + regs.rip = 0; + + sregs.cs.selector = 0; + sregs.cs.base = 0; + + // Set registers + + if (ioctl(cpu[index].vcpu, KVM_SET_REGS, ®s) < 0) { + errln("Couldn't set REGS of VCPU%d", index); + return -1; + } + + if (ioctl(cpu[index].vcpu, KVM_SET_SREGS, &sregs) < 0) { + errln("Couldn't set SREGS of VCPU%d", index); + return -1; + } + + cpu[index].state = ONLINE; + return 0; +} + +int kvm_init(void) +{ + kvm = open("/dev/kvm", O_RDWR); + if (kvm < 0) { + errln("KVM is required: %s", strerror(errno)); + return -1; + } + + /** + * Verify and create VM + */ + + int version = ioctl(kvm, KVM_GET_API_VERSION, 0); + if (version != KVM_API_VERSION) { + errln("Invalid KVM version: %d", version); + return -1; + } + + vm = ioctl(kvm, KVM_CREATE_VM, 0); + if (vm < 0) { + errln("Couldn't create VM: %s", strerror(errno)); + return -1; + } + + // Verify max CPU count + + int max_cpus = ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_MAX_VCPUS); + if (max_cpus < 0) { + errln("Couldn't get CPU count: %s", strerror(errno)); + return -1; + } + assert((unsigned long)max_cpus >= sizeof(cpu) / sizeof(cpu[0])); + + /** + * Memory + */ + + memory_size = 10 << 20; + memory = mmap(NULL, memory_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (memory == MAP_FAILED) { + errln("Couldn't map memory"); + return -1; + } + + if (kvm_register(0, 0, (long)memory, memory_size) < 0) + return -1; + + /** + * CPUs + */ + + for (unsigned long i = 0; i < sizeof(cpu) / sizeof(cpu[0]); i++) + if (kvm_init_cpu(i) < 0) + return -1; + + /** + * Okay! + */ + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = kvm_signal; + sigaction(SIGALRM, &sa, NULL); + + return 0; +} + +int kvm_load(long addr, long data, int size) +{ + memcpy((char *)memory + addr, (void *)data, size); + return 0; +} + +static void *kvm_loop(void *arg) +{ + long index = (long)arg; + assert(cpu[index].state == ONLINE); + + while (cpu[index].state = RUNNING, ioctl(cpu[index].vcpu, KVM_RUN, 0) >= 0) { + cpu[index].state = ONLINE; + + switch (kvm_run->exit_reason) { + case KVM_EXIT_HLT: + logln("Halting CPU"); + goto done; + case KVM_EXIT_FAIL_ENTRY: + errln("CPU failure"); + goto done; + case KVM_EXIT_IO: + logln("IO"); + break; + case KVM_EXIT_MMIO: + case KVM_EXIT_IRQ_WINDOW_OPEN: + default: + errln("Unknown KVM exit reason %d", kvm_run->exit_reason); + goto done; + } + } + + errln("CPU can't be run: %s", strerror(errno)); +done: + cpu[index].state = OFFLINE; + + logln("Done"); + pthread_exit(NULL); +} + +static void *kvm_observe(void *arg) +{ + long index = (long)arg; + +#define R(n) ((int)(regs.r##n)) // TODO: Adjust per arch? + while (1) { + if (cpu[index].state == OFFLINE) + break; + + struct kvm_regs regs = { 0 }; + if (ioctl(cpu[index].vcpu, KVM_GET_REGS, ®s) < 0) { + errln("Couldn't get KVM regs: %s", strerror(errno)); + break; + } + + struct kvm_sregs sregs = { 0 }; + if (ioctl(cpu[index].vcpu, KVM_GET_SREGS, &sregs) < 0) { + errln("Couldn't get KVM sregs: %s", strerror(errno)); + break; + } + + logln("EAX: %08x ECX: %08x EDX: %08x EBX: %08x", R(ax), R(cx), R(dx), R(bx)); + logln("ESP: %08x EBP: %08x ESI: %08x EDI: %08x", R(ax), R(cx), R(dx), R(bx)); + logln("EFLAGS: %08x EIP: %08x", R(flags), R(ip)); + + logln("ES.sel=%04x ES.base=%08x, ES.lim=%08x", sregs.es.selector, sregs.es.base, + sregs.es.limit); + logln("CS.sel=%04x CS.base=%08x, CS.lim=%08x", sregs.cs.selector, sregs.cs.base, + sregs.cs.limit); + logln("SS.sel=%04x SS.base=%08x, SS.lim=%08x", sregs.ss.selector, sregs.ss.base, + sregs.ss.limit); + logln("DS.sel=%04x DS.base=%08x, DS.lim=%08x", sregs.ds.selector, sregs.ds.base, + sregs.ds.limit); + logln("FS.sel=%04x FS.base=%08x, FS.lim=%08x", sregs.fs.selector, sregs.fs.base, + sregs.fs.limit); + logln("GS.sel=%04x GS.base=%08x, GS.lim=%08x", sregs.gs.selector, sregs.gs.base, + sregs.gs.limit); + logln("CR0: %08x CR2: %08x CR3: %08x CR4: %08x", sregs.cr0, sregs.cr2, sregs.cr3, + sregs.cr4); + + logln("GDT.base=%08x GDT.limit=%08x", sregs.gdt.base, sregs.gdt.limit); + logln("LDT.base=%08x LDT.limit=%08x", sregs.ldt.base, sregs.ldt.limit); + logln("IDT.base=%08x IDT.limit=%08x", sregs.idt.base, sregs.idt.limit); + logln("TR.base =%08x TR.limit =%08x", sregs.tr.base, sregs.tr.limit); + + struct timespec req = { 0 }, rem = { 0 }; + req.tv_sec = 1; + int slept = nanosleep(&req, &rem); + assert(slept >= 0); + } + + logln("Done"); + pthread_exit(NULL); +} + +static void *kvm_exec_cpu(void *arg) +{ + pthread_attr_t attr = { 0 }; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + int err; + + pthread_t observee = { 0 }; + err = pthread_create(&observee, &attr, kvm_loop, arg); + if (err) { + errln("Couldn't create observee thread: %d", err); + pthread_exit(NULL); + } + + pthread_t observer = { 0 }; + err = pthread_create(&observer, &attr, kvm_observe, arg); + if (err) { + errln("Couldn't create observer thread: %d", err); + pthread_exit(NULL); + } + + pthread_attr_destroy(&attr); + + logln("Done"); + pthread_exit(NULL); +} + +void kvm_exec(void) +{ + for (unsigned long i = 0; i < sizeof(cpu) / sizeof(cpu[0]); i++) { + if (cpu[i].state == ONLINE) { + logln("Booting CPU %d", i); + + pthread_attr_t attr = { 0 }; + pthread_attr_init(&attr); + pthread_create(&cpu[i].thread, &attr, kvm_exec_cpu, (void *)i); + pthread_attr_destroy(&attr); + } + } + + // Wait until all CPUs are finished + for (unsigned long i = 0; i < sizeof(cpu) / sizeof(cpu[0]); i++) + pthread_join(cpu[i].thread, NULL); + + kvm_destroy(); +} + +void kvm_destroy(void) +{ + for (unsigned long i = 0; i < sizeof(cpu) / sizeof(cpu[0]); i++) + if (cpu[i].vcpu) + close(cpu[i].vcpu); + + if (vm) + close(vm); + if (kvm) + close(kvm); + + logln("Done"); +} @@ -1,3 +1,4 @@ +#include <pthread.h> #include <stdarg.h> #include <stdio.h> @@ -6,8 +7,12 @@ #define LOG_OUT stdout #define ERR_OUT stderr +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + void __logln(const char *func, const char *format, ...) { + pthread_mutex_lock(&mutex); + fprintf(LOG_OUT, "[LOG] %s: ", func); va_list ap; @@ -16,10 +21,14 @@ void __logln(const char *func, const char *format, ...) va_end(ap); fprintf(LOG_OUT, "\n"); + + pthread_mutex_unlock(&mutex); } void __errln(const char *func, const char *format, ...) { + pthread_mutex_lock(&mutex); + fprintf(ERR_OUT, "[ERR] %s: ", func); va_list ap; @@ -28,4 +37,6 @@ void __errln(const char *func, const char *format, ...) va_end(ap); fprintf(ERR_OUT, "\n"); + + pthread_mutex_unlock(&mutex); } @@ -1,16 +1,71 @@ -#include <cpu.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> #include <stdlib.h> +#include <string.h> + +#include <kvm.h> +#include <log.h> + +struct aalloc_info { + void *actual_ptr; +}; + +static void *aalloc(int size, int align) +{ + int adjusted = align - 1; + void *actual = calloc(1, sizeof(void *) + size + adjusted); + struct aalloc_info *ai = + (void *)(((uintptr_t)((long)actual + sizeof(void *) + adjusted) & ~adjusted) - + sizeof(void *)); + ai->actual_ptr = actual; + return ((char *)ai) + sizeof(void *); +} + +static void afree(void *ptr) +{ + struct aalloc_info *a = (void *)((char *)ptr - sizeof(void *)); + free(a->actual_ptr); +} + +static void load_bios(const char *path) +{ + FILE *bios = fopen(path, "rb"); + if (!bios) { + errln("Couldn't open '%s': %s", path, strerror(errno)); + exit(1); + } + fseek(bios, 0, SEEK_END); + int size = ftell(bios); + fseek(bios, 0, SEEK_SET); + + void *data = aalloc(size, 4096); + if (fread(data, size, 1, bios) != 1) { + errln("Couldn't read '%s': %s", path, strerror(errno)); + exit(1); + } + + fclose(bios); + + int load = kvm_load(0, (long)data, size); + if (load < 0) { + errln("Couldn't load '%s'", path); + exit(1); + } + + afree(data); +} int main(int argc, char *argv[]) { (void)argc; (void)argv; - int cpu = cpu_init(); + int cpu = kvm_init(); if (cpu < 0) exit(1); - cpu_run(); - cpu_destroy(); + load_bios("build/test"); + kvm_exec(); return 0; } diff --git a/test.asm b/test.asm new file mode 100644 index 0000000..f115ca4 --- /dev/null +++ b/test.asm @@ -0,0 +1,12 @@ +xor dx, dx + +test: +in ax, 0x60 +mov bx, 42 + +mov cx, 1 +add dx, cx +cmp dx, 255 +jne test + +hlt |