QEMU

QEMU Tips and Tricks

Public Domain
Public Domain

QEMU is an indispensable tool for the virtual machine inclined. It's a command line utility for running virtual machines.

Main documentation: https://www.qemu.org/docs/master/system/

Virtiofs

#!/usr/bin/env bash
set -xeuo pipefail

TEMPDIR="$(mktemp -d)"
trap "rm -rf ${TEMPDIR}" EXIT

# ── 1. Build chroot (unchanged) ────────────────────────────────────────────
if [[ ! -d my-chroot ]]; then
  mkdir -p temp_oci_layout my-chroot
  skopeo copy --format oci docker://registry.fedoraproject.org/fedora:latest dir:./temp_oci_layout/
  cat temp_oci_layout/manifest.json | jq -r '.layers[].digest' | sed -e 's/sha256://' \
    | xargs -I '{}' -- sudo tar -xzkf 'temp_oci_layout/{}' -C ./my-chroot

  sudo systemd-nspawn -D ./my-chroot dnf -y install \
    systemd kernel-core cloud-init \
    dracut dracut-live dracut-network \
    btrfs-progs util-linux rsyslog \
    openssh-server vim tmux
fi

# ── 2. Journald config (unchanged) ────────────────────────────────────────
sudo mkdir -p my-chroot/etc/systemd/journald.conf.d
sudo tee my-chroot/etc/systemd/journald.conf.d/serial.conf <<'EOF'
[Journal]
ForwardToConsole=yes
MaxLevelConsole=debug
EOF

# ── 3. Build initrd with dmsquash-live ────────────────────────────────────
if [[ ! -f my-chroot/boot/initrd.img ]]; then
  # Write a dracut.conf that includes the live modules
  sudo tee my-chroot/etc/dracut.conf.d/live.conf <<'EOF'
add_dracutmodules+=" dmsquash-live "
filesystems+=" squashfs overlay ext4 "
compress="zstd"
hostonly="no"
EOF

  KVER=$(ls my-chroot/lib/modules/ | tail -1)
  sudo systemd-nspawn -D ./my-chroot \
    dracut --force /boot/initrd.img "$KVER"

  sudo chown "${USER}:${USER}" my-chroot/boot/initrd.img
fi

# ── 4. Build squashfs image ───────────────────────────────────────────────
# dracut dmsquash-live looks for /LiveOS/squashfs.img on the root= device.
# We'll put it in a staging dir that virtiofsd will serve.
if [[ ! -f liveos/LiveOS/squashfs.img ]]; then
  mkdir -p liveos/LiveOS
  # Exclude the virtual fs mount points and the squashfs dir itself
  sudo mksquashfs my-chroot liveos/LiveOS/squashfs.img \
    -comp zstd \
    -e my-chroot/proc \
    -e my-chroot/sys \
    -e my-chroot/dev \
    -e my-chroot/run \
    -noappend
fi

# Build squashfs into a disk image that QEMU can present as a block device
if [[ ! -f liveos.img ]]; then
  mkdir -p liveos-staging/LiveOS

  sudo mksquashfs my-chroot liveos-staging/LiveOS/squashfs.img \
    -comp zstd \
    -e my-chroot/proc \
    -e my-chroot/sys \
    -e my-chroot/dev \
    -e my-chroot/run \
    -noappend

  # Size the image to fit + some headroom
  SQSIZE=$(du -sb liveos-staging/LiveOS/squashfs.img | cut -f1)
  # Add 15% headroom for ext4 metadata and journal
  IMGSIZE=$(( SQSIZE * 115 / 100 ))
  # Round up to nearest MB
  IMGSIZE=$(( (IMGSIZE + 1048575) / 1048576 * 1048576 ))
  truncate -s "$IMGSIZE" liveos.img

  # Format as ext4 and copy in the LiveOS layout
  mkfs.ext4 -L LIVEOS liveos.img
  mkdir -p /tmp/liveos-mnt
  sudo mount -o loop liveos.img /tmp/liveos-mnt
  sudo mkdir -p /tmp/liveos-mnt/LiveOS
  sudo cp liveos-staging/LiveOS/squashfs.img /tmp/liveos-mnt/LiveOS/squashfs.img
  sudo umount /tmp/liveos-mnt
