Skip to content

Commit 00b9be0

Browse files
committed
High level API implementation
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
1 parent dcff66a commit 00b9be0

10 files changed

Lines changed: 809 additions & 2 deletions

File tree

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
11
# hv
2-
Rust bindings for Hypervisor Framework
2+
3+
[![CI](https://github.com/mxpv/hv/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mxpv/hv/actions/workflows/ci.yml)
4+
5+
`hv` is a high level Rust bindings for Hypervisor Framework.
6+
7+
Build virtualization solutions on top of a lightweight hypervisor using Rust.
8+
9+
[Documentation](https://developer.apple.com/documentation/hypervisor)
10+
11+
## Requirements
12+
13+
### Hypervisor Framework
14+
15+
At runtime, determine whether the Hypervisor APIs are available on a particular machine with the `sysctl`:
16+
17+
```bash
18+
$ sysctl kern.hv_support
19+
kern.hv_support: 1
20+
```
21+
22+
In order to use Hypervisor API your app must have `com.apple.security.hypervisor` entitlement.
23+
Refer to [example.entitlements](example.entitlements) for example of how entitlement file might look like.
24+
25+
Use the following command to self sign your binary for local development:
26+
27+
```bash
28+
$ codesign --sign - --force --entitlements=example.entitlements ./binary
29+
```

example.entitlements

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<!--
6+
A Boolean value that indicates whether the app creates and manages virtual machines.
7+
The entitlement is required to use the Hypervisor APIs in any process.
8+
-->
9+
<key>com.apple.security.hypervisor</key>
10+
<true/>
11+
</dict>
12+
</plist>

hv-sys/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
name = "hv-sys"
33
version = "0.1.0"
44
edition = "2018"
5-
description = "Low level Rust binding for Hypervisor Framework generated with bindgen"
5+
description = "Unsafe bindings for Hypervisor Framework generated with bindgen"
6+
authors = ["Maksym Pavlenko <pavlenko.maksym@gmail.com>"]
7+
repository = "https://github.com/mxpv/hv"
8+
license-file = "../LICENSE"
9+
readme = "../README.md"
610

711
[dependencies]
812

hv-sys/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ fn main() {
99
.header("wrapper.h")
1010
.clang_arg(format!("-F{}/System/Library/Frameworks", show_sdk_path())) // -F<directory> Add framework to the search path
1111
.allowlist_function("hv_.*")
12+
.allowlist_var("HV.*")
13+
.allowlist_var("VM.*")
14+
.allowlist_var("IRQ.*")
1215
.layout_tests(false)
1316
.generate()
1417
.expect("Failed to generate bindings")
1518
.write_to_file(out_path.join("bindings.rs"))
1619
.expect("Failed to write bindings file");
20+
21+
println!("cargo:rustc-link-lib=framework=Hypervisor");
1722
}
1823

1924
/// Execute `xcrun --sdk macosx --show-sdk-path` to locate MacOS SDK

hv/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name = "hv"
33
version = "0.1.0"
44
edition = "2018"
55
description = "High level Rust bidings to Hypervisor Framework"
6+
authors = ["Maksym Pavlenko <pavlenko.maksym@gmail.com>"]
7+
repository = "https://github.com/mxpv/hv"
8+
license-file = "../LICENSE"
9+
readme = "../README.md"
610

711
[dependencies]
812
hv-sys = { path = "../hv-sys", version = "0.1.0" }

hv/examples/caps.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
fn main() {
2+
hv::vm_create().unwrap();
3+
4+
println!(
5+
"Max vCPUs: {}",
6+
hv::capability(hv::Capability::VCpuMax).unwrap()
7+
);
8+
9+
println!(
10+
"Available address spaces: {}",
11+
hv::capability(hv::Capability::AddrSpaceMax).unwrap()
12+
);
13+
14+
hv::vm_destroy().unwrap();
15+
}

hv/src/lib.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,227 @@
1+
//! `hv` is a high level safe Rust crate to access Hypervisor Framework.
2+
3+
use std::error;
4+
use std::fmt;
5+
6+
/// Low level access to generated bindings.
17
pub use hv_sys as sys;
8+
9+
mod vcpu;
10+
pub mod vmx;
11+
pub mod x86;
12+
13+
pub use vcpu::Vcpu;
14+
15+
/// Helper macro to call unsafe Hypervisor functions and map returned error codes to [Error] type.
16+
#[macro_export]
17+
macro_rules! call {
18+
($f:expr) => {{
19+
let code = unsafe { $f };
20+
match code {
21+
::hv_sys::hv_return_t_HV_SUCCESS => Ok(()),
22+
_ => Err(Error::from(code)),
23+
}
24+
}};
25+
}
26+
27+
/// The type of system capabilities.
28+
#[repr(u64)]
29+
#[non_exhaustive]
30+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
31+
pub enum Capability {
32+
VCpuMax = sys::hv_capability_t_HV_CAP_VCPUMAX,
33+
AddrSpaceMax = sys::hv_capability_t_HV_CAP_ADDRSPACEMAX,
34+
}
35+
36+
/// Type of a user virtual address.
37+
pub type UVAddr = sys::hv_uvaddr_t;
38+
39+
/// Type of a guest physical address.
40+
pub type GPAddr = sys::hv_gpaddr_t;
41+
42+
/// Type of a guest address space.
43+
pub type Space = sys::hv_vm_space_t;
44+
45+
pub const VM_SPACE_DEFAULT: Space = sys::HV_VM_SPACE_DEFAULT;
46+
47+
/// Guest physical memory region permissions.
48+
#[repr(u32)]
49+
#[non_exhaustive]
50+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
51+
pub enum Memory {
52+
Read = sys::HV_MEMORY_READ,
53+
Write = sys::HV_MEMORY_WRITE,
54+
Exec = sys::HV_MEMORY_EXEC,
55+
}
56+
57+
/// Creates a VM instance for the current process.
58+
pub fn vm_create() -> Result<(), Error> {
59+
call!(sys::hv_vm_create(0))
60+
}
61+
62+
/// Gets the value of capabilities of the system.
63+
pub fn capability(cap: Capability) -> Result<u64, Error> {
64+
let mut out = 0_u64;
65+
call!(sys::hv_capability(cap as sys::hv_capability_t, &mut out))?;
66+
Ok(out)
67+
}
68+
69+
/// Creates an additional guest address space for the current task.
70+
pub fn vm_space_create() -> Result<Space, Error> {
71+
let mut space: Space = 0;
72+
call!(sys::hv_vm_space_create(&mut space))?;
73+
Ok(space)
74+
}
75+
76+
/// Destroys the address space instance associated with the current task.
77+
///
78+
/// # Arguments
79+
/// * `asid` - Address space ID
80+
pub fn vm_space_destroy(asid: Space) -> Result<(), Error> {
81+
call!(sys::hv_vm_space_destroy(asid))
82+
}
83+
84+
/// Maps a region in the virtual address space of the current task into the guest physical
85+
/// address space of the VM.
86+
///
87+
/// # Arguments
88+
/// * `uva` - Page aligned virtual address in the current task.
89+
/// * `gpa` - Page aligned address in the guest physical address space.
90+
/// * `size` - Size in bytes of the region to be mapped.
91+
/// * `flags` - READ, WRITE and EXECUTE permissions of the region
92+
pub fn vm_map(uva: UVAddr, gpa: GPAddr, size: u64, flags: Memory) -> Result<(), Error> {
93+
call!(sys::hv_vm_map(
94+
uva,
95+
gpa,
96+
size,
97+
flags as sys::hv_memory_flags_t
98+
))
99+
}
100+
101+
/// Unmaps a region in the guest physical address space of the VM
102+
///
103+
/// # Arguments
104+
/// * `gpa` - Page aligned address in the guest physical address space.
105+
/// * `size` - Size in bytes of the region to be unmapped.
106+
pub fn vm_unmap(gpa: GPAddr, size: u64) -> Result<(), Error> {
107+
call!(sys::hv_vm_unmap(gpa, size))
108+
}
109+
110+
/// Modifies the permissions of a region in the guest physical address space of the VM.
111+
///
112+
/// # Arguments
113+
/// * `gpa` - Page aligned address in the guest physical address space.
114+
/// * `size` - Size in bytes of the region to be modified.
115+
/// * `flags` - New READ, WRITE and EXECUTE permissions of the region.
116+
pub fn vm_protect(gpa: GPAddr, size: u64, flags: Memory) -> Result<(), Error> {
117+
call!(sys::hv_vm_protect(
118+
gpa,
119+
size,
120+
flags as sys::hv_memory_flags_t
121+
))
122+
}
123+
124+
/// Maps a region in the virtual address space of the current task
125+
/// into a guest physical address space of the VM.
126+
///
127+
/// # Arguments
128+
/// * `asid` - Address space ID.
129+
/// * `uva` - Page aligned virtual address in the current task.
130+
/// * `gpa` - Page aligned address in the guest physical address space.
131+
/// * `size` - Size in bytes of the region to be mapped.
132+
/// * `flags` - READ, WRITE and EXECUTE permissions of the region.
133+
pub fn vm_map_space(
134+
asid: Space,
135+
uva: UVAddr,
136+
gpa: GPAddr,
137+
size: u64,
138+
flags: Memory,
139+
) -> Result<(), Error> {
140+
call!(sys::hv_vm_map_space(
141+
asid,
142+
uva,
143+
gpa,
144+
size,
145+
flags as sys::hv_memory_flags_t
146+
))
147+
}
148+
149+
/// Unmaps a region in a guest physical address space of the VM.
150+
///
151+
/// # Arguments
152+
/// * `asid` - Address space ID.
153+
/// * `gpa` - Page aligned address in the guest physical address space.
154+
/// * `size` - Size in bytes of the region to be unmapped.
155+
pub fn vm_unmap_space(asid: Space, gpa: GPAddr, size: u64) -> Result<(), Error> {
156+
call!(sys::hv_vm_unmap_space(asid, gpa, size))
157+
}
158+
159+
/// Modifies the permissions of a region in a guest physical address space of the VM.
160+
///
161+
/// # Arguments
162+
/// * `asid` - Address space ID.
163+
/// * `gpa` - Page aligned address in the guest physical address space.
164+
/// * `size` - Size in bytes of the region to be modified.
165+
/// * `flags` - New READ, WRITE and EXECUTE permissions of the region.
166+
pub fn vm_protect_space(asid: Space, gpa: GPAddr, size: u64, flags: Memory) -> Result<(), Error> {
167+
call!(sys::hv_vm_protect_space(
168+
asid,
169+
gpa,
170+
size,
171+
flags as sys::hv_memory_flags_t
172+
))
173+
}
174+
175+
/// Synchronizes guest TSC across all vCPUs.
176+
pub fn vm_sync_tsc(tcs: u64) -> Result<(), Error> {
177+
call!(sys::hv_vm_sync_tsc(tcs))
178+
}
179+
180+
/// Destroys the VM instance associated with the current process.
181+
pub fn vm_destroy() -> Result<(), Error> {
182+
call!(sys::hv_vm_destroy())
183+
}
184+
185+
/// The return type of framework functions.
186+
/// Wraps the underlying `hv_return_t` type.
187+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
188+
pub enum Error {
189+
Unsuccessful,
190+
Busy,
191+
BadArgument,
192+
NoResources,
193+
NoDevice,
194+
Unsupported,
195+
/// Not mapped error code.
196+
Unknown(sys::hv_return_t),
197+
}
198+
199+
impl error::Error for Error {}
200+
201+
impl fmt::Display for Error {
202+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203+
match self {
204+
Error::Unsuccessful => write!(f, "The operation was unsuccessful"),
205+
Error::Busy => write!(f, "The operation was unsuccessful because the owning resource was busy"),
206+
Error::BadArgument => write!(f, "The operation was unsuccessful because the function call had an invalid argument"),
207+
Error::NoResources => write!(f, "The operation was unsuccessful because the host had no resources available to complete the request"),
208+
Error::NoDevice => write!(f, "The operation was unsuccessful because no VM or vCPU was available"),
209+
Error::Unsupported => write!(f, "The operation requested isn’t supported by the hypervisor"),
210+
Error::Unknown(code) => write!(f, "Error code: {}", *code as i32),
211+
}
212+
}
213+
}
214+
215+
impl From<sys::hv_return_t> for Error {
216+
fn from(value: sys::hv_return_t) -> Self {
217+
match value {
218+
sys::hv_return_t_HV_ERROR => Error::Unsuccessful,
219+
sys::hv_return_t_HV_BUSY => Error::Busy,
220+
sys::hv_return_t_HV_BAD_ARGUMENT => Error::BadArgument,
221+
sys::hv_return_t_HV_NO_RESOURCES => Error::NoResources,
222+
sys::hv_return_t_HV_NO_DEVICE => Error::NoDevice,
223+
sys::hv_return_t_HV_UNSUPPORTED => Error::Unsupported,
224+
_ => Error::Unknown(value),
225+
}
226+
}
227+
}

0 commit comments

Comments
 (0)