use chrono::{TimeZone, Utc};
use failure::Fail;
use std::ops::Deref;
use std::path::Path;
use minidump::{self, *};
use crate::evil;
use crate::process_state::{CallStack, CallStackInfo, LinuxStandardBase, ProcessState};
use crate::stackwalker;
use crate::symbols::*;
use crate::system_info::SystemInfo;
#[derive(Default, Debug, Clone)]
#[non_exhaustive]
pub struct ProcessorOptions<'a> {
pub evil_json: Option<&'a Path>,
}
#[derive(Debug, Fail)]
pub enum ProcessError {
#[fail(display = "Failed to read minidump")]
MinidumpReadError(minidump::Error),
#[fail(display = "An unknown error occurred")]
UnknownError,
#[fail(display = "The system information stream was not found")]
MissingSystemInfo,
#[fail(display = "The thread list stream was not found")]
MissingThreadList,
}
impl From<minidump::Error> for ProcessError {
fn from(err: minidump::Error) -> ProcessError {
ProcessError::MinidumpReadError(err)
}
}
pub fn process_minidump<'a, T, P>(
dump: &Minidump<'a, T>,
symbol_provider: &P,
) -> Result<ProcessState, ProcessError>
where
T: Deref<Target = [u8]> + 'a,
P: SymbolProvider,
{
process_minidump_with_options(dump, symbol_provider, ProcessorOptions::default())
}
pub fn process_minidump_with_options<'a, T, P>(
dump: &Minidump<'a, T>,
symbol_provider: &P,
options: ProcessorOptions,
) -> Result<ProcessState, ProcessError>
where
T: Deref<Target = [u8]> + 'a,
P: SymbolProvider,
{
let thread_list = dump
.get_stream::<MinidumpThreadList>()
.or(Err(ProcessError::MissingThreadList))?;
let thread_names = dump
.get_stream::<MinidumpThreadNames>()
.unwrap_or_else(|_| MinidumpThreadNames::default());
let dump_system_info = dump
.get_stream::<MinidumpSystemInfo>()
.or(Err(ProcessError::MissingSystemInfo))?;
let mut os_version = format!(
"{}.{}.{}",
dump_system_info.raw.major_version,
dump_system_info.raw.minor_version,
dump_system_info.raw.build_number
);
if let Some(csd_version) = dump_system_info.csd_version() {
os_version.push(' ');
os_version.push_str(&csd_version);
}
let linux_standard_base = dump.get_stream::<MinidumpLinuxLsbRelease>().ok();
let linux_cpu_info = dump
.get_stream::<MinidumpLinuxCpuInfo>()
.unwrap_or_default();
let _linux_environ = dump.get_stream::<MinidumpLinuxEnviron>().ok();
let _linux_proc_status = dump.get_stream::<MinidumpLinuxProcStatus>().ok();
let mut cpu_microcode_version = None;
for (key, val) in linux_cpu_info.iter() {
if key.as_bytes() == b"microcode" {
cpu_microcode_version = val
.to_str()
.ok()
.and_then(|val| val.strip_prefix("0x"))
.and_then(|val| u64::from_str_radix(val, 16).ok());
break;
}
}
let linux_standard_base = linux_standard_base.map(|linux_standard_base| {
let mut lsb = LinuxStandardBase::default();
for (key, val) in linux_standard_base.iter() {
match key.as_bytes() {
b"DISTRIB_ID" | b"ID" => lsb.id = val.to_string_lossy().into_owned(),
b"DISTRIB_RELEASE" | b"VERSION_ID" => {
lsb.release = val.to_string_lossy().into_owned()
}
b"DISTRIB_CODENAME" | b"VERSION_CODENAME" => {
lsb.codename = val.to_string_lossy().into_owned()
}
b"DISTRIB_DESCRIPTION" | b"PRETTY_NAME" => {
lsb.description = val.to_string_lossy().into_owned()
}
_ => {}
}
}
lsb
});
let cpu_info = dump_system_info
.cpu_info()
.map(|string| string.into_owned());
let system_info = SystemInfo {
os: dump_system_info.os,
os_version: Some(os_version),
cpu: dump_system_info.cpu,
cpu_info,
cpu_microcode_version,
cpu_count: dump_system_info.raw.number_of_processors as usize,
};
let mac_crash_info = dump
.get_stream::<MinidumpMacCrashInfo>()
.ok()
.map(|info| info.raw);
let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
let (process_id, process_create_time) = if let Some(misc_info) = misc_info.as_ref() {
(
misc_info.raw.process_id().cloned(),
misc_info.process_create_time(),
)
} else {
(None, None)
};
let breakpad_info = dump.get_stream::<MinidumpBreakpadInfo>();
let (dump_thread_id, requesting_thread_id) = if let Ok(info) = breakpad_info {
(info.dump_thread_id, info.requesting_thread_id)
} else {
(None, None)
};
let exception_stream = dump.get_stream::<MinidumpException>().ok();
let exception_ref = exception_stream.as_ref();
let (crash_reason, crash_address, crashing_thread_id) = if let Some(exception) = exception_ref {
(
Some(exception.get_crash_reason(system_info.os, system_info.cpu)),
Some(exception.get_crash_address(system_info.os)),
Some(exception.get_crashing_thread_id()),
)
} else {
(None, None, None)
};
let exception_context =
exception_ref.and_then(|e| e.context(&dump_system_info, misc_info.as_ref()));
let assertion = None;
let modules = match dump.get_stream::<MinidumpModuleList>() {
Ok(module_list) => module_list,
Err(_) => MinidumpModuleList::new(),
};
let unloaded_modules = match dump.get_stream::<MinidumpUnloadedModuleList>() {
Ok(module_list) => module_list,
Err(_) => MinidumpUnloadedModuleList::new(),
};
let memory_list = dump.get_stream::<MinidumpMemoryList>().unwrap_or_default();
let memory_info_list = dump.get_stream::<MinidumpMemoryInfoList>().ok();
let linux_maps = dump.get_stream::<MinidumpLinuxMaps>().ok();
let _memory_info = UnifiedMemoryInfoList::new(memory_info_list, linux_maps).unwrap_or_default();
let evil = options
.evil_json
.and_then(evil::handle_evil)
.unwrap_or_default();
let mut threads = vec![];
let mut requesting_thread = None;
for (i, thread) in thread_list.threads.iter().enumerate() {
if dump_thread_id.is_some() && dump_thread_id.unwrap() == thread.raw.thread_id {
threads.push(CallStack::with_info(CallStackInfo::DumpThreadSkipped));
continue;
}
let thread_context = thread.context(&dump_system_info, misc_info.as_ref());
let context = if crashing_thread_id
.or(requesting_thread_id)
.map(|id| id == thread.raw.thread_id)
.unwrap_or(false)
{
requesting_thread = Some(i);
exception_context
.as_deref()
.or_else(|| thread_context.as_deref())
} else {
thread_context.as_deref()
};
let stack = thread.stack_memory(&memory_list);
let mut stack =
stackwalker::walk_stack(&context, stack.as_deref(), &modules, symbol_provider);
let name = thread_names
.get_name(thread.raw.thread_id)
.map(|cow| cow.into_owned())
.or_else(|| evil.thread_names.get(&thread.raw.thread_id).cloned());
stack.thread_name = name;
stack.last_error_value = thread.last_error(system_info.cpu, &memory_list);
threads.push(stack);
}
let unknown_streams = dump.unknown_streams().collect();
let unimplemented_streams = dump.unimplemented_streams().collect();
let symbol_stats = symbol_provider.stats();
Ok(ProcessState {
process_id,
time: Utc.timestamp(dump.header.time_date_stamp as i64, 0),
process_create_time,
cert_info: evil.certs,
crash_reason,
crash_address,
assertion,
requesting_thread,
system_info,
linux_standard_base,
mac_crash_info,
threads,
modules,
unloaded_modules,
unknown_streams,
unimplemented_streams,
symbol_stats,
})
}