Skip to content

Instantly share code, notes, and snippets.

Forked from citruz/
Created May 5, 2022 05:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daluu/bb5832838e413e6b30e73f1d0eb7d95e to your computer and use it in GitHub Desktop.
Save daluu/bb5832838e413e6b30e73f1d0eb7d95e to your computer and use it in GitHub Desktop.
Create Ubuntu and Windows VMs with QEMU on Apple Silicon

Running Linux and Windows on M1 with QEMU

30.11.2020: Updated with the new patchseries and instructions for Windows

02.12.2020: Added tweaks

08.12.2020: Updated with patchseries v4

31.01.2020: Updated with patchseries v6

07.03.2021: Updated instructions to apply patch cleanly

01.06.2021: Updated instructions for Xcode 12.4 and above

Building QEMU

  • Clone QEMU and checkout version 5.2.0
git clone
cd qemu
git checkout v5.2.0
curl | git am --exclude=MAINTAINERS
  • If you use Xcode 12.4 or above, you will need another patch to fix the QEMU build. Download xcode-12-4.patch from below and apply it using
git apply xcode-12-4.patch
  • Install the ARM version of the brew package manager. The (recommended) installation via Rosetta will cause problems when building QEMU. Even if brew screams at you at every launch that this is not a supported configuration I had no major problems so far. You can follow this guide (see the "Multiple Homebrews" section).
  • Install required packages for building:
brew install libffi gettext pkg-config autoconf automake pixman
  • Run the following commands to build qemu:
mkdir build
cd build
../configure --target-list=aarch64-softmmu --disable-gnutls
make -j8
sudo make install
  • For some reason, the qemu binary is modified during make install. You need to resign it with the correct entitlements, otherwise you will get an Unknown Error:
sudo codesign --entitlements /path/to/qemu/accel/hvf/entitlements.plist --force -s - `which qemu-system-aarch64`

Create Ubuntu VM

qemu-img create -f qcow2 disk.qcow2 10G
  • Create an empty file for persisting UEFI variables:
dd if=/dev/zero conv=sync bs=1m count=64 of=ovmf_vars.fd
  • Run qemu with the following command-line arguments:
qemu-system-aarch64 \
    -accel hvf \
    -m 2048 \
    -cpu cortex-a57 -M virt,highmem=off  \
    -drive file=/usr/local/share/qemu/edk2-aarch64-code.fd,if=pflash,format=raw,readonly=on \
    -drive file=ovmf_vars.fd,if=pflash,format=raw \
    -serial telnet::4444,server,nowait \
    -drive if=none,file=disk.qcow2,format=qcow2,id=hd0 \
    -device virtio-blk-device,drive=hd0,serial="dummyserial" \
    -device virtio-net-device,netdev=net0 \
    -netdev user,id=net0 \
    -vga none -device ramfb \
    -cdrom /path/to/ubuntu.iso \
    -device usb-ehci -device usb-kbd -device usb-mouse -usb \
    -monitor stdio
  • You should be able to install Ubuntu as normal
  • If you want a desktop environment, you can install it using sudo apt-get install ubuntu-desktop

Create Windows VM

  • Download Windows for ARM from here
  • Create an empty file for persisting UEFI variables:
dd if=/dev/zero conv=sync bs=1m count=64 of=ovmf_vars.fd
  • For Windows, we need to replace the VirtIO block device with something that is supported natively by the OS. Otherwise, the command-line is almost unchanged
  • You may want to pass multiple cores to the VM using -smp X:
