<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged qemu</title>
<link>https://coderlegion.com/tag/qemu</link>
<description>Powered by Question2Answer</description>
<item>
<title>Porting my Rust KVM hypervisor to ARM64: Working with registers</title>
<link>https://coderlegion.com/22243/porting-my-rust-kvm-hypervisor-to-arm64-working-with-registers</link>
<description>&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;This is a follow-up to my KVM in Rust series where I showed how to use &lt;code&gt;strace&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;GDB&lt;/code&gt; to reverse-engineer QEMU/KVM and reimplement pieces of it in my own&lt;br&gt;
hypervisor in Rust. So far this was done on a modern x86 CPU. This time we'll&lt;br&gt;
see how to get KVM running on 64-bit ARM architecture. For this I have used my&lt;br&gt;
Raspberry Pi 4B which has a CPU with hardware virtualization support.&lt;/p&gt;
&lt;h3 id=&quot;cross-compilation&quot;&gt;Cross-compilation&lt;/h3&gt;
&lt;p&gt;The first thing to do was to set up an environment for cross-compilation and for&lt;br&gt;
this kind of thing I almost always use docker. So I have written a simple&lt;br&gt;
&lt;code&gt;Dockerfile&lt;/code&gt; installing the cross-compilation toolchain for 64-bit ARM along&lt;br&gt;
with Rust essentials:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/blob/kvm-arm-code/Dockerfile&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/blob/kvm-arm-code/Dockerfile&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Building this image is straightforward, but it does require passing user and&lt;br&gt;
group id to have the resulting file permissions correct:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker build --network host \
       --build-arg GID=$(id -g) \
       --build-arg=UID=$(id -u) \
       -t arm-rust-build .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, to cross-compile just run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker run --network host -it --rm \
       -v &quot;$(pwd)&quot;:&quot;/home/docker/kvm-rust&quot; \
       arm-rust-build \
       cargo build --release --target=aarch64-unknown-linux-gnu
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;first-roadblock-api-difference&quot;&gt;First roadblock: API difference&lt;/h3&gt;
&lt;p&gt;At first, I thought the difference would be mostly in CPU registers, so I&lt;br&gt;
removed &lt;code&gt;KVM_GET_SREGS2&lt;/code&gt; and &lt;code&gt;KVM_SET_SREGS2&lt;/code&gt; and some x86-specific code just to&lt;br&gt;
make it compile. However, when I ran it on Raspberry Pi, the program returned an&lt;br&gt;
error on &lt;code&gt;KVM_GET_REGS&lt;/code&gt;. I once again turned to use &lt;code&gt;strace&lt;/code&gt; on QEMU to see what&lt;br&gt;
was to be done. I downloaded the Linux kernel source code (from www.kernel.org),&lt;br&gt;
cross-compiled it for ARM and ran it in QEMU:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;#!/bin/sh