fi

# ── 5. virtiofsd serving the LiveOS directory ─────────────────────────────
VIRTIOFS_SOCKET="/tmp/vfs-$(uuidgen).sock"
/usr/libexec/virtiofsd \
  --socket-path="$VIRTIOFS_SOCKET" \
  --shared-dir="${PWD}/liveos" \
  --cache always \
  --readonly \
  --sandbox none &
VFS_PID=$!
trap 'kill $VFS_PID; rm -f "$VIRTIOFS_SOCKET"' EXIT

# ── 6. cloud-init (unchanged) ─────────────────────────────────────────────
cat <<EOF > user-data
#cloud-config
users:
  - name: agent
    sudo: ["ALL=(ALL) NOPASSWD:ALL"]
chpasswd:
  expire: False
  users:
  - name: agent
    password: agent
    type: text
EOF
cat <<EOF > meta-data
instance-id: someid/somehostname
EOF
touch vendor-data

python -um http.server --directory . 0 2>&1 > "${TEMPDIR}/listening.txt" &
CLOUD_INIT_HTTPD_PID=$!
trap 'kill $CLOUD_INIT_HTTPD_PID' EXIT
until grep http "${TEMPDIR}/listening.txt"; do sleep 0.1; done
PORT=$(cat "${TEMPDIR}/listening.txt" | awk '{print $6}')

# ── 7. Boot ───────────────────────────────────────────────────────────────
KVER=$(ls my-chroot/lib/modules/ | tail -1)
KERNEL=$(find my-chroot/usr/lib/modules -name vmlinuz)


qemu-system-x86_64 \
  -net nic \
  -net user \
  -smbios type=1,serial=ds="nocloud;s=http://10.0.2.2:${PORT}/" \
  -no-reboot \
  -enable-kvm \
  -cpu host \
  -smp cpus=2 \
  -m 4G \
  -nographic \
  -initrd "${PWD}/my-chroot/boot/initrd.img" \
  -kernel "${PWD}/${KERNEL}" \
  -drive file=liveos.img,format=raw,if=virtio,readonly=on \
  -append "console=ttyS0 \
    root=live:LABEL=LIVEOS \
    rd.live.image \
    rd.overlayfs=1 \
    rd.live.overlay.overlayfs=1 \
    init=/usr/lib/systemd/systemd"

Example Flags

Run a 64 bit Intel / AMD system

qemu-system-x86_64 ...

KVM

Without KVM your VM will be VERY VERY VERY slow. You'll want to enable this.

  -enable-kvm

Make sure your use account has access to /dev/kvm, use chown to make the group kvm, and add your user to that group. You'll need log out and log back in for changes to take effect. Or run bash --login.

$ groupadd kvm
$ sudo usermod -aG kvm $USER
$ ll /dev/kvm
crw-rw-rw- 1 root root 10, 232 Jul 22 12:10 /dev/kvm
$ chown root:kvm /dev/kvm
$ ll /dev/kvm
crw-rw-rw- 1 root kvm 10, 232 Jul 22 12:10 /dev/kvm

Multiple CPUs

  -smp cpus=4

More detailed

  -smp sockets=1,cpus=4,cores=2 -cpu host

Memory

  -m 8192M

Networking - NAT

  -netdev user,id=mynet0 \
  -device virtio-net-pci,netdev=mynet0

You can also do what's known as "bridged" networking. It can be a bit of a mess though. It's covered in the main QEMU documentation at the top.

TODO Cover bridged networking

USB

You may want to give a VM control over a USB device, such as a USB NIC.

Creating a ehci device and attaching the usb device to it is important!

Use lsusb -v to find the idProduct (productid) and idVendor (vendorid)

  -usb \
  -device usb-ehci,id=ehci \
  -device usb-host,bus=ehci.0,vendorid=0x0424,productid=0xEC00

Share small files

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

Fast Random Number Generator

  -device virtio-rng-pci

Port Forwarding

  -net \
    user,hostfwd=tcp::2222-:22,hostfwd=tcp::4444-:2222

Guest Image

For a raw .img or .iso

  -drive \
    file="image.iso",if=virtio,aio=threads,format=raw

