How to run the ARM version of Debian on x86 with QEMU

August 23, 2023

I want to set up an Infrastructure as Code (IaC) for my machines running at home, including ARM devices, with a complete virtualized environment for tests. And since some of my devices are running on ARM, that's how I found myself playing around with libvirt+QEMU to have ARM systems running headless on my x86 computer.

I never found resources how to do it exactly like I wanted, I fought against a lot of boot errors in the process.
Finally, I succeeded with Debian ARM, here are the steps.

# Create a virtual disk
qemu-img create -f qcow2 debian.qcow2 8G
# Download debian ARM iso
wget - "https://cdimage.debian.org/debian-cd/current/armhf/iso-dvd/debian-11.6.0-armhf-DVD-1.iso"
# Mount the debian ARM iso
mkdir mnt
sudo mount -o loop debian-11.6.0-armhf-DVD-1.iso ./mnt
# Find the path to boot files
head -n 10 mnt/boot/grub/grub.cfg
# Copy boot files from the ISO to the host
cp mnt/install.ahf/vmlinuz ./
cp mnt/install.ahf/initrd.gz ./
# Unmount the ISO
sudo umount ./mnt

Now that we've extracted the boot files, let's install Debian from the ISO.

sudo qemu-system-arm \
  -machine virt \
  -cpu cortex-a15 \
  -m 1024 \
  -nographic \
  -kernel ./vmlinuz \
  -initrd ./initrd.gz \
  -netdev user,id=n0 -device virtio-net-device,netdev=n0 \
  -drive file=./debian.qcow2,if=none,format=qcow2,id=hd0 -device virtio-blk-device,drive=hd0 \
  -drive file=./debian-11.6.0-armhf-DVD-1.iso,if=none,format=raw,id=hd1 -device virtio-blk-device,drive=hd1

The Debian installer won't be happy, don't worry, follow those steps to access a shell to fix the issue:

We now have access to a shell:

# Check drives, you should see "virtio_blk virtio0: [vda]"
dmseg
# Mount the ISO directly in /cdrom
modprobe isofs
mount /dev/vda1 /cdrom
# Close the shell
exit

Back to the Debian installer, we can use the ISO as an installation media:

Follow the normal steps of the installer, when done, you will be back to the menu and finish the installation:

After the installation steps, you will end up in a boot loop and you can force-close the virtual machine with the shortcut Ctrl-A X.

We can extract the boot files from the virtual disk:

# Find the path of the boot files
sudo virt-ls -l ./debian.qcow2 /boot
# Copy the boot files from virtual disk to the host
sudo virt-copy-out -a ./debian.qcow2 /boot/initrd.img-5.10.0-23-armmp-lpae ./
sudo virt-copy-out -a ./debian.qcow2 /boot/vmlinuz-5.10.0-23-armmp-lpae ./

Start the virtual machine with the boot files as qemu arguments:

sudo qemu-system-arm \
  -machine virt \
  -cpu cortex-a15 \
  -m 1024 \
  -nographic \
  -append "root=/dev/vda2" \
  -kernel ./vmlinuz-5.10.0-23-armmp-lpae \
  -initrd ./initrd.img-5.10.0-23-armmp-lpae \
  -netdev user,id=n0 -device virtio-net-device,netdev=n0 \
  -drive file=./debian.qcow2,if=none,format=qcow2,id=hd0 -device virtio-blk-device,drive=hd0

Once we're logged in, we can install SSH to simulate remote access:

root@debian:~# echo 'deb http://deb.debian.org/debian/ bullseye main' >> /etc/apt/sources.list
root@debian:~# apt update && apt install -y openssh-server
root@debian:~# sed -i '/#PermitRootLogin/c PermitRootLogin yes' /etc/ssh/sshd_config

You can force-close the virtual machine with the shortcut Ctrl-A X.

You can run the virtual machine in the background with a port forwarding of host:5555 -> vm:22:

sudo qemu-system-arm \
  -machine virt \
  -cpu cortex-a15 \
  -m 1024 \
  -daemonize \
  -display none \
  -append "root=/dev/vda2" \
  -kernel ./vmlinuz-5.10.0-23-armmp-lpae \
  -initrd ./initrd.img-5.10.0-23-armmp-lpae \
  -netdev user,id=net0,hostfwd=tcp::5555-:22 -device virtio-net-device,netdev=net0 \
  -drive file=./debian.qcow2,if=none,format=qcow2,id=hd0 -device virtio-blk-device,drive=hd0
ssh root@localhost -p 5555 -o "StrictHostKeyChecking=no"

🎉