Skip to content

Commit

Permalink
Merge pull request #45 from Wenzel/add_mem_dump_example
Browse files Browse the repository at this point in the history
Add mem dump example
  • Loading branch information
Wenzel committed Feb 19, 2021
2 parents baed0c7 + b9bfbbf commit e7798bd
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ nix = "0.16.1"
libc = "0.2.66"
libloading = "0.6.1"
mockall = "0.7.1"
thiserror = "1.0"

[dev-dependencies]
env_logger = "0.8.3"
clap = "2.33.3"
indicatif = "0.15.0"

[package.metadata.release]
# releases are managed by cargo release, but publication is done on the CI
Expand Down
131 changes: 131 additions & 0 deletions examples/mem-dump.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use std::fs::File;
use std::io::Write;
use std::path::Path;

use kvmi::constants::PAGE_SIZE;
use kvmi::{create_kvmi, KVMIntrospectable, SocketType};

use clap::{App, Arg, ArgMatches};
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, trace};

fn parse_args() -> ArgMatches<'static> {
App::new(file!())
.version("0.1")
.author("Mathieu Tarral")
.about("Dumps VM physical memory")
.arg(Arg::with_name("vm_name").index(1).required(true))
.arg(
Arg::with_name("unix_socket")
.short("u")
.takes_value(true)
.help("KVMi UNIX socket"),
)
.arg(
Arg::with_name("vsock_port")
.short("v")
.takes_value(true)
.help("KVMi vSock port"),
)
.arg(
Arg::with_name("output")
.short("o")
.takes_value(true)
.help("Output path"),
)
.get_matches()
}

fn main() {
env_logger::init();

// handle args
let matches = parse_args();
let domain_name = matches
.value_of("vm_name")
.expect("The VM name is required");

// either one of unix or vsock socket
let unix_socket = matches
.value_of("unix_socket")
.map(|s| SocketType::UnixSocket(s.to_string()));
if unix_socket.is_none() {
println!("empty unix socket !");
}
let vsock_socket = matches.value_of("vsock_port").map(|s| {
SocketType::VSock(s.parse::<u32>().expect(&*format!(
"Failed to convert command line value \"{}\" to vSock port integer",
s
)))
});
let kvmi_sock_type = unix_socket.unwrap_or_else(|| {
vsock_socket.expect("One of UNIX or vSock connection method must be specified.")
});

let dump_path = Path::new(
matches
.value_of("output")
.map_or(&*format!("{}.dump", domain_name), |v| v),
)
.to_path_buf();
let mut dump_file = File::create(&dump_path).expect("Fail to open dump file");
// canonicalize now that the file exists
dump_path.canonicalize().unwrap();

// create KVMi and init
let mut kvmi = create_kvmi();

let spinner = ProgressBar::new_spinner();
spinner.enable_steady_tick(200);
spinner.set_message("Initializing KVMi...");
kvmi.init(kvmi_sock_type)
.expect("Failed to initialize KVMi");
spinner.finish_and_clear();

// ensure paused before dumping the RAM
println!("Pausing the VM");
kvmi.pause().expect("Failed to pause the VM");

let max_addr = kvmi
.get_maximum_paddr()
.expect("Failed to retrieve the highest physical address");
println!(
"Dumping {} memory to {} until {:#X}",
domain_name,
dump_path.file_name().unwrap().to_str().unwrap(),
max_addr
);
let bar = ProgressBar::new(max_addr);
bar.set_style(ProgressStyle::default_bar().template(
"{prefix} {wide_bar} {bytes_per_sec} • {bytes}/{total_bytes} • {percent}% • {elapsed}",
));
// redraw every 0.1% change, otherwise the redraw becomes the bottleneck
bar.set_draw_delta(max_addr / 1000);

// dump memory, frame by frame
for cur_paddr in (0..max_addr).step_by(PAGE_SIZE) {
trace!(
"reading {:#X} bytes of memory at {:#X}",
PAGE_SIZE,
cur_paddr
);
// reset buffer each loop
let mut buffer: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
kvmi.read_physical(cur_paddr, &mut buffer)
.unwrap_or_else(|_| debug!("failed to read memory at {:#X}", cur_paddr));
dump_file
.write_all(&buffer)
.expect("failed to write to file");
// update bar
bar.set_prefix(&*format!("{:#X}", cur_paddr));
bar.inc(PAGE_SIZE as u64);
}
bar.finish();
println!(
"Finished dumping physical memory at {}",
dump_path.display()
);

println!("Resuming the VM");
kvmi.resume().expect("Failed to resume VM");
}
2 changes: 2 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub const PAGE_SHIFT: usize = 12;
pub const PAGE_SIZE: usize = 4096;
16 changes: 16 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::KVMiEventType;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum KVMiError {
/// When waiting for the next Pause event timeouts while resuming the VM
#[error("no pause events are available")]
NoPauseEventAvailable,
/// When expecting a Pause event while resuming the VM and receiving another event
#[error("unexpected event {0:?} while resuming")]
UnexpectedEventWhileResuming(KVMiEventType),
/// Catch-all for underlying IO errors
#[error("IO error")]
IOError(#[from] io::Error),
}
46 changes: 46 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#![allow(clippy::mutex_atomic)] // prevent fp with idiomatic condvar code
#[macro_use]
extern crate log;
pub mod constants;
pub mod errors;
mod libkvmi;
use enum_primitive_derive::Primitive;
pub use kvmi_sys::{kvm_dtable, kvm_msrs, kvm_regs, kvm_segment, kvm_sregs, kvmi_dom_event};
Expand All @@ -26,6 +28,8 @@ use std::ptr::null_mut;
use std::slice;
use std::sync::{Condvar, Mutex};

use constants::PAGE_SHIFT;
use errors::KVMiError;
use libkvmi::Libkvmi;

#[derive(Debug)]
Expand Down Expand Up @@ -233,13 +237,19 @@ pub trait KVMIntrospectable: std::fmt::Debug {
fn read_physical(&self, gpa: u64, buffer: &mut [u8]) -> Result<(), Error>;
fn write_physical(&self, gpa: u64, buffer: &[u8]) -> Result<(), Error>;
fn set_page_access(&self, gpa: u64, access: KVMiPageAccess, view: u16) -> Result<(), Error>;
/// Resumes the VM
fn pause(&self) -> Result<(), Error>;
/// Pauses the VM
fn resume(&mut self) -> Result<(), KVMiError>;
fn get_vcpu_count(&self) -> Result<u32, Error>;
fn get_registers(&self, vcpu: u16) -> Result<(kvm_regs, kvm_sregs, KvmMsrs), Error>;
fn set_registers(&self, vcpu: u16, regs: &kvm_regs) -> Result<(), Error>;
fn wait_and_pop_event(&self, ms: i32) -> Result<Option<KVMiEvent>, Error>;
fn reply(&self, event: &KVMiEvent, reply_type: KVMiEventReply) -> Result<(), Error>;
/// Returns the highest Guest Frame Number
fn get_maximum_gfn(&self) -> Result<u64, Error>;
/// Returns the highest physical address
fn get_maximum_paddr(&self) -> Result<u64, KVMiError>;
}

pub fn create_kvmi() -> KVMi {
Expand All @@ -251,6 +261,9 @@ pub struct KVMi {
ctx: *mut c_void,
dom: *mut c_void,
libkvmi: Libkvmi,
// amount of pause events that we expect to receive since the last pause
// each VCPU needs to send a pause event and be acknowledged
expect_pause_ev: u32,
}

impl KVMi {
Expand All @@ -259,6 +272,7 @@ impl KVMi {
ctx: null_mut(),
dom: null_mut(),
libkvmi,
expect_pause_ev: 0,
}
}
}
Expand Down Expand Up @@ -377,6 +391,7 @@ impl KVMIntrospectable for KVMi {
Ok(())
}

/// Pauses the VM
fn pause(&self) -> Result<(), Error> {
let vcpu_count = self.get_vcpu_count()?;
let res = (self.libkvmi.pause_all_vcpus)(self.dom, vcpu_count);
Expand All @@ -386,6 +401,30 @@ impl KVMIntrospectable for KVMi {
Ok(())
}

/// Resumes the VM
fn resume(&mut self) -> Result<(), KVMiError> {
// already resumed ?
if self.expect_pause_ev == 0 {
return Ok(());
}

while self.expect_pause_ev > 0 {
// wait
let kvmi_event = self
.wait_and_pop_event(1000)?
.ok_or(KVMiError::NoPauseEventAvailable)?;
match kvmi_event.ev_type {
KVMiEventType::PauseVCPU => {
debug!("VCPU {} - Received Pause Event", kvmi_event.vcpu);
self.expect_pause_ev -= 1;
self.reply(&kvmi_event, KVMiEventReply::Continue)?;
}
_ => return Err(KVMiError::UnexpectedEventWhileResuming(kvmi_event.ev_type)),
}
}
Ok(())
}

fn get_vcpu_count(&self) -> Result<u32, Error> {
let mut vcpu_count: c_uint = 0;
let res = (self.libkvmi.get_vcpu_count)(self.dom, &mut vcpu_count);
Expand Down Expand Up @@ -602,6 +641,7 @@ impl KVMIntrospectable for KVMi {
Ok(())
}

/// Returns the highest Guest Frame Number
fn get_maximum_gfn(&self) -> Result<u64, Error> {
let mut max_gfn: u64 = 0;
let res = (self.libkvmi.get_maximum_gfn)(self.dom, &mut max_gfn);
Expand All @@ -610,6 +650,12 @@ impl KVMIntrospectable for KVMi {
}
Ok(max_gfn)
}

/// Returns the highest physical address
fn get_maximum_paddr(&self) -> Result<u64, KVMiError> {
let max_gfn = self.get_maximum_gfn()?;
Ok(max_gfn << PAGE_SHIFT)
}
}

impl Drop for KVMi {
Expand Down

0 comments on commit e7798bd

Please sign in to comment.