Safe Rust bindings for the Apple Silicon Hypervisor.framework.
Create a Rust project and add Applevisor as a dependency in Cargo.toml. You can either pull it from crates.io ...
# Check which version is the latest, this part of the README might not be updated
# in future releases.
applevisor = "1.0"... or directly from the GitHub repository.
applevisor = { git="https://github.com/impalabs/applevisor", branch="master" }Since the Hypervisor.Framework is still evolving, depending on your macOS version (and those you want to support), some methods won't be available.
The following features can be used to determine the API available to your project:
macos-26-0: all methods available up to MacOS 26.0 (current default feature);macos-15-2: all methods available up to MacOS 15.2;macos-15-0: all methods available up to MacOS 15.0;macos-13-0: all methods available up to MacOS 13.0;macos-12-1: all methods available up to MacOS 12.1;
If you only want the base methods introduced before MacOS 12.1, you must disable default-features when declaring applevisor as a dependency:
applevisor = {version = "x.x.x", default-features = false}Refer to the Hypervisor.Framework API section to get the list of available methods per macOS versions.
To be able to reach the Hypervisor Framework, a binary executable has to have been granted the hypervisor entitlement. There are multiple entitlements you can add, which are documented by Apple, but the most important one is com.apple.security.hypervisor.
Create a file called entitlements.txt in the project's root directory and add the following:
<?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.security.hypervisor</key>
<true/>
</dict>
</plist>Build your project, and then sign the binary with your entitlements:
cargo build --release
codesign --sign - --entitlements entitlements.xml --deep --force target/release/${PROJECT_NAME}You can now run your program:
target/release/${PROJECT_NAME}The documentation is available online at the following address: https://docs.rs/applevisor
Alternatively, you can generate the documentation using cargo:
cargo doc --openThe following example:
- creates a virtual machine for the current process;
- creates a virtual CPU;
- enables the hypervisor's debug features to be able to use breakpoints later on;
- creates a physical memory mapping of 0x1000 bytes and maps it at address 0x4000 with RWX permissions;
- writes the instructions
mov x0, #0x42; brk #0;at address 0x4000; - sets PC to 0x4000;
- starts the vCPU and runs our program;
- returns when it encounters the breakpoint.
use applevisor::prelude::*;
fn main() -> Result<()> {
// Creates a new virtual machine. There can be one, and only one, per process. Operations
// on the virtual machine remains possible as long as this object is valid.
let vm = VirtualMachine::new()?;
// Creates a new virtual CPU. This object abstracts operations that can be performed on
// CPUs, such as starting and stopping them, changing their registers, etc.
let vcpu = vm.vcpu_create()?;
// Enables debug features for the hypervisor. This is optional, but it might be required
// for certain features to work, such as breakpoints.
vcpu.set_trap_debug_exceptions(true)?;
vcpu.set_trap_debug_reg_accesses(true)?;
// Creates a mapping object that represents a 0x1000-byte physical memory range.
let mut mem = vm.memory_create(0x1000)?;
// This mapping needs to be mapped to effectively allocate physical memory for the guest.
// Here we map the region at address 0x4000 and set the permissions to Read-Write-Execute.
mem.map(0x4000, MemPerms::RWX)?;
// Writes a `mov x0, #0x42` instruction at address 0x4000.
mem.write_u32(0x4000, 0xd2800840)?;
// Writes a `brk #0` instruction at address 0x4004.
mem.write_u32(0x4004, 0xd4200000)?;
// Sets PC to 0x4000.
vcpu.set_reg(Reg::PC, 0x4000)?;
// Starts the Vcpu. It will execute our mov and breakpoint instructions before stopping.
vcpu.run()?;
// The *exit information* can be used to used to retrieve different pieces of
// information about the CPU exit status (e.g. exception type, fault address, etc.).
let exit_info = vcpu.get_exit_info();
// If everything went as expected, the value in X0 is 0x42...
assert_eq!(vcpu.get_reg(Reg::X0), Ok(0x42));
// ... the vcpu has stopped because of an exception ...
assert_eq!(exit_info.reason, ExitReason::EXCEPTION);
// ... and the exception syndrome corresponds to a breakpoint exception (which would
// have been a different value without the call to `set_trap_debug_exceptions()`).
assert_eq!(exit_info.exception.syndrome >> 26, 0b111100);
Ok(())
}To run tests using the Makefile provided with the project, you'll first need to install jq. You can do so using brew:
brew install jqYou can then run the tests with the provided Makefile using the following command:
# To run all tests
make tests-all
# To run stable-only tests
make tests-stable-all
# To run nightly-only tests
make tests-nightly-allTo get the tests coverage, make sure LLVM is installed, and llvm-cov and llvm-profdata are in your PATH.
You can then get the coverage with the provided Makefile using the following command:
# Coverage for the latest macos version
make coverage-macos-26-0
# Other targets are available in the Makefile to get the coverage for previous macos versions.
make coverage-macos-$(major)-$(minor)-
MacOS >=26.0
- Use feature
macos-26-0. - Available methods:
- Use feature
-
MacOS >=15.2 and <26.0
- Use feature
macos-15-2. - Available methods:
- Use feature
-
MacOS >=15.0 and <15.2
- Use feature
macos-15-0. - Available methods:
hv_gic_createhv_gic_set_spihv_gic_send_msihv_gic_get_distributor_reghv_gic_set_distributor_reghv_gic_get_redistributor_basehv_gic_get_redistributor_reghv_gic_set_redistributor_reghv_gic_get_icc_reghv_gic_set_icc_reghv_gic_get_ich_reghv_gic_set_ich_reghv_gic_get_icv_reghv_gic_set_icv_reghv_gic_get_msi_reghv_gic_set_msi_reghv_gic_set_statehv_gic_resethv_gic_config_createhv_gic_config_set_distributor_basehv_gic_config_set_redistributor_basehv_gic_config_set_msi_region_basehv_gic_config_set_msi_interrupt_rangehv_gic_get_distributor_sizehv_gic_get_distributor_base_alignmenthv_gic_get_redistributor_region_sizehv_gic_get_redistributor_sizehv_gic_get_redistributor_base_alignmenthv_gic_get_msi_region_sizehv_gic_get_msi_region_base_alignmenthv_gic_get_spi_interrupt_rangehv_gic_get_intidhv_gic_state_createhv_gic_state_get_sizehv_gic_state_get_datahv_vm_config_get_el2_supportedhv_vm_config_get_el2_enabledhv_vm_config_set_el2_enabled
- Use feature
-
MacOS >=13.0 and <15.0
- Use feature
macos-13-0. - Available methods:
- Use feature
-
MacOS >=12.1 and <13.0
- Use feature
macos-12-1. - Available methods:
- Use feature
-
MacOS >=11.0 and <12.1
- Use
--no-default-featuresor disabledefault-features. - Available methods:
hv_vcpu_createhv_vcpu_destroyhv_vcpu_get_reghv_vcpu_set_reghv_vcpu_get_simd_fp_reghv_vcpu_set_simd_fp_reghv_vcpu_get_sys_reghv_vcpu_set_sys_reghv_vcpu_get_pending_interrupthv_vcpu_set_pending_interrupthv_vcpu_get_trap_debug_exceptionshv_vcpu_set_trap_debug_exceptionshv_vcpu_get_trap_debug_reg_accesseshv_vcpu_set_trap_debug_reg_accesseshv_vcpu_runhv_vcpus_exithv_vcpu_get_exec_timehv_vcpu_get_vtimer_maskhv_vcpu_set_vtimer_maskhv_vcpu_get_vtimer_offsethv_vcpu_set_vtimer_offsethv_vcpu_config_createhv_vcpu_config_get_feature_reghv_vcpu_config_get_ccsidr_el1_sys_reg_valueshv_vm_get_max_vcpu_counthv_vm_createhv_vm_destroyhv_vm_maphv_vm_unmaphv_vm_protect
- Use