KERNEL=/home/pi/Image
qemu-system-aarch64                                             \
        -M virt                                                 \
        -smp 1                                                  \
        -enable-kvm                                             \
        -cpu host                                               \
        -kernel ${KERNEL}                                       \
        -append &quot;console=ttyAMA0&quot;                               \
        -serial stdio                                           \
        -nographic                                              \
        -nodefaults
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that here I used the &lt;code&gt;virt&lt;/code&gt; machine specifically: that's the option to&lt;br&gt;
use for KVM-based virtualization (machines like &lt;code&gt;raspi4b&lt;/code&gt; would be emulated as&lt;br&gt;
they require a very specific CPU and device tree, so they cannot simply run&lt;br&gt;
directly on the host CPU). I put this into &lt;code&gt;start-qemu.sh&lt;/code&gt; file and then ran:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;strace -yy -f -X verbose -e trace=ioctl,openat,read,write,mmap -o kvm.log ./start-qemu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;discovering-kvm-register-api-for-arm64&quot;&gt;Discovering KVM register API for ARM64&lt;/h4&gt;
&lt;p&gt;First thing I did was to try and find &lt;code&gt;KVM_GET_REGS&lt;/code&gt; string, but there were no&lt;br&gt;
results. I tried something more generic like &lt;code&gt;grep -i regs&lt;/code&gt; and got this as&lt;br&gt;
first results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4010aeab /* KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG */, 0x7fff57e008) = 0
137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4010aeab /* KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG */, 0x7fff57e008) = 0
137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4010aeab /* KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG */, 0x7fff57e008) = 0
137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4010aeab /* KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG */, 0x7fff57e008) = 0
137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4010aeab /* KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG */, 0x7fff57e008) = 0
137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4010aeab /* KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG */, 0x7fff57e008) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So this is when I realized it was going to be a bit harder. I wasn't getting&lt;br&gt;
names of fields and corresponding values, just addresses. I installed Linux&lt;br&gt;
headers on Raspberry Pi and searched for &lt;code&gt;KVM_[GS]ET_ONE_REG&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pi@raspberrypi:~ $ grep -Rn KVM_[GS]ET_ONE_REG /usr/include/linux/
/usr/include/linux/kvm.h:1618:#define KVM_GET_ONE_REG             _IOW(KVMIO,  0xab, struct kvm_one_reg)
/usr/include/linux/kvm.h:1619:#define KVM_SET_ONE_REG             _IOW(KVMIO,  0xac, struct kvm_one_reg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Searching in the same file we can see that the &lt;code&gt;struct kvm_one_reg&lt;/code&gt; is defined&lt;br&gt;
as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct kvm_one_reg {
       __u64 id;
       __u64 addr;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;constructing-cpu-id&quot;&gt;Constructing CPU id&lt;/h4&gt;
&lt;p&gt;As it usually is with great discoveries, I learned how to construct the register&lt;br&gt;
id in &lt;code&gt;kvm_one_reg&lt;/code&gt; by accident. Before even knowing how KVM on ARM works with&lt;br&gt;
registers, I naively tried searching for &lt;code&gt;struct kvm_regs&lt;/code&gt; and found two&lt;br&gt;
interesting matches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pi@raspberrypi:~ $ grep -Rn kvm_regs /usr/include/
/usr/include/aarch64-linux-gnu/asm/kvm.h:50:struct kvm_regs {
/usr/include/aarch64-linux-gnu/asm/kvm.h:208:#define KVM_REG_ARM_CORE_REG(name)(offsetof(struct kvm_regs, name) / sizeof(__u32))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;struct kvm_regs&lt;/code&gt; here was just a list of registers and&lt;br&gt;
&lt;code&gt;KVM_REG_ARM_CORE_REG&lt;/code&gt; macro seemed to be taking a register name as input and&lt;br&gt;
looking for offset of the register in the &lt;code&gt;struct kvm_regs&lt;/code&gt;, but I didn't find&lt;br&gt;
it anywhere in the Linux headers.&lt;/p&gt;
&lt;p&gt;I knew, however, that QEMU was one place where this might be used and I searched&lt;br&gt;
for &lt;code&gt;KVM_REG_ARM_CORE_REG&lt;/code&gt; and found this in &lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/qemu/v11.0.2/source/target/arm/kvm.c#L2080&quot;&gt;target/arm/kvm.c&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define AARCH64_CORE_REG(x)   (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | \
                 KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What was left to do was just to find these macro definitions and reconstruct&lt;br&gt;
them in my Rust KVM code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;const KVM_REG_ARM64 : u64 = 0x6000000000000000;
const KVM_REG_SIZE_U64 : u64 = 0x0030000000000000;
const KVM_REG_ARM_COPROC_SHIFT : u64 = 16;
const KVM_REG_ARM_CORE : u64 = 0x0010 &amp;lt;&amp;lt; KVM_REG_ARM_COPROC_SHIFT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, the &lt;code&gt;KVM_REG_ARM_CORE_REG&lt;/code&gt; needed to be more thought-through as it&lt;br&gt;
worked with offsets in the &lt;code&gt;struct kvm_regs&lt;/code&gt;. So I have written a function&lt;br&gt;
taking the register name as string and mapping it to the register &lt;code&gt;id&lt;/code&gt; that KVM&lt;br&gt;
understands:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn AARCH64_CORE_REG(name: &amp;amp;str) -&amp;gt; io::Result&amp;lt;u64&amp;gt; {
    let base = KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE;
    if name.starts_with(&quot;x&quot;) {
        let reg : u64 = name[1..]
            .parse()
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
        return Ok(base | reg * 2); }

    match name {
        &quot;sp&quot; =&amp;gt; { return Ok(base | (31 * 2)); },
        &quot;pc&quot; =&amp;gt; { return Ok(base | (32 * 2)); },
        &quot;pstate&quot; =&amp;gt; { return Ok(base | (33 * 2)); },
        _ =&amp;gt; ()
    };

    Err(io::Error::other(&quot;Invalid register.&quot;))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my implementation I have improved this further by utilizing &lt;code&gt;HashMap&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;LazyLock&lt;/code&gt; so that the parsing and calculation isn't done on-the fly but rather&lt;br&gt;
computed once and cached for subsequent calls.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;At this point I understood how ARM64 exposes CPU registers through&lt;br&gt;
&lt;code&gt;KVM_GET_ONE_REG&lt;/code&gt; and &lt;code&gt;KVM_SET_ONE_REG&lt;/code&gt; calls and had reproduced the register&lt;br&gt;
&lt;code&gt;id&lt;/code&gt; encoding in Rust. Unfortunately, that wasn't enough to make the hypervisor&lt;br&gt;
work. My first attempt at accessing the program counter (&lt;code&gt;PC&lt;/code&gt; register)&lt;br&gt;
immediately failed with an &lt;code&gt;ENOEXEC&lt;/code&gt; error: a clue that I was missing another&lt;br&gt;
crucial step in KVM ARM setup.&lt;/p&gt;
&lt;p&gt;The code showcasing creation of register &lt;code&gt;id&lt;/code&gt;s in a &lt;code&gt;HashMap&lt;/code&gt; and subsequent&lt;br&gt;
error when calling &lt;code&gt;KVM_SET_ONE_REG&lt;/code&gt; can be found on my GitHub page:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-arm-part1-code&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-arm-part1-code&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22243/porting-my-rust-kvm-hypervisor-to-arm64-working-with-registers</guid>
<pubDate>Fri, 10 Jul 2026 17:44:14 +0000</pubDate>
</item>
<item>
<title>Building a KVM Virtual Machine in Rust: Running a binary</title>
<link>https://coderlegion.com/21481/building-a-kvm-virtual-machine-in-rust-running-a-binary</link>
<description>&lt;h3 id=&quot;recap&quot;&gt;Recap&lt;/h3&gt;
&lt;p&gt;This is a continuation of &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-part2&quot;&gt;Part 2&lt;/a&gt; of KVM in Rust series,&lt;br&gt;
where we successfully set up a memory region for our guest VM. Now we will see&lt;br&gt;
how to proceed further and run a very simple binary in our hypervisor.&lt;/p&gt;
&lt;h3 id=&quot;loading-the-binary&quot;&gt;Loading the binary&lt;/h3&gt;
&lt;p&gt;Now, we have only set up a memory region, but we still haven't loaded any binary&lt;br&gt;
or code to actually run. For this, I have written a small &quot;Hello world&quot; x86&lt;br&gt;
assembly file: &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/blob/kvm-part3-code/samples/hello-world.asm&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/blob/kvm-part3-code/samples/hello-world.asm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notice that the &lt;code&gt;org 0x1000&lt;/code&gt; header is quite arbitrary and it's actually a good&lt;br&gt;
lead of what we also need to do (set the instruction pointer). Let's compile the&lt;br&gt;
assembly file with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;nasm ./samples/hello-world.asm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we want to load our binary into the memory region at address &lt;code&gt;0x1000&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut file = File::open(&quot;./samples/hello-world&quot;)?;
let mut code = Vec::new();
file.read_to_end(&amp;amp;mut code)?;

unsafe {
std::ptr::copy_nonoverlapping(code.as_ptr(),
  (mem_ptr as *mut u8).add(0x1000),
  code.len()) };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are using the more optimal &lt;code&gt;copy_nonoverlapping&lt;/code&gt; because we can be quite&lt;br&gt;
certain that the memory where &lt;code&gt;code&lt;/code&gt; is stored is not overlapping with our&lt;br&gt;
memory region.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-vcpu&quot;&gt;Creating a vCPU&lt;/h3&gt;
&lt;p&gt;As we saw in &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-part1&quot;&gt;Part 1&lt;/a&gt;, we have also discovered that QEMU also&lt;br&gt;
calls &lt;code&gt;KVM_SET_VCPU&lt;/code&gt; to obtain a vCPU file descriptor on which it will call&lt;br&gt;
&lt;code&gt;KVM_RUN&lt;/code&gt;. So, all that we have to do to set up a vCPU is to take a look at the&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt; and recreate it in Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;// 140904 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0xae41 /* KVM_CREATE_VCPU */, 0) = 10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;
let vcpu_fd = unsafe { libc::ioctl(self.fd, KVM_CREATE_VCPU, 0usize) };
if vcpu_fd &amp;lt; 0 {
return Err(io::Error::last_os_error());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;investigating-vcpu-operations&quot;&gt;Investigating vCPU operations&lt;/h4&gt;
&lt;p&gt;It would be tempting to simply call &lt;code&gt;KVM_RUN&lt;/code&gt; on this file descriptor, however,&lt;br&gt;
we don't really know how vCPU is set up, where the instruction pointer is and&lt;br&gt;
even if the registers are in the valid state. If we look at the &lt;code&gt;ioctl&lt;/code&gt; calls&lt;br&gt;
referring to &lt;code&gt;kvm-vcpu:0&lt;/code&gt; in our log (and filtering out the spamming &lt;code&gt;KVM_RUN&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;KVM_SMI&lt;/code&gt;), we can see a lot of lines like these being repeated:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep 'kvm-vcpu:0' kvm.log | grep -v 'RUN\|SMI'

                ### omitted a lot of repetitive strace output ###

140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xc080aebe /* KVM_GET_NESTED_STATE */, 0x7768f8002010) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xc028ae92 /* KVM_TPR_ACCESS_REPORTING */, 0x77690198f090) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f2f0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4090ae82 /* KVM_SET_REGS */, {rax=0x20, ..., rsp=0x6d88, rbp=0, ..., rip=0x18, rflags=0x246}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x5000aea5 /* KVM_SET_XSAVE */, 0x7768f8001000) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4188aea7 /* KVM_SET_XCRS */, 0x77690198f2c0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4008ae89 /* KVM_SET_MSRS */, 0x7768f80040a0) = 59
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4040aea0 /* KVM_SET_VCPU_EVENTS */, 0x77690198f500) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4008ae89 /* KVM_SET_MSRS */, 0x7768f80040a0) = 1
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4080aea2 /* KVM_SET_DEBUGREGS */, 0x77690198f500) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xc008ae88 /* KVM_GET_MSRS */, 0x77690198f5a0) = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we see that QEMU is setting vCPU registers via &lt;code&gt;KVM_SET_REGS&lt;/code&gt; and in this log&lt;br&gt;
we can see their exact values. It's also calling &lt;code&gt;KVM_SET_SREGS2&lt;/code&gt; which is&lt;br&gt;
setting system registers; unfortunately, we cannot see the values used here.&lt;br&gt;
Also, we do not need to follow the whole logic here, we just want to see what&lt;br&gt;
QEMU is doing with registers before calling &lt;code&gt;KVM_RUN&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ awk '/KVM_RUN/ {print; exit} {print}' kvm.log | grep '[GS]ET_[S]\?REGS\|KVM_RUN'
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f310) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4090ae82 /* KVM_SET_REGS */, {rax=0, ..., rsp=0, rbp=0, ..., rip=0xfff0, rflags=0x2}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x8090ae81 /* KVM_GET_REGS */, {rax=0, ..., rsp=0, rbp=0, ..., rip=0xfff0, rflags=0x2}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x8140aecc /* KVM_GET_SREGS2 */, 0x77690198f310) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f310) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4090ae82 /* KVM_SET_REGS */, {rax=0, ..., rsp=0, rbp=0, ..., rip=0xfff0, rflags=0x2}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = -1 EINTR (Interrupted system call)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since QEMU first calls &lt;code&gt;SET_SREGS2&lt;/code&gt; and &lt;code&gt;SET_REGS&lt;/code&gt; we should actually&lt;br&gt;
investigate what the default (if any) values are in our own implementation.&lt;/p&gt;
&lt;h4 id=&quot;implementing-register-get-set-operations-in-rust&quot;&gt;Implementing register get/set operations in Rust&lt;/h4&gt;
&lt;p&gt;The procedure for this is as with all other KVM &lt;code&gt;ioctl&lt;/code&gt; calls we implemented.&lt;br&gt;
We find out how to construct their numbers (consult the previous article here)&lt;br&gt;
and reimplement these constants in Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;const KVM_GET_SREGS2 : u64 = _IOR::&amp;lt;kvm_sregs2&amp;gt;(KVMIO, 0xcc);
const KVM_SET_SREGS2 : u64 = _IOW::&amp;lt;kvm_sregs2&amp;gt;(KVMIO, 0xcd);
const KVM_GET_REGS : u64 = _IOR::&amp;lt;kvm_regs&amp;gt;(KVMIO, 0x81);
const KVM_SET_REGS : u64 = _IOW::&amp;lt;kvm_regs&amp;gt;(KVMIO, 0x82);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to add &lt;code&gt;kvm_sregs2&lt;/code&gt; and &lt;code&gt;kvm_regs&lt;/code&gt; in our &lt;code&gt;build.rs&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;    bindgen::Builder::default()
        .header(&quot;/usr/include/linux/kvm.h&quot;)
        .allowlist_type(&quot;kvm_sregs2&quot;)
        .allowlist_type(&quot;kvm_userspace_memory_region&quot;)
        .allowlist_type(&quot;kvm_regs&quot;)
        .generate_comments(false)
        .generate()?
        .write_to_file(out_path.join(&quot;kvm-bindings.rs&quot;))?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, &lt;code&gt;ioctl&lt;/code&gt; calls are as follows (I will only give example for &lt;code&gt;get_sregs2&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;get_regs&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut sregs2 = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::ioctl(self.fd, KVM_GET_SREGS2, &amp;amp;mut sregs2) };