qemu-system-aarch64 \
    -accel hvf \
    -m 2048 -smp 2 \
    -cpu cortex-a72 -M virt,highmem=off  \
    -drive file=/usr/local/share/qemu/edk2-aarch64-code.fd,if=pflash,format=raw,readonly=on \
    -drive file=ovmf_vars.fd,if=pflash,format=raw \
    -serial telnet::4444,server,nowait \
    -drive if=none,file=Windows10_InsiderPreview_Client_ARM64_en-us_20231.VHDX,format=vhdx,id=hd0,cache=writethrough \
    -device nvme,drive=hd0,serial="dummyserial" \
    -nic user,model=virtio \
    -vga none -device ramfb \
    -device usb-ehci -device usb-kbd -device usb-mouse -usb \
    -monitor stdio


Networking on Windows

Windows does not support VirtIO network interfaces out of the box. To get it working, you need to install additional drivers. See this gist for a guide (be sure to use version 0.1.190 instead of 0.1.185)


The resolution is set to 800x600 by default. To change it, hit Esc at the immediately after starting the VM, while you see the tianocore logo, to get into the OVMF config menu. Choose Device Manager -> OVMF Platform Configuration -> Change Preferred -> Select 1024x768 -> Commit Changes and Exit -> Esc -> Reset.


Port Forwarding

Proper NAT networking is currently not possible with QEMU due to the lack of tap devices in macOS Big Sur. If you just want to be able to connect to a port on the VM (e.g. for SSH or RDP), you can configure QEMU to forward a local port to the VM:

    -nic user,model=virtio,hostfwd=tcp: \

In this case the port for RDP is forwarded so that I can connect to the VM at localhost:3389. The same for Ubuntu/SSH:

    -netdev user,id=net0,hostfwd=tcp: \

Disk Snapshots

Some users experience a random filesystem corruptions when booting Windows which can be avoided with the cache=writethrough for the hard drive. You can also perform disk snapshots to save the state of the hard disk at a certain point in time and restore it later. To do this, shut the VM down and create a new disk with your original image as the backing file:

qemu-img create -b Windows10_InsiderPreview_Client_ARM64_en-us_20231.VHDX -F vhdx -f qcow2 disk.qcow2

Now, adjust the -drive parameter so that QEMU boots from your new image:

    -drive if=none,file=disk.qcow2,format=qcow2,id=hd0,cache=writethrough \

If something goes wrong you can now delete disk.qcow2 and recreate it using the same commmand to back to the original state.

diff --git a/Makefile b/Makefile
index bcbbec71a1..8b75085fa5 100644
--- a/Makefile
+++ b/Makefile
@@ -85,7 +85,7 @@ x := $(shell rm -rf meson-private meson-info meson-logs)
# 1. ensure config-host.mak is up-to-date
-config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios $(SRC_PATH)/VERSION
+config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios $(SRC_PATH)/QEMU_VERSION
@echo config-host.mak is out-of-date, running configure
@if test -f meson-private/coredata.dat; then \
./config.status --skip-meson; \
@@ -204,7 +204,7 @@ clean: recurse-clean
rm -f TAGS cscope.* *.pod *~ */*~
rm -f fsdev/*.pod scsi/*.pod
-VERSION = $(shell cat $(SRC_PATH)/VERSION)
dist: qemu-$(VERSION).tar.bz2
new file mode 100644
index 0000000000..5214c0b8b9
--- /dev/null
@@ -0,0 +1 @@
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 5214c0b8b9..0000000000
+++ /dev/null
@@ -1 +0,0 @@
diff --git a/ b/
index 2dc66ae930..a8f8a02b3f 100644
--- a/
+++ b/
@@ -1,7 +1,7 @@
project('qemu', ['c'], meson_version: '>=0.55.0',
default_options: ['warning_level=1', 'c_std=gnu99', 'cpp_std=gnu++11', 'b_colorout=auto'] +
(meson.version().version_compare('>=0.56.0') ? [ 'b_staticpic=false' ] : []),
- version: run_command('head', meson.source_root() / 'VERSION').stdout().strip())
+ version: run_command('head', meson.source_root() / 'QEMU_VERSION').stdout().strip())
not_found = dependency('', required: false)
if meson.version().version_compare('>=0.56.0')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment