Running ARM64 Virtual Machines with QEMU on Apple M3

Recently, I wanted to read the content of a Raspberry Pi’s SD-Card on my MacBook Pro M3. The problem was that macos is not using Linux’s filesystems like ext4, and it’s therefore not possible to mount such SD-Card and read/write its content natively.

What is the best software that can fix my problem ? Linux obviously.

So, my idea was to

  1. build a virtual machine with a drive being my SD-Card,
  2. boot a Linux distribution like Debian on it
  3. and finally mount SD-Card on this Linux VM.

To complicate things I don’t want to use software such as UTM or Orbstack that would make things to easy, but that would also not teach me anything.

Prepare QEMU

First let’s get the tool needed : qemu.

On my machine I used Homebrew to get qemu. As brew.sh says, I ran this command :

brew install qemu

This command installs some binaries and more notably qemu-system-aarch64 and qemu-img. The first binary allows us to start ARM64 virtual machines. The seconds allows us to manipulate disk images.

Build the disk

Before we start the virtual machine let’s actually jump to step 2 and prepare the OS which will be a Debian Linux.

Let’s download a Debian image from here : https://cloud.debian.org/images/cloud/. Why here especially ? Because these are Cloud images which means that we can directly boot them and immediately get a running Debian system without installing it. This repository provides different kind of cloud images. I used the nocloud version for simplicity because I didn’t want to bother with cloud-init on this very little project. Therefore I just ran :

$ curl -OL https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-arm64.qcow2

Let’s check that the download was fine :

$ qemu-img info images/debian-13-nocloud-arm64.qcow2
image: images/debian-13-nocloud-arm64.qcow2
file format: qcow2
virtual size: 3 GiB (3221225472 bytes)
disk size: 384 MiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false
Child node '/file':
    filename: images/debian-13-nocloud-arm64.qcow2
    protocol type: file
    file length: 382 MiB (400984576 bytes)
    disk size: 384 MiB

Mmmmh the disk size is only 3GB, which is actually enough for the needs of this project but I always prefer to resize this. Let’s copy this file into a machines directory and resize the disk :

$ mkdir -p machines/dark-maul
$ cp images/debian-13-nocloud-arm64.qcow2 machines/dark-maul/disk.qcow2
$ qemu-img resize machines/dark-maul/disk.qcow2 50G

Let’s check the size now :

$ qemu-img info machines/dark-maul/disk.qcow2
image: machines/dark-maul/disk.qcow2
file format: qcow2
virtual size: 50 GiB (53687091200 bytes)
disk size: 670 MiB
cluster_size: 65536
Format specific information:
    compat: 1.1
    compression type: zlib
    lazy refcounts: false
    refcount bits: 16
    corrupt: false
    extended l2: false
Child node '/file':
    filename: machines/dark-maul/disk.qcow2
    protocol type: file
    file length: 665 MiB (697040896 bytes)
    disk size: 670 MiB

Perfect 50GiB ! Notice that it’s virtual, meaning that on my macbook only 670MiB are currently used but the future VM will think that this disk is 50GiB big 🙂

Build and start the VM

The best way to learn qemu is, for me, to read the documentation : https://www.qemu.org/docs/master/

But let’s remember that a VM is just a computer. So theorically to define a VM you would need to define these specs:

  • a processor / motherboard
    • number of cores
    • architecture
  • RAM
  • IO devices
    • disks
    • graphic cards
    • sound cards
    • inputs
    • etc…

Board and cpu

The qemu-system-aarch64 command will allow us to define a ARM64 cpu and motherboard. We will do that by adding these flags :

-machine virt,accel=hvf \
-cpu host \
-smp 4

The -machine flag specify the machine (processor/motherboard) we want to emulate. Qemu allows you to emulate many machines. You can find the list by running qemu-system-aarch64 -machine help. We will be emulating the virt machine. About that, the documentation says :

