<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stjepan</title>
    <description>The latest articles on DEV Community by Stjepan (@stjepan86).</description>
    <link>https://dev.to/stjepan86</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3935981%2F6fc2fd86-a222-4abd-a4c4-8471e94ebf6b.png</url>
      <title>DEV Community: Stjepan</title>
      <link>https://dev.to/stjepan86</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stjepan86"/>
    <language>en</language>
    <item>
      <title>Building a KVM Virtual Machine in Rust: Running a binary</title>
      <dc:creator>Stjepan</dc:creator>
      <pubDate>Sun, 28 Jun 2026 05:26:47 +0000</pubDate>
      <link>https://dev.to/stjepan86/building-a-kvm-virtual-machine-in-rust-running-a-binary-ian</link>
      <guid>https://dev.to/stjepan86/building-a-kvm-virtual-machine-in-rust-running-a-binary-ian</guid>
      <description>&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;This is a continuation of &lt;a href="//../kvm-rust-part2"&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;h2&gt;
  
  
  Loading the binary
&lt;/h2&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 "Hello world" x86&lt;br&gt;
assembly file: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/StjepanPoljak/kvm-rust/blob/kvm-part3-code/samples/hello-world.asm" rel="noopener noreferrer"&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nasm ./samples/hello-world.asm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;File&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./samples/hello-world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="nf"&gt;.read_to_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;copy_nonoverlapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem_ptr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0x1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                  &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  Creating a vCPU
&lt;/h2&gt;

