Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a C compatible interface #43

Merged
merged 6 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Wenzel marked this conversation as resolved.
Show resolved Hide resolved
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>
Wenzel marked this conversation as resolved.
Show resolved Hide resolved
#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);
Wenzel marked this conversation as resolved.
Show resolved Hide resolved
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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rageagainsthepc can you explain why you had to introduce a new API here, it's hard to follow for me ?
I would like to avoid changing the core of the Rust library just to provide a C compatibility layer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if we initialize the driver with None, we don't know the type of the driver in advance. But we need to know since we are casting back and forth between void* and the actual type of the driver. As far as I know it's not possible to cast directly from void* to a trait type. There are probably different ways to approach this, but this one seemed pretty straight forward to me. I'm open to alternatives though ;)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the issue was that pointers to traits are fat pointers. Maybe there is way to pass fat pointers to C code and cast them back to traits afterwards. I'll look into that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There exists a std::raw::TraitObject but it's marked as unstable because its representation may change in the future rust-lang/rust#27751 . Also cbindgen reports TraitObject types as unsupported. In theory we could split the fat pointer into two separate pointers, store them in a struct and transmute the memory back to an Introspectable, but imho that sounds super ugly and unstable.

Copy link
Collaborator Author

@rageagainsthepc rageagainsthepc Jan 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, we could just loose the support for passing None instead of the DriverType, at least for the C-API ;)

Copy link
Collaborator Author

@rageagainsthepc rageagainsthepc Jan 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've found the crate downcast-rs. With this crate you are able to check a trait object for its concrete type.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks for the update.

I'm okay to merge this as long as you add a comment that this API has been introduced for the solve purpose of the C interoperability, and that it's a workaround, to be deprecated in the future when new better solutions will emerge to deal with this, either from Rust unstable, or simply by switching to dynamic library loading.

}
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