Skip to content

Commit

Permalink
Creates KVM VM and setup WHP partition (obhq#738)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Mar 16, 2024
1 parent cf8251b commit 48a82eb
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 72 deletions.
19 changes: 5 additions & 14 deletions src/hv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,12 @@ libc = "0.2.153"
[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.52"
features = [
"Wdk",
"Wdk_Foundation",
"Wdk_Storage",
"Wdk_Storage_FileSystem",
"Win32",
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Diagnostics_Debug",
"Win32_System",
"Win32_System_Hypervisor",
"Win32_System_IO",
"Win32_System_Kernel",
"Win32_System_Memory",
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_System_Time",
"Win32_System_WindowsProgramming",
"Win32_System_SystemInformation"
]

[build-dependencies]
cc = "1.0.90"
11 changes: 7 additions & 4 deletions src/hv/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
fn main() {
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();

if os == "macos" {
println!("cargo:rustc-link-lib=framework=Hypervisor");
match std::env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
"linux" | "android" => cc::Build::new()
.cpp(true)
.file("src/linux/kvm.cpp")
.compile("hvkvm"),
"macos" => println!("cargo:rustc-link-lib=framework=Hypervisor"),
_ => {}
}
}
31 changes: 25 additions & 6 deletions src/hv/src/darwin.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
#![allow(non_camel_case_types)]

use crate::NewError;
use std::ffi::c_int;
use std::ptr::null_mut;

/// RAII struct for `hv_vm_create` and `hv_vm_destroy`.
pub struct Vm(());

impl Vm {
pub fn new() -> Result<Self, NewError> {
match unsafe { hv_vm_create(null_mut()) } {
0 => Ok(Self(())),
v => Err(NewError::CreateVmFailed(v)),
}
}
}

impl Drop for Vm {
fn drop(&mut self) {
let status = unsafe { hv_vm_destroy() };

#[repr(C)]
pub struct hv_vm_config_t([u8; 0]);
if status != 0 {
panic!("hv_vm_destroy() fails with {status:#x}");
}
}
}

extern "C" {
pub fn hv_vm_create(config: *mut hv_vm_config_t) -> c_int;
pub fn hv_vm_destroy() -> c_int;
fn hv_vm_create(config: *mut ()) -> c_int;
fn hv_vm_destroy() -> c_int;
}
219 changes: 195 additions & 24 deletions src/hv/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicBool, Ordering};
use thiserror::Error;

Expand All @@ -13,49 +14,118 @@ mod win32;
/// Each process can have only one VM. The reason this type is not a global variable is because we
/// want to be able to drop it.
pub struct Hypervisor {
#[cfg(any(target_os = "linux", target_os = "android"))]
vm: std::os::fd::OwnedFd, // Drop before KVM.
#[cfg(any(target_os = "linux", target_os = "android"))]
kvm: std::os::fd::OwnedFd,
#[cfg(target_os = "windows")]
whp: self::win32::Partition,
#[cfg(target_os = "macos")]
vm: self::darwin::Vm,
#[allow(dead_code)]
active: Active, // Drop as the last one.
}

impl Hypervisor {
pub fn new() -> Result<Self, NewError> {
/// # Safety
/// `ram` cannot be null and must be allocated with a Virtual Memory API (e.g. `mmap` on *nix or
/// `VirtualAlloc` on Windows). This memory must be valid throughout the lifetime of the VM.
pub unsafe fn new(
cpu: NonZeroUsize,
ram: *mut u8,
addr: usize,
len: NonZeroUsize,
) -> Result<Self, NewError> {
// Check if another instance already active.
let active = Active::new().ok_or(NewError::Active)?;

#[cfg(target_os = "macos")]
match unsafe { self::darwin::hv_vm_create(std::ptr::null_mut()) } {
0 => {}
v => return Err(NewError::HostFailed(v)),
// Make sure memory size is valid.
let host_page_size = match Self::host_page_size() {
#[cfg(unix)]
Ok(v) => v,
#[cfg(unix)]
Err(e) => return Err(NewError::GetHostPageSizeFailed(e)),
#[cfg(windows)]
v => v,
};

if len.get() % host_page_size != 0 {
return Err(NewError::InvalidMemorySize);
}

Ok(Self {
#[cfg(any(target_os = "linux", target_os = "android"))]
kvm: self::linux::kvm_new()?,
#[cfg(target_os = "windows")]
whp: self::win32::Partition::new()?,
active,
})
// Initialize platform hypervisor.
#[cfg(any(target_os = "linux", target_os = "android"))]
return Self::new_linux(active, ram, addr, len.get());

#[cfg(target_os = "windows")]
return Self::new_windows(active, cpu, ram, addr, len.get());

#[cfg(target_os = "macos")]
return Self::new_mac(active, ram, addr, len.get());
}
}

impl Drop for Hypervisor {
#[cfg(any(target_os = "linux", target_os = "android"))]
fn drop(&mut self) {}
unsafe fn new_linux(
active: Active,
ram: *mut u8,
addr: usize,
len: usize,
) -> Result<Self, NewError> {
use std::os::fd::AsFd;

let kvm = self::linux::open_kvm()?;
let vm = self::linux::create_vm(kvm.as_fd())?;

Ok(Self { vm, kvm, active })
}

#[cfg(target_os = "windows")]
fn drop(&mut self) {}
unsafe fn new_windows(
active: Active,
cpu: NonZeroUsize,
ram: *mut u8,
addr: usize,
len: usize,
) -> Result<Self, NewError> {
let mut whp = self::win32::Partition::new(cpu)?;

whp.setup()?;

Ok(Self { whp, active })
}

#[cfg(target_os = "macos")]
fn drop(&mut self) {
let status = unsafe { self::darwin::hv_vm_destroy() };
unsafe fn new_mac(
active: Active,
ram: *mut u8,
addr: usize,
len: usize,
) -> Result<Self, NewError> {
let vm = self::darwin::Vm::new()?;

if status != 0 {
panic!("hv_vm_destroy() was failed with {status:#x}");
Ok(Self { vm, active })
}

#[cfg(unix)]
fn host_page_size() -> Result<usize, std::io::Error> {
let v = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) };

if v < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(v.try_into().unwrap())
}
}

#[cfg(windows)]
fn host_page_size() -> usize {
use windows_sys::Win32::System::SystemInformation::GetSystemInfo;

let mut i = unsafe { std::mem::zeroed() };
unsafe { GetSystemInfo(&mut i) };

i.dwPageSize.try_into().unwrap()
}
}

/// RAII object to set release ACTIVE.
Expand All @@ -76,23 +146,54 @@ impl Drop for Active {
}
}

/// Represents an error when [`Hypervisor::new()`] was failed.
/// Represents an error when [`Hypervisor::new()`] fails.
#[derive(Debug, Error)]
pub enum NewError {
#[error("there is an active hypervisor")]
Active,

#[cfg(unix)]
#[error("couldn't determine page size of the host")]
GetHostPageSizeFailed(#[source] std::io::Error),

#[cfg(target_os = "windows")]
#[error("the number of CPU is not valid")]
InvalidCpuCount,

#[error("the specified memory size is not valid")]
InvalidMemorySize,

#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("couldn't open {0}")]
OpenKvmFailed(&'static str, #[source] std::io::Error),

#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("couldn't get KVM version")]
GetKvmVersionFailed(#[source] std::io::Error),

#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("unexpected KVM version")]
KvmVersionMismatched,

#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("couldn't create a VM")]
CreateVmFailed(#[source] std::io::Error),

#[cfg(target_os = "windows")]
#[error("couldn't create WHP partition object ({0:#x})")]
CreatePartitionFailed(windows_sys::core::HRESULT),

#[cfg(target_os = "windows")]
#[error("couldn't set number of CPU ({0:#x})")]
SetCpuCountFailed(windows_sys::core::HRESULT),

#[cfg(target_os = "windows")]
#[error("couldn't setup WHP partition ({0:#x})")]
SetupPartitionFailed(windows_sys::core::HRESULT),

#[cfg(target_os = "macos")]
#[error("the host failed to create the hypervisor ({0:#x})")]
HostFailed(std::ffi::c_int),
#[error("couldn't create a VM ({0:#x})")]
CreateVmFailed(std::ffi::c_int),
}

static ACTIVE: AtomicBool = AtomicBool::new(false);
Expand All @@ -103,9 +204,79 @@ static ACTIVE: AtomicBool = AtomicBool::new(false);
#[cfg(test)]
mod tests {
use super::*;
use std::io::Error;

#[test]
fn new() {
Hypervisor::new().unwrap();
let cpu = unsafe { NonZeroUsize::new_unchecked(8) };
let ram = Ram::new();

unsafe { Hypervisor::new(cpu, ram.addr, 0, Ram::SIZE).unwrap() };
}

struct Ram {
addr: *mut u8,
}

impl Ram {
const SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1024 * 1024 * 1024 * 8) };

#[cfg(unix)]
fn new() -> Self {
use libc::{mmap, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE};
use std::ptr::null_mut;

let addr = unsafe {
mmap(
null_mut(),
Self::SIZE.get(),
PROT_NONE,
MAP_PRIVATE | MAP_ANON,
-1,
0,
)
};

if addr == MAP_FAILED {
panic!("mmap() fails: {}", Error::last_os_error());
}

Self { addr: addr.cast() }
}

#[cfg(windows)]
fn new() -> Self {
use std::ptr::null;
use windows_sys::Win32::System::Memory::{VirtualAlloc, MEM_RESERVE, PAGE_NOACCESS};

let addr =
unsafe { VirtualAlloc(null(), Self::SIZE.get(), MEM_RESERVE, PAGE_NOACCESS) };

if addr.is_null() {
panic!("VirtualAlloc() fails: {}", Error::last_os_error());
}

Self { addr: addr.cast() }
}
}

impl Drop for Ram {
#[cfg(unix)]
fn drop(&mut self) {
use libc::munmap;

if unsafe { munmap(self.addr.cast(), Self::SIZE.get()) } < 0 {
panic!("munmap() fails: {}", Error::last_os_error());
}
}

#[cfg(windows)]
fn drop(&mut self) {
use windows_sys::Win32::System::Memory::{VirtualFree, MEM_RELEASE};

if unsafe { VirtualFree(self.addr.cast(), 0, MEM_RELEASE) } == 0 {
panic!("VirtualFree() fails: {}", Error::last_os_error());
}
}
}
}
17 changes: 0 additions & 17 deletions src/hv/src/linux.rs

This file was deleted.

Loading

0 comments on commit 48a82eb

Please sign in to comment.