// https://www.youtube.com/watch?v=KvP1y9P453g // https://docs.kernel.org/virt/kvm/api.html #include #include #include #include #include #include #include #include #include // ioctl() stands for input/output control. // It's a Linux/Unix system call that lets a userspace program send // a special command to a device or kernel subsystem through a file descriptor. static void die(const char* msg) { perror(msg); exit(1); } int main(void) { // read and write, close on exec, closes fd when new program is run int kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC); if (kvm < 0) die("open /dev/kvm"); int api = ioctl(kvm, KVM_GET_API_VERSION, 0); if (api < 0) die("KVM_GET_API_VERSION"); if (api != KVM_API_VERSION) { fprintf(stderr, "unexpected KVM API version: %d\n", api); return 1; } int vm = ioctl(kvm, KVM_CREATE_VM, 0); if (vm < 0) die("KVM_CREATE_VM"); // - NULL: let the kernel choose the virtual address. // - mem_size: allocate 4096 bytes. // - PROT_READ | PROT_WRITE: allow reading and writing. // - MAP_PRIVATE: changes belong only to this process. // - MAP_ANONYMOUS: allocate fresh zero-initialized memory instead of mapping a file. // - -1: no file descriptor, because the mapping is anonymous. // - 0: no file offset. size_t mem_size = 4096; uint8_t* mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) die("mmap guest memory"); // mov eax, 42 // hlt // // encoding: // // B8 2A 00 00 00 mov eax, 42 // F4 hlt // // printf '.intel_syntax noprefix\nmov eax, 42\nhlt\n' > example.s // as --32 -o example.o example.s // objdump -d -Mintel example.o uint8_t code[] = {0xB8, 0x2A, 0x00, 0x00, 0x00, 0xF4}; memcpy(mem, code, sizeof(code)); struct kvm_userspace_memory_region region = { .slot = 0, .flags = 0, .guest_phys_addr = 0, .memory_size = mem_size, .userspace_addr = (uint64_t)mem, }; if (ioctl(vm, KVM_SET_USER_MEMORY_REGION, ®ion) < 0) die("KVM_SET_USER_MEMORY_REGION"); int vcpu = ioctl(vm, KVM_CREATE_VCPU, 0); if (vcpu < 0) die("KVM_CREATE_VCPU"); int run_size_result = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, 0); if (run_size_result < 0) die("KVM_GET_VCPU_MMAP_SIZE"); size_t run_size = (size_t)run_size_result; struct kvm_run* run = mmap(NULL, run_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu, 0); if (run == MAP_FAILED) die("mmap kvm_run"); /* * Start in 16-bit real mode. * * KVM's initial CPU state is close to reset state, but explicitly * configuring CS makes our intended mapping obvious: * * linear address = cs.base + rip = 0 + 0 */ struct kvm_sregs sregs; if (ioctl(vcpu, KVM_GET_SREGS, &sregs) < 0) die("KVM_GET_SREGS"); sregs.cs.base = 0; sregs.cs.selector = 0; if (ioctl(vcpu, KVM_SET_SREGS, &sregs) < 0) die("KVM_SET_SREGS"); struct kvm_regs regs = { .rip = 0, .rflags = 0x2, }; if (ioctl(vcpu, KVM_SET_REGS, ®s) < 0) die("KVM_SET_REGS"); for (;;) { if (ioctl(vcpu, KVM_RUN, 0) < 0) die("KVM_RUN"); switch (run->exit_reason) { case KVM_EXIT_HLT: { struct kvm_regs out; if (ioctl(vcpu, KVM_GET_REGS, &out) < 0) die("KVM_GET_REGS"); printf("guest halted\n"); printf("RAX = %llu\n", (unsigned long long)out.rax); goto done; } case KVM_EXIT_FAIL_ENTRY: fprintf(stderr, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx\n", (unsigned long long)run->fail_entry.hardware_entry_failure_reason); goto done; case KVM_EXIT_INTERNAL_ERROR: fprintf(stderr, "KVM_EXIT_INTERNAL_ERROR\n"); goto done; default: fprintf(stderr, "unhandled KVM exit: %u\n", run->exit_reason); goto done; } } done: munmap(run, run_size); close(vcpu); munmap(mem, mem_size); close(vm); close(kvm); return 0; }