let mut regs = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::ioctl(self.fd, KVM_GET_REGS, &amp;amp;mut regs) };
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;peeking-at-registers&quot;&gt;Peeking at registers&lt;/h4&gt;
&lt;p&gt;After obtaining &lt;code&gt;sregs2&lt;/code&gt; and &lt;code&gt;regs&lt;/code&gt; we can print them out and see what they are&lt;br&gt;
set to by default (to accomplish this we consult the &lt;code&gt;struct&lt;/code&gt; definition in our&lt;br&gt;
&lt;code&gt;kvm-bindings.rs&lt;/code&gt; (or in the Linux headers) and print out all the values of the&lt;br&gt;
fields we find). For &lt;code&gt;regs&lt;/code&gt; we see they are pre-set to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;RAX=0x0 RBX=0x0 RCX=0x0 RDX=0x600
RSI=0x0 RDI=0x0 RSP=0x0 RBP=0x0
R8=0x0  R9=0x0  R10=0x0 R11=0x0
R12=0x0 R13=0x0 R14=0x0 R15=0x0
RIP=0xfff0      RFLAGS=0x2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Taking a look at our trace from &lt;code&gt;kvm.log&lt;/code&gt; we can see that the ones QEMU sets are&lt;br&gt;
exactly the same (except for &lt;code&gt;RDX&lt;/code&gt; but we can get away with this one).&lt;/p&gt;
&lt;h4 id=&quot;peeking-at-system-registers&quot;&gt;Peeking at system registers&lt;/h4&gt;
&lt;p&gt;Now, for &lt;code&gt;sregs2&lt;/code&gt; it gets a bit more complicated. Our defaults are as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CS      base=0xffff0000 selector=0xf000 limit=0xffff    type=0xb        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

