QEMU’s vmapple machine can boot macOS guests on Apple Silicon through HVF, but reaching a working desktop on a current Mac needs three open patch series and one local change first. This guide covers the full path to get macOS running under QEMU: host setup, the disk image, the patches, and the boot command. The last section sets up a serial console for kernel debugging.
What you need
- An Apple Silicon Mac. The patches were developed on an M4 Max running macOS 26.4.1 (
25E253). M3 and earlier need fewer workarounds (one patch is M4-specific), but the rest of the guide is unchanged. - About 150 GB of free disk for the base image and the QEMU build tree.
- A macOS installer
.ipswfor the version you want the guest to run. The guide assumes 26.4.1.
The guest runs on 4 GB of RAM by default. Give it more if the workload calls for it; §5d covers what to do above 62 GB.
The patches and command lines below were tested on a macOS 26.4.1 host with a macOS 26.4.1 guest. Hosts on macOS 15.4 and later work too. macOS hosts older than 15.4 don’t need the apple-gfx patch in §5a but still need the others.
1. Disable SIP and AMFI on the host
The patched QEMU binary will codesign with com.apple.private.hypervisor. The kernel only honors that entitlement when SIP is off and AMFI is told to allow private entitlements. This takes two reboots.
Reboot 1: disable SIP from recoveryOS. Shut down, hold the power button until “Loading startup options” appears, then click Options. After logging in, open Terminal from the Utilities menu and run:
csrutil disableReboot to macOS.
Reboot 2: set the AMFI boot-arg from a regular Terminal. nvram from recoveryOS won’t allow you to set boot-args; set the boot-args from the running system. Open Terminal and run:
sudo nvram boot-args="amfi_get_out_of_my_way=1"Reboot one more time, then verify:
csrutil status # System Integrity Protection status: disabled.
nvram boot-args # boot-args amfi_get_out_of_my_way=1Without both pieces in place, every QEMU launch will fail.
2. Install the build tools
Xcode command-line tools, Homebrew, and the QEMU build dependencies:
xcode-select --install
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install ninja pkg-config glib pixman dtc make python@3.123. Install the base disk under macosvm
QEMU’s vmapple boots disks that have already been initialized by Apple’s installer. The simplest way to produce one is to run the installer once under macosvm, Stéphane Sudre’s open-source wrapper around Virtualization.framework.
Grab the prebuilt arm64 binary from the 0.2-2 release:
curl -LO https://github.com/s-u/macosvm/releases/download/0.2-2/macosvm-0.2-2-arm64-darwin21.tar.gz
tar -xzf macosvm-0.2-2-arm64-darwin21.tar.gz
sudo mv macosvm /usr/local/bin/Or build from source:
git clone https://github.com/s-u/macosvm.git
cd macosvm
makeMake a working directory and put a macOS .ipsw in it (download from developer.apple.com or ipsw.me):
mkdir -p ~/Developer/vmapple-disk
cd ~/Developer/vmapple-disk
# example: UniversalMac_26.4.1_25E253.ipswPlace the macosvm command in the just-created directory:
Initialize a 64 GB disk and run the installer:
./macosvm --disk disk.img,size=64g \
--aux aux.img \
--restore UniversalMac_26.4.1_25E253.ipsw \
-c 4 -r 8g \
macosvm.jsonThe installer takes around 5 minutes. When it’s done, run the following command to start the VM and finish the welcome assistant:
./macosvm -g macosvm.jsonTurn on FileVault when the welcome assistant offers it. That is the easiest way to get the on-disk keybag entries QEMU needs (see the next section for why).
Enable SSH in the guest by going to System Settings > Sharing and turning on Remote Login. This is optional but makes it easier to run commands in the guest for the next few steps.
Keep the file named macosvm.json as it is needed by QEMU’s contrib/vmapple/uuid.sh script, which reads it by that name in §7.
Turn on FileVault before the first QEMU boot
macosvm runs the guest happily without FileVault, but the first boot under QEMU has to land with FileVault on. QEMU’s vmapple does not satisfy Apple’s APFS encryption check at runtime: the kernel reads two NVRAM blobs (boop-storage-misc and krn.c1*) to decide whether the data volume is acceptable, and FileVault provisioning is what writes them. Without those entries, QEMU panics at apfs_vfsops.c:2361 (“unencrypted data volume is not allowed”) and never reaches the login window.
If you skipped FileVault during the welcome assistant, start the VM using macosvm and turn it on now from inside the guest:
sudo fdesetup enable -user $(whoami)Wait for the process to finish, then:
sudo shutdown -h nowAfter the first successful boot under QEMU you can turn FileVault back off (sudo fdesetup disable) and the guest will keep booting.
Trim the auxiliary image
macosvm produces an aux.img next to the disk. QEMU wants it without the leading 16 KiB header:
dd if=aux.img of=aux.img.trimmed bs=$(( 0x4000 )) skip=14. Clone QEMU
mkdir -p ~/Developer
cd ~/Developer
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
git checkout ee7eb612be # tested at 2026-05-06 staging tagEnable the vmapple machine in the aarch64 build (it’s off by default):
sed -i '' '/CONFIG_VMAPPLE=n/d' configs/devices/aarch64-softmmu/default.mak5. Apply the patches
Three upstream series and one local change. Pull each one as a git am from the mailing-list archive (b4 am <message-id> is the easiest path), or apply by hand.
a. apple-gfx for macOS 15.4 (PGMemoryMapDescriptor)
Apple removed the mapMemory/unmapMemory block API in macOS 15.4 and replaced it with a PGMemoryMapDescriptor that the host fills in once at descriptor build time. This series adapts the apple-gfx and apple-gfx-mmio code to the new API and adds the related enableArgumentBuffers, enableProcessIsolation, and enableProtectedContent toggles to the descriptors.
| Series | [PATCH v4 0/3] vmapple: making it work on the latest macOS host releases |
| Author | Mohamed Mediouni <mohamed@unpredictable.fr> |
| Date | January 2026 |
| Files | hw/display/apple-gfx.{h,m}, hw/display/apple-gfx-mmio.m |
b. M4 and recent-macOS workarounds for HVF
The same author’s earlier RFC fixes the path from hv_vm_create through AVPBooter and into XNU on a current Apple Silicon host:
- Private hypervisor ISA.
vmappleguests use an Apple-private hypervisor ISA level. The series calls_hv_vm_config_set_isa(config, 3)afterhv_vm_config_set_ipa_size. The public entitlement does not allow this; that’s why §6 codesigns with the private one. - GICv2M with the macOS-compat bit. Apple’s IOPCIFamily reads bit 31 of
MSI_TYPERas a “MSI supported” capability bit. Without it, AVPBooter halt-loops at PC0x10044cwaiting for an interrupt controller it accepts. The series adds anarm-gicv2minstance at0x1FFF0000and amacos-compatproperty that sets the bit. - AVP RTC and CTRR stubs. XNU pokes MMIO at
0x30240000(avp-rtc) and0x30260000(avp-ctrr) during early boot. An unhandled access there is a panic. The series wires up zero-on-read, no-op-on-write stubs. SCTLR_EL1trap for FEAT_SSBS on M4. M4 does not implementFEAT_SSBS, but XNU expects to read and writeSCTLR_EL1.DSSBS. The series turns on Fine-Grained Traps (HFGRTR/HFGWTR) for the register and synthesizes the bit in the read handler. Hosts that do implementFEAT_SSBS(M1 through M3) skip the trap.
| Series | [RFC v2 0/4] vmapple: making it work on the latest macOS releases and Apple M4 |
| Author | Mohamed Mediouni <mohamed@unpredictable.fr> |
| Date | October 2025 |
| Files | accel/hvf/hvf-accel-ops.c, hw/intc/arm_gicv2m.c, hw/vmapple/vmapple.c, include/system/hvf_int.h, target/arm/hvf/hvf.c |
c. ISV=0 data-abort emulation
Apple’s HVF does not populate the syndrome register (ISV=0) for SIMD, atomic, or pair load/store traps, so QEMU sees an opaque data abort and the default code asserts. This series adds a decodetree-based AArch64 load/store emulator and dispatches to it from the HVF data-abort handler.
The library covers LDR/STR (immediate, register, and SIMD forms), LDP/STP, LDXR/STXR/LDXP/STXP, the atomic ops (LDADD, LDCLR, LDEOR, LDSET, LD{S,U}{MIN,MAX}, SWP), CAS/CASP, LDRA, and NOP. The HVF handler reads four bytes at env->pc, runs them through arm_emul_insn, and advances the PC.
| Series | [PATCH v5 0/6] target/arm: ISV=0 data abort emulation library |
| Author | Lucas Amaral |
| Date | March 2026 |
| Files | target/arm/emulate/ (new), target/arm/hvf/hvf.c, target/arm/meson.build |
d. Local patch: support -m larger than 62 GB
HVF’s default Intermediate Physical Address (IPA) size is 36 bits. With vmapple placing RAM at base 0x70000000, that caps the guest at roughly 62 GB before hv_vm_map returns HV_BAD_ARGUMENT. Add a MachineClass.get_physical_address_range hook to vmapple that mirrors hw/arm/virt.c::virt_get_physical_address_range: when the requested top GPA exceeds the default, it asks HVF for the host’s max IPA size and returns that.
static int vmapple_get_physical_address_range(MachineState *ms,
int default_ipa_size,
int max_ipa_size)
{
uint64_t highest_gpa = memmap[VMAPPLE_MEM].base + ms->ram_size - 1;
int requested_ipa_size = 64 - clz64(highest_gpa);
if (requested_ipa_size <= default_ipa_size) {
return default_ipa_size;
} else if (requested_ipa_size <= max_ipa_size) {
return max_ipa_size;
}
error_report("-m value requires an IPA range of %d bits, but the host "
"only supports %d bits", requested_ipa_size, max_ipa_size);
return -1;
}Wired into vmapple_machine_class_init as mc->get_physical_address_range = vmapple_get_physical_address_range;. Not yet upstream.
Build
mkdir build && cd build
../configure --target-list=aarch64-softmmu --enable-hvf --enable-cocoa
ninjaOutput is build/qemu-system-aarch64-unsigned. The build takes about five to ten minutes.
6. Codesign with the private entitlement
ninja left you in ~/Developer/qemu/build. Step back to the source root and replace accel/hvf/entitlements.plist so it grants com.apple.private.hypervisor:
cd ~/Developer/qemu
cat > accel/hvf/entitlements.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.private.hypervisor</key>
<true/>
</dict>
</plist>
EOF
codesign --force --sign - \
--entitlements accel/hvf/entitlements.plist \
build/qemu-system-aarch64-unsigned
mv build/qemu-system-aarch64-unsigned build/qemu-system-aarch64Verify:
codesign -d --entitlements - build/qemu-system-aarch64The output must contain com.apple.private.hypervisor. If it shows com.apple.security.hypervisor instead, the file edit didn’t take or codesign wasn’t re-run. The two entitlements are mutually exclusive at runtime: the public one blocks _hv_vm_config_set_isa, the private one allows it.
7. Boot
The vmapple machine derives the platform’s ECID from a UUID, and that ECID has to match the one macosvm used when it provisioned the disk. The QEMU tree ships a small extractor that reads the UUID out of macosvm.json:
export UUID="$(~/Developer/qemu/contrib/vmapple/uuid.sh ~/Developer/vmapple-disk/macosvm.json)"
export AUX=~/Developer/vmapple-disk/aux.img.trimmed
export DISK=~/Developer/vmapple-disk/disk.img
export AVPBOOTER=/System/Library/Frameworks/Virtualization.framework/Resources/AVPBooter.vmapple2.binUsing a different UUID will boot AVPBooter, but the FileVault keybag entries from §3 won’t unlock the data volume and the kernel will panic the same way an un-FileVaulted disk would.
Then:
~/Developer/qemu/build/qemu-system-aarch64 \
-display cocoa,show-cursor=on -smp 4 -m 4G -accel hvf \
-qmp unix:/tmp/qemu-qmp.sock,server,nowait \
-serial mon:stdio \
-M vmapple,uuid="$UUID" \
-bios "$AVPBOOTER" \
-drive file="$AUX",if=pflash,format=raw \
-drive file="$DISK",if=pflash,format=raw \
-drive file="$AUX",if=none,id=aux,format=raw \
-drive file="$DISK",if=none,id=root,format=raw \
-device vmapple-virtio-blk-pci,variant=aux,drive=aux \
-device vmapple-virtio-blk-pci,variant=root,drive=root \
-netdev user,id=net0,ipv6=off,hostfwd=tcp::2222-:22 \
-device virtio-net-pci,netdev=net0To run with more than 62 GB, raise -m and rely on the IPA-size hook from §5d.
AVPBooter takes a few seconds, then macOS boots to the login window. SSH is available on localhost:2222 once login completes.
8. Fix HID after login
Universal Control’s input filter loads on the user-session WindowServer and silently drops mouse and keyboard events on vmapple guests. Login-window HID still works, but everything stops the moment you authenticate.
Two ways to fix it. SSH into the guest (ssh -p 2222 user@localhost) for either one.
Disable Universal Control:
defaults -currentHost write com.apple.universalcontrol Disable -bool true
sudo sysctl kern.usbac.requireUserApproval
killall -HUP WindowServerAfter the next reboot, the mouse and keyboard will work in the QEMU window.
9. Serial console for kernel work
The vmapple machine wires a PL011 UART at 0x20010000 to QEMU’s serial_hd(0). Two changes get the kernel log on your terminal: route the chardev somewhere readable, and tell XNU to use it.
-serial mon:stdio will tell QEMU to multiplex the monitor and the UART onto stdio. For kernel boot logs and kprintf, set the boot-args inside the guest:
sudo nvram boot-args='-v keepsyms=1 debug=0x8 serial=3'serial=3 enables SERIALMODE_OUTPUT | SERIALMODE_INPUT (kernel printf to PL011, kdb input from PL011). The bit definitions are in XNU’s osfmk/console/serial_protos.h:
| Bit | Macro | Effect |
|---|---|---|
0x01 | SERIALMODE_OUTPUT | kernel printf goes to serial |
0x02 | SERIALMODE_INPUT | serial is an input device for kdb |
0x04 | SERIALMODE_SYNCDRAIN | block on TX (slow but lossless for early hangs) |
0x08 | SERIALMODE_BASE_TTY | /dev/console is the serial port |
Attribution
This guide follows three open patch series and one local change.
| Source | Author | Adds |
|---|---|---|
vmapple: making it work on the latest macOS releases and Apple M4 (RFC v2), Oct 2025 | Mohamed Mediouni <mohamed@unpredictable.fr> | Private hypervisor ISA, GICv2M with macOS-compat bit, AVP RTC/CTRR stubs, M4 SCTLR_EL1 / FEAT_SSBS workaround |
vmapple: making it work on the latest macOS host releases (v4), Jan 2026 | Mohamed Mediouni <mohamed@unpredictable.fr> | apple-gfx PGMemoryMapDescriptor path for macOS 15.4 hosts |
target/arm: ISV=0 data abort emulation library (v5), Mar 2026 | Lucas Amaral | AArch64 load/store emulator for ISV=0 data aborts in HVF |
| Local patch | adalric | vmapple_get_physical_address_range hook for guests larger than 62 GB |
The base disk is built with s-u/macosvm by Stéphane Sudre. The vmapple machine itself was contributed to QEMU by Alexander Graf and is documented in the upstream manual.