use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
use std::ffi::{CStr, CString};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::os::raw::{c_char, c_void};
use std::str::FromStr;
use std::{fmt, ptr, slice, str};
use failure::Fail;
use lazy_static::lazy_static;
use regex::Regex;
use symbolic_common::{Arch, ByteView, CpuFamily, DebugId, ParseDebugIdError, Uuid};
use crate::cfi::CfiCache;
use crate::utils;
lazy_static! {
static ref LINUX_BUILD_RE: Regex =
Regex::new(r"^Linux ([^ ]+) (.*) \w+(?: GNU/Linux)?$").unwrap();
}
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 code_modules_delete(state: *mut *const CodeModule);
fn stack_frame_return_address(frame: *const StackFrame) -> u64;
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 stack_frame_registers(
frame: *const StackFrame,
family: u32,
size_out: *mut usize,
) -> *mut IRegVal;
fn regval_delete(state: *mut IRegVal);
fn call_stack_thread_id(stack: *const CallStack) -> u32;
fn call_stack_frames(stack: *const CallStack, size_out: *mut usize)
-> *const *const StackFrame;
fn system_info_os_name(info: *const SystemInfo) -> *mut c_char;
fn system_info_os_version(info: *const SystemInfo) -> *mut c_char;
fn system_info_cpu_family(info: *const SystemInfo) -> *mut c_char;
fn system_info_cpu_info(info: *const SystemInfo) -> *mut c_char;
fn system_info_cpu_count(info: *const SystemInfo) -> u32;
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;
fn process_state_requesting_thread(state: *const IProcessState) -> i32;
fn process_state_timestamp(state: *const IProcessState) -> u64;
fn process_state_crashed(state: *const IProcessState) -> bool;
fn process_state_crash_address(state: *const IProcessState) -> u64;
fn process_state_crash_reason(state: *const IProcessState) -> *mut c_char;
fn process_state_assertion(state: *const IProcessState) -> *mut c_char;
fn process_state_system_info(state: *const IProcessState) -> *mut SystemInfo;
fn process_state_modules(
state: *const IProcessState,
size_out: *mut usize,
) -> *mut *const CodeModule;
}
pub type ParseCodeModuleIdError = ParseDebugIdError;
#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct CodeModuleId {
inner: DebugId,
}
impl CodeModuleId {
pub fn from_parts(uuid: Uuid, age: u32) -> CodeModuleId {
CodeModuleId {
inner: DebugId::from_parts(uuid, age),
}
}
pub fn uuid(&self) -> Uuid {
self.inner.uuid()
}
pub fn age(&self) -> u32 {
self.inner.appendix()
}
pub fn as_object_id(&self) -> DebugId {
self.inner
}
}
impl From<DebugId> for CodeModuleId {
fn from(inner: DebugId) -> Self {
CodeModuleId { inner }
}
}
impl Into<DebugId> for CodeModuleId {
fn into(self) -> DebugId {
self.inner
}
}
impl fmt::Display for CodeModuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.breakpad().fmt(f)
}
}
impl str::FromStr for CodeModuleId {
type Err = ParseCodeModuleIdError;
fn from_str(string: &str) -> Result<CodeModuleId, ParseCodeModuleIdError> {
Ok(CodeModuleId {
inner: DebugId::from_breakpad(string)?,
})
}
}
#[cfg(feature = "serde")]
impl ::serde::ser::Serialize for CodeModuleId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::ser::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::de::Deserialize<'de> for CodeModuleId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
<::std::borrow::Cow<str>>::deserialize(deserializer)?
.parse()
.map_err(::serde::de::Error::custom)
}
}
#[repr(C)]
pub struct CodeModule(c_void);
impl CodeModule {
pub fn id(&self) -> Option<CodeModuleId> {
match self.debug_identifier().as_str() {
"" => None,
id => CodeModuleId::from_str(id).ok(),
}
}
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 {
let id = unsafe {
let ptr = code_module_code_identifier(self);
utils::ptr_to_string(ptr)
};
if id == "id" {
String::new()
} else {
id
}
}
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 {
let id = unsafe {
let ptr = code_module_debug_identifier(self);
utils::ptr_to_string(ptr)
};
if id == "000000000000000000000000000000000" {
String::new()
} else {
id
}
}
}
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()
}
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum FrameTrust {
None,
Scan,
CFIScan,
FP,
CFI,
Prewalked,
Context,
}
impl fmt::Display for FrameTrust {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = match *self {
FrameTrust::None => "none",
FrameTrust::Scan => "stack scanning",
FrameTrust::CFIScan => "call frame info with scanning",
FrameTrust::FP => "previous frame's frame pointer",
FrameTrust::CFI => "call frame info",
FrameTrust::Prewalked => "recovered by external stack walker",
FrameTrust::Context => "given as instruction pointer in context",
};
write!(f, "{}", string)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ParseFrameTrustError;
impl fmt::Display for ParseFrameTrustError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "failed to parse frame trust")
}
}
impl FromStr for FrameTrust {
type Err = ParseFrameTrustError;
fn from_str(string: &str) -> Result<FrameTrust, Self::Err> {
Ok(match string {
"none" => FrameTrust::None,
"scan" => FrameTrust::Scan,
"cfiscan" => FrameTrust::CFIScan,
"fp" => FrameTrust::FP,
"cfi" => FrameTrust::CFI,
"prewalked" => FrameTrust::Prewalked,
"context" => FrameTrust::Context,
_ => return Err(ParseFrameTrustError),
})
}
}
impl Default for FrameTrust {
fn default() -> FrameTrust {
FrameTrust::None
}
}
#[cfg(feature = "serde")]
impl ::serde::ser::Serialize for FrameTrust {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ::serde::ser::Serializer,
{
serializer.serialize_str(match *self {
FrameTrust::None => "none",
FrameTrust::Scan => "scan",
FrameTrust::CFIScan => "cfiscan",
FrameTrust::FP => "fp",
FrameTrust::CFI => "cfi",
FrameTrust::Prewalked => "prewalked",
FrameTrust::Context => "context",
})
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::de::Deserialize<'de> for FrameTrust {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
<::std::borrow::Cow<str>>::deserialize(deserializer)?
.parse()
.map_err(::serde::de::Error::custom)
}
}
#[repr(C)]
struct IRegVal {
name: *const c_char,
value: u64,
size: u8,
}
#[derive(Clone, Copy, Debug)]
pub enum RegVal {
U32(u32),
U64(u64),
}
impl fmt::Display for RegVal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
RegVal::U32(u) => write!(f, "{:#010x}", u),
RegVal::U64(u) => write!(f, "{:#018x}", u),
}
}
}
#[repr(C)]
pub struct StackFrame(c_void);
impl StackFrame {
pub fn instruction(&self) -> u64 {
unsafe { stack_frame_instruction(self) }
}
pub fn return_address(&self, arch: Arch) -> u64 {
let address = unsafe { stack_frame_return_address(self) };
match arch.cpu_family() {
CpuFamily::Arm32 => address + 2,
CpuFamily::Arm64 => address + 4,
_ => address,
}
}
pub fn module(&self) -> Option<&CodeModule> {
unsafe { stack_frame_module(self).as_ref() }
}
pub fn trust(&self) -> FrameTrust {
unsafe { stack_frame_trust(self) }
}
pub fn registers(&self, arch: Arch) -> BTreeMap<&'static str, RegVal> {
unsafe {
let mut size = 0 as usize;
let values = stack_frame_registers(self, arch.cpu_family() as u32, &mut size);
let map = slice::from_raw_parts(values, size)
.iter()
.filter_map(|v| {
Some((
CStr::from_ptr(v.name).to_str().unwrap(),
match v.size {
4 => RegVal::U32(v.value as u32),
8 => RegVal::U64(v.value),
_ => return None,
},
))
})
.collect();
regval_delete(values);
map
}
}
}
impl fmt::Debug for StackFrame {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StackFrame")
.field("return_address", &self.return_address(Arch::Unknown))
.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);
slice::from_raw_parts(data as *const &StackFrame, size)
}
}
}
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)]
pub struct SystemInfo(c_void);
impl SystemInfo {
pub fn os_name(&self) -> String {
unsafe {
let ptr = system_info_os_name(self);
utils::ptr_to_string(ptr)
}
}
pub fn os_parts(&self) -> (String, String) {
let string = unsafe {
let ptr = system_info_os_version(self);
utils::ptr_to_string(ptr)
};
let mut parts = string.splitn(2, ' ');
let version = parts.next().unwrap_or("0.0.0");
let build = parts.next().unwrap_or("");
if version == "0.0.0" {
if let Some(captures) = LINUX_BUILD_RE.captures(&build) {
let version = captures.get(1).unwrap();
let build = captures.get(2).unwrap();
return (version.as_str().into(), build.as_str().into());
}
}
(version.into(), build.into())
}
pub fn os_version(&self) -> String {
self.os_parts().0
}
pub fn os_build(&self) -> String {
self.os_parts().1
}
pub fn cpu_family(&self) -> String {
unsafe {
let ptr = system_info_cpu_family(self);
utils::ptr_to_string(ptr)
}
}
pub fn cpu_arch(&self) -> Arch {
self.cpu_family().parse().unwrap_or_default()
}
pub fn cpu_info(&self) -> String {
unsafe {
let ptr = system_info_cpu_info(self);
utils::ptr_to_string(ptr)
}
}
pub fn cpu_count(&self) -> u32 {
unsafe { system_info_cpu_count(self) }
}
}
impl fmt::Debug for SystemInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemInfo")
.field("os_name", &self.os_name())
.field("os_version", &self.os_version())
.field("cpu_family", &self.cpu_family())
.field("cpu_info", &self.cpu_info())
.field("cpu_count", &self.cpu_count())
.finish()
}
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ProcessResult {
Ok,
MinidumpNotFound,
NoMinidumpHeader,
NoThreadList,
InvalidThreadIndex,
InvalidThreadId,
DuplicateRequestingThreads,
SymbolSupplierInterrupted,
}
impl ProcessResult {
pub fn is_usable(self) -> bool {
match self {
ProcessResult::Ok | ProcessResult::NoThreadList => true,
_ => false,
}
}
}
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 => "file could not be opened",
ProcessResult::NoMinidumpHeader => "minidump header missing",
ProcessResult::NoThreadList => "minidump has no thread list",
ProcessResult::InvalidThreadIndex => "could not get thread data",
ProcessResult::InvalidThreadId => "could not get a thread by id",
ProcessResult::DuplicateRequestingThreads => "multiple requesting threads",
ProcessResult::SymbolSupplierInterrupted => "processing was interrupted (not fatal)",
};
write!(f, "{}", formatted)
}
}
#[derive(Debug, Fail, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[fail(display = "minidump processing failed: {}", _0)]
pub struct ProcessMinidumpError(ProcessResult);
impl ProcessMinidumpError {
pub fn kind(self) -> ProcessResult {
self.0
}
}
#[repr(C)]
struct SymbolEntry {
debug_identifier: *const c_char,
symbol_size: usize,
symbol_data: *const u8,
}
pub type FrameInfoMap<'a> = BTreeMap<CodeModuleId, CfiCache<'a>>;
type IProcessState = c_void;
pub struct ProcessState<'a> {
internal: *mut IProcessState,
_ty: PhantomData<ByteView<'a>>,
}
impl<'a> ProcessState<'a> {
pub fn from_minidump(
buffer: &ByteView<'a>,
frame_infos: Option<&FrameInfoMap<'_>>,
) -> Result<ProcessState<'a>, ProcessMinidumpError> {
let cfi_count = frame_infos.map_or(0, BTreeMap::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.as_slice().len(),
v.as_slice().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.is_usable() && !internal.is_null() {
Ok(ProcessState {
internal,
_ty: PhantomData,
})
} else {
unsafe { process_state_delete(internal) };
Err(ProcessMinidumpError(result))
}
}
pub fn requesting_thread(&self) -> i32 {
unsafe { process_state_requesting_thread(self.internal) }
}
pub fn timestamp(&self) -> u64 {
unsafe { process_state_timestamp(self.internal) }
}
pub fn crashed(&self) -> bool {
unsafe { process_state_crashed(self.internal) }
}
pub fn crash_address(&self) -> u64 {
unsafe { process_state_crash_address(self.internal) }
}
pub fn crash_reason(&self) -> String {
unsafe {
let ptr = process_state_crash_reason(self.internal);
utils::ptr_to_string(ptr)
}
}
pub fn assertion(&self) -> String {
unsafe {
let ptr = process_state_assertion(self.internal);
utils::ptr_to_string(ptr)
}
}
pub fn system_info(&self) -> &SystemInfo {
unsafe { process_state_system_info(self.internal).as_ref().unwrap() }
}
pub fn threads(&self) -> &[&CallStack] {
unsafe {
let mut size = 0 as usize;
let data = process_state_threads(self.internal, &mut size);
slice::from_raw_parts(data as *const &CallStack, size)
}
}
pub fn modules(&self) -> Vec<&CodeModule> {
unsafe {
let mut size = 0 as usize;
let data = process_state_modules(self.internal, &mut size);
let vec = slice::from_raw_parts(data as *mut &CodeModule, size).to_vec();
code_modules_delete(data);
vec
}
}
pub fn referenced_modules(&self) -> BTreeSet<&CodeModule> {
self.threads()
.iter()
.flat_map(|stack| stack.frames().iter())
.filter_map(|frame| frame.module())
.collect()
}
}
impl<'a> Drop for ProcessState<'a> {
fn drop(&mut self) {
unsafe { process_state_delete(self.internal) };
}
}
impl<'a> fmt::Debug for ProcessState<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProcessState")
.field("requesting_thread", &self.requesting_thread())
.field("timestamp", &self.timestamp())
.field("crash_address", &self.crash_address())
.field("crash_reason", &self.crash_reason())
.field("assertion", &self.assertion())
.field("system_info", &self.system_info())
.field("threads", &self.threads())
.field("modules", &self.modules())
.finish()
}
}