Installing Debian 11 for armhf in QEMU on an M-series Mac

Miu is computing

I needed to run a 32-bit ARM Debian image in QEMU on my M-series Mac to build some custom armhf software, mainly cuz statically cross-compiling anything remotely complicated sucks to the point where slow emulation of an armhf system felt preferable.

I didn't find a comprehensive guide for this, and I ran into some issues that took a moment to resolve, so I figured I'd detail the steps here for future reference.

This guide assumes you have QEMU installed. If you don't, you can install it with brew install qemu.

On Linux just use your package manager to install QEMU.

1. Download the netinst image

Current QEMU (9.0.2) seems to only support up to Debian 11, so you need to grab the 11.10.0 installer image.

wget https://cdimage.debian.org/mirror/cdimage/archive/11.10.0/armhf/iso-cd/debian-11.10.0-armhf-netinst.iso

2. Create a disk image

Create a disk image to install the OS on.

qemu-img create -f qcow2 debian-11.10.0-armhf.img 10G

This will create a 10GB disk image named debian-11.10.0-armhf.img.

3. Grab initrd.gz and vmlinuz from the ISO

You'll need these files to boot the installer and later the installed OS as there won't be a bootloader installed.

If you're unable to mount the ISO for some reason, I've zipped them up here. Note that this is for the 11.10.0-armhf netinst image only.

Mount the ISO and copy the initrd.gz and vmlinuz files to your working directory.

mkdir iso && sudo mount -o loop debian-11.10.0-armhf-netinst.iso iso
This will mount the ISO to the iso directory.
cp iso/install.ahf/initrd.gz .
cp iso/install.ahf/vmlinuz .
This will copy the initrd.gz and vmlinuz files to your working directory.

4. Install

Run the following command to install the OS. Note that we're booting up with 2GB of RAM which is the maximum a 32-bit ARM system can address. If you boot up with more, the installer will fail.

qemu-system-arm \
-M virt \
-m 2G \
-cpu max \
-kernel vmlinuz \
-initrd initrd.gz \
-drive file=debian-11.10.0-armhf-netinst.iso,if=none,id=cd,media=cdrom,read-only -device virtio-blk-device,drive=cd \
-drive file=debian-11.10.0-armhf.img,if=none,id=hd -device virtio-blk-device,drive=hd -device virtio-net-device,netdev=net0 \
-netdev user,id=net0 \
-serial stdio
This will boot the installer with 2GB of RAM and max CPU.

Fixing "No device for installation media was detected"

The installer will most likely fail to detect the installation media as QEMU will mount stuff under /dev/vda & vdb instead of the usual places.

When you get the "No device for installation media was detected." screen, select "No" and then "Yes" in the following screen to specify another device. Select "cdrom" as the type, and try either /dev/vda or /dev/vdb as the device; you might have to try both- the installer will loop around if it can't find the correct device. For me, it was /dev/vdb.

Once you've selected the correct device, the installer should continue as expected.

Touch grass for a bit

As it's emulating the environment, the installation will take a while (probably an hour or two). You will need to press enter here and there, but the first leg of it is long enough to cook dinner.

GRUB install fails

GRUB install will fail as it's unable to install the bootloader to the disk image. This is fine; you can ignore it and select "Continue without boot loader" in the menu.

It will tell you:
"No boot loader has been installed, either because you chose not to or because your specific architecture doesn't support a boot loader yet. You will need to boot manually with the /vmlinuz kernel on partition /dev/vda1 and root=/dev/vda2 passed as a kernel argument."

5. Finish and boot to Debian

Once the installation is complete, you want to make a copy of the image so you can mess around with a fresh installation later if you mess something up without having to reinstall.

You can boot the installed OS with the following command:

qemu-system-arm \
-M virt -m 2G
-smp cpus=2,maxcpus=2 \
-cpu cortex-a15 \
-kernel ./vmlinuz-5.10.0-31-armmp-lpae \
-initrd ./initrd.img-5.10.0-31-armmp-lpae \
-drive file=debian-11.10.0-armhf.img,if=none,id=hd \
-device virtio-blk-device,drive=hd \
-nographic \
-device virtio-net-device,netdev=net0 -netdev user,id=net0 \
--append "root=/dev/debian-vg/root console=ttyAMA0,115200 loglevel=7"
This will boot the installed OS with 2GB of RAM and max CPU.