use std::{fmt, mem, ptr, slice};
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::ffi::CString;
use std::hash::{Hash, Hasher};
use std::os::raw::{c_char, c_void};
use std::path::Path;
use uuid::Uuid;
use errors::ErrorKind::{ParseIdError, ProcessError};
use errors::Result;
use utils;
extern "C" {
fn code_module_base_address(module: *const CodeModule) -> u64;
fn code_module_size(module: *const CodeModule) -> u64;
fn code_module_code_file(module: *const CodeModule) -> *mut c_char;
fn code_module_code_identifier(module: *const CodeModule) -> *mut c_char;
fn code_module_debug_file(module: *const CodeModule) -> *mut c_char;
fn code_module_debug_identifier(module: *const CodeModule) -> *mut c_char;
fn stack_frame_instruction(frame: *const StackFrame) -> u64;
fn stack_frame_module(frame: *const StackFrame) -> *const CodeModule;
fn stack_frame_trust(frame: *const StackFrame) -> FrameTrust;
fn call_stack_thread_id(stack: *const CallStack) -> u32;
fn call_stack_frames(stack: *const CallStack, size_out: *mut usize)
-> *const *const StackFrame;
fn process_minidump(
buffer: *const c_char,
buffer_size: usize,
symbols: *const SymbolEntry,
symbol_count: usize,
result: *mut ProcessResult,
) -> *mut IProcessState;
fn process_state_delete(state: *mut IProcessState);
fn process_state_threads(
state: *const IProcessState,
size_out: *mut usize,
) -> *const *const CallStack;
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct CodeModuleId {
uuid: Uuid,
age: u32,
}
impl CodeModuleId {
pub fn parse(input: &str) -> Result<CodeModuleId> {
if input.len() != 33 {
return Err(ParseIdError("Invalid input string length".into()).into());
}
let uuid = Uuid::parse_str(&input[..32])?;
let age = u32::from_str_radix(&input[32..], 16)?;
Ok(CodeModuleId { uuid, age })
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn age(&self) -> u32 {
self.age
}
}
impl fmt::Display for CodeModuleId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let uuid = self.uuid.simple().to_string().to_uppercase();
write!(f, "{}{:X}", uuid, self.age)
}
}
impl Into<String> for CodeModuleId {
fn into(self) -> String {
self.to_string()
}
}
#[repr(C)]
pub struct CodeModule(c_void);
impl CodeModule {
pub fn id(&self) -> CodeModuleId {
CodeModuleId::parse(&self.debug_identifier()).unwrap()
}
pub fn base_address(&self) -> u64 {
unsafe { code_module_base_address(self) }
}
pub fn size(&self) -> u64 {
unsafe { code_module_size(self) }
}
pub fn code_file(&self) -> String {
unsafe {
let ptr = code_module_code_file(self);
utils::ptr_to_string(ptr)
}
}
pub fn code_identifier(&self) -> String {
unsafe {
let ptr = code_module_code_identifier(self);
utils::ptr_to_string(ptr)
}
}
pub fn debug_file(&self) -> String {
unsafe {
let ptr = code_module_debug_file(self);
utils::ptr_to_string(ptr)
}
}
pub fn debug_identifier(&self) -> String {
unsafe {
let ptr = code_module_debug_identifier(self);
utils::ptr_to_string(ptr)
}
}
}
impl Eq for CodeModule {}
impl PartialEq for CodeModule {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Hash for CodeModule {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state)
}
}
impl Ord for CodeModule {
fn cmp(&self, other: &Self) -> Ordering {
self.id().cmp(&other.id())
}
}
impl PartialOrd for CodeModule {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Debug for CodeModule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CodeModule")
.field("id", &self.id())
.field("base_address", &self.base_address())
.field("size", &self.size())
.field("code_file", &self.code_file())
.field("code_identifier", &self.code_identifier())
.field("debug_file", &self.debug_file())
.field("debug_identifier", &self.debug_identifier())
.finish()
}
}
#[test]
fn test_parse() {
assert_eq!(
CodeModuleId::parse("DFB8E43AF2423D73A453AEB6A777EF75A").unwrap(),
CodeModuleId {
uuid: Uuid::parse_str("DFB8E43AF2423D73A453AEB6A777EF75").unwrap(),
age: 10,
}
);
}
#[test]
fn test_to_string() {
let id = CodeModuleId {
uuid: Uuid::parse_str("DFB8E43AF2423D73A453AEB6A777EF75").unwrap(),
age: 10,
};
assert_eq!(id.to_string(), "DFB8E43AF2423D73A453AEB6A777EF75A");
}
#[test]
fn test_parse_error() {
assert!(CodeModuleId::parse("DFB8E43AF2423D73A").is_err());
}
#[repr(C)]
#[derive(Debug)]
pub enum FrameTrust {
None,
Scan,
CFIScan,
FP,
CFI,
Prewalked,
Context,
}
#[repr(C)]
pub struct StackFrame(c_void);
impl StackFrame {
pub fn instruction(&self) -> u64 {
unsafe { stack_frame_instruction(self) }
}
pub fn module(&self) -> Option<&CodeModule> {
unsafe { stack_frame_module(self).as_ref() }
}
pub fn trust(&self) -> FrameTrust {
unsafe { stack_frame_trust(self) }
}
}
impl fmt::Debug for StackFrame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("StackFrame")
.field("instruction", &self.instruction())
.field("trust", &self.trust())
.field("module", &self.module())
.finish()
}
}
#[repr(C)]
pub struct CallStack(c_void);
impl CallStack {
pub fn thread_id(&self) -> u32 {
unsafe { call_stack_thread_id(self) }
}
pub fn frames(&self) -> &[&StackFrame] {
unsafe {
let mut size = 0 as usize;
let data = call_stack_frames(self, &mut size);
let slice = slice::from_raw_parts(data, size);
mem::transmute(slice)
}
}
}
impl fmt::Debug for CallStack {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CallStack")
.field("thread_id", &self.thread_id())
.field("frames", &self.frames())
.finish()
}
}
#[repr(C)]
#[derive(Debug, Eq, PartialEq)]
pub enum ProcessResult {
Ok,
MinidumpNotFound,
NoMinidumpHeader,
ErrorNoThreadList,
ErrorGettingThread,
ErrorGettingThreadId,
DuplicateRequestingThreads,
SymbolSupplierInterrupted,
}
impl fmt::Display for ProcessResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let formatted = match self {
&ProcessResult::Ok => "Dump processed successfully",
&ProcessResult::MinidumpNotFound => "Minidump file was not found",
&ProcessResult::NoMinidumpHeader => "Minidump file had no header",
&ProcessResult::ErrorNoThreadList => "Minidump file has no thread list",
&ProcessResult::ErrorGettingThread => "Error getting one thread's data",
&ProcessResult::ErrorGettingThreadId => "Error getting a thread id",
&ProcessResult::DuplicateRequestingThreads => {
"There was more than one requesting thread"
}
&ProcessResult::SymbolSupplierInterrupted => "Processing was interrupted (not fatal)",
};
write!(f, "{}", formatted)
}
}
#[repr(C)]
struct SymbolEntry {
debug_identifier: *const c_char,
symbol_size: usize,
symbol_data: *const u8,
}
type IProcessState = c_void;
pub struct ProcessState {
internal: *mut IProcessState,
}
pub type FrameInfoMap<'a> = BTreeMap<CodeModuleId, &'a [u8]>;
impl ProcessState {
pub fn from_minidump_file<P: AsRef<Path>>(
file_path: P,
frame_infos: Option<&FrameInfoMap>,
) -> Result<ProcessState> {
let buffer = utils::read_buffer(file_path)?;
Self::from_minidump_buffer(buffer.as_slice(), frame_infos)
}
pub fn from_minidump_buffer(
buffer: &[u8],
frame_infos: Option<&FrameInfoMap>,
) -> Result<ProcessState> {
let cfi_count = frame_infos.map_or(0, |s| s.len());
let mut result: ProcessResult = ProcessResult::Ok;
let cfi_vec: Vec<_> = frame_infos.map_or(Vec::new(), |s| {
s.iter()
.map(|(k, v)| (CString::new(k.to_string()), v.len(), v.as_ptr()))
.collect()
});
let cfi_entries: Vec<_> = cfi_vec
.iter()
.map(|&(ref id, size, data)| {
SymbolEntry {
debug_identifier: id.as_ref().map(|i| i.as_ptr()).unwrap_or(ptr::null()),
symbol_size: size,
symbol_data: data,
}
})
.collect();
let internal = unsafe {
process_minidump(
buffer.as_ptr() as *const c_char,
buffer.len(),
cfi_entries.as_ptr(),
cfi_count,
&mut result,
)
};
if result == ProcessResult::Ok && !internal.is_null() {
Ok(ProcessState { internal })
} else {
Err(ProcessError(result).into())
}
}
pub fn threads(&self) -> &[&CallStack] {
unsafe {
let mut size = 0 as usize;
let data = process_state_threads(self.internal, &mut size);
let slice = slice::from_raw_parts(data, size);
mem::transmute(slice)
}
}
pub fn referenced_modules(&self) -> HashSet<&CodeModule> {
self.threads()
.iter()
.flat_map(|stack| stack.frames().iter())
.filter_map(|frame| frame.module())
.collect()
}
}
impl Drop for ProcessState {
fn drop(&mut self) {
unsafe { process_state_delete(self.internal) };
}
}
impl fmt::Debug for ProcessState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ProcessState")
.field("threads", &self.threads())
.finish()
}
}