Skip to content
Snippets Groups Projects
Commit 1404a61a authored by Michal Privoznik's avatar Michal Privoznik
Browse files

04: KVM API example

parent daf7eae8
No related branches found
No related tags found
No related merge requests found
bits 16
start:
mov dx, 0x3f8 ; What port to output data to (COM1)
mov si, msg
mov cl, msg_len
cld
loop:
lodsb ; load byte from DS:SI registers
out dx, al ; write byte to serial line
dec cl
jnz loop
end:
hlt
msg: db 'Hello world!', 0x0a
msg_len: equ $ - msg
04/main.c 0 → 100644
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <linux/kvm.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#define DEBUG(...) \
do { \
fprintf(stderr, "DEBUG %s:%d : ", __FUNCTION__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#define ERROR(...) \
do { \
fprintf(stderr, "ERROR %s:%d : ", __func__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
} while (0)
#define ERROR_ERRNO(...) \
do { \
char ebuf[1024]; \
strerror_r(errno, ebuf, sizeof(ebuf)); \
fprintf(stderr, "ERROR %s:%d : ", __func__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, " : %s\n", ebuf); \
fprintf(stderr, "\n"); \
} while (0)
static void
closeFD(int *fd)
{
if (*fd < 0)
return;
if (close(*fd) < 0) {
ERROR_ERRNO("Unable to close FD %d", *fd);
} else {
*fd = -1;
}
}
static int
setupKVM(void)
{
int kvmfd;
int kvmver;
kvmfd = open("/dev/kvm", O_RDWR | O_CLOEXEC);
if (kvmfd == -1) {
ERROR_ERRNO("failed to open /dev/kvm");
return -1;
}
kvmver = ioctl(kvmfd, KVM_GET_API_VERSION, 0);
if (kvmver < 0) {
ERROR("Unable to get KVM version");
goto error;
}
if (kvmver != KVM_API_VERSION) {
ERROR("Unexpected KVM version");
goto error;
}
return kvmfd;
error:
closeFD(&kvmfd);
return -1;
}
static int
setupVirtualMachine(int kvmfd,
const size_t guest_mem_size,
const size_t guest_mem_phys_addr,
void **guest_mem)
{
int vmfd;
struct kvm_userspace_memory_region region = {
.slot = 0,
.flags = 0,
.guest_phys_addr = guest_mem_phys_addr,
.memory_size = guest_mem_size,
};
vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
if (vmfd == -1) {
ERROR_ERRNO("failed to create VM");
return -1;
}
*guest_mem = mmap(NULL, guest_mem_size,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
if (*guest_mem == MAP_FAILED) {
ERROR_ERRNO("failed to allocate memory");
goto error;
}
region.userspace_addr = (size_t)*guest_mem;
if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region) == -1) {
ERROR("failed to set user memory region");
goto error;
}
return vmfd;
error:
closeFD(&vmfd);
if (*guest_mem != MAP_FAILED) {
munmap(*guest_mem, guest_mem_size);
}
return -1;
}
static int
populateGuestMemory(void *guest_mem,
const size_t guest_mem_size,
const char *file)
{
int fd = -1;
size_t nread = 0;
int ret = -1;
fd = open(file, O_RDONLY);
if (fd < 0) {
ERROR_ERRNO("failed to open file %s", file);
return -1;
}
while (1) {
char buffer[1024];
ssize_t n;
n = read(fd, buffer, sizeof(buffer));
if (n < 0) {
ERROR_ERRNO("Unable to read from file: %s", file);
goto cleanup;
} else if (n == 0) {
break;
} else {
if (nread + n >= guest_mem_size) {
ERROR("File %s too big to fit into guest memory (%zu)",
file, guest_mem_size);
goto cleanup;
}
memcpy(((char *)guest_mem) + nread, buffer, n);
nread += n;
}
}
if (nread == 0) {
ERROR("File %s is empty", file);
goto cleanup;
}
ret = 0;
cleanup:
closeFD(&fd);
return ret;
}
static int
setupVCPU(int vmfd,
const size_t guest_mem_size,
const size_t guest_mem_phys_addr)
{
int vcpufd = -1;
struct kvm_sregs sregs;
struct kvm_regs regs;
vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, 0);
if (vcpufd == -1) {
ERROR_ERRNO("failed to create vCPU");
return -1;
}
if (ioctl(vcpufd, KVM_GET_SREGS, &sregs) < 0) {
ERROR("Unable to get special registers");
goto error;
}
sregs.cs.base = 0;
sregs.cs.selector = 0;
sregs.ds.base = guest_mem_phys_addr;
sregs.ds.selector = 0;
if (ioctl(vcpufd, KVM_SET_SREGS, &sregs) < 0) {
ERROR("Unable to set special registers");
goto error;
}
if (ioctl(vcpufd, KVM_GET_REGS, &regs) < 0) {
ERROR("Unable to get general purpose registers");
goto error;
}
regs.rflags |= 0x2; /* The 0x2 bit should always be set */
regs.rsp = guest_mem_size; /* Stack address */
regs.rip = guest_mem_phys_addr; /* Start of guest program */
if (ioctl(vcpufd, KVM_SET_REGS, &regs) < 0) {
ERROR("Unable to set general purpose registers");
goto error;
}
return vcpufd;
error:
closeFD(&vcpufd);
return -1;
}
static int
runVirt(int kvmfd,
int vcpufd)
{
size_t vcpu_mmap_size;
struct kvm_run *run = MAP_FAILED;
struct kvm_regs regs;
int ret = -1;
vcpu_mmap_size = ioctl(kvmfd, KVM_GET_VCPU_MMAP_SIZE, 0);
if (vcpu_mmap_size < 0) {
ERROR("Unable to get size of vCPU mmap area");
return -1;
}
run = mmap(NULL, vcpu_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);
if (run == MAP_FAILED) {
ERROR("Failed to allocate memory for vCPU");
return -1;
}
while (1) {
int halt = 0;
if (ioctl(vcpufd, KVM_RUN, 0) < 0) {
ERROR_ERRNO("KVM_RUN");
goto cleanup;
}
switch (run->exit_reason) {
case KVM_EXIT_HLT:
DEBUG("KVM_EXIT_HLT");
halt = 1;
break;
case KVM_EXIT_SHUTDOWN:
DEBUG("KVM_EXIT_SHUTDOWN");
halt = 1;
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));
fflush(stdout);
} else {
ERROR("Unhandled KVM_EXIT_IO");
goto cleanup;
}
break;
case KVM_EXIT_FAIL_ENTRY:
ERROR("KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx",
run->fail_entry.hardware_entry_failure_reason);
goto cleanup;
case KVM_EXIT_INTERNAL_ERROR:
ERROR("KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x", run->internal.suberror);
goto cleanup;
default:
ERROR("Unhandled exit reason: %d", run->exit_reason);
if (ioctl(vcpufd, KVM_GET_REGS, &regs) >= 0) {
ERROR("\nrax=%020llx rbx=%020llx rcx=%020llx rdx=%020llx\n"
"rsi=%020llx rdi=%020llx rsp=%020llx rbp=%020llx\n"
"r8=%020llx r9=%020llx r10=%020llx r11=%020llx\n"
"r12=%020llx r13=%020llx r14=%020llx r15=%020llx\n"
"rip=%020llx rflags=%020llx",
regs.rax, regs.rbx, regs.rcx, regs.rdx,
regs.rsi, regs.rdi, regs.rsp, regs.rbp,
regs.r8, regs.r9, regs.r10, regs.r11,
regs.r12, regs.r13, regs.r14, regs.r15,
regs.rip, regs.rflags);
}
goto cleanup;
}
if (halt != 0) {
break;
}
}
ret = 0;
cleanup:
if (run != MAP_FAILED) {
munmap(run, vcpu_mmap_size);
}
return ret;
}
int main(int argc, char **argv) {
int kvmfd = -1;
int vmfd = -1;
int vcpufd = -1;
const size_t guest_mem_size = 0x200000; /* 2MiB */
void *guest_mem = MAP_FAILED;
const size_t guest_mem_phys_addr = 0x1000; /* First page */
int ret = EXIT_FAILURE;
if (argc != 2) {
ERROR("Usage: %s <file>\n", argv[0]);
goto cleanup;
}
kvmfd = setupKVM();
if (kvmfd < 0) {
goto cleanup;
}
vmfd = setupVirtualMachine(kvmfd, guest_mem_size,
guest_mem_phys_addr, &guest_mem);
if (vmfd < 0) {
goto cleanup;
}
if (populateGuestMemory(guest_mem, guest_mem_size, argv[1]) < 0) {
goto cleanup;
}
vcpufd = setupVCPU(vmfd, guest_mem_size, guest_mem_phys_addr);
if (vcpufd < 0) {
goto cleanup;
}
if (runVirt(kvmfd, vcpufd) < 0) {
goto cleanup;
}
ret = EXIT_SUCCESS;
cleanup:
closeFD(&vcpufd);
if (guest_mem != MAP_FAILED) {
munmap(guest_mem, guest_mem_size);
}
closeFD(&vmfd);
closeFD(&kvmfd);
return ret;
}
project('kvm', 'c', default_options : ['c_std=gnu18'])
guest_binary = custom_target(
'guest.bin',
input: 'guest.asm',
output: 'guest.bin',
build_by_default: true,
command: [
'nasm', '-f', 'bin', '@INPUT@', '-o', '@OUTPUT@'
],
)
executable('kvm', 'main.c')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment