Skip to content

Commit

Permalink
Merge pull request #43 from rageagainsthepc/add-ffi
Browse files Browse the repository at this point in the history
add a C compatible interface
  • Loading branch information
Wenzel authored Jan 24, 2020
2 parents 12549e8 + 690d3d7 commit 3e0ab8c
Show file tree
Hide file tree
Showing 15 changed files with 364 additions and 7 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
23 changes: 23 additions & 0 deletions c_examples/Makefile
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions c_examples/README.md
Original file line number Diff line number Diff line change
@@ -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" <example> <vm_name>
~~~
47 changes: 47 additions & 0 deletions c_examples/mem-dump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "libmicrovmi.h"
#include <stdio.h>
#include <string.h>

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;
}
35 changes: 35 additions & 0 deletions c_examples/pause.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <stdio.h>
#include <stdlib.h>
#include "libmicrovmi.h"
#include <unistd.h>

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 <vm_name> <sleep_seconds>.\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;
}
44 changes: 44 additions & 0 deletions c_examples/regs-dump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <stdio.h>
#include <string.h>
#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(&regs, 0, sizeof(regs));
if (microvmi_read_registers(driver, 0, &regs) == 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;
}
6 changes: 6 additions & 0 deletions cbindgen.toml
Original file line number Diff line number Diff line change
@@ -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"]
7 changes: 7 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::error::Error;

#[repr(C)]
#[derive(Debug)]
pub enum DriverType {
Dummy,
Expand All @@ -13,6 +14,7 @@ pub enum DriverType {
Xen,
}

#[repr(C)]
#[derive(Debug)]
pub struct X86Registers {
pub rax: u64,
Expand All @@ -35,6 +37,7 @@ pub struct X86Registers {
pub rflags: u64,
}

#[repr(C)]
#[derive(Debug)]
pub enum Registers {
X86(X86Registers),
Expand Down Expand Up @@ -64,4 +67,8 @@ pub trait Introspectable {
fn resume(&mut self) -> Result<(), Box<dyn Error>> {
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;
}
153 changes: 153 additions & 0 deletions src/capi.rs
Original file line number Diff line number Diff line change
@@ -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<DriverType> = 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<dyn Introspectable> {
match context.driver_type {
DriverType::Dummy => Box::from_raw(context.driver as *mut Dummy) as Box<dyn Introspectable>,
#[cfg(feature = "kvm")]
DriverType::KVM => Box::from_raw(context.driver as *mut Kvm) as Box<dyn Introspectable>,
#[cfg(feature = "virtualbox")]
DriverType::VirtualBox => {
Box::from_raw(context.driver as *mut VBox) as Box<dyn Introspectable>
}
#[cfg(feature = "xen")]
DriverType::Xen => Box::from_raw(context.driver as *mut Xen) as Box<dyn Introspectable>,
#[cfg(feature = "hyper-v")]
DriverType::HyperV => {
Box::from_raw(context.driver as *mut HyperV) as Box<dyn Introspectable>
}
}
}
8 changes: 6 additions & 2 deletions src/driver/dummy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::api;
use crate::api::{DriverType, Introspectable};
use std::error::Error;

// unit struct
Expand All @@ -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<dyn Error>> {
debug!("read physical - @{}, {:#?}", paddr, buf);
Ok(())
Expand All @@ -31,4 +31,8 @@ impl api::Introspectable for Dummy {
debug!("resume");
Ok(())
}

fn get_driver_type(&self) -> DriverType {
DriverType::Dummy
}
}
Loading

0 comments on commit 3e0ab8c

Please sign in to comment.