NANDHOO.

Emulation and QEMU

Chapter 13: Emulation and QEMU


Introduction


Emulation allows you to run software designed for one architecture on another, or to create virtual machines for testing. QEMU (Quick Emulator) is the most important tool for OS development and system programming. It lets you test bootloaders, kernels, and system software safely without risking hardware damage, and provides powerful debugging capabilities.


Why This Matters


System programming is dangerous. A bug in a bootloader or kernel can brick hardware, corrupt disks, or cause data loss. Emulators provide a safe sandbox for development. QEMU is essential for kernel developers, embedded systems engineers, and anyone working on low-level software. It supports multiple architectures (x86, ARM, RISC-V, etc.) and integrates with debuggers.


How to Study This Chapter


  1. Install QEMU - Get hands-on experience immediately
  2. Start simple - Boot a basic bootloader before complex kernels
  3. Use debugging features - Learn QEMU monitor and GDB integration
  4. Test on multiple architectures - Try x86, x64, and ARM emulation

What is Emulation?


Emulation vs Virtualization


Emulation: Simulates different hardware architecture

  • Software simulates CPU instructions
  • Can run ARM code on x86 host
  • Slower (instruction translation overhead)
  • Example: QEMU in emulation mode

Virtualization: Runs code on same architecture with hardware support

  • Uses CPU virtualization extensions (Intel VT-x, AMD-V)
  • Near-native performance
  • Example: QEMU with KVM, VirtualBox, VMware

Types of Emulators


  1. Full System Emulation: Emulates complete computer (CPU, RAM, devices)
  2. User Mode Emulation: Runs foreign architecture binaries on host OS
  3. Hardware-Assisted Virtualization: Uses CPU extensions for speed

Installing QEMU


Linux (Ubuntu/Debian)

sudo apt-get update
sudo apt-get install qemu-system-x86 qemu-system-arm qemu-utils

macOS

brew install qemu

Windows

Download from qemu.org or use WSL2.


Verify Installation

qemu-system-x86_64 --version

QEMU Basics


Running a Bootable Image


# Create a floppy disk image
dd if=/dev/zero of=floppy.img bs=512 count=2880  # 1.44 MB

Write bootloader to image

dd if=boot.bin of=floppy.img bs=512 count=1 conv=notrunc


Boot the image in QEMU

qemu-system-x86_64 -drive file=floppy.img,format=raw,if=floppy


Alternative: Use as hard disk

qemu-system-x86_64 -drive file=disk.img,format=raw


Common QEMU Options


# Boot from floppy
qemu-system-x86_64 -fda floppy.img

Boot from hard disk

qemu-system-x86_64 -hda disk.img


Specify RAM size (default: 128 MB)

qemu-system-x86_64 -m 512M -hda disk.img


Enable KVM acceleration (Linux only, same architecture)

qemu-system-x86_64 -enable-kvm -hda disk.img


Boot from CD-ROM

qemu-system-x86_64 -cdrom os.iso


No graphical window (serial console only)

qemu-system-x86_64 -nographic -hda disk.img


Serial output to stdio

qemu-system-x86_64 -serial stdio -hda disk.img


Multiple Devices


# Multiple disks
qemu-system-x86_64 \
    -drive file=disk1.img,format=raw \
    -drive file=disk2.img,format=raw

Network card

qemu-system-x86_64 -netdev user,id=net0 -device e1000,netdev=net0


USB device

qemu-system-x86_64 -usb -device usb-mouse


QEMU Monitor


The QEMU monitor is a powerful interactive console for controlling the virtual machine.


Accessing the Monitor


Press Ctrl-Alt-2 to switch to monitor (Ctrl-Alt-1 returns to VM).


Or start QEMU with monitor on stdio:

qemu-system-x86_64 -monitor stdio -hda disk.img

Or use telnet:

qemu-system-x86_64 -monitor telnet:127.0.0.1:55555,server,nowait -hda disk.img

In another terminal:

telnet 127.0.0.1 55555


Common Monitor Commands


# Get help
(qemu) help

Show CPU registers

(qemu) info registers


Show memory mappings

(qemu) info mem


Show TLB contents

(qemu) info tlb


Examine physical memory (10 hex words at address 0x7c00)

(qemu) xp /10x 0x7c00


Examine virtual memory

(qemu) x /10x 0x7c00


Display disassembly

