How to Run a Linux Kernel vmlinux in QEMU: Direct ELF Boot & Debug in QEMU

This mega-guide shows how to run a linux kernel vmlinux in qemu from scratch, moving step-by-step from “What even is vmlinux?” to a full, debuggable guest that boots in under a second on modern hardware. We cover tool-chain setup, kernel compilation, QEMU invocation for x86-64 and arm64, serial-only boots, initrd/rootfs strategies, PVH-enabled ELF loading, and GDB integration—sprinkling lessons learned from real-world bug-hunts along the way. Every major command is backed by upstream documentation or battle-tested field notes so you can reproduce the exact same results on your laptop tonight. Newbies welcome; no prior kernel-hacking experience required.


Part 1 – The anatomy of vmlinux, bzImage, and why the keyword matters

Before you can master how to run a linux kernel vmlinux in qemu, you need to know what the file actually is.
vmlinux is the uncompressed, ELF-formatted output of the kernel link step, containing the full symbol table—great for debugging but useless for legacy bootloaders that expect a self-extracting header such as bzImage or vmlinuz (superuser.com). QEMU, however, can skip the firmware entirely and load an ELF kernel directly with the -kernel switch—provided the image contains either a PVH entry on x86-64 or the Flattened Image Tree (FIT) header on arm64 (qemu-project.gitlab.io, cdn.kernel.org).

How to Run a Linux Kernel vmlinux in QEMU
How to Run a Linux Kernel vmlinux in QEMU

Why choose vmlinux over bzImage?

  • Symbol-rich: GDB understands the ELF and can resolve every function/variable without System.map (kernel.org).
  • One-step bisects: There is no decompression step, shaving precious milliseconds from every test boot (ops.tips).
  • PVH boots: Since Linux 5.5, setting CONFIG_PVH=y embeds the Xen PVH note that QEMU (4.0+) can use for instant entry at startup_xen64 (superuser.com).

Pro-tip: When someone on Stack Overflow complains that “qemu-system-arm -kernel vmlinux gives me a black console,” nine times out of ten they passed the wrong machine type or forgot a matching console= argument (stackoverflow.com). We’ll solve that later.


Part 2 – Preparing the build & emulation environment

2.1 Host packages

On any modern Debian/Ubuntu host you can grab everything with

sudo apt install build-essential bc flex bison libssl-dev libelf-dev \
                 qemu-system-x86 qemu-system-aarch64 qemu-efi-aarch64 \
                 ovmf git make

Ubuntu’s own docs recommend qemu-kvm plus OVMF when you plan to use UEFI guests (documentation.ubuntu.com).

2.2 Cloning and configuring the kernel

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
make defconfig
./scripts/config -e CONFIG_GDB_SCRIPTS -e CONFIG_PVH   # x86-64 fast-boot helpers
make -j$(nproc)

The result appears at ./vmlinux (uncompressed ELF) and ./arch/x86/boot/bzImage (compressed) (nickdesaulniers.github.io).

2.3 Quick-and-dirty initramfs

Many tutorials waste hours on disk images; for smoke tests a BusyBox initramfs is enough. The ops.tips recipe automates this in four commands (ops.tips). When finished, place the archive at initramfs.cpio.gz.

2.4 QEMU in a nutshell

QEMU acts as both firmware and hypervisor. In Direct Linux Boot mode you only need:

qemu-system-x86_64 -kernel ./vmlinux \
                   -initrd initramfs.cpio.gz \
                   -append "console=ttyS0 nokaslr" \
                   -nographic -m 1G

-nographic re-maps the serial port and monitor to your terminal for an ultra-lightweight headless session (qemu-project.gitlab.io).

For hardware-assisted speed add -enable-kvm -cpu host (x86) or -machine virtualization=true (arm64) (documentation.ubuntu.com, cdn.kernel.org).


Part 3 – Building a reproducible workflow

3.1 Workspace layout

The M47r1x blog suggests keeping kernel, rootfs, and QEMU command scripts under a single $WORKSPACE folder (m47r1x.github.io). Adopt something like:

~/kdev/
├── build/linux/        # O= build dir
├── build/initramfs/
└── scripts/run-qemu.sh

3.2 Automating rebuild-and-run

Combine make -j$(nproc) with a post-build hook that restarts QEMU when the image changes. This is the secret to the “edit-compile-boot in <10 s” loop praised by Nick Desaulniers (nickdesaulniers.github.io).