For a .qcow2

  -drive \
    file="image.qcow2",if=virtio,aio=threads,format=qcow2

Kernel

Boot directly to a Linux kernel binary (skips some BIOS stuff)

  -kernel \
    "linux-source-tree/arch/x86/boot/bzImage"

Kernel cmdline

The root* options here correspond to the Host Filesystem Passthrough section.

  -append \
    "console=ttyS0 rootfstype=9p root=fsdev-root ro rootflags=trans=virtio,version=9p2000.u init=/usr/lib/systemd/systemd"

Disable GUI

  -nographic

CPU Emulation

Specify host to have QEMU not emulate another CPU, just use the host CPU.

  -cpu host

Specify BIOS

You'll need this if you want to use UEFI

  -bios \
    "path/to/OVMF.fd"

BIOS Debugging Connection

  -chardev \
    pipe,path=qemudebugpipe,id=seabios \
  -device \
    isa-debugcon,iobase=0x402,chardev=seabios

Reference: https://www.seabios.org/Debugging#Debugging_with_gdb_on_QEMU

Host Filesystem Passthrough

Use a directory on the host as a filesystem for the guest.

This requires that the guest kernel has been configured with:

CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y
CONFIG_NET_9P=y
CONFIG_NET_9P_VIRTIO=y

9P fs is buggy as all hell In particular, fsync seems to be broken.

  -fsdev \
    local,id=fsdev-root,path="${CHROOT}",security_model=passthrough,readonly \
  -device \
    virtio-9p-pci,fsdev=fsdev-root,mount_tag=/dev/root

Make sure you're using the corresponding kernel cmdline options.

Creating A Bootable UEFI Guest Image

Create qcow2 image

$ qemu-img create -f qcow2 image.qcow2 20G

Source of NBD commands: https://gist.github.com/shamil/62935d9b456a6f9877b5

Enable network block devices

$ sudo modprobe nbd max_part=8

Map the image file to the /dev/nbd0 network block device

$ sudo qemu-nbd --connect=/dev/nbd0 image.qcow2

Create GPT partition table (UEFI)

$ sudo parted /dev/nbd0 << 'EOF'
mklabel gpt
mkpart primary fat32 1MiB 261MiB
set 1 esp on
mkpart primary linux-swap 261MiB 10491MiB
mkpart primary ext4 10491MiB 100%
EOF

Format partitions

$ sudo mkfs.fat /dev/nbd0p1
$ sudo mkswap /dev/nbd0p2
$ sudo mkfs.ext4 /dev/nbd0p3

Unmount and disconnect

$ sudo umount -R /mnt/somepoint/
$ sudo qemu-nbd --disconnect /dev/nbd0

parted commands from: https://wiki.archlinux.org/index.php/Parted#UEFI/GPT_examples

CPU Hotplug

  -smp 1,maxcpus=2 -qmp unix:/tmp/q,server,nowait

In another shell

$ sudo qemu/scripts/qmp/qmp-shell -p -v /tmp/q
Welcome to the QMP low-level shell!
Connected to QEMU 4.1.0

(QEMU) device_add id=cpu-2 driver=host-x86_64-cpu socket-id=1 core-id=0 thread-id=0 die-id=0

Back in your Linux guest you'll see [ 51.190460] CPU1 has been hot-added in the dmesg logs.

To initialize the CPU within the guest

# echo 1 > /sys/devices/system/cpu/cpu1/online

Reference: https://wiki.qemu.org/Features/CPUHotplug

Precompiled UEFI Firmware

https://cdn.download.clearlinux.org/image/OVMF.fd

UEFI_BIOS="-bios OVMF.fd"

if [ -f OVMF_VARS.fd -a -f OVMF_CODE.fd ]; then
    UEFI_BIOS=" -drive file=OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on "
    UEFI_BIOS+=" -drive file=OVMF_VARS.fd,if=pflash,format=raw,unit=1 "
fi

1184122 – Can't mount virtio-9p fs at boot time https://bugzilla.redhat.com/show_bug.cgi?id=1184122#c1

bootc-dev/bootc: Boot and upgrade via container images https://github.com/bootc-dev/bootc

cloud-hypervisor/docs/vsock.md at main · cloud-hypervisor/cloud-hypervisor https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/vsock.md