(qemu) x /10i 0x7c00


Show PCI devices

(qemu) info pci


Show block devices

(qemu) info block


Take screenshot

(qemu) screendump screen.ppm


Pause VM

(qemu) stop


Resume VM

(qemu) cont


Reset VM

(qemu) system_reset


Quit QEMU

(qemu) quit


Save VM state

(qemu) savevm snapshot1


Load VM state

(qemu) loadvm snapshot1


Change removable media

(qemu) change floppy0 newdisk.img


Memory Inspection


# Examine memory at bootloader location
(qemu) xp /128x 0x7c00

Check if boot signature is present

(qemu) xp /2hx 0x7dfe

Should output: 0x7dfe: 0x55aa


Examine GDT

(qemu) x /16x gdt_address


Look at stack

(qemu) xp /32x $esp


Debugging with QEMU and GDB


QEMU can act as a GDB remote debugging target.


Basic GDB Debugging


Terminal 1: Start QEMU with GDB server

qemu-system-x86_64 -s -S -drive file=disk.img,format=raw

-s : Shorthand for -gdb tcp::1234 (start GDB server on port 1234)

-S : Freeze CPU at startup (wait for GDB)


Terminal 2: Connect GDB

gdb
(gdb) target remote localhost:1234
(gdb) set architecture i386:x86-64
(gdb) break *0x7c00               # Breakpoint at bootloader
(gdb) continue                     # Start execution

GDB Commands for System Programming


# Set breakpoint at address
(gdb) break *0x7c00

Show registers

(gdb) info registers


Show all registers (including segment registers)

(gdb) info all-registers


Examine memory (hex)

(gdb) x/32xb 0x7c00


Examine as instructions

(gdb) x/10i 0x7c00


Disassemble

(gdb) disassemble 0x7c00,+64


Step one instruction

(gdb) stepi (gdb) si


Step one instruction (step over calls)

(gdb) nexti (gdb) ni


Continue execution

(gdb) continue (gdb) c


Layout with assembly

(gdb) layout asm


Layout with registers

(gdb) layout regs


Watch memory location

(gdb) watch *0x7c00


Set value in register

(gdb) set $eax = 0x42


Write to memory

(gdb) set {int}0x7c00 = 0x12345678


Debugging Protected Mode Transition


# Connect GDB
(gdb) target remote :1234

Set breakpoint before switching to protected mode

(gdb) break *0x7c00 (gdb) continue


Step through instructions

(gdb) si


Watch CR0 register (when bit 0 set, protected mode enabled)

(gdb) watch $cr0


After protected mode enabled, change architecture

(gdb) set architecture i386:x86-64 (gdb) continue


Creating GDB Script


debug.gdb:

# Connect to QEMU
target remote localhost:1234

Set architecture

set architecture i386:x86-64


Add symbols if you have them

symbol-file kernel.elf


Set breakpoints

break *0x7c00 break *0x1000


Define custom commands

define hook-stop info registers x/10i $pc end


Start execution

continue


Run with: gdb -x debug.gdb


QEMU Disk Images


Creating Disk Images


# Create raw disk image (1 GB)
qemu-img create -f raw disk.img 1G

Create qcow2 image (more efficient, supports snapshots)

qemu-img create -f qcow2 disk.qcow2 10G


Convert raw to qcow2

qemu-img convert -f raw -O qcow2 disk.img disk.qcow2


Get info about image

qemu-img info disk.qcow2


Mounting Disk Images (Linux)


# Mount raw image with loopback
sudo losetup /dev/loop0 disk.img
sudo mount /dev/loop0 /mnt

Or use offset for partitions

sudo mount -o loop,offset=1048576 disk.img /mnt


Unmount

sudo umount /mnt sudo losetup -d /dev/loop0


Partitioning Disk Images


# Create partition table
fdisk disk.img

Or use parted

parted disk.img mklabel msdos parted disk.img mkpart primary ext4 1MiB 100%


Creating a Complete Development Environment


Makefile for Building and Testing


# Makefile for bootloader and kernel development

ASM = nasm CC = gcc LD = ld


BOOT_SRC = boot.asm KERNEL_SRC = kernel.c BOOT_BIN = boot.bin KERNEL_BIN = kernel.bin DISK_IMG = os.img


all: $(DISK_IMG)


Assemble bootloader