DS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

ES      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

FS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

GS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

SS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

TR      base=0x0        selector=0x0    limit=0xffff    type=0xb        present=0x1
        dpl=0x0         db=0x0          s=0x0   l=0x0   g=0x0           avl=0x0

LDT     base=0x0        selector=0x0    limit=0xffff    type=0x2        present=0x1
        dpl=0x0         db=0x0          s=0x0   l=0x0   g=0x0           avl=0x0

GDT     base=0x0        limit=0xffff

IDT     base=0x0        limit=0xffff

CR0=0x60000010          CR2=0x0         CR3=0x0         CR4=0x0         CR8=0x0

EFER=0x0                APIC_BASE=0xfee00900            FLAGS=0x0

PDPTRS[0]=0x0           PDPTRS[1]=0x0           PDPTRS[2]=0x0           PDPTRS[3]=0x0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Problem is that we don't see any values in our &lt;code&gt;strace&lt;/code&gt; log, only the address of&lt;br&gt;
the variable. So we need to be a bit creative here if we want to find out what &lt;br&gt;
QEMU sets these values to. We could use &lt;code&gt;ptrace&lt;/code&gt; (in fact &lt;code&gt;strace&lt;/code&gt; is built upon&lt;br&gt;
&lt;code&gt;ptrace&lt;/code&gt; API), but it may be a bit too much. Same for &lt;code&gt;uprobes&lt;/code&gt; and &lt;code&gt;eBPF&lt;/code&gt;. We&lt;br&gt;
do have GDB, though, and it's just perfect as a one-off thing here. All we have&lt;br&gt;
to do is run QEMU under GDB and then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(gdb) break ioctl if $rsi == 0x4140aecd
(gdb) run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;0x4140aecd&lt;/code&gt; is the exact value that we extracted from &lt;code&gt;SET_SREGS2&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt; call (in x86 ABI, &lt;code&gt;RSI&lt;/code&gt; is holding the value of second argument):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f310) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once GDB breaks, we can dump memory. However, we don't know the exact size of&lt;br&gt;
&lt;code&gt;struct kvm_sregs2&lt;/code&gt;; we could guess or manually inspect, but quickest way to get&lt;br&gt;
it is to actually have a small C program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;linux/kvm.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

int main() {
  printf(&quot;%lu&quot;, sizeof(struct kvm_sregs2));
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program will return value of &lt;code&gt;320&lt;/code&gt; (at least on my system) and we can then&lt;br&gt;
use this in GDB:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(gdb) dump memory dump.bin $rdx $rdx+320
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;RDX&lt;/code&gt; is holding the third argument, i.e. the address we actually saw&lt;br&gt;
in our &lt;code&gt;strace&lt;/code&gt; log. Now, we can reuse our function for printing &lt;code&gt;sregs2&lt;/code&gt; in&lt;br&gt;
Rust. We simply hack our main function to load the file and reinterpret its data&lt;br&gt;
as &lt;code&gt;sregs2&lt;/code&gt; and then print it. We got:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CSbase=0xffff0000selector=0xf000limit=0xfffftype=0xbpresent=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

DSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

ESbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

FSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

GSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

SSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

TRbase=0x0selector=0x0limit=0xfffftype=0xbpresent=0x1
dpl=0x0db=0x0s=0x0l=0x0g=0x0avl=0x0

LDTbase=0x0selector=0x0limit=0xfffftype=0x2present=0x1
dpl=0x0db=0x0s=0x0l=0x0g=0x0avl=0x0

GDTbase=0x0limit=0xffff

IDTbase=0x0limit=0xffff

CR0=0x60000010CR2=0x0CR3=0x0CR4=0x0CR8=0x0

EFER=0x0APIC_BASE=0xfee00900FLAGS=0x0

PDPTRS[0]=0x0PDPTRS[1]=0x4b275f5fce32f200PDPTRS[2]=0x0PDPTRS[3]=0x3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that this more or less corresponds to the defaults we observed in our&lt;br&gt;
KVM implementation. If we want to change our registers (which we will), then we&lt;br&gt;
will always first get the ones that are current in the vCPU, change the ones we&lt;br&gt;
need and then set them using &lt;code&gt;KVM_SET_REGS&lt;/code&gt; (or &lt;code&gt;KVM_SET_SREGS2&lt;/code&gt;). So one of&lt;br&gt;
first things to do is to set &lt;code&gt;CS&lt;/code&gt; to flat mapping:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut sregs2 = vcpu.get_sregs2()?;
sregs2.cs.base = 0;
sregs2.cs.selector = 0;
vcpu.set_sregs2(sregs2)?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recall that our assembly file was assembled with &lt;code&gt;org 0x1000&lt;/code&gt;, so setting RIP to&lt;br&gt;
&lt;code&gt;0x1000&lt;/code&gt; causes execution to begin at the start of the loaded binary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut regs = vcpu.get_regs()?;
regs.rip = 0x1000;
vcpu.set_regs(regs)?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, we now have a very good process of finding out what QEMU does with&lt;br&gt;
registers.&lt;/p&gt;
&lt;h3 id=&quot;running-the-binary&quot;&gt;Running the binary&lt;/h3&gt;
&lt;p&gt;Now this is where &lt;code&gt;strace&lt;/code&gt; stopped being useful and my understanding of the KVM&lt;br&gt;
run loop became more useful. Another positive thing is that this part of QEMU&lt;br&gt;
source code is quite readable and can be found in function &lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/qemu/v11.0.1/source/accel/kvm/kvm-accel-ops.c&quot;&gt;kvm_vcpu_thread_fn&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The more x86-specific code with exit reasons can be found in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/qemu/v11.0.1/source/target/i386/kvm/kvm.c&quot;&gt;kvm_arch_handle_exit&lt;/a&gt; function.&lt;/p&gt;
&lt;h4 id=&quot;setting-kvm-run-shared-memory-region&quot;&gt;Setting kvm_run shared memory region&lt;/h4&gt;
&lt;p&gt;To wrap things up, first we need to mmap the shared kvm_run structure that KVM&lt;br&gt;
uses to communicate VM-exit information and other runtime state between the&lt;br&gt;
kernel and userspace:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;// 140904 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae04 /* KVM_GET_VCPU_MMAP_SIZE */, 0) = 12288
let kvm_run_size = unsafe {
libc::ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, 0usize) };
let kvm_run_mem = unsafe {
libc::mmap(ptr::null_mut(), kvm_run_size as usize,
   libc::PROT_READ|libc::PROT_WRITE, libc::MAP_SHARED,
   vcpu_fd, 0) };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when we call &lt;code&gt;KVM_RUN&lt;/code&gt; we simply match the exit reasons and act&lt;br&gt;
accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let run = vcpu.kvm_run_mem as *mut kvm_run;

loop {
    let ret = unsafe { libc::ioctl(vcpu.fd, KVM_RUN, 0usize) };
    if ret &amp;lt; 0 {
        return Err(io::Error::last_os_error());
    }

    let exit_reason = unsafe { (*run).exit_reason };

    match exit_reason {
        KVM_EXIT_MMIO =&amp;gt; { /* omitted */ }
        KVM_EXIT_HLT =&amp;gt; {
            println!(&quot;Guest halted.&quot;);
            break; }
        KVM_EXIT_SHUTDOWN =&amp;gt; {
            println!(&quot;Guest shutdown.&quot;);
            break; }
        KVM_EXIT_INTERNAL_ERROR =&amp;gt; {
            return Err(io::Error::other(&quot;KVM internal error.&quot;)); }
        _ =&amp;gt; {
            println!(&quot;EXIT REASON = {}&quot;, exit_reason);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;our-first-output&quot;&gt;Our first output&lt;/h4&gt;
&lt;p&gt;Being that our assembly file uses VGA MMIO to output the &quot;Hello world!&quot; string,&lt;br&gt;
I have only implemented &lt;code&gt;KVM_EXIT_MMIO&lt;/code&gt;. Then we can finally see the output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ cargo run ./samples/hello-world
     ## ommitted sregs2 output and warnings ##
RAX=0x0 RBX=0x0 RCX=0x0 RDX=0x600
RSI=0x0 RDI=0x0 RSP=0x0 RBP=0x0
R8=0x0  R9=0x0  R10=0x0 R11=0x0
R12=0x0 R13=0x0 R14=0x0 R15=0x0
RIP=0x1000      RFLAGS=0x2
Hello from KVM!
Guest halted.
RAX=0xb800      RBX=0x0 RCX=0x0 RDX=0x600
RSI=0x102d      RDI=0x22        RSP=0x0 RBP=0x0
R8=0x0  R9=0x0  R10=0x0 R11=0x0
R12=0x0 R13=0x0 R14=0x0 R15=0x0
RIP=0x101b      RFLAGS=0x46
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This concludes the three-part series on figuring out KVM via &lt;code&gt;strace&lt;/code&gt; and&lt;br&gt;
reimplementing it in Rust. A lot of next steps actually come down to x86&lt;br&gt;
architecture and bootloader specifics, so we may leave it here for now. The full&lt;br&gt;
code capable of running simple binaries can be found on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part3-code&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part3-code&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21481/building-a-kvm-virtual-machine-in-rust-running-a-binary</guid>
<pubDate>Sun, 28 Jun 2026 05:24:29 +0000</pubDate>
</item>
<item>
<title>Building a KVM Virtual Machine in Rust: Memory Setup</title>
<link>https://coderlegion.com/20611/building-a-kvm-virtual-machine-in-rust-memory-setup</link>
<description>&lt;h3 id=&quot;recap-1&quot;&gt;Recap&lt;/h3&gt;
&lt;p&gt;This is a continuation of my &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-part1&quot;&gt;previous article&lt;/a&gt; which dealt&lt;br&gt;
with reverse-engineering QEMU with &lt;code&gt;strace&lt;/code&gt; to learn how KVM works. Now it's&lt;br&gt;
time to try and follow the steps we got from the &lt;code&gt;strace&lt;/code&gt; logs to build our own&lt;br&gt;
KVM-based virtual machine in Rust.&lt;/p&gt;
&lt;h3 id=&quot;kvm-headers&quot;&gt;KVM Headers&lt;/h3&gt;
&lt;p&gt;I haven't actually used existing KVM libraries written specifically for Rust but&lt;br&gt;
opted to use the &lt;code&gt;libc&lt;/code&gt; crate which provides the required &lt;code&gt;ioctl&lt;/code&gt; bindings and&lt;br&gt;
helper macros. The main reason is that I want a complete understanding of what&lt;br&gt;
is happening under the hood. Now for each of these KVM &lt;code&gt;ioctl&lt;/code&gt; calls we can use&lt;br&gt;
the Linux headers for reference. For example, to find out how to construct&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt; number for &lt;code&gt;KVM_CREATE_VM&lt;/code&gt; we simply can do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'KVM_CREATE_VM' /usr/include/linux/
/usr/include/linux/kvm.h:855:/* machine type bits, to be used as argument to KVM_CREATE_VM */
/usr/include/linux/kvm.h:882:#define KVM_CREATE_VM             _IO(KVMIO,   0x01) /* returns a VM fd */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luckily in &lt;code&gt;libc&lt;/code&gt; Rust crate we have macros for &lt;code&gt;_IO&lt;/code&gt; (and the like), but we&lt;br&gt;
still need &lt;code&gt;KVMIO&lt;/code&gt; macro:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'define KVMIO' /usr/include/linux/
/usr/include/linux/kvm.h:853:#define KVMIO 0xAE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now construct the &lt;code&gt;ioctl&lt;/code&gt; number for &lt;code&gt;KVM_CREATE_VM&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;use libc::{_IOW, _IO, _IOR};

const KVMIO : u32 = 0xae;
const KVM_CREATE_VM : u64 = _IO(KVMIO, 0x01);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that exact integer type depends on the platform and &lt;code&gt;libc&lt;/code&gt; definitions.&lt;br&gt;
Then, in the main we can do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(&quot;/dev/kvm&quot;)
        .expect(&quot;failed to open /dev/kvm&quot;);

    let fd = file.as_raw_fd();
    let vm_fd = unsafe { libc::ioctl(fd, KVM_CREATE_VM, 0usize) };
Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the procedure we will follow for each relevant &lt;code&gt;ioctl&lt;/code&gt; call. In fact,&lt;br&gt;
we can &quot;reverse-engineer&quot; our own program and then compare it with the original,&lt;br&gt;
to make sure we are doing the right thing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ strace cargo run

     ### omitted irrelevant strace output ###

openat(AT_FDCWD, &quot;/dev/kvm&quot;, O_RDWR|O_CLOEXEC) = 3
ioctl(3, KVM_CREATE_VM, 0)              = 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;important-note&quot;&gt;Important note&lt;/h4&gt;
&lt;p&gt;In production code we would immediately check for a negative return value and&lt;br&gt;
convert errno into a Rust error. To keep the example focused, I am omitting&lt;br&gt;
proper error handling in this article.&lt;/p&gt;
&lt;h3 id=&quot;setting-memory-region&quot;&gt;Setting memory region&lt;/h3&gt;
&lt;p&gt;Now, to recall, the next step is setting up the memory region used by both the&lt;br&gt;
KVM guest and the host. This region will be the memory of our virtual machine, a&lt;br&gt;
place where we will load our binary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 mmap(NULL, 1075838976, 0 /* PROT_NONE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x7768b3e00000
140900 mmap(0x7768b3e00000, 1073741824, 0x3 /* PROT_READ|PROT_WRITE */, 0x32 /* MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS */, -1, 0) = 0x7768b3e00000
140900 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0x4020ae46 /* KVM_SET_USER_MEMORY_REGION */, {slot=0, flags=0, guest_phys_addr=0, memory_size=1073741824, userspace_addr=0x7768b3e00000}) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;recreating-mmap-call&quot;&gt;Recreating mmap call&lt;/h4&gt;
&lt;p&gt;So, first thing we need to do is follow the same logic for &lt;code&gt;mmap&lt;/code&gt; which is also&lt;br&gt;
available in the &lt;code&gt;libc&lt;/code&gt; Rust crate. After creating the virtual machine, we could&lt;br&gt;
simply recreate our own &lt;code&gt;mmap&lt;/code&gt; calls based on the &lt;code&gt;strace&lt;/code&gt; output. However,&lt;br&gt;
notice that QEMU first reserves a larger address range with &lt;code&gt;PROT_NONE&lt;/code&gt; and then&lt;br&gt;
maps only the portion it actually intends to use. For our prototype we do not&lt;br&gt;
actually need to mimic this exact reservation pattern.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mem_size: u64 = 256 * 1024;
let mem = unsafe {
libc::mmap(ptr::null_mut(),
   mem_size as usize,
   libc::PROT_READ|libc::PROT_WRITE,
   libc::MAP_PRIVATE|libc::MAP_ANONYMOUS,
   -1,
   0)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this experiment we are only allocating 256 kilobytes because we are not yet&lt;br&gt;
booting a full operating system and therefore need very little guest memory.&lt;br&gt;
Also note that, as with &lt;code&gt;ioctl&lt;/code&gt;, production code should check whether mmap()&lt;br&gt;
returned &lt;code&gt;MAP_FAILED&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;recreating-ioctl-call&quot;&gt;Recreating ioctl call&lt;/h4&gt;
&lt;p&gt;First we need to see the definition of the &lt;code&gt;KVM_SET_USER_MEMORY_REGION&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'define KVM_SET_USER' /usr/include/linux/ -A1
/usr/include/linux/kvm.h:1433:#define KVM_SET_USER_MEMORY_REGION _IOW(KVMIO, 0x46, \
/usr/include/linux/kvm.h-1434-                                  struct kvm_userspace_memory_region)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see that for this one, we need &lt;code&gt;struct kvm_userspace_memory_region&lt;/code&gt;. The&lt;br&gt;
values we need are visible in the &lt;code&gt;strace&lt;/code&gt; output, but copying struct definition&lt;br&gt;
to our Rust program is really not advisable. Luckily, Rust has &lt;code&gt;bindgen&lt;/code&gt; which&lt;br&gt;
we can use to get this KVM struct (and others) from Linux headers. For this&lt;br&gt;
purpose we have a separate &lt;code&gt;build.rs&lt;/code&gt; file which will contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;use bindgen;
use std::path::PathBuf;
use std::env;

fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    let out_path = PathBuf::from(env::var(&quot;OUT_DIR&quot;)?);

    bindgen::Builder::default()
.header(&quot;/usr/include/linux/kvm.h&quot;)
.allowlist_type(&quot;kvm_userspace_memory_region&quot;)
.generate_comments(false)
.generate()?
.write_to_file(out_path.join(&quot;kvm-bindings.rs&quot;))?;

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now in the &lt;code&gt;main.rs&lt;/code&gt; we can import these bindings with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;include!(concat!(env!(&quot;OUT_DIR&quot;), &quot;/kvm-bindings.rs&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, after &lt;code&gt;mmap()&lt;/code&gt; calls, we set the memory region, also imitating what&lt;br&gt;
QEMU is doing in the &lt;code&gt;strace&lt;/code&gt; output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let region = kvm_userspace_memory_region {
slot : 0,
flags : 0,
guest_phys_addr : 0x0,
memory_size : mem_size,
userspace_addr : mem as u64
};

let _ret = unsafe { libc::ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &amp;amp;region) };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We register this region starting at guest physical address &lt;code&gt;0x0&lt;/code&gt;, meaning the&lt;br&gt;
first byte of our allocated host memory will appear as physical address 0 inside&lt;br&gt;
the guest. Also note that the &lt;code&gt;KVM_SET_USER_MEMORY_REGION&lt;/code&gt; call does not copy&lt;br&gt;
memory, but rather tells KVM that guest physical address will be backed by a&lt;br&gt;
specific userspace memory region.&lt;/p&gt;
&lt;h3 id=&quot;running-the-code&quot;&gt;Running the code&lt;/h3&gt;
&lt;p&gt;Next thing to do is to run it. We still haven't added any checks after &lt;code&gt;mmap&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;ioctl&lt;/code&gt; calls, but for this prototype we can again simply use &lt;code&gt;strace&lt;/code&gt; our&lt;br&gt;
own code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ strace -yy -X verbose -e trace=ioctl,mmap,openat,read,write cargo run

                ### omitted irrelevant strace output ###

openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM/rust&amp;gt;, &quot;/dev/kvm&quot;, 0x80002 /* O_RDWR|O_CLOEXEC */) = 3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;
ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae01 /* KVM_CREATE_VM */, 0) = 4&amp;lt;anon_inode:kvm-vm&amp;gt;
mmap(NULL, 262144, 0x3 /* PROT_READ|PROT_WRITE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x7d63fdfa2000
ioctl(4&amp;lt;anon_inode:kvm-vm&amp;gt;, 0x4020ae46 /* KVM_SET_USER_MEMORY_REGION */, {slot=0, flags=0, guest_phys_addr=0, memory_size=262144, userspace_addr=0x7d63fdfa2000}) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see our output is fine and no errors were reported. Note that a full&lt;br&gt;
working example with proper checking and Rust idiomatic approaches can be found&lt;br&gt;
on my GitHub page:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part2-code&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part2-code&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;next-steps&quot;&gt;Next steps&lt;/h4&gt;
&lt;p&gt;At this point we have a VM object and guest memory, but nothing is actually executing yet. In the next part we will create a vCPU, initialize its state, load a small binary into guest memory and enter the first &lt;code&gt;KVM_RUN&lt;/code&gt; loop.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20611/building-a-kvm-virtual-machine-in-rust-memory-setup</guid>
<pubDate>Mon, 15 Jun 2026 05:20:13 +0000</pubDate>
</item>
<item>
<title>Learning KVM by Reverse-Engineering QEMU with strace</title>
<link>https://coderlegion.com/20315/learning-kvm-by-reverse-engineering-qemu-with-strace</link>
<description>&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;I work with virtual machines in QEMU/KVM environment (a lot). In order to debug,&lt;br&gt;
optimize and customize the VMs requires an in-depth knowledge of both QEMU and&lt;br&gt;
KVM, the Linux kernel virtualization subsystem that exposes hardware&lt;br&gt;
virtualization features such as Intel VT-x and AMD-V to userspace applications&lt;br&gt;
like QEMU. Not only that, but I work on a lot of hobby projects requiring quick&lt;br&gt;
bare-metal boot-ups and debugging workflows, and, to be honest, a lot of times&lt;br&gt;
QEMU is an overkill for these sorts of tasks.&lt;/p&gt;
&lt;h3 id=&quot;kvm-vs-tcg&quot;&gt;KVM vs TCG&lt;/h3&gt;
&lt;p&gt;Also when it comes to QEMU, it's worth noting that we always have an option of&lt;br&gt;
using TCG, which has a completely different purpose than KVM. TCG is short for&lt;br&gt;
Tiny Code Generator, which works by translating guest instructions into host&lt;br&gt;
instructions at runtime. This is quite slow compared to running code without&lt;br&gt;
translation overhead. So, if we want to test our bare-metal code, we may want to&lt;br&gt;
test it on our own real CPU at native speed. This is where KVM comes in. Unlike&lt;br&gt;
TCG, KVM does not emulate the CPU itself. Instead, it allows guest code to&lt;br&gt;
execute directly on the host processor while the Linux kernel manages&lt;br&gt;
transitions between guest and host execution.&lt;/p&gt;
&lt;h3 id=&quot;how-kvm-works-in-linux&quot;&gt;How KVM works in Linux&lt;/h3&gt;
&lt;p&gt;So, I do already know some basics. KVM driver exposes a driver interface in&lt;br&gt;
Linux root filesystem, &lt;code&gt;/dev/kvm&lt;/code&gt;. Communicating with the driver is done via&lt;br&gt;
&lt;code&gt;ioctl()&lt;/code&gt; system call on a file descriptor. What we need to find out is how QEMU&lt;br&gt;
communicates with Linux kernel and try and follow the QEMU logic without&lt;br&gt;
reading QEMU source code and KVM API (both can be a bit more intimidating than&lt;br&gt;
just seeing how it works under the hood).&lt;/p&gt;
&lt;h3 id=&quot;reverse-engineering-kvm&quot;&gt;Reverse-engineering KVM&lt;/h3&gt;
&lt;p&gt;Now we can take some lightweight Debian Linux image and load it into the QEMU,&lt;br&gt;
with KVM enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;QEMU_IMAGE=./debian-12-nocloud-amd64.qcow2

qemu-system-x86_64\
-m 1024\
-drive file=&quot;${QEMU_IMAGE}&quot;,if=virtio,cache=none\
-serial stdio\
-enable-kvm\
-cpu host\
-nodefaults\
-nographic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a pretty straightforward way to run QEMU with minimal setup. The most&lt;br&gt;
relevant options for us are &lt;code&gt;-enable-kvm&lt;/code&gt; and &lt;code&gt;-cpu host&lt;/code&gt;, which will enable&lt;br&gt;
KVM and use host CPU instead of emulating some specific CPU.&lt;/p&gt;
&lt;h4 id=&quot;tracing-qemu-kvm-with-strace&quot;&gt;Tracing QEMU/KVM with strace&lt;/h4&gt;
&lt;p&gt;Now, we want to see what QEMU is really doing by utilizing &lt;code&gt;strace&lt;/code&gt;. We can put this command in a &lt;code&gt;start-qemu.sh&lt;/code&gt; script and call it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;strace -yy -f -X verbose\
   -e trace=ioctl,openat,read,write,mmap\
   -o kvm.log\
   ./start-qemu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will trace all &lt;code&gt;ioctl&lt;/code&gt;, &lt;code&gt;openat&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt; and &lt;code&gt;mmap&lt;/code&gt; system&lt;br&gt;
calls. Although I mentioned only &lt;code&gt;ioctl&lt;/code&gt; calls so far, I always like to include&lt;br&gt;
some other common system calls that could be used. As far as we know &lt;code&gt;/dev/kvm&lt;/code&gt;&lt;br&gt;
is the interface to KVM driver and QEMU will probably use &lt;code&gt;openat&lt;/code&gt; on it.&lt;br&gt;
Similarly, we also want to see what QEMU is doing with memory and what it's&lt;br&gt;
reading and writing in general.&lt;/p&gt;
&lt;p&gt;Note: Information on &lt;code&gt;strace&lt;/code&gt; arguments as above can be found in &lt;code&gt;strace --help&lt;/code&gt;&lt;br&gt;
or &lt;code&gt;man strace&lt;/code&gt;, but essentially, the &lt;code&gt;-yy&lt;/code&gt; tells strace to print all available&lt;br&gt;
information when decoding file descriptors, &lt;code&gt;-f&lt;/code&gt; follows forks (we need this one&lt;br&gt;
as we're wrapping it in scripts and QEMU might also do similar stuff). The&lt;br&gt;
&lt;code&gt;-X verbose&lt;/code&gt; will print names of constants and flags (very important when&lt;br&gt;
analyzing &lt;code&gt;ioctl&lt;/code&gt; calls);&lt;/p&gt;
&lt;h4 id=&quot;interpreting-the-logs&quot;&gt;Interpreting the logs&lt;/h4&gt;
&lt;p&gt;Now, we start the above command and, as soon as system boots, we can kill it&lt;br&gt;
with &lt;code&gt;CTRL+C&lt;/code&gt;. This will be quite sufficient to see how QEMU/KVM works without&lt;br&gt;
spamming our logs with redundant information. When we read the &lt;code&gt;kvm.log&lt;/code&gt; file,&lt;br&gt;
we will see a lot of traces that are not really interesting. However, we already&lt;br&gt;
have some knowledge: we know QEMU should be opening &lt;code&gt;/dev/kvm&lt;/code&gt; so a quick&lt;br&gt;
search for &lt;code&gt;kvm&lt;/code&gt; reveals exactly what we need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, &quot;/dev/kvm&quot;, 0x80002 /* O_RDWR|O_CLOEXEC */) = 3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae00 /* KVM_GET_API_VERSION */, 0) = 12
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x88 /* KVM_CAP_IMMEDIATE_EXIT */) = 1
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0xa /* KVM_CAP_NR_MEMSLOTS */) = 32764
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x76 /* KVM_CAP_MULTI_ADDRESS_SPACE */) = 2
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae01 /* KVM_CREATE_VM */, 0) = 9&amp;lt;anon_inode:kvm-vm&amp;gt;
140900 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x9 /* KVM_CAP_NR_VCPUS */) = 4
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x42 /* KVM_CAP_MAX_VCPUS */) = 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that QEMU is opening &lt;code&gt;/dev/kvm&lt;/code&gt; and that it's checking API version&lt;br&gt;
and various extensions. We may skip these checks and focus on the calls that&lt;br&gt;
look most important; one of these here is &lt;code&gt;KVM_CREATE_VM&lt;/code&gt; which also returns a&lt;br&gt;
file descriptor &lt;code&gt;9&amp;lt;anon_inode:kvm-vm&amp;gt;&lt;/code&gt; which we can use as a further reference.&lt;/p&gt;
&lt;h5 id=&quot;setting-up-memory-regions&quot;&gt;Setting up memory regions&lt;/h5&gt;
&lt;p&gt;We know QEMU must eventually load firmware and guest memory into the VM. Looking&lt;br&gt;
for file operations after &lt;code&gt;KVM_CREATE_VM&lt;/code&gt;, we quickly encounter SeaBIOS being&lt;br&gt;
loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, &quot;/usr/share/seabios/bios-256k.bin&quot;, 0 /* O_RDONLY */) = 12&amp;lt;/usr/share/seabios/bios-256k.bin&amp;gt;
140900 mmap(NULL, 2359296, 0 /* PROT_NONE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x776900dc1000
140900 mmap(0x776900e00000, 262144, 0x3 /* PROT_READ|PROT_WRITE */, 0x32 /* MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS */, -1, 0) = 0x776900e00000
140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, &quot;/usr/share/seabios/bios-256k.bin&quot;, 0 /* O_RDONLY */) = 12&amp;lt;/usr/share/seabios/bios-256k.bin&amp;gt;
140900 mmap(NULL, 266240, 0x3 /* PROT_READ|PROT_WRITE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x776900fc0000
140900 read(12&amp;lt;/usr/share/seabios/bios-256k.bin&amp;gt;, &quot;\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&quot;..., 262144) = 262144
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see QEMU opening and reading SeaBIOS binary and reserving memory along&lt;br&gt;
with it. We can use the return of the &lt;code&gt;read&lt;/code&gt; system call (the size of the&lt;br&gt;
SeaBIOS binary) and see what KVM is doing with it. Searching through the log&lt;br&gt;
for this size gives us further information on what KVM is doing with SeaBIOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0x4020ae46 /* KVM_SET_USER_MEMORY_REGION */, {slot=3, flags=0x2 /* KVM_MEM_READONLY */, guest_phys_addr=0xfffc0000, memory_size=262144, userspace_addr=0x776900e00000}) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we see that it's now setting this as the memory region for KVM at guest&lt;br&gt;
physical address &lt;code&gt;0xfffc0000&lt;/code&gt; from userspace address that was actually&lt;br&gt;
obtained by &lt;code&gt;mmap&lt;/code&gt; in one of the traces above. In other words, KVM does not&lt;br&gt;
allocate guest RAM itself; userspace applications such as QEMU remain&lt;br&gt;
responsible for managing the backing memory.&lt;/p&gt;
&lt;h5 id=&quot;creating-vcpu-and-running&quot;&gt;Creating vCPU and running&lt;/h5&gt;
&lt;p&gt;Now, it gets very busy in the logs, but most of the stuff we see is still just&lt;br&gt;
checking for extensions and capabilities. However, if we take a look at the&lt;br&gt;
tail of the log, we will see a lot of these &lt;code&gt;ioctl&lt;/code&gt; calls:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xaeb7 /* KVM_SMI */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xaeb7 /* KVM_SMI */, 0) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now both &lt;code&gt;KVM_RUN&lt;/code&gt; and &lt;code&gt;KVM_SMI&lt;/code&gt; are operating on a &lt;code&gt;kvm-vcpu&lt;/code&gt; file descriptor,&lt;br&gt;
something we haven't yet seen. So if we search the logs for it, we can actually&lt;br&gt;
see where it's created:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140904 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0xae41 /* KVM_CREATE_VCPU */, 0) = 10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now we have a more complete picture of how QEMU is setting up KVM. First,&lt;br&gt;
&lt;code&gt;/dev/kvm&lt;/code&gt; is opened to obtain a file descriptor representing the KVM subsystem.&lt;br&gt;
From it we create a new virtual machine and get &lt;code&gt;kvm-vm&lt;/code&gt; file descriptor. On&lt;br&gt;
this file descriptor we are setting up memory regions and later use it to create&lt;br&gt;
a vCPU, on which we can call &lt;code&gt;KVM_RUN&lt;/code&gt;. The following diagram explains it&lt;br&gt;
better:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/dev/kvm   kvm fd
    |
    +--&amp;gt; KVM_CREATE_VM   kvm-vm fd
            |
            +--&amp;gt; KVM_SET_USER_MEMORY_REGION
            |
            +--&amp;gt; KVM_CREATE_VCPU   kvm-vcpu fd
                            |
                            +--&amp;gt; KVM_RUN (loop)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we can see from the logs, &lt;code&gt;KVM_RUN&lt;/code&gt; appears repeatedly during guest&lt;br&gt;
execution, while occasional &lt;code&gt;KVM_SMI&lt;/code&gt; calls inject System Management Interrupts&lt;br&gt;
into the guest. This repeated interaction between userspace and KVM is what&lt;br&gt;
ultimately drives virtual CPU execution.&lt;/p&gt;
&lt;p&gt;Next time we will recreate this exact behavior in Rust and also see about just&lt;br&gt;
a few missing pieces to get our first virtual machine running in KVM.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20315/learning-kvm-by-reverse-engineering-qemu-with-strace</guid>
<pubDate>Thu, 11 Jun 2026 15:56:48 +0000</pubDate>
</item>
</channel>
</rss>