Independent Rust workspace for modular dynamic binary translation.
volt-boot launches a raw arm64 Linux Image with a PL011 console, GICv2,
and optional initramfs or VirtIO block root filesystem.
cargo run --release --bin volt-boot -- \
~/src/linux/arch/arm64/boot/Image \
--initrd initramfs.cpio.zst \
20 'console=ttyAMA0 rdinit=/sbin/init nokaslr'--initrd (also --initramfs) places the image in guest RAM and publishes
linux,initrd-start and linux,initrd-end in /chosen. Raw and zstd-compressed
images are accepted. The default RAM size grows for an initramfs; override it with
--memory 512M when needed.
For a disk-backed root filesystem, pass --disk (aliases: --drive, --rootfs):
cargo run --release --bin volt-boot -- \
~/src/linux/arch/arm64/boot/Image \
--disk rootfs.ext4 \
20The disk form supplies root=/dev/vda rw by default. --cpus, --idle,
--fast, and --spin control the virtual CPU count and WFI policy. Use
volt-boot --help for the complete generated interface.
volt-boot also boots an XNU kernel. A Mach-O image is detected by its magic, so no
flag is needed:
cargo run --release --bin volt-boot -- \
path/to/kernel.development.qemu 20 --fastThe loader follows the bootxnu U-Boot command: it places the Mach-O segments at a
physBase that is congruent to the link-time virtBase modulo a 32 MiB L2 block,
builds an Apple flattened device tree (the equivalent of mkafdt.py) and a boot_args
block, and enters at the kernel entry with the MMU off and x0 pointing at
boot_args. The machine is the QEMU virt layout XNU's QEMU platform expects:
PL011 uart0 at 0x09000000, and a GICv3 (distributor 0x08000000, redistributor
0x080a0000, ICC_* system registers) that forwards the virtual timer as a FIQ. The
last 512 KiB of RAM is the panic log (chosen/pram), and memSize excludes it.
Defaults for a Mach-O kernel are 1 GiB of RAM and the boot-args
-v serial=3 debug=0x14e keepsyms=1 serial-device-name=uart0. To pass your own, the
first word must not start with a dash (serial=3 -v ...), or it is read as a flag.
--initrd, --disk and --cpus above 1 are rejected for XNU.
The bare kernel boots through the pmap and VM bootstrap, zone and IOKit start-up, the
scheduler and IONVRAM, and then panics in read_random: the corecrypto kext that
registers the kernel PRNG is not part of the kernel image. That is the kernel's own
behavior on any machine, and the panic message and backtrace appear on the console.
Going further needs a kernelcache with corecrypto prelinked.
Not modelled: privileged-access-never is stored and saved in SPSR_EL1 but not
enforced by translation, the physical timer is stored but never fires, floating point
rounds to nearest even and sets no FPSR flags, and the GICv3 carries only the
virtual timer (no SPIs, SGIs or group 1).
Translating a block costs much more than running it once, and a kernel boot runs most
of its code only a few times. While the vCPU runs a block it has just built, spare cores
build the blocks that statically follow it (same-page branch targets and fall-throughs)
into the shared block cache. By default up to three helper threads are used, leaving one
core for the vCPU; set VOLT_JIT_THREADS=0 to translate on demand only, or a number to
choose the count. Speculative blocks are keyed by their bytes like any other, so they
never change what the guest executes.