&lt;p&gt;As we saw in &lt;a href="//../kvm-rust-part1"&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&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;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vcpu_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_CREATE_VCPU&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vcpu_fd&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;last_os_error&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Investigating vCPU operations
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;h3&gt;
  
  
  Implementing register get/set operations in Rust
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KVM_GET_SREGS2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;_IOR&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kvm_sregs2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KVMIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0xcc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KVM_SET_SREGS2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;_IOW&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kvm_sregs2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KVMIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0xcd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KVM_GET_REGS&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;_IOR&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kvm_regs&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KVMIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x81&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KVM_SET_REGS&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;_IOW&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;kvm_regs&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KVMIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x82&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="nn"&gt;bindgen&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/usr/include/linux/kvm.h"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.allowlist_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kvm_sregs2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.allowlist_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kvm_userspace_memory_region"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.allowlist_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kvm_regs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.generate_comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.write_to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kvm-bindings.rs"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sregs2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;zeroed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_GET_SREGS2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sregs2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;zeroed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_GET_REGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Peeking at registers
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;h3&gt;
  
  
  Peeking at system registers
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) break ioctl if $rsi == 0x4140aecd
(gdb) run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/kvm.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%lu"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;kvm_sregs2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(gdb) dump memory dump.bin $rdx $rdx+320
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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]=0x4b275f5fce32f200        PDPTRS[2]=0x0       PDPTRS[3]=0x3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sregs2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vcpu&lt;/span&gt;&lt;span class="nf"&gt;.get_sregs2&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sregs2&lt;/span&gt;&lt;span class="py"&gt;.cs.base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;sregs2&lt;/span&gt;&lt;span class="py"&gt;.cs.selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;vcpu&lt;/span&gt;&lt;span class="nf"&gt;.set_sregs2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sregs2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;regs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vcpu&lt;/span&gt;&lt;span class="nf"&gt;.get_regs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="py"&gt;.rip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0x1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;vcpu&lt;/span&gt;&lt;span class="nf"&gt;.set_regs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  Running the binary
&lt;/h2&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 href="https://elixir.bootlin.com/qemu/v11.0.1/source/accel/kvm/kvm-accel-ops.c" rel="noopener noreferrer"&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 href="https://elixir.bootlin.com/qemu/v11.0.1/source/target/i386/kvm/kvm.c" rel="noopener noreferrer"&gt;kvm_arch_handle_exit&lt;/a&gt; function.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting kvm_run shared memory region
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&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&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kvm_run_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kvm_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_GET_VCPU_MMAP_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kvm_run_mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;null_mut&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;kvm_run_size&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PROT_READ&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PROT_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MAP_SHARED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;vcpu_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vcpu&lt;/span&gt;&lt;span class="py"&gt;.kvm_run_mem&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;kvm_run&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vcpu&lt;/span&gt;&lt;span class="py"&gt;.fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_RUN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;last_os_error&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;exit_reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="py"&gt;.exit_reason&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;exit_reason&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;KVM_EXIT_MMIO&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* omitted */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;KVM_EXIT_HLT&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Guest halted."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;KVM_EXIT_SHUTDOWN&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Guest shutdown."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;KVM_EXIT_INTERNAL_ERROR&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;other&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KVM internal error."&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"EXIT REASON = {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exit_reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Our first output
&lt;/h3&gt;

&lt;p&gt;Being that our assembly file uses VGA MMIO to output the "Hello world!" 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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&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 href="https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part3-code" rel="noopener noreferrer"&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part3-code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kvm</category>
      <category>qemu</category>
      <category>rust</category>
    </item>
    <item>
      <title>Building a KVM Virtual Machine in Rust: Memory Setup</title>
      <dc:creator>Stjepan</dc:creator>
      <pubDate>Mon, 15 Jun 2026 05:21:59 +0000</pubDate>
      <link>https://dev.to/stjepan86/building-a-kvm-virtual-machine-in-rust-memory-setup-3fh7</link>
      <guid>https://dev.to/stjepan86/building-a-kvm-virtual-machine-in-rust-memory-setup-3fh7</guid>
      <description>&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;This is a continuation of my &lt;a href="//../kvm-rust-part1"&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;h2&gt;
  
  
  KVM Headers
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ grep -Rn 'define KVMIO' /usr/include/linux/
/usr/include/linux/kvm.h:853:#define KVMIO 0xAE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;_IOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_IO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_IOR&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KVMIO&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0xae&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KVM_CREATE_VM&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_IO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KVMIO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OpenOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/dev/kvm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to open /dev/kvm"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="nf"&gt;.as_raw_fd&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vm_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_CREATE_VM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 "reverse-engineer" our own program and then compare it with the original,&lt;br&gt;
to make sure we are doing the right thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ strace cargo run

     ### omitted irrelevant strace output ###

openat(AT_FDCWD, "/dev/kvm", O_RDWR|O_CLOEXEC) = 3
ioctl(3, KVM_CREATE_VM, 0)              = 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Important note
&lt;/h3&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;h2&gt;
  
  
  Setting memory region
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&gt;



&lt;h3&gt;
  
  
  Recreating mmap call
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;mem_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;null_mut&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
               &lt;span class="n"&gt;mem_size&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PROT_READ&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PROT_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MAP_PRIVATE&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MAP_ANONYMOUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h3&gt;
  
  
  Recreating ioctl call
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;bindgen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;out_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OUT_DIR"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nn"&gt;bindgen&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/usr/include/linux/kvm.h"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.allowlist_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kvm_userspace_memory_region"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.generate_comments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.write_to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_path&lt;/span&gt;&lt;span class="nf"&gt;.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kvm-bindings.rs"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in the &lt;code&gt;main.rs&lt;/code&gt; we can import these bindings with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;include!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;concat!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;env!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OUT_DIR"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"/kvm-bindings.rs"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kvm_userspace_memory_region&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;guest_phys_addr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;memory_size&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mem_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;userspace_addr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nn"&gt;libc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KVM_SET_USER_MEMORY_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h2&gt;
  
  
  Running the code
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;, "/dev/kvm", 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;/div&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 href="https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part2-code" rel="noopener noreferrer"&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part2-code&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps
&lt;/h3&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>kvm</category>
      <category>rust</category>
      <category>qemu</category>
    </item>
    <item>
      <title>Learning KVM by Reverse-Engineering QEMU with strace</title>
      <dc:creator>Stjepan</dc:creator>
      <pubDate>Fri, 05 Jun 2026 18:42:53 +0000</pubDate>
      <link>https://dev.to/stjepan86/learning-kvm-by-reverse-engineering-qemu-with-strace-445a</link>
      <guid>https://dev.to/stjepan86/learning-kvm-by-reverse-engineering-qemu-with-strace-445a</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I work with virtual machines in QEMU/KVM environment (a lot). In order to debug, optimize and customize the VMs requires an in-depth knowledge of both QEMU and KVM, the Linux kernel virtualization subsystem that exposes hardware virtualization features such as Intel VT-x and AMD-V to userspace applications 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 QEMU is an overkill for these sorts of tasks.&lt;/p&gt;
&lt;h2&gt;
  
  
  KVM vs TCG
&lt;/h2&gt;

&lt;p&gt;Also when it comes to QEMU, it's worth noting that we always have an option of using TCG, which has a completely different purpose than KVM. TCG is short for Tiny Code Generator, which works by translating guest instructions into host 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 test it on our own real CPU at native speed. This is where KVM comes in. Unlike TCG, KVM does not emulate the CPU itself. Instead, it allows guest code to execute directly on the host processor while the Linux kernel manages&lt;br&gt;
transitions between guest and host execution.&lt;/p&gt;
&lt;h2&gt;
  
  
  How KVM works in Linux
&lt;/h2&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;h2&gt;
  
  
  Reverse-engineering KVM
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;QEMU_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./debian-12-nocloud-amd64.qcow2

qemu-system-x86_64                                      &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-m&lt;/span&gt; 1024                                             &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-drive&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;QEMU_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,if&lt;span class="o"&gt;=&lt;/span&gt;virtio,cache&lt;span class="o"&gt;=&lt;/span&gt;none    &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-serial&lt;/span&gt; stdio                                       &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-enable-kvm&lt;/span&gt;                                         &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-cpu&lt;/span&gt; host                                           &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-nodefaults&lt;/span&gt;                                         &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-nographic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h3&gt;
  
  
  Tracing QEMU/KVM with strace
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strace &lt;span class="nt"&gt;-yy&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; verbose                                &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ioctl,openat,read,write,mmap            &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-o&lt;/span&gt; kvm.log                                       &lt;span class="se"&gt;\&lt;/span&gt;
       ./start-qemu.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;h3&gt;
  
  
  Interpreting the logs
&lt;/h3&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, "/dev/kvm", 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;/div&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;h4&gt;
  
  
  Setting up memory regions
&lt;/h4&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, "/usr/share/seabios/bios-256k.bin", 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;, "/usr/share/seabios/bios-256k.bin", 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;, "\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"..., 262144) = 262144
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;h4&gt;
  
  
  Creating vCPU and running
&lt;/h4&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&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;/div&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>kvm</category>
      <category>linux</category>
      <category>qemu</category>
    </item>
    <item>
      <title>Automating Stack Corruption Analysis in GDB with Python</title>
      <dc:creator>Stjepan</dc:creator>
      <pubDate>Mon, 25 May 2026 12:12:18 +0000</pubDate>
      <link>https://dev.to/stjepan86/automating-stack-corruption-analysis-in-gdb-with-python-1h3</link>
      <guid>https://dev.to/stjepan86/automating-stack-corruption-analysis-in-gdb-with-python-1h3</guid>
      <description>&lt;h2&gt;
  
  
  A bug in my operating system
&lt;/h2&gt;

&lt;p&gt;During a recent visit to my wife's family in Sarajevo, I decided to revisit my&lt;br&gt;
hobby operating system in QEMU. I discovered that the boot process consistently&lt;br&gt;
froze while printing the BIOS memory map. What initially looked like a&lt;br&gt;
protected-mode issue eventually turned into a useful exercise in automating&lt;br&gt;
debugging using GDB's Python scripting.&lt;/p&gt;
&lt;h2&gt;
  
  
  Manual debugging failure
&lt;/h2&gt;

&lt;p&gt;After reinspecting some common pitfalls in my protected mode setup I was more&lt;br&gt;
confident that the issue was stack corruption in my &lt;code&gt;print_memory_map&lt;/code&gt; function.&lt;br&gt;
If I had an unmatched push or pop, it could have corrupted return addresses and&lt;br&gt;
eventually redirected execution flow into invalid memory. Single-stepping&lt;br&gt;
through the routine manually quickly became impractical. The function mixed BIOS&lt;br&gt;
interrupt handling, memory map parsing, and multiple helper calls, making it&lt;br&gt;
difficult to reason about stack state over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up GDB scripting in Python
&lt;/h2&gt;

&lt;p&gt;I needed to automate this and GDB's integration with Python was the most&lt;br&gt;
promising route I could take. The idea was to do exactly what I started&lt;br&gt;
manually: break at a specific (suspicious) function and then start single&lt;br&gt;
stepping while inspecting how the stack pointer behaved.&lt;/p&gt;

&lt;p&gt;First things first, we import GDB module in Python, connect to the remote&lt;br&gt;
target and set up a breakpoint (this is done by inheriting from&lt;br&gt;
&lt;code&gt;gdb.Breakpoint&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gdb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StackTraceBreakpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Breakpoint&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StackTraceBreakpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recursion detected - stopping.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target remote :5555&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol-file ../build/arch/x86/bios-legacy/boot-stage1-5.elf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StackTraceBreakpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;print_memory_map&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;continue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  StackTraceBreakpoint
&lt;/h3&gt;

&lt;p&gt;So this is kind of bare-bones of what I wanted to do. This code will simply add&lt;br&gt;
a breakpoint with custom logic after connecting to QEMU and loading symbols.&lt;br&gt;
As soon as we do &lt;code&gt;gdb.execute("continue")&lt;/code&gt; GDB will run and, if and when it hits&lt;br&gt;
our breakpoint, it will execute whatever we wrote in the &lt;code&gt;stop()&lt;/code&gt; method. For&lt;br&gt;
now I only added a kind of assertion that we cannot analyze recursions (I didn't&lt;br&gt;
use any in my code anyway and logic would be a bit more complex).&lt;/p&gt;
&lt;h3&gt;
  
  
  Single-stepping
&lt;/h3&gt;

&lt;p&gt;Now what we need to do is start single-stepping after we hit our breakpoint. So&lt;br&gt;
we add a while loop with &lt;code&gt;gdb.execute("stepi")&lt;/code&gt; after &lt;code&gt;gdb.execute("continue")&lt;/code&gt;.&lt;br&gt;
Note that the &lt;code&gt;stepi&lt;/code&gt; instruction steps over machine instructions, not over&lt;br&gt;
source code statements. Also note that We cannot start single-stepping in&lt;br&gt;
&lt;code&gt;stop()&lt;/code&gt; method because GDB won't be in a state which can accept these kinds of&lt;br&gt;
debugging requests.&lt;/p&gt;
&lt;h2&gt;
  
  
  Detecting stack imbalance
&lt;/h2&gt;

&lt;p&gt;Furthermore I have wrapped this single-stepping logic in &lt;code&gt;trace_step()&lt;/code&gt; method&lt;br&gt;
in our breakpoint class. This method is not part of GDB breakpoint API, but&lt;br&gt;
rather as a convenience for tracking the number of pushes and pops in a&lt;br&gt;
consistent manner. To run this script we need to call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gdb &lt;span class="nt"&gt;-ex&lt;/span&gt; &lt;span class="s1"&gt;'source debug-stack.py'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another thing we need to track is whether we have entered another function (in&lt;br&gt;
which case we won't be counting pushes and pops) and if we have returned from&lt;br&gt;
it. If I ever wanted to inspect routines being called, I would just run the same&lt;br&gt;
script for them (my intention here is not creating a custom emulator on top of&lt;br&gt;
GDB). So I added a counter &lt;code&gt;func_count&lt;/code&gt; which will increase on &lt;code&gt;call&lt;/code&gt; and&lt;br&gt;
decrease on &lt;code&gt;ret&lt;/code&gt; instruction. Here is a rough idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trace_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;insn_full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x/i $pc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;insn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^=&amp;gt;.*:\s*([^\s].*)$&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insn_full&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="n"&gt;insn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func_count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pushes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func_count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pops&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func_count&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can see how I'm extracting the instruction from GDB. And what is left&lt;br&gt;
is just improving logic and also fetching registers like PC, SP and CS for&lt;br&gt;
debugging. I log everything into a file as GDB can get really noisy with&lt;br&gt;
standard output (I didn't find a way to turn off all logging in GDB completely&lt;br&gt;
when single stepping). The script is available in my GitHub repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/StjepanPoljak/raspios/tree/master/scripts/debug-stack.py" rel="noopener noreferrer"&gt;https://github.com/StjepanPoljak/raspios/tree/master/scripts/debug-stack.py&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding the root cause
&lt;/h2&gt;

&lt;p&gt;Finally, you can see an example output detecting my very issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[START] print_memory_map SP=fffd
[0000:8183] push %ax (SP=0xfffd)
[0000:8185] push %bx (SP=0xfff9)
[0000:8187] push %cx (SP=0xfff5)
[0000:8189] push %dx (SP=0xfff1)
[0000:81a5] call 0x66e980a5 (SP=0xffed)
[0000:80a3] push %ax (SP=0xffeb)
[0000:80a7] call 0xab18056 (SP=0xffe7)
(...)
[0000:806e] ret  (SP=0xffef)
[0000:8098] call 0xf6ec8056 (SP=0xfff1)
[0000:8054] push %ax (SP=0xffef)
[0000:8056] push %bx (SP=0xffeb)
[0000:806a] pop %bx (SP=0xffe7)
[0000:806c] pop %ax (SP=0xffeb)
[0000:806e] ret  (SP=0xffef)
[0000:8098] call 0xf6ec8056 (SP=0xfff1)
[0000:8054] push %ax (SP=0xffef)
[0000:8056] push %bx (SP=0xffeb)
[0000:806a] pop %bx (SP=0xffe7)
[0000:806c] pop %ax (SP=0xffeb)
[0000:806e] ret  (SP=0xffef)
[0000:809d] pop %esi (SP=0xfff1)
[0000:809e] pop %bx (SP=0xfff3)
[0000:80a0] pop %ax (SP=0xfff7)
[0000:80a2] ret  (SP=0xfffb)
[FAIL] Extra pop detected at [0000:81fa].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the real culprit was an extra &lt;code&gt;pop eax&lt;/code&gt; in my &lt;code&gt;print_memory_map&lt;/code&gt; routine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print_memory_map:
    push eax
    push ebx
    push ecx
    push edx

; --- ommited print loop logic ---

.noprint_newline:
    pop eax 

    cmp ecx, [memory_map_size]
    jne .print_memory_map_loop

    pop edx
    pop ecx
    pop ebx
    pop eax
    ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing this line will cause my debugging script to successfully pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out yourself
&lt;/h2&gt;

&lt;p&gt;You can try it out yourself, just check out my operating system, &lt;code&gt;raspios&lt;/code&gt;, on&lt;br&gt;
GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/StjepanPoljak/raspios" rel="noopener noreferrer"&gt;https://github.com/StjepanPoljak/raspios&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build and run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;build
&lt;span class="nb"&gt;cd &lt;/span&gt;build
&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;x86 cmake ..
make
make qemu_debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the &lt;code&gt;scripts&lt;/code&gt; folder run &lt;code&gt;gdb -ex 'source debug-stack.py'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a good reminder that low-level debugging often benefits from&lt;br&gt;
lightweight tooling tailored to the problem at hand. In this case, a small&lt;br&gt;
amount of Python automation around GDB made stack corruption analysis&lt;br&gt;
significantly more manageable than manual instruction tracing.&lt;/p&gt;

</description>
      <category>python</category>
      <category>gdb</category>
      <category>osdev</category>
      <category>assembly</category>
    </item>
  </channel>
</rss>