dracut tries to mount virtiofs root before kernel has enumerated available virtiofs tags · Issue #22 https://github.com/dracut-ng/dracut-ng/issues/2242

dracut-ng/modules.d/71overlayfs-crypt/prepare-overlayfs-crypt.sh at e1bf57a772198becf126aebdfd2240ce https://github.com/dracut-ng/dracut-ng/blob/e1bf57a772198becf126aebdfd2240cebc49b2f3/modules.d/71overlayfs-crypt/prepare-overlayfs-crypt.sh

DRACUT.CMDLINE(7) :: Dracut https://dracut-ng.github.io/dracut-ng/man/dracut.cmdline.7.html

DRACUT(8) :: Dracut https://dracut-ng.github.io/dracut-ng/man/dracut.8.html#using-the-dracut-shell

firecracker/docs/vsock.md at main · firecracker-microvm/firecracker https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md

Getting Started with Bootable Containers :: Fedora Docs https://docs.fedoraproject.org/en-US/bootc/getting-started/

hyperlight-dev/hyperlight-sandbox: A multi-backend sandboxing framework for running untrusted code w https://github.com/hyperlight-dev/hyperlight-sandbox

hyperlight-dev/hyperlight: Hyperlight is a lightweight Virtual Machine Manager (VMM) designed to be https://github.com/hyperlight-dev/hyperlight

Invocation — QEMU documentation https://qemu-project.gitlab.io/qemu/system/invocation.html#hxtool-6

Linux Kernel Development Tips And Tricks : Public Domain Relay https://publicdomainrelay.com/linux-kernel/

linux/block/early-lookup.c at master · torvalds/linux https://github.com/torvalds/linux/blob/master/block/early-lookup.c

linux/block/early-lookup.c at master · torvalds/linux https://github.com/torvalds/linux/blob/master/block/early-lookup.c#L197

model-spec/docs/aikit.md at 783d1c84157ebde6c4014ac975d96c838cdee8f9 · modelpack/model-spec https://github.com/modelpack/model-spec/blob/783d1c84157ebde6c4014ac975d96c838cdee8f9/docs/aikit.md

modelpack/model-spec at 783d1c84157ebde6c4014ac975d96c838cdee8f9 https://github.com/modelpack/model-spec/tree/783d1c84157ebde6c4014ac975d96c838cdee8f9

Post by @filippo.abyssdomain.expert — Bluesky https://bsky.app/profile/filippo.abyssdomain.expert/post/3mkldvg6iec2h

skopeo/docs/skopeo-copy.1.md at main · containers/skopeo https://github.com/containers/skopeo/blob/main/docs/skopeo-copy.1.md

ssh virtio systemd at DuckDuckGo https://duckduckgo.com/?q=ssh+virtio+systemd&ia=web

sulogin(8) - Linux manual page https://www.man7.org/linux/man-pages/man8/sulogin.8.html

sysroot.mount: About to execute: /usr/bin/mount virtfs:foo /sysroot -o ro · Issue #1397 · dracut-ng/ https://github.com/dracut-ng/dracut-ng/issues/1397

systemd-ssh-proxy https://www.freedesktop.org/software/systemd/man/latest/systemd-ssh-proxy.html\

systemd-ssh-proxy(1) - Linux manual page https://www.man7.org/linux/man-pages//man1/systemd-ssh-proxy.1.html

systemd-ssh-proxy(1) — Arch manual pages https://man.archlinux.org/man/systemd-ssh-proxy.1.en

rpmfile for downloading kernel oob https://github.com/srossross/rpmfile

The kernel’s command-line parameters — The Linux Kernel documentation https://www.kernel.org/doc/html/v6.19/admin-guide/kernel-parameters.html

virtio-fs / virtiofsd · GitLab https://gitlab.com/virtio-fs/virtiofsd

virtiofs - shared file system for virtual machines / Standalone usage https://virtio-fs.gitlab.io/howto-qemu.html

virtiofs: virtio-fs host<->guest shared file system — The Linux Kernel documentation https://www.kernel.org/doc/html/v6.19/filesystems/virtiofs.html

VM Interface https://systemd.io/VM_INTERFACE/