(BOOTBIN):(BOOT_BIN): (BOOT_SRC) (ASM)fbino(ASM) -f bin -o @ $<


Compile kernel

kernel.o: (KERNELSRC)(KERNEL_SRC) (CC) -m32 -ffreestanding -fno-pie -c -o @@ <


Link kernel

(KERNELBIN):kernel.o(KERNEL_BIN): kernel.o (LD) -m elf_i386 -T linker.ld -o @@ < objcopy -O binary @@ @


Create disk image

(DISKIMG):(DISK_IMG): (BOOT_BIN) (KERNELBIN)ddif=/dev/zeroof=(KERNEL_BIN) dd if=/dev/zero of=@ bs=512 count=2880 dd if=(BOOTBIN)of=(BOOT_BIN) of=@ bs=512 count=1 conv=notrunc dd if=(KERNELBIN)of=(KERNEL_BIN) of=@ bs=512 seek=1 conv=notrunc


Run in QEMU

run: (DISKIMG)qemusystemx8664drivefile=(DISK_IMG) qemu-system-x86_64 -drive file=(DISK_IMG),format=raw


Debug with GDB

debug: (DISKIMG)qemusystemx8664sSdrivefile=(DISK_IMG) qemu-system-x86_64 -s -S -drive file=(DISK_IMG),format=raw & gdb -ex "target remote :1234"
-ex "break *0x7c00"
-ex "continue"


Clean build artifacts

clean: rm -f *.o *.bin $(DISK_IMG)


.PHONY: all run debug clean


Usage:

make           # Build disk image
make run       # Build and run in QEMU
make debug     # Build and debug with GDB
make clean     # Clean build files

ARM Emulation


QEMU supports various ARM machines.


Available ARM Machines


# List available ARM machines
qemu-system-arm -machine help

Common machines:

- versatilepb: ARM Versatile Platform Baseboard

- vexpress-a9: ARM Versatile Express Cortex-A9

- raspi2: Raspberry Pi 2

- raspi3: Raspberry Pi 3


Running ARM Code


# Compile for ARM
arm-none-eabi-gcc -mcpu=arm926ej-s -c -o boot.o boot.s
arm-none-eabi-ld -T linker.ld -o kernel.elf boot.o
arm-none-eabi-objcopy -O binary kernel.elf kernel.bin

Run in QEMU

qemu-system-arm -M versatilepb -m 128M -kernel kernel.bin -serial stdio


Debug with GDB

qemu-system-arm -M versatilepb -kernel kernel.bin -s -S arm-none-eabi-gdb kernel.elf (gdb) target remote :1234


Simple ARM Boot Code


/* boot.s - ARM bootloader */
.section .text
.global _start

_start: /* Set up stack */ ldr sp, =stack_top


/* Call main function */
bl main

/* Halt */

hang: b hang


.section .bss .align 4 stack_bottom: .space 4096 stack_top:


Advanced QEMU Features


Serial Port Communication


In your bootloader/kernel:

// Write to COM1 serial port (x86)
void serial_putchar(char c) {
    while (!(inb(0x3F8 + 5) & 0x20));  // Wait for transmit buffer empty
    outb(0x3F8, c);                     // Write character
}

void serial_puts(const char *str) { while (*str) { serial_putchar(*str++); } }


Run QEMU with serial output:

qemu-system-x86_64 -serial stdio -hda disk.img

Logging


# Enable debug logging
qemu-system-x86_64 -d int,cpu_reset -hda disk.img

Log to file

qemu-system-x86_64 -d int -D qemu.log -hda disk.img


Available debug categories (see qemu-system-x86_64 -d help):

- int: Interrupts

- cpu_reset: CPU resets

- guest_errors: Guest errors

- mmu: MMU operations

- pcall: x86 only: protected mode far calls

- unimp: Unimplemented functionality


Snapshots


# Create snapshot
(qemu) savevm snapshot1

List snapshots

(qemu) info snapshots


Load snapshot

(qemu) loadvm snapshot1


Delete snapshot

(qemu) delvm snapshot1


Networking


# User mode networking (no root required)
qemu-system-x86_64 -netdev user,id=net0 -device e1000,netdev=net0

Tap networking (requires root)

sudo qemu-system-x86_64 -netdev tap,id=net0 -device e1000,netdev=net0


Dump network traffic

