Created
July 17, 2023 13:45
-
-
Save harrisonturton/d19ce2276fc03ecc1fb297128db13154 to your computer and use it in GitHub Desktop.
Using the KVM API (LWN, 2015)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This follows the LWN tutorial "Using the KVM API": | |
* | |
* https://lwn.net/Articles/658511/ | |
* | |
* Which runs a virtual machine that executes the following | |
* 16-bit x86 code (the assembled binary is hardcoded in): | |
* | |
* mov $0x3f8, %dx | |
* add %bl, %al | |
* add $'0', %al | |
* out %al, (%dx) | |
* mov $'\n', %al | |
* out %al, (%dx) | |
* hlt | |
* | |
* To demonstrate usage of KVM without an operating system. | |
* This emulates a trivial serial port on port 0x3f8. | |
*/ | |
#include <err.h> | |
#include <fcntl.h> | |
#include <linux/kvm.h> | |
#include <stdio.h> | |
#include <sys/ioctl.h> | |
#include <stdlib.h> | |
#include <string.h> /* memcpy */ | |
#include <sys/mman.h> /* mmap */ | |
#include <stdint.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
int main(void) { | |
int kvm, vmfd, vcpufd, ret; | |
uint8_t *mem; | |
struct kvm_sregs sregs; | |
size_t mmap_size; | |
struct kvm_run *run; | |
const uint8_t code[] = { | |
0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ | |
0x00, 0xd8, /* add %bl, %al */ | |
0x04, '0', /* add $'0', %al */ | |
0xee, /* out %al, (%dx) */ | |
0xb0, '\n', /* mov $'\n', %al */ | |
0xee, /* out %al, (%dx) */ | |
0xf4, /* hlt */ | |
}; | |
// All opens that aren't explicitly intended to live | |
// across an exec should be marked O_CLOEXEC | |
kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC); | |
if (kvm == -1) | |
err(1, "/dev/kvm"); | |
// Make sure we have the stable version of the API (12) | |
ret = ioctl(kvm, KVM_GET_API_VERSION, NULL); | |
if (ret == -1) | |
err(1, "KVM_GET_API_VERSION"); | |
if (ret != 12) | |
errx(1, "KVM_GET_API_VERSION %d, expected 12", ret); | |
vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long) 0); | |
if (vmfd == -1) | |
err(1, "KVM_CREATE_VM"); | |
// Allocate one aligned page of guest memory to hold the code | |
mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); | |
if (!mem) | |
err(1, "allocating guest memory"); | |
memcpy(mem, code, sizeof(code)); | |
// Map it to the second page from (to avoid real-mode IDT at 0) | |
struct kvm_userspace_memory_region region = { | |
.slot = 0, | |
.guest_phys_addr = 0x1000, | |
.memory_size = 0x1000, | |
.userspace_addr = (unsigned long) mem, | |
}; | |
ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); | |
if (ret == -1) | |
err(1, "KVM_SET_USER_MEMORY_REGION"); | |
vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long) 0); | |
if (vcpufd == -1) | |
err(1, "KVM_CREATE_VCPU"); | |
// Map the shared kvm_run structure and the following data | |
ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL); | |
if (ret == -1) | |
err(1, "KVM_GET_VCPU_MMAP_SIZE"); | |
mmap_size = ret; | |
if (mmap_size < sizeof(*run)) | |
errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small"); | |
run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0); | |
if (!run) | |
err(1, "mmap vcpu"); | |
// Initialize CS to point at 0 via a read-modify-write of sregs | |
ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs); | |
if (ret == -1) | |
err(1, "KVM_SET_SREGS"); | |
sregs.cs.base = 0; | |
sregs.cs.selector = 0; | |
ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs); | |
if (ret == -1) | |
err(1, "KVM_SET_SREGS"); | |
// Initialize registers: instruction pointer for our code, addends, and | |
// initial flags required by the x86 architecture | |
struct kvm_regs regs = { | |
.rip = 0x1000, | |
.rax = 2, | |
.rbx = 2, | |
.rflags = 0x2, | |
}; | |
ret = ioctl(vcpufd, KVM_SET_REGS, ®s); | |
if (ret == -1) | |
err(1, "KVM_SET_REGS"); | |
/* Repeatedly run code and handle VM exits */ | |
while (1) { | |
ret = ioctl(vcpufd, KVM_RUN, NULL); | |
if (ret == -1) | |
err(1, "KVM_RUN"); | |
switch (run->exit_reason) { | |
case KVM_EXIT_HLT: | |
puts("KVM_EXIT_HALT"); | |
return 0; | |
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_FAIL_ENTRY: | |
errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reasion = 0x%dx", | |
run->internal.suberror); | |
default: | |
errx(1, "exit_reason = 0x%x", run->exit_reason); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment