Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 71

KVM/QEMU VM Setup & Exit Tracing

Recommended resources

Introduction

Welcome to the next lesson in our module on Hardware-Assisted Virtualization. In our previous session, we explored the theory of paravirtualization and the VirtIO framework. We learned how VirtIO replaces slow, trap-heavy emulated I/O with an efficient, batched communication mechanism based on virtqueues. However, we also identified that the standard "pure VirtIO" data path still has a bottleneck: the context switch between the host kernel (KVM) and the QEMU userspace process.

Today, we move from theory to practice. Our learning outcome is to set up a KVM-based VM using QEMU and observe VM exits using kernel tracing tools. This hands-on experience is crucial for any developer working on virtualization or low-level performance analysis. You will learn how to launch a KVM-accelerated VM, enable the kernel's built-in tracing infrastructure, and see the very VM exits we've been discussing in real time. This practical skill will form the foundation for diagnosing complex performance issues, which we'll tackle in the next lesson.


1. Preparing Your Host for KVM

Before we can launch a VM, we need to ensure your host machine is properly configured for KVM. This involves two main steps: verifying that your CPU supports hardware virtualization and installing the necessary software packages.

KVM Virtualization Architecture with QEMU
This diagram illustrates the overall architecture. KVM is a Linux kernel module that turns the kernel into a Type-1 hypervisor. QEMU is the userspace component that emulates machine hardware and peripherals, using the KVM module to accelerate guest code execution via hardware virtualization extensions (Intel VT-x or AMD-V).

Step 1: Verify Hardware Support

KVM requires CPU virtualization extensions, either Intel VT-x or AMD-V (also known as AMD-SVM). You can quickly check if your CPU supports these and if they are enabled in the BIOS.

Getting Started with Kernel-based Virtual Machine (KVM) - Leonard Sheng Sheng Lee, Computas

The video 'Getting Started with Kernel-based Virtual Machine (KVM)' provides a clear, practical guide to this initial setup. We'll use it to walk through the verification and installation steps.

Please watch from 09:59 to 14:10. The speaker demonstrates how to use grep on /proc/cpuinfo to check for the vmx (Intel) or svm (AMD) flags. If these flags are not present, it often means virtualization needs to be enabled in your system's BIOS/UEFI settings.

On your Linux host, you can run the following command:

egrep -c '(vmx|svm)' /proc/cpuinfo

If the output is greater than 0, your CPU has the required features. If it's 0, you may need to enable virtualization in your BIOS/UEFI.

Step 2: Install Virtualization Packages

Next, you need to install QEMU, KVM, and other essential virtualization management tools.

Getting Started with Kernel-based Virtual Machine (KVM) - Leonard Sheng Sheng Lee, Computas

The same video also covers the package installation for various Linux distributions.

Now watch from 15:04 to 17:28. This section covers the packages needed for both Debian/Ubuntu-based and Red Hat/Fedora-based systems. It also shows how to ensure the kvm kernel modules are loaded correctly using lsmod.

For a Debian/Ubuntu system, a typical command would be:

sudo apt-get install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager

For a Fedora/CentOS/RHEL system:

sudo dnf install @virtualization

After installation, ensure your user is part of the libvirt and kvm groups to manage VMs without needing sudo for every command:

sudo usermod -aG libvirt $(whoami)
sudo usermod -aG kvm $(whoami)

You will need to log out and log back in for these group changes to take effect.


2. Launching a KVM-Accelerated VM with QEMU

While tools like virsh and virt-manager provide a high-level interface for managing VMs, for kernel development and debugging, it's often more powerful to work directly with the QEMU command line. This gives you fine-grained control over every aspect of the virtual machine.

You've already gained experience compiling your own kernel. QEMU allows you to boot that kernel directly, which is perfect for a rapid development and testing cycle.

Kernel Recipes 2015 - Speed up your kernel development cycle with QEMU - Stefan Hajnoczi

In the talk 'Speed up your kernel development cycle with QEMU', Stefan Hajnoczi provides an excellent walkthrough of a typical kernel developer's workflow using QEMU. Let's look at the specific command-line options for launching a VM with a custom kernel.

Please watch from 12:42 to 14:13 and then from 17:36 to 18:09. Focus on these key QEMU options: -enable-kvm: This is the crucial flag that tells QEMU to use the KVM kernel module for hardware acceleration. -kernel: Specifies the path to the guest kernel image (e.g., your compiled bzImage or vmlinux). -initrd: Specifies an initial RAM disk, which can contain a minimal root filesystem and test scripts. -append: Passes command-line arguments to the guest kernel. -nographic and -serial stdio: Redirects the guest's serial console to your host terminal, which is ideal for debugging.

Here is a complete example command to launch a VM. This assumes you have a compiled x86_64 kernel (bzImage), a root filesystem image (rootfs.img), and you want to allocate 2 CPU cores and 2GB of RAM.

qemu-system-x86_64 \
  -enable-kvm \
  -m 2G \
  -smp 2 \
  -kernel ./bzImage \
  -hda ./rootfs.img \
  -append "root=/dev/sda console=ttyS0" \
  -nographic

For today's exercise, you can use a pre-built cloud image which combines the kernel and rootfs for convenience. You can download a minimal CirrOS image here:

wget http://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img

Then launch it with this command:

qemu-system-x86_64 \
  -enable-kvm \
  -m 1G \
  -smp 1 \
  -drive file=cirros-0.6.2-x86_64-disk.img,format=qcow2 \
  -nographic

You should see the guest OS boot and eventually present you with a login prompt (user: cirros, password: gocubsgo). You can exit the QEMU session by pressing Ctrl-a then x.


3. Observing VM Exits with Kernel Tracing

Now for the core of today's lesson. With a VM running, we can use the host's kernel tracing tools to observe the interactions between the guest and KVM.

VM Entry and VM Exit Flow
A VM exit is a hardware-enforced transition from guest mode to host mode (the hypervisor), triggered by events the guest cannot handle directly. Our goal is to trace these transitions.

The primary tool we'll use is ftrace, a powerful and low-overhead tracer built directly into the Linux kernel. We can interact with it via the debugfs filesystem, typically mounted at /sys/kernel/debug/tracing/.

Tracing QEMU-KVM Interactions

The GitHub Gist 'Tracing QEMU-KVM Interactions' provides a concise set of commands for tracing VM exits using both perf and ftrace.

Please review the section 'Tracking VM-KVM Interactions'. It provides the two main methods we will use: one for getting aggregate statistics with perf, and one for getting a live trace stream with ftrace.

Practical Exercise: Tracing Live VM Exits

Let's put this into practice.

1. Launch your VM:
In one terminal, start the CirrOS VM again using the QEMU command from the previous section. Log in to the guest.

2. Get Aggregate Statistics with perf:
In a second terminal on your host, run the following command. This will count all KVM-related tracepoint events system-wide over a 5-second period.

sudo perf stat -e 'kvm:*' -a -- sleep 5

You will see output showing the counts of different events like kvm_entry, kvm_exit, kvm_msr, etc. Even with an idle guest, you'll see thousands of exits, many of them due to the HLT instruction, where the guest CPU tries to enter a sleep state.

3. Generate a Workload:
Inside the guest VM's terminal, generate some I/O activity. A simple command will do:




# This command continuously reads from a block device and discards the output.
# It will generate a lot of I/O-related VM exits.
dd if=/dev/vda of=/dev/null

While this is running, execute the perf stat command again on the host. You should see a significant increase in the number of VM exits, particularly for reasons related to I/O.

4. View the Live Trace with ftrace:
Now let's get a more detailed, real-time view. On the host, first enable all KVM tracepoints:




# This command might require you to be root or use sudo
echo 1 | sudo tee /sys/kernel/debug/tracing/events/kvm/enable

Then, stream the trace buffer to your terminal:

sudo cat /sys/kernel/debug/tracing/trace_pipe

You will see a continuous flood of text. Each line represents a kernel event. With your dd command still running in the guest, you should be able to see kvm_exit events scrolling by. Look for lines like this:

qemu-system-x86-1337 [000] ..s. 12345.67890: kvm_exit: reason IO_INSTRUCTION rip 0xffffffff8107b5a0 info 78 4

This line tells you:

  • The process is qemu-system-x86.
  • The event is kvm_exit.
  • The reason is IO_INSTRUCTION. This is a classic trap-and-emulate exit for port I/O.
  • rip is the instruction pointer in the guest at the time of the exit.

To stop the trace, press Ctrl-c in the cat terminal and then disable the tracepoints:

echo 0 | sudo tee /sys/kernel/debug/tracing/events/kvm/enable

4. Interpreting the Results and Deeper Analysis

By observing the reason code in the kvm_exit trace, you can begin to understand why your VM is exiting to the host. The resource LINK contains a list of these exit reasons at the end of the document. Common ones you might see include:

  • KVM_EXIT_HLT: The guest executed the HLT (halt) instruction, putting the vCPU to sleep.
  • KVM_EXIT_IO: The guest attempted to perform port I/O, which is trapped and emulated by QEMU.
  • KVM_EXIT_MMIO: The guest accessed a memory-mapped I/O address that KVM is configured to trap.
  • KVM_EXIT_INTR: KVM is notifying the vCPU of a pending external interrupt.
  • KVM_EXIT_EPT_VIOLATION or EPT_MISCONFIG: An exit related to memory management and the Extended Page Tables. This can indicate anything from a normal page fault to a bug in the guest's page table setup.

For a fantastic real-world example of how these tracing tools are used to solve a complex bug, I highly recommend reading the following blog post.

Debugging QEMU/KVM Setup If Something Goes Wrong...

The blog post 'Debugging QEMU/KVM Setup If Something Goes Wrong...' is a masterclass in using tracing to debug a KVM issue. The author encounters a cryptic KVM internal error and systematically uses perf, ftrace, and systemtap to drill down to the root cause: an unsupported CPU feature for 1GB huge pages.

Read through this post to see a professional debugging workflow in action. You don't need to reproduce the steps, but focus on the methodology: Start Broad: Use trace-cmd to capture all KVM tracepoints to get an initial clue (EPT_MISCONFIG). Go Deeper: Enable more specific kvm_mmu tracepoints to understand the page table walk error. Identify the Root Cause: Correlate the trace logs with the KVM source code to realize the issue is a missing CPUID feature in the guest. Find the Solution: Correct the QEMU command line to enable the necessary CPU feature.

This example perfectly illustrates the power of the tools you've just learned and aligns directly with your goal of deep kernel debugging.


Interview Scenario

Interviewer: "We've deployed a VM running a legacy application that relies on an emulated serial port for communication. Performance is poor, and monitoring shows high CPU usage on the host. You suspect the cause is excessive VM exits. How would you verify this and pinpoint the cause?"

Your Thought Process:

  1. Hypothesis: The legacy app's reliance on an emulated serial port is likely causing frequent KVM_EXIT_IO traps as it communicates byte-by-byte.
  2. Verification Step 1 (Aggregate): "First, I would use perf stat -e 'kvm:*' -p <QEMU_PID> while the application is running to get a baseline. I'd look for a very high rate of kvm_exit events per second, confirming that virtualization overhead is a major factor."
  3. Verification Step 2 (Detailed Trace): "Next, I would use ftrace to get a detailed view. I would enable the kvm tracepoints and stream the output from trace_pipe. I would expect to see a flood of kvm_exit events with the reason IO_INSTRUCTION, confirming that port I/O is the primary driver of the exits."
  4. Pinpointing the Cause: "By correlating the timestamps of the traces with the application's activity, I could definitively link the high exit rate to the serial port communication. The rip (instruction pointer) value in the trace could even be used with the guest kernel's symbol file to identify the exact driver code (inb/outb instructions) causing the traps."
  5. Proposed Solution: "Based on this analysis, the long-term solution would be to modify the application to use a more efficient communication mechanism, like a virtio-serial device, which would batch communications and dramatically reduce the number of VM exits."

Conclusion

In this lesson, you've taken a significant step from theory to practice. You now have the fundamental skills to set up a virtualized environment and peer directly into the heart of its operation.

Key Takeaways:

  • You can set up a KVM-accelerated VM using a direct QEMU command line, giving you maximum control for development and debugging.
  • The perf stat command provides a high-level overview of VM exit statistics.
  • The kernel's ftrace facility (/sys/kernel/debug/tracing) allows you to capture a live, detailed stream of KVM events.
  • By analyzing the reason code in kvm_exit traces, you can determine the cause of guest-to-host transitions.

Preview of the Next Lesson:

You can now observe and identify VM exits. In our next lesson, we will put this skill to the test. You will be tasked to diagnose a VM performance issue caused by excessive VM exits and identify mitigation strategies. We will create a specific performance problem and use the tools you learned today to analyze it, then explore advanced solutions like vhost to resolve the underlying bottleneck.