3.3 Serial-only consoles

Why does every tutorial pass console=ttyS0? Because once you drop VGA, QEMU can start with zero GPU emulation, saving CPU and letting you copy-paste dmesg logs easily (qemu-project.gitlab.io, nickdesaulniers.github.io).

3.4 Troubleshooting early panics

If you see Kernel panic - not syncing: VFS: Unable to mount root fs…, your initramfs or root= parameter is wrong. Either embed a tiny BusyBox root or point root=/dev/vda to a virtio-blk disk; see the ArchWiki and Ubuntu Core examples for common layouts (wiki.archlinux.org, documentation.ubuntu.com).


Part 4 – how to run a linux kernel vmlinux in qemu step-by-step

  1. Compile with PVH (x86-64 only). ./scripts/config -e CONFIG_PVH make olddefconfig && make -j QEMU ≥ 4.0 will then “just work” with an ELF vmlinux (superuser.com).
  2. Launch the guest. qemu-system-x86_64 -kernel ./vmlinux \ -append "earlyprintk=ttyS0 console=ttyS0 root=/dev/ram rw" \ -initrd initramfs.cpio.gz -m 2048 -enable-kvm \ -cpu host -nographic The console should stop at the initramfs shell prompt within 0.7 s on a modern laptop.
  3. Add storage.
    For a writable root: qemu-img create -f qcow2 root.qcow2 8G qemu-system-x86_64 ... -drive file=root.qcow2,format=qcow2,if=virtio \ -append "root=/dev/vda" QCOW2 overlays let you revert broken boots instantly (en.wikipedia.org).
  4. Cross-arch demo (arm64).
    The kernel.org HOWTO boots an arm64 Debian image inside x86-64 QEMU by mixing qemu-system-aarch64, UEFI firmware, and a custom kernel passed via -kernel Image (cdn.kernel.org). Swap Image for your freshly built vmlinux (after objcopy -O binary) and keep the same -dtb & -append flags.
  5. Persist commands in a script.
    Save the full QEMU line in run-qemu.sh; future runs are a single keystroke away (ops.tips).

Part 5 – Debugging, profiling, and going further

5.1 GDB stub

Launch QEMU with -S -s to pause at reset and expose port 1234. Then:

gdb vmlinux
(gdb) target remote :1234
(gdb) hbreak start_kernel

Kernel-provided helper scripts (lx-dmesg, lx-symbols, etc.) turn GDB into a live /proc browser (kernel.org).

5.2 Panic-time forensics

If your guest hard-locks, switch to the monitor with Ctrl-a c, run info registers, or dump memory. Serial logs remain intact because of the earlier -nographic choice (nickdesaulniers.github.io).

5.3 Networking tricks

User-mode networking (-net user,hostfwd=tcp::8022-:22) lets you SSH from host to guest without bridges—used by Ubuntu Core’s reference command lines (documentation.ubuntu.com).

5.4 File-sharing with 9pfs

Add

-virtfs local,path=$PWD/share,mount_tag=host0,security_model=mapped,id=host0

and mount inside the guest with mount -t 9p host0 /mnt (cdn.kernel.org).

5.5 From sandbox to real hardware

Remember the caveat: “If it boots in QEMU, it’s a good sign—not a guarantee” (nickdesaulniers.github.io). Hardware quirks (ACPI tables, missing drivers) still bite, but QEMU removes the turn-around time barrier so you can iterate hundreds of kernel rebuilds per day.


FAQ (quick answers for frantic Googlers)

QuestionAnswer
Q1. Why does qemu-system-x86_64 -kernel vmlinux complain about “PVH ELF Note”?Set CONFIG_PVH=y when you build; without it, QEMU refuses a raw ELF image (superuser.com).
Q2. My console is blank. What gives?Pass console=ttyS0 (x86) or console=ttyAMA0 (arm) and add -nographic so serial output lands in your terminal (stackoverflow.com).
Q3. The kernel panics on VFS: Unable to mount root fs.Either add an initramfs (-initrd) or point root= to a virtio disk (-drive … if=virtio) (m47r1x.github.io).
Q4. How do I debug early boot code?Use -S -s and GDB’s hbreak start_kernel, then employ lx-dmesg and friends for live introspection (kernel.org).

Leave a comment