Generally, LWN runs top quality articles. I always read them with pleasure and they are good enough to make me a paid subscriber. Every now and then though, they would publish something pretty great even by their standards. I read this and was amazed. I had not realized that it is this easy to create and run a simple virtual machine. I typed the code in and played with it for a couple of hours. You can get the file that actually compiles (C++14) and runs here.

  1//------------------------------------------------------------------------------
  2// Based on: https://lwn.net/Articles/658511/
  3//------------------------------------------------------------------------------
  4
  5#include <iostream>
  6#include <iomanip>
  7#include <cstdint>
  8#include <cstring>
  9#include <cerrno>
 10#include <sys/stat.h>
 11#include <fcntl.h>
 12#include <unistd.h>
 13#include <sys/ioctl.h>
 14#include <sys/mman.h>
 15#include <linux/kvm.h>
 16
 17//------------------------------------------------------------------------------
 18// Error handling macro
 19//------------------------------------------------------------------------------
 20#define RUN(var, command) \
 21var = command;       \
 22if (var == -1) {     \
 23  std::cerr << #command ": " << strerror(errno) << std::endl; \
 24  return 1;          \
 25}
 26
 27//------------------------------------------------------------------------------
 28// The code to be run inside of the virtual machine
 29//------------------------------------------------------------------------------
 30const uint8_t code[] = {
 31  0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
 32  0x00, 0xd8,       /* add %bl, %al */
 33  0x04, '0',        /* add $'0', %al */
 34  0xee,             /* out %al, (%dx) */
 35  0xb0, '\n',       /* mov $'\n', %al */
 36  0xee,             /* out %al, (%dx) */
 37  0xf4              /* hlt */
 38};
 39
 40int main(int argc, char **argv)
 41{
 42  using namespace std;
 43
 44  //----------------------------------------------------------------------------
 45  // Open KVM
 46  //----------------------------------------------------------------------------
 47  int kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC);
 48  if (kvm == -1) {
 49    cerr << "Unable to open /dev/kvm: " << strerror(errno) << endl;
 50    return 1;
 51  }
 52
 53  //----------------------------------------------------------------------------
 54  // Check the version of the API, we need 12
 55  //----------------------------------------------------------------------------
 56  int ret;
 57  RUN(ret, ioctl(kvm, KVM_GET_API_VERSION, 0));
 58
 59  if (ret != 12) {
 60    cerr << "KVM_GET_API_VERSION " << ret << ", expected 12" << endl;
 61    return 1;
 62  }
 63
 64  //----------------------------------------------------------------------------
 65  // Check if the extension required to set up guest memory is present
 66  //----------------------------------------------------------------------------
 67  RUN(ret, ioctl(kvm, KVM_CHECK_EXTENSION, KVM_CAP_USER_MEMORY));
 68
 69  if (!ret) {
 70    cerr << "KVM_CAP_USER_MEM extension is not available" << endl;
 71    return 1;
 72  }
 73
 74  //----------------------------------------------------------------------------
 75  // Set up a virtual machine
 76  //----------------------------------------------------------------------------
 77  int vmfd;
 78  RUN(vmfd, ioctl(kvm, KVM_CREATE_VM, (unsigned long)0));
 79
 80  //----------------------------------------------------------------------------
 81  // Get some page-aligned memory and copy the code to it
 82  //----------------------------------------------------------------------------
 83  void *mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
 84                   -1, 0);
 85  if (mem == MAP_FAILED) {
 86    cerr << "Failed to get a page of memory: " << strerror(errno) << endl;
 87    return 1;
 88  }
 89  memcpy(mem, code, sizeof(code));
 90
 91  //----------------------------------------------------------------------------
 92  // Tell the virtual machine about this region
 93  //----------------------------------------------------------------------------
 94  kvm_userspace_memory_region region;
 95  memset(&region, 0, sizeof(region));
 96  region.slot            = 0;
 97  region.guest_phys_addr = 0x1000;
 98  region.memory_size     = 0x1000;
 99  region.userspace_addr  = (uint64_t)mem;
