diff --git a/Cargo.toml b/Cargo.toml index 327c09c9..e29152b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ winapi = { version = "0.3.8", features = ["tlhelp32", "winnt", "handleapi", "sec widestring = { version = "0.4.0", optional = true } ntapi = { version = "0.3.3", optional = true } vid-sys = { version = "0.3.0", features = ["deprecated-apis"], optional = true } +cty = "0.2.1" [dev-dependencies] env_logger = "0.7.1" diff --git a/c_examples/Makefile b/c_examples/Makefile new file mode 100644 index 00000000..16e709d5 --- /dev/null +++ b/c_examples/Makefile @@ -0,0 +1,23 @@ +CC = gcc +CFLAGS = -lmicrovmi -L../target/debug +CWD := $(shell pwd) + +.PHONY: all clean + +all: mem-dump pause regs-dump + +libmicrovmi.h: ../target/debug/libmicrovmi.so + cd ..; \ + cbindgen --config cbindgen.toml --crate microvmi --output "${CWD}/libmicrovmi.h" + +mem-dump: libmicrovmi.h mem-dump.c + $(CC) $(CFLAGS) -o mem-dump mem-dump.c + +pause: libmicrovmi.h pause.c + $(CC) $(CFLAGS) -o pause pause.c + +regs-dump: libmicrovmi.h regs-dump.c + $(CC) $(CFLAGS) -o regs-dump regs-dump.c + +clean: + rm -f libmicrovmi.h mem-dump pause regs-dump diff --git a/c_examples/README.md b/c_examples/README.md new file mode 100644 index 00000000..ffa66ea1 --- /dev/null +++ b/c_examples/README.md @@ -0,0 +1,20 @@ +# C Interoperability + +It is possible to call *libmicrovmi* functions from C code. To this end, a header file has to be generated. +This requires the `cbindgen` tool which can be installed via the following command: + +~~~ +cargo install --force cbindgen +~~~ + +## Building the examples + +To build the examples just use the makefile located in `c_examples`. +It will also generate the header file for you provided you have installed `cbindgen`. +You just have to make sure that you have already built *libmicrovmi*. + +## Executing the examples + +~~~ +LD_LIBRARY_PATH="$LD_LIBRARY_PATH:../target/debug" +~~~ \ No newline at end of file diff --git a/c_examples/mem-dump.c b/c_examples/mem-dump.c new file mode 100644 index 00000000..44dee35e --- /dev/null +++ b/c_examples/mem-dump.c @@ -0,0 +1,47 @@ +#include "libmicrovmi.h" +#include +#include + +size_t PAGE_SIZE = 4096; + +void dump_memory(MicrovmiContext* driver, const char* vm_name) { + if (microvmi_pause(driver) == MicrovmiSuccess) { + printf("Paused.\n"); + } else { + printf("Unable to pause VM.\n"); + return; + } + uint64_t max_address; + if (microvmi_get_max_physical_addr(driver, &max_address) == MicrovmiSuccess) { + printf("Max physical address: %llx\n", max_address); + } else { + printf("Unable to retrieve the max physical address.\n"); + return; + } + FILE* dump_file = fopen("vm.dump", "wb"); + uint8_t buffer[PAGE_SIZE]; + for (int i = 0; i <= max_address / PAGE_SIZE; i++) { + memset(buffer, 0, PAGE_SIZE); + if (microvmi_read_physical(driver, i * PAGE_SIZE, buffer, PAGE_SIZE) == MicrovmiSuccess) { + fwrite(buffer, sizeof(uint8_t), PAGE_SIZE, dump_file); + } + } + fclose(dump_file); + if (microvmi_resume(driver) == MicrovmiSuccess) { + printf("Resumed.\n"); + } else { + printf("Unable to resume VM.\n"); + } +} + + +int main(int argc, char* argv[]) { + if (argc < 2) { + printf("No domain name given.\n"); + return 1; + } + MicrovmiContext* driver = microvmi_init(argv[1], NULL); + dump_memory(driver, argv[1]); + microvmi_destroy(driver); + return 0; +} diff --git a/c_examples/pause.c b/c_examples/pause.c new file mode 100644 index 00000000..65faff82 --- /dev/null +++ b/c_examples/pause.c @@ -0,0 +1,35 @@ +#include +#include +#include "libmicrovmi.h" +#include + +void pause_vm(MicrovmiContext* driver, unsigned long sleep_duration) { + if (microvmi_pause(driver) == MicrovmiSuccess) { + printf("Paused.\n"); + } else { + printf("Unable to pause VM.\n"); + return; + } + usleep(sleep_duration); + if (microvmi_resume(driver) == MicrovmiSuccess) { + printf("Resumed.\n"); + } else { + printf("Unable to resume VM.\n"); + } +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + printf("Usage: regs-dump .\n"); + return 1; + } + unsigned long sleep_duration_sec = strtoul(argv[2], NULL, 0); + if (sleep_duration_sec == 0) { + printf("Unable to parse sleep duration or zero provided.\n"); + return 1; + } + MicrovmiContext* driver = microvmi_init(argv[1], NULL); + pause_vm(driver, sleep_duration_sec * 1000000); + microvmi_destroy(driver); + return 0; +} diff --git a/c_examples/regs-dump.c b/c_examples/regs-dump.c new file mode 100644 index 00000000..f3d1f85e --- /dev/null +++ b/c_examples/regs-dump.c @@ -0,0 +1,44 @@ +#include +#include +#include "libmicrovmi.h" + +void read_registers(MicrovmiContext* driver, const char* vm_name) { + if (microvmi_pause(driver) == MicrovmiSuccess) { + printf("Paused.\n"); + } else { + printf("Unable to pause VM.\n"); + return; + } + Registers regs; + memset(®s, 0, sizeof(regs)); + if (microvmi_read_registers(driver, 0, ®s) == MicrovmiSuccess) { + printf("rax: 0x%llx\n", regs.x86._0.rax); + printf("rbx: 0x%llx\n", regs.x86._0.rbx); + printf("rcx: 0x%llx\n", regs.x86._0.rcx); + printf("rdx: 0x%llx\n", regs.x86._0.rdx); + printf("rsi: 0x%llx\n", regs.x86._0.rsi); + printf("rdi: 0x%llx\n", regs.x86._0.rdi); + printf("rsp: 0x%llx\n", regs.x86._0.rsp); + printf("rbp: 0x%llx\n", regs.x86._0.rbp); + printf("rip: 0x%llx\n", regs.x86._0.rip); + printf("rflags: 0x%llx\n", regs.x86._0.rflags); + } else { + printf("Unable to read registers.\n"); + } + if (microvmi_resume(driver) == MicrovmiSuccess) { + printf("Resumed.\n"); + } else { + printf("Unable to resume VM.\n"); + } +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + printf("No domain name given.\n"); + return 1; + } + MicrovmiContext* driver = microvmi_init(argv[1], NULL); + read_registers(driver, argv[1]); + microvmi_destroy(driver); + return 0; +} diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 00000000..7a5db214 --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,6 @@ +language = "C" +tab_width = 4 +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_guard = "LIBMICROVMI_H" +no_includes = true +sys_includes = ["stddef.h", "stdint.h"] diff --git a/src/api.rs b/src/api.rs index c4fdca85..c86f9f86 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,5 +1,6 @@ use std::error::Error; +#[repr(C)] #[derive(Debug)] pub enum DriverType { Dummy, @@ -13,6 +14,7 @@ pub enum DriverType { Xen, } +#[repr(C)] #[derive(Debug)] pub struct X86Registers { pub rax: u64, @@ -35,6 +37,7 @@ pub struct X86Registers { pub rflags: u64, } +#[repr(C)] #[derive(Debug)] pub enum Registers { X86(X86Registers), @@ -64,4 +67,8 @@ pub trait Introspectable { fn resume(&mut self) -> Result<(), Box> { unimplemented!(); } + + // Introduced for the sole purpose of C interoperability. + // Should be deprecated as soon as more suitable solutions become available. + fn get_driver_type(&self) -> DriverType; } diff --git a/src/capi.rs b/src/capi.rs new file mode 100644 index 00000000..cc90888e --- /dev/null +++ b/src/capi.rs @@ -0,0 +1,153 @@ +use crate::api::{DriverType, Introspectable, Registers}; +use crate::driver::dummy::Dummy; +#[cfg(feature = "hyper-v")] +use crate::driver::hyperv::HyperV; +#[cfg(feature = "kvm")] +use crate::driver::kvm::Kvm; +#[cfg(feature = "virtualbox")] +use crate::driver::virtualbox::VBox; +#[cfg(feature = "xen")] +use crate::driver::xen::Xen; +use crate::init; +use cty::{c_char, size_t, uint16_t, uint64_t, uint8_t}; +use std::ffi::{c_void, CStr}; +use std::slice; + +#[repr(C)] +pub struct MicrovmiContext { + driver: *mut c_void, + driver_type: DriverType, +} + +#[repr(C)] +pub enum MicrovmiStatus { + MicrovmiSuccess, + MicrovmiFailure, +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_init( + domain_name: *const c_char, + driver_type: *const DriverType, +) -> *mut MicrovmiContext { + let safe_domain_name = CStr::from_ptr(domain_name).to_string_lossy().into_owned(); + let optional_driver_type: Option = if driver_type.is_null() { + None + } else { + Some(driver_type.read()) + }; + let driver = init(&safe_domain_name, optional_driver_type); + let inferred_driver_type = driver.get_driver_type(); + Box::into_raw(Box::new(MicrovmiContext { + driver: Box::into_raw(driver) as *mut c_void, + driver_type: inferred_driver_type, + })) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_destroy(context: *mut MicrovmiContext) { + let boxed_context = Box::from_raw(context); + let _ = get_driver_box(&boxed_context); +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_pause(context: *mut MicrovmiContext) -> MicrovmiStatus { + let driver = get_driver_mut_ptr(context.as_ref().unwrap()); + match (*driver).pause() { + Ok(_) => MicrovmiStatus::MicrovmiSuccess, + Err(_) => MicrovmiStatus::MicrovmiFailure, + } +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_resume(context: *mut MicrovmiContext) -> MicrovmiStatus { + let driver = get_driver_mut_ptr(context.as_ref().unwrap()); + match (*driver).resume() { + Ok(_) => MicrovmiStatus::MicrovmiSuccess, + Err(_) => MicrovmiStatus::MicrovmiFailure, + } +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_read_physical( + context: *mut MicrovmiContext, + physical_address: uint64_t, + buffer: *mut uint8_t, + size: size_t, +) -> MicrovmiStatus { + let driver = get_driver_mut_ptr(context.as_ref().unwrap()); + match (*driver).read_physical(physical_address, slice::from_raw_parts_mut(buffer, size)) { + Ok(_) => MicrovmiStatus::MicrovmiSuccess, + Err(_) => MicrovmiStatus::MicrovmiFailure, + } +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_get_max_physical_addr( + context: *mut MicrovmiContext, + address_ptr: *mut uint64_t, +) -> MicrovmiStatus { + let driver = get_driver_mut_ptr(context.as_ref().unwrap()); + match (*driver).get_max_physical_addr() { + Ok(max_addr) => { + address_ptr.write(max_addr); + MicrovmiStatus::MicrovmiSuccess + } + Err(_) => MicrovmiStatus::MicrovmiFailure, + } +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn microvmi_read_registers( + context: *mut MicrovmiContext, + vcpu: uint16_t, + registers: *mut Registers, +) -> MicrovmiStatus { + let driver = get_driver_mut_ptr(context.as_ref().unwrap()); + match (*driver).read_registers(vcpu) { + Ok(regs) => { + registers.write(regs); + MicrovmiStatus::MicrovmiSuccess + } + Err(_) => MicrovmiStatus::MicrovmiFailure, + } +} + +unsafe fn get_driver_mut_ptr(context: &MicrovmiContext) -> *mut dyn Introspectable { + match context.driver_type { + DriverType::Dummy => context.driver as *mut Dummy as *mut dyn Introspectable, + #[cfg(feature = "kvm")] + DriverType::KVM => context.driver as *mut Kvm as *mut dyn Introspectable, + #[cfg(feature = "virtualbox")] + DriverType::VirtualBox => context.driver as *mut VBox as *mut dyn Introspectable, + #[cfg(feature = "xen")] + DriverType::Xen => context.driver as *mut Xen as *mut dyn Introspectable, + #[cfg(feature = "hyper-v")] + DriverType::HyperV => context.driver as *mut HyperV as *mut dyn Introspectable, + } +} + +unsafe fn get_driver_box(context: &MicrovmiContext) -> Box { + match context.driver_type { + DriverType::Dummy => Box::from_raw(context.driver as *mut Dummy) as Box, + #[cfg(feature = "kvm")] + DriverType::KVM => Box::from_raw(context.driver as *mut Kvm) as Box, + #[cfg(feature = "virtualbox")] + DriverType::VirtualBox => { + Box::from_raw(context.driver as *mut VBox) as Box + } + #[cfg(feature = "xen")] + DriverType::Xen => Box::from_raw(context.driver as *mut Xen) as Box, + #[cfg(feature = "hyper-v")] + DriverType::HyperV => { + Box::from_raw(context.driver as *mut HyperV) as Box + } + } +} diff --git a/src/driver/dummy.rs b/src/driver/dummy.rs index 00a78705..0cf538d0 100644 --- a/src/driver/dummy.rs +++ b/src/driver/dummy.rs @@ -1,4 +1,4 @@ -use crate::api; +use crate::api::{DriverType, Introspectable}; use std::error::Error; // unit struct @@ -11,7 +11,7 @@ impl Dummy { } } -impl api::Introspectable for Dummy { +impl Introspectable for Dummy { fn read_physical(&self, paddr: u64, buf: &mut [u8]) -> Result<(), Box> { debug!("read physical - @{}, {:#?}", paddr, buf); Ok(()) @@ -31,4 +31,8 @@ impl api::Introspectable for Dummy { debug!("resume"); Ok(()) } + + fn get_driver_type(&self) -> DriverType { + DriverType::Dummy + } } diff --git a/src/driver/hyperv.rs b/src/driver/hyperv.rs index fdcf79fa..fa1ec47b 100644 --- a/src/driver/hyperv.rs +++ b/src/driver/hyperv.rs @@ -6,7 +6,7 @@ use std::ptr::{null, null_mut}; use std::slice; use std::vec::Vec; -use crate::api; +use crate::api::{DriverType, Introspectable}; use ntapi::ntexapi::{ NtQuerySystemInformation, SystemHandleInformation, SYSTEM_HANDLE_INFORMATION, @@ -255,7 +255,11 @@ impl HyperV { } } -impl api::Introspectable for HyperV {} +impl Introspectable for HyperV { + fn get_driver_type(&self) -> DriverType { + DriverType::HyperV + } +} impl Drop for HyperV { fn drop(&mut self) { diff --git a/src/driver/kvm.rs b/src/driver/kvm.rs index 6aaf42ed..1c6be7cd 100644 --- a/src/driver/kvm.rs +++ b/src/driver/kvm.rs @@ -1,4 +1,4 @@ -use crate::api::{Introspectable, Registers, X86Registers}; +use crate::api::{DriverType, Introspectable, Registers, X86Registers}; use kvmi::{KVMi, KVMiEventType}; use std::error::Error; @@ -100,6 +100,10 @@ impl Introspectable for Kvm { } Ok(()) } + + fn get_driver_type(&self) -> DriverType { + DriverType::KVM + } } impl Drop for Kvm { diff --git a/src/driver/virtualbox.rs b/src/driver/virtualbox.rs index 20b40bcf..591318e5 100644 --- a/src/driver/virtualbox.rs +++ b/src/driver/virtualbox.rs @@ -2,7 +2,7 @@ use std::error::Error; use fdp::FDP; -use crate::api::Introspectable; +use crate::api::{DriverType, Introspectable}; // unit struct #[derive(Debug)] @@ -34,4 +34,8 @@ impl Introspectable for VBox { fn resume(&mut self) -> Result<(), Box> { self.fdp.resume() } + + fn get_driver_type(&self) -> DriverType { + DriverType::VirtualBox + } } diff --git a/src/driver/xen.rs b/src/driver/xen.rs index 3338d628..dfde1060 100644 --- a/src/driver/xen.rs +++ b/src/driver/xen.rs @@ -1,4 +1,4 @@ -use crate::api::{Introspectable, Registers, X86Registers}; +use crate::api::{DriverType, Introspectable, Registers, X86Registers}; use libc::PROT_READ; use std::error::Error; use xenctrl::consts::{PAGE_SHIFT, PAGE_SIZE}; @@ -121,6 +121,10 @@ impl Introspectable for Xen { debug!("resume"); Ok(self.xc.domain_unpause(self.domid)?) } + + fn get_driver_type(&self) -> DriverType { + DriverType::Xen + } } impl Drop for Xen { diff --git a/src/lib.rs b/src/lib.rs index b8308882..9b42be68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod api; +pub mod capi; mod driver; #[macro_use]