QEMU is the quickest way to run darwinOS. It works on Linux, macOS, and Windows (via WSL), and it’s what the CI fleet uses to smoke-test every pull request. This guide assumes you’ve already built darwinOS from source. If you want UTM (the macOS-native QEMU frontend), there’s a dedicated UTM guide.
Prerequisites
- QEMU 8.0 or newer, with
qemu-system-aarch64(for arm64) orqemu-system-x86_64(for x86_64) - The build artefacts under
build/<arch>-<profile>/:kernel.img,initrd.img,rootfs.img,boot.efi - About 4 GB of free RAM for the VM
Check your QEMU version:
qemu-system-aarch64 --versionIf it’s older than 8.0 you may hit boot regressions — upgrade via your package manager or build from source.
Boot an arm64 image
The canonical arm64 invocation runs the virt machine with a UEFI loader:
qemu-system-aarch64 \
-machine virt,gic-version=3 \
-cpu max \
-smp 4 \
-m 4096 \
-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
-drive if=virtio,file=build/arm64-debug/rootfs.img,format=raw \
-kernel build/arm64-debug/kernel.img \
-initrd build/arm64-debug/initrd.img \
-append "console=ttyAMA0 root=/dev/vda" \
-nographic-nographic pipes the serial console to your terminal. You’ll see the kernel banner, then launchd coming up, then a shell prompt. Press Ctrl-a x to kill QEMU.
What the flags mean
-machine virt,gic-version=3— the virt machine with a GICv3 interrupt controller (what XNU expects)-cpu max— expose every architectural feature QEMU supports; drops to the host’s set on native hosts-smp 4— four virtual CPUs-m 4096— 4 GiB of RAM; drop to 2048 on low-memory hosts-bios …/QEMU_EFI.fd— the AAVMF UEFI firmware shipped by your distribution-drive if=virtio,…— exposes the root filesystem as a virtio-blk device; XNU’s virtio driver picks it up-kernel … -initrd … -append— direct kernel boot, bypassing the EFI boot stub. Faster for dev than going throughboot.efi
Locating the EFI firmware
The -bios path varies by distribution:
- Debian/Ubuntu:
/usr/share/qemu-efi-aarch64/QEMU_EFI.fd - Fedora:
/usr/share/edk2/aarch64/QEMU_EFI.fd - Arch:
/usr/share/edk2-armvirt/aarch64/QEMU_EFI.fd - Homebrew on macOS:
$(brew --prefix)/share/qemu/edk2-aarch64-code.fd
Boot an x86_64 image
qemu-system-x86_64 \
-machine q35 \
-cpu max \
-smp 4 \
-m 4096 \
-drive if=virtio,file=build/x86_64-debug/rootfs.img,format=raw \
-kernel build/x86_64-debug/kernel.img \
-initrd build/x86_64-debug/initrd.img \
-append "console=ttyS0 root=/dev/vda" \
-nographicOn Linux hosts you can add -enable-kvm for near-native speed; on macOS, -accel hvf enables Apple’s Hypervisor.framework. Without acceleration, expect noticeably slower boots.
Networking
By default QEMU gives the guest a user-mode NAT — the guest can reach the outside world, but nothing can reach the guest. For SSH-style access, add a host forward:
-netdev user,id=n0,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=n0Then from the host:
ssh -p 2222 root@localhost(Once sshd is part of the image — currently root autologs on the serial console.)
Snapshots
To take a snapshot of a running VM:
Ctrl-a c
(qemu) savevm my-snapshot
(qemu) quitRestore with -loadvm my-snapshot on the next boot. Great for skipping boot time during iterative debugging.
Common problems
“No bootable device” in the UEFI menu
The -drive path is wrong or rootfs.img is empty. Double-check the path and ls -lh the image.
Kernel panics immediately after the banner
Almost always a mismatch between kernel.img and rootfs.img. Rebuild both from the same config:
make clean && make worldTerminal control characters are weird
Some shells mangle the serial console. If line editing looks broken, try -serial mon:stdio instead of -nographic — it gives you the QEMU monitor on the same terminal.
Next steps
- Your first darwinOS program — compile something for the guest and copy it in.
- Debug the kernel — wire
lldbto QEMU’s gdb stub.