A tiny C++ microVM hypervisor for macOS on Apple Silicon, built directly on
Hypervisor.framework.
It's intentionally small — under ~500 lines of C++17 — and gives you the plumbing to run a bare-metal aarch64 guest payload inside a hardware-isolated VM:
- Creates a VM and a vCPU via
hv_vm_create/hv_vcpu_create - Allocates and maps guest RAM
- Loads a flat binary at a configurable entry address
- Boots EL1h with the MMU off and runs the vCPU
- Handles HVC traps so the guest can call back into the VMM
- Reports data / instruction aborts and unknown exceptions cleanly
./scripts/run-vm.shThat's it. The script builds the hypervisor and the bundled aarch64 demo guest if needed, ad-hoc-signs the binary with the hypervisor entitlement, spins up a microVM, and prints:
Hello from the chubby-cat microVM guest!
Other ways to use the script:
# boot a custom flat-binary guest
./scripts/run-vm.sh path/to/payload.bin
# pass options through to chubby-cat (after `--`)
./scripts/run-vm.sh -- --mem 128 -v
# combine: custom payload + custom options
./scripts/run-vm.sh path/to/payload.bin -- --mem 128 --entry 0x80000000 -vThe script is idempotent — re-runs reuse the existing build, and rebuild
only when source files in src/ are newer than build/chubby-cat.
- macOS 11+ on Apple Silicon (arm64 only)
- Xcode command-line tools (
xcode-select --install) — provides clang, ld, codesign, and segedit - CMake ≥ 3.20 (optional — a plain Makefile also works)
With the included Makefile (no extra deps):
make # builds build/chubby-cat
make example # builds examples/hello.bin (the demo guest)Or with CMake:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildEither build step ad-hoc-signs the binary with the
com.apple.security.hypervisor entitlement (required by
Hypervisor.framework).
The fastest path is ./scripts/run-vm.sh (see Quick start
above). Equivalent direct invocation:
./build/chubby-cat examples/hello.binExpected output:
Hello from the chubby-cat microVM guest!
CLI options:
--mem <MiB> Guest RAM size in MiB (default: 64)
--entry <addr> Guest entry physical address (default: 0x80000000)
--stack <addr> Initial SP_EL1 (default: top of RAM)
-v, --verbose Verbose VMM output
┌──────────────────────────── chubby-cat (host EL2 / userspace) ───────────────┐
│ │
│ main.cpp ── parses args, creates VM, loads payload, runs vCPU │
│ vm.cpp ── hv_vm_create + hv_vm_map (mmap'd anonymous RAM) │
│ vcpu.cpp ── hv_vcpu_create + run-loop + ESR_EL2 decoding │
│ │
└──────────────────────────────┬───────────────────────────────────────────────┘
│ hv_vcpu_run() / exit (HVC, abort, …)
▼
┌──────────────────────── guest (EL1h, MMU off, flat-mapped) ─────────────────┐
│ │
│ flat binary loaded at --entry, vCPU starts there with SP_EL1 = --stack │
│ uses HVCs as a "syscall" interface back into the VMM │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
A guest interacts with the host via HVC immediates:
| HVC # | Name | Args |
|---|---|---|
#1 |
putchar | x0 = byte to print |
#2 |
exit | x0 = exit code (low 8 bits) |
#3 |
puts | x0 = guest physical address, x1 = length |
This is the smallest useful surface for a "hello world" microVM. It's easy
to extend — every new HVC immediate is one new case in vcpu.cpp.
CPSR= EL1h, all DAIF interrupts maskedPC=--entrySP_EL1=--stack(defaults to the top of RAM)CPACR_EL1.FPEN= 0b11 (FP/SIMD usable without trapping)- All other system registers retain Hypervisor.framework defaults — notably the MMU is off, so guest physical and virtual addresses coincide.
chubby-cat is intentionally minimal. The same plumbing extends naturally
to a real Linux microVM:
- PSCI handler (
CPU_ON,SYSTEM_OFF,SYSTEM_RESET) for clean shutdown and SMP - Generic Interrupt Controller (GICv3) trap-and-emulate
- virtio-mmio transport with a virtio-console for
stdout - virtio-block backed by a host file
- virtio-net backed by
vmnet.framework - Linux kernel
Imageboot protocol + flattened device tree (FDT) - Multi-vCPU SMP
The architecture is set up so that each of these is a separable component
plugged into the existing exit handler in vcpu.cpp.
MIT — see LICENSE.