If you don’t care about reproducing the idiosyncrasies of a particular bit of hardware, such as small amount of RAM, no PCI or other hard disk, etc., and just want to run Linux, the best option is to use the virt board. 

Next to virt you can see accel=hvf. This property enables qemu to use the hypervisor framework of Apple to not emulate but virtualize the VM.

Finally the -smp flag set the number of core to 4.

RAM

To specify the RAM we will add this flag:

-m 4096

This sets 4GB of RAM which is more than enough.

Graphics and network

I don’t want any graphic card on this virtual machine. I only want access to a console. This flag is fine for me :

-nographic

Then I want only one network interface that will give internet access to the VM and I want to be able to SSH to my VM. The following flag will do the trick :

-nic user,hostfwd=tcp::5555-:22

It’s set up an interface in user mode where any TCP connection to localhost port 5555 will be redirected to the guest on localhost port 22 (SSH). This interface mode (user) allows me to run the VM without privileges.

Drive

Now let’s specify the disk the VM will have access to. In order to do that the following flag should do it :

-hda machines/dark-maul/disk.qcow2

By the ways the -hda flag is not recommended read this. More on that later.

BIOS

Something very important to know with ARM64 : there is no default UEFI firmware like in x86 emulation. Therefore you need to have an ARM64 UEFI firmware and you need to specify it. Fortunately for us QEMU provides such firmware. You can find it here :

/opt/homebrew/share/qemu/edk2-aarch64-code.fd

To use this firmware you can add the following flag :

-bios edk2-aarch64-code.fd

First test

Let’s run the complete command :

$ qemu-system-aarch64 \
 -machine virt,accel=hvf \
 -cpu host \
 -smp 4 \
 -m 4096 \
 -nographic \
 -nic user,hostfwd=tcp::5555-:22 \
 -hda machines/dark-maul/disk.qcow2 \
 -bios edk2-aarch64-code.fd

Everything looks great ! I was asked the Timezone and a first root password. I could then log in as root user !

Let’s try to ssh to the VM now …

ssh root@localhost -p 5555

It doesn’t work. Let’s fix that.

When I run ps -ef | grep ssh, I don’t see any ssh process running.

Let’s install it :

# apt update
# apt install ssh

Now let’s allow root user to log in via SSH (don’t do this in production) :

# sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# systemctl restart ssh

I can now login with my root password via SSH.

Share the SD-Card to the guest

Let’s shutdown the VM :

# poweroff 

When we specified the main VM’s disk, I mentionned that the -hda flag should be avoided. The documentation recommend to use to flag instead :

  • -device : defines what guest sees (or frontend)
  • -blockdev : defines what the actual device is (or backend)

For the backend, you must specify a driver for the emulated device. The problem is that it’s not an emulated device but a real one. The driver for our need is host_device. The documentation doesn’t mention it. See this for more information. This driver needs the path on host to the real device, and a node-name to identify this backend.

-blockdev driver=host_device,node-name=sdcard0,filename=/dev/disk4

To find the path to your device on macos you can use the diskutil list command.

For the frontend, you can list all the devices you can emulate with qemu-system-aarch64 -device help.

I’ll use virtio-blk-device driver, I just need to specify the backend with the drive prop.

-device virtio-blk-pci,drive=sdcard0

Let’s start the whole VM again :

$ qemu-system-aarch64 \
 -machine virt,accel=hvf \
 -cpu host \
 -smp 4 \
 -m 4096 \
 -nographic \
 -nic user,hostfwd=tcp::5555-:22 \
 -hda machines/dark-maul/disk.qcow2 \
 -bios edk2-aarch64-code.fd \
 -blockdev driver=host_device,node-name=sdcard0,filename=/dev/disk4 \
 -device virtio-blk-pci,drive=sdcard0

You may need to unmount the SD-Card first :

$ diskutil unmountDisk /dev/disk4

On the VM let’s verify that the SD-Card is here.

$ fdisk -l

Bingo ! You can now mount it and read/write its content.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top