100
101  RUN(ret, ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &region));
102
103  //----------------------------------------------------------------------------
104  // Create a virtual CPU #0
105  //----------------------------------------------------------------------------
106  int vcpufd;
107  RUN(vcpufd, ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0));
108
109  //----------------------------------------------------------------------------
110  // Allocate memory for kvm_run data structure
111  //----------------------------------------------------------------------------
112  int vcpu_run_size;
113  RUN(vcpu_run_size, ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, 0));
114
115  kvm_run *run;
116  run = (kvm_run *)mmap(0, vcpu_run_size, PROT_READ | PROT_WRITE, MAP_SHARED,
117                        vcpufd, 0);
118  if (run == MAP_FAILED) {
119    cerr << "Allocating VCPU run struct failed: " << strerror(errno) << endl;
120    return 1;
121  }
122
123  //----------------------------------------------------------------------------
124  // Set up the special registers of the VCPU #0
125  //----------------------------------------------------------------------------
126  kvm_sregs sregs;
127  RUN(ret, ioctl(vcpufd, KVM_GET_SREGS, &sregs));
128  sregs.cs.base = 0;
129  sregs.cs.selector = 0;
130  RUN(ret, ioctl(vcpufd, KVM_SET_SREGS, &sregs));
131
132  //----------------------------------------------------------------------------
133  // Set up the standard registers
134  //----------------------------------------------------------------------------
135  kvm_regs regs;
136  memset(&regs, 0, sizeof(regs));
137  regs.rip    = 0x1000;
138  regs.rax    = 2;
139  regs.rbx    = 2;
140  regs.rflags = 0x2; // starting the VM will fail with this not set, x86
141                     // architecture requirement
142  RUN(ret, ioctl(vcpufd, KVM_SET_REGS, &regs));
143
144  //----------------------------------------------------------------------------
145  // Run the VCPU #0
146  //----------------------------------------------------------------------------
147  while (1) {
148    RUN(ret, ioctl(vcpufd, KVM_RUN, 0));
149    switch (run->exit_reason) {
150
151      //------------------------------------------------------------------------
152      // HLT instruction - we're done
153      //------------------------------------------------------------------------
154      case KVM_EXIT_HLT:
155        cerr << "KVM_EXIT_HLT" << endl;
156        return 0;
157
158      //------------------------------------------------------------------------
159      // Simulate an IO port at 0x3f8
160      //------------------------------------------------------------------------
161      case KVM_EXIT_IO:
162        if (run->io.direction == KVM_EXIT_IO_OUT &&
163            run->io.size      == 1 &&
164            run->io.port      == 0x3f8 &&
165            run->io.count     == 1)
166          cout << *(((char *)run) + run->io.data_offset) << flush;
167        else
168          cerr << "Unhandled KVM_EXIT_IO" << endl;
169        break;
170
171      //------------------------------------------------------------------------
172      // Underlying virtualization mechanism can't start the VM
173      //------------------------------------------------------------------------
174      case KVM_EXIT_FAIL_ENTRY:
175        cerr << "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x";
176        cerr << hex;
177        cerr << (unsigned long long)run->fail_entry.hardware_entry_failure_reason;
178        cerr << endl;
179        return 1;
180
181      //------------------------------------------------------------------------
182      // Error from the KVM subsystem
183      //------------------------------------------------------------------------
184      case KVM_EXIT_INTERNAL_ERROR:
185        cerr << "KVM_EXIT_INTERNAL_ERROR: suberror = 0x" << hex;
186        cerr << run->internal.suberror << endl;
187        return 1;
188    }
189  }
190
191  //----------------------------------------------------------------------------
192  // Cleanup
193  //----------------------------------------------------------------------------
194  munmap(mem, 0x1000);
195  munmap(run, vcpu_run_size);
196  close(vcpufd);
197  close(vmfd);
198  close(kvm);
199
200  return 0;
201}
If you like this kind of content, you can subscribe to my newsletter, follow me on Twitter, or subscribe to my RSS channel.