qemu-system-x86_64 -netdev user,id=net0 -device e1000,netdev=net0
-object filter-dump,id=f1,netdev=net0,file=network.pcap


Troubleshooting


Common Issues


Problem: Bootloader doesn't run

# Check boot signature
hexdump -C boot.bin | tail
# Should see: ... 55 aa

Verify size

ls -l boot.bin

Should be 512 bytes


Problem: Kernel not loaded

# Check disk image
qemu-system-x86_64 -monitor stdio -hda disk.img
(qemu) xp /512x 0x1000
# Should see kernel code

Problem: GDB won't connect

# Ensure QEMU is listening
netstat -an | grep 1234

Try explicit port in GDB

(gdb) target remote localhost:1234


Problem: Wrong architecture

# Verify image architecture
file boot.bin
# Should show: DOS/MBR boot sector

Use correct QEMU binary

qemu-system-x86_64 # For x64 qemu-system-arm # For ARM


Testing Checklist


# 1. Test bootloader loads
qemu-system-x86_64 -drive file=disk.img,format=raw

2. Test serial output

qemu-system-x86_64 -serial stdio -drive file=disk.img,format=raw


3. Inspect bootloader in memory

qemu-system-x86_64 -monitor stdio -drive file=disk.img,format=raw (qemu) xp /128x 0x7c00


4. Debug with GDB

qemu-system-x86_64 -s -S -drive file=disk.img,format=raw & gdb -ex "target remote :1234"


5. Check registers after boot

(qemu) info registers


6. Verify kernel loaded

(qemu) xp /256x 0x1000


Key Concepts


  • QEMU emulates complete computer systems
  • Emulation simulates foreign architectures, slower than virtualization
  • QEMU Monitor provides introspection and control
  • GDB integration enables source-level debugging
  • Serial output simplifies debugging (no video driver needed)
  • Disk images can be raw or qcow2 format
  • Multiple architectures supported (x86, ARM, RISC-V, etc.)
  • Snapshots save and restore VM state

Common Mistakes


  1. Wrong architecture binary - Using qemu-system-arm for x86 code
  2. Missing boot signature - Forgetting 0x55AA
  3. Not using -S flag - VM starts before GDB connects
  4. Wrong memory inspection - Use xp for physical, x for virtual
  5. Ignoring serial output - Easier than video for early debugging
  6. Not saving work - Use snapshots during long tests
  7. Testing on hardware first - Always use QEMU first!

Debugging Tips


  • Start with serial output - Easier than video
  • Use QEMU monitor - Inspect memory and registers
  • Enable debug logging - -d int,cpu_reset
  • Use GDB breakpoints - Stop at specific addresses
  • Check boot signature - Most common bootloader error
  • Verify disk reads - Use monitor to check loaded data
  • Test incrementally - Small changes, frequent testing
  • Read QEMU docs - Extensive documentation available

Mini Exercises


  1. Boot a simple bootloader in QEMU
  2. Use QEMU monitor to examine bootloader memory
  3. Set up GDB debugging for bootloader
  4. Create Makefile that builds and runs in QEMU
  5. Use serial port to print debug messages
  6. Create a QEMU snapshot and restore it
  7. Log interrupts to file using -d int
  8. Run an ARM bootloader in QEMU
  9. Use GDB to step through protected mode transition
  10. Create multi-stage bootloader and test in QEMU

Review Questions


  1. What's the difference between emulation and virtualization?
  2. What QEMU option enables GDB debugging?
  3. How do you access the QEMU monitor?
  4. What command shows CPU registers in QEMU monitor?
  5. How do you enable serial output in QEMU?

Reference Checklist


By the end of this chapter, you should be able to:

  • Install and run QEMU
  • Boot disk images in QEMU
  • Use QEMU monitor to inspect memory and registers
  • Debug bootloaders and kernels with GDB
  • Create and manage disk images
  • Use serial port for debugging output
  • Test code on multiple architectures
  • Create automated build and test workflows
  • Enable debug logging
  • Use snapshots for testing

Next Steps


With QEMU as your development environment, you're ready to build a kernel. The next chapter covers kernel fundamentals: what a kernel does, kernel architecture, interrupt handling, and the basics of kernel development.




Key Takeaway: QEMU is an indispensable tool for system programming. It provides a safe, debuggable environment for developing and testing bootloaders, kernels, and low-level software across multiple architectures.