use chrono::prelude::*;
use encoding::all::UTF_16LE;
use encoding::{DecoderTrap, Encoding};
use failure::Fail;
use memmap::Mmap;
use num_traits::FromPrimitive;
use scroll::ctx::{SizeWith, TryFromCtx};
use scroll::{self, Pread, BE, LE};
use std::borrow::Cow;
use std::boxed::Box;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::iter;
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::path::Path;
use std::str;
pub use crate::context::*;
use crate::system_info::{Cpu, Os};
use minidump_common::format as md;
use minidump_common::format::{CvSignature, MINIDUMP_STREAM_TYPE};
use minidump_common::traits::{IntoRangeMapSafe, Module};
use range_map::{Range, RangeMap};
#[derive(Debug)]
pub struct Minidump<'a, T>
where
T: Deref<Target = [u8]> + 'a,
{
data: T,
pub header: md::MINIDUMP_HEADER,
streams: HashMap<u32, (u32, md::MINIDUMP_DIRECTORY)>,
pub endian: scroll::Endian,
_phantom: PhantomData<&'a [u8]>,
}
#[derive(Debug, Fail, PartialEq)]
pub enum Error {
#[fail(display = "File not found")]
FileNotFound,
#[fail(display = "I/O error")]
IoError,
#[fail(display = "Missing minidump header")]
MissingHeader,
#[fail(display = "Header mismatch")]
HeaderMismatch,
#[fail(display = "Minidump version mismatch")]
VersionMismatch,
#[fail(display = "Missing stream directory")]
MissingDirectory,
#[fail(display = "Error reading stream")]
StreamReadFailure,
#[fail(
display = "Stream size mismatch: expected {} byes, found {} bytes",
expected, actual
)]
StreamSizeMismatch { expected: usize, actual: usize },
#[fail(display = "Stream not found")]
StreamNotFound,
#[fail(display = "Module read failure")]
ModuleReadFailure,
#[fail(display = "Memory read failure")]
MemoryReadFailure,
#[fail(display = "Data error")]
DataError,
#[fail(display = "Error reading CodeView data")]
CodeViewReadFailure,
}
pub trait MinidumpStream<'a>: Sized {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE;
fn read(bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian) -> Result<Self, Error>;
}
#[derive(Debug, Clone)]
pub enum CodeView {
Pdb20(md::CV_INFO_PDB20),
Pdb70(md::CV_INFO_PDB70),
Elf(md::CV_INFO_ELF),
Unknown(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct MinidumpModule {
pub raw: md::MINIDUMP_MODULE,
pub name: String,
pub codeview_info: Option<CodeView>,
pub misc_info: Option<md::IMAGE_DEBUG_MISC>,
}
#[derive(Debug, Clone)]
pub struct MinidumpModuleList {
modules: Vec<MinidumpModule>,
modules_by_addr: RangeMap<u64, usize>,
}
#[derive(Debug, Clone)]
pub struct MinidumpUnloadedModule {
pub raw: md::MINIDUMP_UNLOADED_MODULE,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct MinidumpUnloadedModuleList {
modules: Vec<MinidumpUnloadedModule>,
modules_by_addr: RangeMap<u64, usize>,
}
#[derive(Debug)]
pub struct MinidumpThread<'a> {
pub raw: md::MINIDUMP_THREAD,
pub context: Option<MinidumpContext>,
pub stack: Option<MinidumpMemory<'a>>,
}
#[derive(Debug)]
pub struct MinidumpThreadList<'a> {
pub threads: Vec<MinidumpThread<'a>>,
thread_ids: HashMap<u32, usize>,
}
#[derive(Debug)]
pub struct MinidumpSystemInfo {
pub raw: md::MINIDUMP_SYSTEM_INFO,
pub os: Os,
pub cpu: Cpu,
csd_version: Option<String>,
cpu_info: Option<String>,
}
#[derive(Debug)]
pub struct MinidumpMemory<'a> {
pub desc: md::MINIDUMP_MEMORY_DESCRIPTOR,
pub base_address: u64,
pub size: u64,
pub bytes: &'a [u8],
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum RawMiscInfo {
MiscInfo(md::MINIDUMP_MISC_INFO),
MiscInfo2(md::MINIDUMP_MISC_INFO_2),
MiscInfo3(md::MINIDUMP_MISC_INFO_3),
MiscInfo4(md::MINIDUMP_MISC_INFO_4),
MiscInfo5(md::MINIDUMP_MISC_INFO_5),
}
#[derive(Debug)]
pub struct MinidumpMiscInfo {
pub raw: RawMiscInfo,
}
#[derive(Debug)]
pub struct MinidumpBreakpadInfo {
raw: md::MINIDUMP_BREAKPAD_INFO,
pub dump_thread_id: Option<u32>,
pub requesting_thread_id: Option<u32>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CrashReason {
MacGeneral(md::ExceptionCodeMac, u32),
MacBadAccessKern(md::ExceptionCodeMacBadAccessKernType),
MacBadAccessArm(md::ExceptionCodeMacBadAccessArmType),
MacBadAccessPpc(md::ExceptionCodeMacBadAccessPpcType),
MacBadAccessX86(md::ExceptionCodeMacBadAccessX86Type),
MacBadInstructionArm(md::ExceptionCodeMacBadInstructionArmType),
MacBadInstructionPpc(md::ExceptionCodeMacBadInstructionPpcType),
MacBadInstructionX86(md::ExceptionCodeMacBadInstructionX86Type),
MacArithmeticPpc(md::ExceptionCodeMacArithmeticPpcType),
MacArithmeticX86(md::ExceptionCodeMacArithmeticX86Type),
MacSoftware(md::ExceptionCodeMacSoftwareType),
MacBreakpointArm(md::ExceptionCodeMacBreakpointArmType),
MacBreakpointPpc(md::ExceptionCodeMacBreakpointPpcType),
MacBreakpointX86(md::ExceptionCodeMacBreakpointX86Type),
LinuxGeneral(md::ExceptionCodeLinux, u32),
LinuxSigill(md::ExceptionCodeLinuxSigillKind),
LinuxSigbus(md::ExceptionCodeLinuxSigbusKind),
LinuxSigfpe(md::ExceptionCodeLinuxSigfpeKind),
LinuxSigsegv(md::ExceptionCodeLinuxSigsegvKind),
WindowsGeneral(md::ExceptionCodeWindows),
WindowsAccessViolation(md::ExceptionCodeWindowsAccessType),
WindowsInPageError(md::ExceptionCodeWindowsInPageErrorType, u64),
WindowsStackBufferOverrun(u64),
WindowsUnknown(u32),
Unknown(u32, u32),
}
#[derive(Debug)]
pub struct MinidumpException {
pub raw: md::MINIDUMP_EXCEPTION_STREAM,
pub thread_id: u32,
pub context: Option<MinidumpContext>,
}
#[derive(Debug)]
pub struct MinidumpMemoryList<'a> {
regions: Vec<MinidumpMemory<'a>>,
regions_by_addr: RangeMap<u64, usize>,
}
#[derive(Debug)]
pub struct MinidumpAssertion {
pub raw: md::MINIDUMP_ASSERTION_INFO,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum MinidumpAnnotation {
Invalid,
String(String),
UserDefined(md::MINIDUMP_ANNOTATION),
Unsupported(md::MINIDUMP_ANNOTATION),
}
impl PartialEq for MinidumpAnnotation {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Invalid, Self::Invalid) => true,
(Self::String(a), Self::String(b)) => a == b,
_ => false,
}
}
}
#[derive(Debug)]
pub struct MinidumpModuleCrashpadInfo {
pub raw: md::MINIDUMP_MODULE_CRASHPAD_INFO,
pub module_index: usize,
pub list_annotations: Vec<String>,
pub simple_annotations: BTreeMap<String, String>,
pub annotation_objects: BTreeMap<String, MinidumpAnnotation>,
}
#[derive(Debug)]
pub struct MinidumpCrashpadInfo {
pub raw: md::MINIDUMP_CRASHPAD_INFO,
pub simple_annotations: BTreeMap<String, String>,
pub module_list: Vec<MinidumpModuleCrashpadInfo>,
}
fn format_time_t(t: u32) -> String {
if let Some(datetime) = NaiveDateTime::from_timestamp_opt(t as i64, 0) {
datetime.format("%Y-%m-%d %H:%M:%S").to_string()
} else {
String::new()
}
}
fn format_system_time(time: &md::SYSTEMTIME) -> String {
if let Some(date) =
NaiveDate::from_ymd_opt(time.year as i32, time.month as u32, time.day as u32)
{
let time = NaiveTime::from_hms_milli(
time.hour as u32,
time.minute as u32,
time.second as u32,
time.milliseconds as u32,
);
let datetime = NaiveDateTime::new(date, time);
datetime.format("%Y-%m-%d %H:%M:%S:%f").to_string()
} else {
"<invalid date>".to_owned()
}
}
fn location_slice<'a>(
bytes: &'a [u8],
loc: &md::MINIDUMP_LOCATION_DESCRIPTOR,
) -> Result<&'a [u8], Error> {
let start = loc.rva as usize;
start
.checked_add(loc.data_size as usize)
.and_then(|end| bytes.get(start..end))
.ok_or(Error::StreamReadFailure)
}
fn read_string_utf16(
offset: &mut usize,
bytes: &[u8],
endian: scroll::Endian,
) -> Result<String, ()> {
let u: u32 = bytes.gread_with(offset, endian).or(Err(()))?;
let size = u as usize;
if size % 2 != 0 || (*offset + size) > bytes.len() {
return Err(());
}
match UTF_16LE.decode(&bytes[*offset..*offset + size], DecoderTrap::Strict) {
Ok(s) => {
*offset += size;
Ok(s)
}
Err(_) => Err(()),
}
}
#[inline]
fn read_string_utf8_unterminated<'a>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<&'a str, ()> {
let length: u32 = bytes.gread_with(offset, endian).or(Err(()))?;
let slice = bytes.gread_with(offset, length as usize).or(Err(()))?;
std::str::from_utf8(slice).or(Err(()))
}
fn read_string_utf8<'a>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<&'a str, ()> {
let string = read_string_utf8_unterminated(offset, bytes, endian)?;
match bytes.gread(offset) {
Ok(0u8) => Ok(string),
_ => Err(()),
}
}
fn string_from_bytes_nul(bytes: &[u8]) -> Option<Cow<'_, str>> {
bytes
.split(|&b| b == 0)
.next()
.map(|b| String::from_utf8_lossy(b))
}
fn bytes_to_hex(bytes: &[u8]) -> String {
let hex_bytes: Vec<String> = bytes.iter().map(|b| format!("{:02x}", b)).collect();
hex_bytes.join("")
}
fn read_codeview(
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
data: &[u8],
endian: scroll::Endian,
) -> Result<CodeView, failure::Error> {
let bytes = location_slice(data, location)?;
let signature: u32 = bytes.pread_with(0, endian)?;
Ok(match CvSignature::from_u32(signature) {
Some(CvSignature::Pdb70) => CodeView::Pdb70(bytes.pread_with(0, endian)?),
Some(CvSignature::Pdb20) => CodeView::Pdb20(bytes.pread_with(0, endian)?),
Some(CvSignature::Elf) => CodeView::Elf(bytes.pread_with(0, endian)?),
_ => CodeView::Unknown(bytes.to_owned()),
})
}
impl MinidumpModule {
pub fn new(base: u64, size: u32, name: &str) -> MinidumpModule {
MinidumpModule {
raw: md::MINIDUMP_MODULE {
base_of_image: base,
size_of_image: size,
..md::MINIDUMP_MODULE::default()
},
name: String::from(name),
codeview_info: None,
misc_info: None,
}
}
pub fn read(
raw: md::MINIDUMP_MODULE,
bytes: &[u8],
endian: scroll::Endian,
) -> Result<MinidumpModule, Error> {
let mut offset = raw.module_name_rva as usize;
let name =
read_string_utf16(&mut offset, bytes, endian).or(Err(Error::CodeViewReadFailure))?;
let codeview_info = if raw.cv_record.data_size == 0 {
None
} else {
Some(read_codeview(&raw.cv_record, bytes, endian).or(Err(Error::CodeViewReadFailure))?)
};
Ok(MinidumpModule {
raw,
name,
codeview_info,
misc_info: None,
})
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_MODULE
base_of_image = {:#x}
size_of_image = {:#x}
checksum = {:#x}
time_date_stamp = {:#x} {}
module_name_rva = {:#x}
version_info.signature = {:#x}
version_info.struct_version = {:#x}
version_info.file_version = {:#x}:{:#x}
version_info.product_version = {:#x}:{:#x}
version_info.file_flags_mask = {:#x}
version_info.file_flags = {:#x}
version_info.file_os = {:#x}
version_info.file_type = {:#x}
version_info.file_subtype = {:#x}
version_info.file_date = {:#x}:{:#x}
cv_record.data_size = {}
cv_record.rva = {:#x}
misc_record.data_size = {}
misc_record.rva = {:#x}
(code_file) = \"{}\"
(code_identifier) = \"{}\"
",
self.raw.base_of_image,
self.raw.size_of_image,
self.raw.checksum,
self.raw.time_date_stamp,
format_time_t(self.raw.time_date_stamp),
self.raw.module_name_rva,
self.raw.version_info.signature,
self.raw.version_info.struct_version,
self.raw.version_info.file_version_hi,
self.raw.version_info.file_version_lo,
self.raw.version_info.product_version_hi,
self.raw.version_info.product_version_lo,
self.raw.version_info.file_flags_mask,
self.raw.version_info.file_flags,
self.raw.version_info.file_os,
self.raw.version_info.file_type,
self.raw.version_info.file_subtype,
self.raw.version_info.file_date_hi,
self.raw.version_info.file_date_lo,
self.raw.cv_record.data_size,
self.raw.cv_record.rva,
self.raw.misc_record.data_size,
self.raw.misc_record.rva,
self.code_file(),
self.code_identifier(),
)?;
match self.codeview_info {
Some(CodeView::Pdb70(ref raw)) => {
let pdb_file_name =
string_from_bytes_nul(&raw.pdb_file_name).unwrap_or(Cow::Borrowed("(invalid)"));
write!(f, " (cv_record).cv_signature = {:#x}
(cv_record).signature = {:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}
(cv_record).age = {}
(cv_record).pdb_file_name = \"{}\"
",
raw.cv_signature,
raw.signature.data1,
raw.signature.data2,
raw.signature.data3,
raw.signature.data4[0],
raw.signature.data4[1],
raw.signature.data4[2],
raw.signature.data4[3],
raw.signature.data4[4],
raw.signature.data4[5],
raw.signature.data4[6],
raw.signature.data4[7],
raw.age,
pdb_file_name,
)?;
}
Some(CodeView::Pdb20(ref raw)) => {
let pdb_file_name =
string_from_bytes_nul(&raw.pdb_file_name).unwrap_or(Cow::Borrowed("(invalid)"));
write!(
f,
" (cv_record).cv_header.signature = {:#x}
(cv_record).cv_header.offset = {:#x}
(cv_record).signature = {:#x} {}
(cv_record).age = {}
(cv_record).pdb_file_name = \"{}\"
",
raw.cv_signature,
raw.cv_offset,
raw.signature,
format_time_t(raw.signature),
raw.age,
pdb_file_name,
)?;
}
Some(CodeView::Elf(ref raw)) => {
write!(
f,
" (cv_record).cv_signature = {:#x}
(cv_record).build_id = {}
",
raw.cv_signature,
bytes_to_hex(&raw.build_id),
)?;
}
Some(CodeView::Unknown(ref bytes)) => {
writeln!(
f,
" (cv_record) = {}",
bytes_to_hex(bytes),
)?;
}
None => {
writeln!(f, " (cv_record) = (null)")?;
}
}
if let Some(ref _misc) = self.misc_info {
writeln!(f, " (misc_record) = (unimplemented)")?;
} else {
writeln!(f, " (misc_record) = (null)")?;
}
write!(
f,
r#" (debug_file) = "{}"
(debug_identifier) = "{}"
(version) = "{}"
"#,
self.debug_file().unwrap_or(Cow::Borrowed("")),
self.debug_identifier().unwrap_or(Cow::Borrowed("")),
self.version().unwrap_or(Cow::Borrowed("")),
)?;
Ok(())
}
fn memory_range(&self) -> Range<u64> {
Range::new(self.base_address(), self.base_address() + self.size() - 1)
}
}
impl Module for MinidumpModule {
fn base_address(&self) -> u64 {
self.raw.base_of_image
}
fn size(&self) -> u64 {
self.raw.size_of_image as u64
}
fn code_file(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.name)
}
fn code_identifier(&self) -> Cow<'_, str> {
match self.codeview_info {
Some(CodeView::Elf(ref raw)) => Cow::Owned(bytes_to_hex(&raw.build_id)),
_ => {
Cow::Owned(format!(
"{0:08X}{1:x}",
self.raw.time_date_stamp, self.raw.size_of_image
))
}
}
}
fn debug_file(&self) -> Option<Cow<'_, str>> {
match self.codeview_info {
Some(CodeView::Pdb70(ref raw)) => string_from_bytes_nul(&raw.pdb_file_name),
Some(CodeView::Pdb20(ref raw)) => string_from_bytes_nul(&raw.pdb_file_name),
Some(CodeView::Elf(_)) => Some(Cow::Borrowed(&self.name)),
_ => None,
}
}
fn debug_identifier(&self) -> Option<Cow<'_, str>> {
match self.codeview_info {
Some(CodeView::Pdb70(ref raw)) => {
let id = format!("{:#}{:x}", raw.signature, raw.age,);
Some(Cow::Owned(id))
}
Some(CodeView::Pdb20(ref raw)) => {
let id = format!("{:08X}{:x}", raw.signature, raw.age);
Some(Cow::Owned(id))
}
Some(CodeView::Elf(ref raw)) => {
let guid_size = <md::GUID>::size_with(&LE);
let guid = if raw.build_id.len() < guid_size {
let v: Vec<u8> = raw
.build_id
.iter()
.cloned()
.chain(iter::repeat(0))
.take(guid_size)
.collect();
v.pread_with::<md::GUID>(0, LE).ok()
} else {
raw.build_id.pread_with::<md::GUID>(0, LE).ok()
};
guid.map(|g| Cow::Owned(format!("{:#}0", g)))
}
_ => None,
}
}
fn version(&self) -> Option<Cow<'_, str>> {
if self.raw.version_info.signature == md::VS_FFI_SIGNATURE
&& self.raw.version_info.struct_version == md::VS_FFI_STRUCVERSION
{
let ver = format!(
"{}.{}.{}.{}",
self.raw.version_info.file_version_hi >> 16,
self.raw.version_info.file_version_hi & 0xffff,
self.raw.version_info.file_version_lo >> 16,
self.raw.version_info.file_version_lo & 0xffff
);
Some(Cow::Owned(ver))
} else {
None
}
}
}
impl MinidumpUnloadedModule {
pub fn new(base: u64, size: u32, name: &str) -> MinidumpUnloadedModule {
MinidumpUnloadedModule {
raw: md::MINIDUMP_UNLOADED_MODULE {
base_of_image: base,
size_of_image: size,
..md::MINIDUMP_UNLOADED_MODULE::default()
},
name: String::from(name),
}
}
pub fn read(
raw: md::MINIDUMP_UNLOADED_MODULE,
bytes: &[u8],
endian: scroll::Endian,
) -> Result<MinidumpUnloadedModule, Error> {
let mut offset = raw.module_name_rva as usize;
let name = read_string_utf16(&mut offset, bytes, endian).or(Err(Error::DataError))?;
Ok(MinidumpUnloadedModule { raw, name })
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_UNLOADED_MODULE
base_of_image = {:#x}
size_of_image = {:#x}
checksum = {:#x}
time_date_stamp = {:#x} {}
module_name_rva = {:#x}
(code_file) = \"{}\"
(code_identifier) = \"{}\"
",
self.raw.base_of_image,
self.raw.size_of_image,
self.raw.checksum,
self.raw.time_date_stamp,
format_time_t(self.raw.time_date_stamp),
self.raw.module_name_rva,
self.code_file(),
self.code_identifier(),
)?;
Ok(())
}
fn memory_range(&self) -> Range<u64> {
Range::new(self.base_address(), self.base_address() + self.size() - 1)
}
}
impl Module for MinidumpUnloadedModule {
fn base_address(&self) -> u64 {
self.raw.base_of_image
}
fn size(&self) -> u64 {
self.raw.size_of_image as u64
}
fn code_file(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.name)
}
fn code_identifier(&self) -> Cow<'_, str> {
Cow::Owned(format!(
"{0:08X}{1:x}",
self.raw.time_date_stamp, self.raw.size_of_image
))
}
fn debug_file(&self) -> Option<Cow<'_, str>> {
None
}
fn debug_identifier(&self) -> Option<Cow<'_, str>> {
None
}
fn version(&self) -> Option<Cow<'_, str>> {
None
}
}
fn read_stream_list<'a, T>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<Vec<T>, Error>
where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
T: SizeWith<scroll::Endian>,
{
let u: u32 = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
let count = u as usize;
let counted_size = match count
.checked_mul(<T>::size_with(&endian))
.and_then(|v| v.checked_add(mem::size_of::<u32>()))
{
Some(s) => s,
None => return Err(Error::StreamReadFailure),
};
if bytes.len() < counted_size {
return Err(Error::StreamSizeMismatch {
expected: counted_size,
actual: bytes.len(),
});
}
match bytes.len() - counted_size {
0 => {}
4 => {
*offset += 4;
}
_ => {
return Err(Error::StreamSizeMismatch {
expected: counted_size,
actual: bytes.len(),
})
}
};
let mut raw_entries = Vec::with_capacity(count);
for _ in 0..count {
let raw: T = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
raw_entries.push(raw);
}
Ok(raw_entries)
}
fn read_ex_stream_list<'a, T>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<Vec<T>, Error>
where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
T: SizeWith<scroll::Endian>,
{
let size_of_header: u32 = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
let size_of_entry: u32 = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
let number_of_entries: u32 = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
let expected_size_of_entry = <T>::size_with(&endian);
if size_of_entry as usize != expected_size_of_entry {
return Err(Error::StreamReadFailure);
}
let stream_size = match number_of_entries
.checked_mul(size_of_entry)
.and_then(|v| v.checked_add(size_of_header))
{
Some(s) => s as usize,
None => return Err(Error::StreamReadFailure),
};
if bytes.len() < stream_size {
return Err(Error::StreamSizeMismatch {
expected: stream_size,
actual: bytes.len(),
});
}
let header_padding = match (size_of_header as usize).checked_sub(*offset) {
Some(s) => s,
None => return Err(Error::StreamReadFailure),
};
*offset += header_padding;
let mut raw_entries = Vec::with_capacity(number_of_entries as usize);
for _ in 0..number_of_entries {
let raw: T = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
raw_entries.push(raw);
}
Ok(raw_entries)
}
impl MinidumpModuleList {
pub fn new() -> MinidumpModuleList {
MinidumpModuleList {
modules: vec![],
modules_by_addr: RangeMap::new(),
}
}
pub fn from_modules(modules: Vec<MinidumpModule>) -> MinidumpModuleList {
let modules_by_addr = modules
.iter()
.enumerate()
.map(|(i, module)| (module.memory_range(), i))
.into_rangemap_safe();
MinidumpModuleList {
modules,
modules_by_addr,
}
}
pub fn main_module(&self) -> Option<&MinidumpModule> {
if !self.modules.is_empty() {
Some(&self.modules[0])
} else {
None
}
}
pub fn module_at_address(&self, address: u64) -> Option<&MinidumpModule> {
self.modules_by_addr
.get(address)
.map(|&index| &self.modules[index])
}
pub fn iter(&self) -> impl Iterator<Item = &MinidumpModule> {
self.modules.iter()
}
pub fn by_addr(&self) -> impl Iterator<Item = &MinidumpModule> {
self.modules_by_addr
.ranges_values()
.map(move |&(_, index)| &self.modules[index])
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MinidumpModuleList
module_count = {}
",
self.modules.len()
)?;
for (i, module) in self.modules.iter().enumerate() {
writeln!(f, "module[{}]", i)?;
module.print(f)?;
}
Ok(())
}
}
impl Default for MinidumpModuleList {
fn default() -> Self {
Self::new()
}
}
impl<'a> MinidumpStream<'a> for MinidumpModuleList {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::ModuleListStream;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpModuleList, Error> {
let mut offset = 0;
let raw_modules: Vec<md::MINIDUMP_MODULE> = read_stream_list(&mut offset, bytes, endian)?;
let mut modules = Vec::with_capacity(raw_modules.len());
for raw in raw_modules.into_iter() {
if raw.size_of_image == 0
|| raw.size_of_image as u64 > (u64::max_value() - raw.base_of_image)
{
return Err(Error::ModuleReadFailure);
}
modules.push(MinidumpModule::read(raw, all, endian)?);
}
Ok(MinidumpModuleList::from_modules(modules))
}
}
impl MinidumpUnloadedModuleList {
pub fn new() -> MinidumpUnloadedModuleList {
MinidumpUnloadedModuleList {
modules: vec![],
modules_by_addr: RangeMap::new(),
}
}
pub fn from_modules(modules: Vec<MinidumpUnloadedModule>) -> MinidumpUnloadedModuleList {
let modules_by_addr = modules
.iter()
.enumerate()
.map(|(i, module)| (module.memory_range(), i))
.into_rangemap_safe();
MinidumpUnloadedModuleList {
modules,
modules_by_addr,
}
}
pub fn module_at_address(&self, address: u64) -> Option<&MinidumpUnloadedModule> {
self.modules_by_addr
.get(address)
.map(|&index| &self.modules[index])
}
pub fn iter(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
self.modules.iter()
}
pub fn by_addr(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
self.modules_by_addr
.ranges_values()
.map(move |&(_, index)| &self.modules[index])
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MinidumpUnloadedModuleList
module_count = {}
",
self.modules.len()
)?;
for (i, module) in self.modules.iter().enumerate() {
writeln!(f, "module[{}]", i)?;
module.print(f)?;
}
Ok(())
}
}
impl Default for MinidumpUnloadedModuleList {
fn default() -> Self {
Self::new()
}
}
impl<'a> MinidumpStream<'a> for MinidumpUnloadedModuleList {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::UnloadedModuleListStream;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpUnloadedModuleList, Error> {
let mut offset = 0;
let raw_modules: Vec<md::MINIDUMP_UNLOADED_MODULE> =
read_ex_stream_list(&mut offset, bytes, endian)?;
let mut modules = Vec::with_capacity(raw_modules.len());
for raw in raw_modules.into_iter() {
if raw.size_of_image == 0
|| raw.size_of_image as u64 > (u64::max_value() - raw.base_of_image)
{
return Err(Error::ModuleReadFailure);
}
modules.push(MinidumpUnloadedModule::read(raw, all, endian)?);
}
Ok(MinidumpUnloadedModuleList::from_modules(modules))
}
}
impl<'a> MinidumpMemory<'a> {
pub fn read(
desc: &md::MINIDUMP_MEMORY_DESCRIPTOR,
data: &'a [u8],
) -> Result<MinidumpMemory<'a>, Error> {
if desc.memory.rva == 0 || desc.memory.data_size == 0 {
return Err(Error::MemoryReadFailure);
}
let bytes = location_slice(data, &desc.memory).or(Err(Error::StreamReadFailure))?;
Ok(MinidumpMemory {
desc: *desc,
base_address: desc.start_of_memory_range,
size: desc.memory.data_size as u64,
bytes,
})
}
pub fn get_memory_at_address<T>(&self, addr: u64) -> Option<T>
where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
T: SizeWith<scroll::Endian>,
{
let in_range = |a: u64| a >= self.base_address && a < (self.base_address + self.size);
let size = <T>::size_with(&LE);
if !in_range(addr) || !in_range(addr + size as u64 - 1) {
return None;
}
let start = (addr - self.base_address) as usize;
self.bytes.pread_with::<T>(start, LE).ok()
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_MEMORY_DESCRIPTOR
start_of_memory_range = {:#x}
memory.data_size = {:#x}
memory.rva = {:#x}
Memory
",
self.desc.start_of_memory_range, self.desc.memory.data_size, self.desc.memory.rva,
)?;
self.print_contents(f)?;
writeln!(f)
}
pub fn print_contents<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(f, "0x")?;
for byte in self.bytes.iter() {
write!(f, "{:02x}", byte)?;
}
writeln!(f)?;
Ok(())
}
fn memory_range(&self) -> Range<u64> {
Range::new(self.base_address, self.base_address + self.size - 1)
}
}
#[allow(missing_debug_implementations)]
pub struct MemoryRegions<'iter, 'data> {
iter: Box<dyn Iterator<Item = &'iter MinidumpMemory<'data>> + 'iter>,
}
impl<'iter, 'data> Iterator for MemoryRegions<'iter, 'data>
where
'data: 'iter,
{
type Item = &'iter MinidumpMemory<'data>;
fn next(&mut self) -> Option<&'iter MinidumpMemory<'data>> {
self.iter.next()
}
}
impl<'mdmp> MinidumpMemoryList<'mdmp> {
pub fn new() -> MinidumpMemoryList<'mdmp> {
MinidumpMemoryList {
regions: vec![],
regions_by_addr: RangeMap::new(),
}
}
pub fn from_regions(regions: Vec<MinidumpMemory<'mdmp>>) -> MinidumpMemoryList<'mdmp> {
let regions_by_addr = regions
.iter()
.enumerate()
.map(|(i, region)| (region.memory_range(), i))
.into_rangemap_safe();
MinidumpMemoryList {
regions,
regions_by_addr,
}
}
pub fn memory_at_address(&self, address: u64) -> Option<&MinidumpMemory<'mdmp>> {
self.regions_by_addr
.get(address)
.map(|&index| &self.regions[index])
}
pub fn iter<'slf>(&'slf self) -> MemoryRegions<'slf, 'mdmp> {
MemoryRegions {
iter: Box::new(self.regions.iter()),
}
}
pub fn by_addr<'slf>(&'slf self) -> MemoryRegions<'slf, 'mdmp> {
MemoryRegions {
iter: Box::new(
self.regions_by_addr
.ranges_values()
.map(move |&(_, index)| &self.regions[index]),
),
}
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MinidumpMemoryList
region_count = {}
",
self.regions.len()
)?;
for (i, region) in self.regions.iter().enumerate() {
writeln!(f, "region[{}]", i)?;
region.print(f)?;
}
Ok(())
}
}
impl<'a> Default for MinidumpMemoryList<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> MinidumpStream<'a> for MinidumpMemoryList<'a> {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::MemoryListStream;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpMemoryList<'a>, Error> {
let mut offset = 0;
let descriptors: Vec<md::MINIDUMP_MEMORY_DESCRIPTOR> =
read_stream_list(&mut offset, bytes, endian)?;
let mut regions = Vec::with_capacity(descriptors.len());
for raw in descriptors.into_iter() {
if let Ok(memory) = MinidumpMemory::read(&raw, all) {
regions.push(memory);
} else {
continue;
}
}
Ok(MinidumpMemoryList::from_regions(regions))
}
}
impl<'a> MinidumpThread<'a> {
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
r#"MINIDUMP_THREAD
thread_id = {:#x}
suspend_count = {}
priority_class = {:#x}
priority = {:#x}
teb = {:#x}
stack.start_of_memory_range = {:#x}
stack.memory.data_size = {:#x}
stack.memory.rva = {:#x}
thread_context.data_size = {:#x}
thread_context.rva = {:#x}
"#,
self.raw.thread_id,
self.raw.suspend_count,
self.raw.priority_class,
self.raw.priority,
self.raw.teb,
self.raw.stack.start_of_memory_range,
self.raw.stack.memory.data_size,
self.raw.stack.memory.rva,
self.raw.thread_context.data_size,
self.raw.thread_context.rva,
)?;
if let Some(ref ctx) = self.context {
ctx.print(f)?;
} else {
write!(f, " (no context)\n\n")?;
}
if let Some(ref stack) = self.stack {
writeln!(f, "Stack")?;
stack.print_contents(f)?;
} else {
writeln!(f, "No stack")?;
}
writeln!(f)?;
Ok(())
}
}
impl<'a> MinidumpStream<'a> for MinidumpThreadList<'a> {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::ThreadListStream;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpThreadList<'a>, Error> {
let mut offset = 0;
let raw_threads: Vec<md::MINIDUMP_THREAD> = read_stream_list(&mut offset, bytes, endian)?;
let mut threads = Vec::with_capacity(raw_threads.len());
let mut thread_ids = HashMap::with_capacity(raw_threads.len());
for raw in raw_threads.into_iter() {
thread_ids.insert(raw.thread_id, threads.len());
let context_data = location_slice(all, &raw.thread_context)?;
let context = MinidumpContext::read(context_data, endian).ok();
let stack = MinidumpMemory::read(&raw.stack, all).ok();
threads.push(MinidumpThread {
raw,
context,
stack,
});
}
Ok(MinidumpThreadList {
threads,
thread_ids,
})
}
}
impl<'a> MinidumpThreadList<'a> {
pub fn get_thread(&self, id: u32) -> Option<&MinidumpThread<'a>> {
self.thread_ids.get(&id).map(|&index| &self.threads[index])
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
r#"MinidumpThreadList
thread_count = {}
"#,
self.threads.len()
)?;
for (i, thread) in self.threads.iter().enumerate() {
writeln!(f, "thread[{}]", i)?;
thread.print(f)?;
}
Ok(())
}
}
impl<'a> MinidumpStream<'a> for MinidumpSystemInfo {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::SystemInfoStream;
fn read(
bytes: &[u8],
_all: &[u8],
endian: scroll::Endian,
) -> Result<MinidumpSystemInfo, Error> {
use std::fmt::Write;
let raw: md::MINIDUMP_SYSTEM_INFO = bytes
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
let os = Os::from_platform_id(raw.platform_id);
let cpu = Cpu::from_processor_architecture(raw.processor_architecture);
let mut csd_offset = raw.csd_version_rva as usize;
let csd_version = read_string_utf16(&mut csd_offset, bytes, endian).ok();
let cpu_info = match cpu {
Cpu::X86 | Cpu::X86_64 => {
let mut cpu_info = String::new();
if let Cpu::X86 = cpu {
let x86_info: md::X86CpuInfo = raw
.cpu
.data
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
cpu_info.extend(
x86_info
.vendor_id
.iter()
.flat_map(|i| std::array::IntoIter::new(i.to_le_bytes()))
.map(char::from),
);
cpu_info.push(' ');
}
write!(
&mut cpu_info,
"family {} model {} stepping {}",
raw.processor_level,
(raw.processor_revision >> 8) & 0xff,
raw.processor_revision & 0xff
)
.unwrap();
Some(cpu_info)
}
Cpu::Arm => {
let arm_info: md::ARMCpuInfo = raw
.cpu
.data
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
let vendors = [
(0x41, "ARM"),
(0x51, "Qualcomm"),
(0x56, "Marvell"),
(0x69, "Intel/Marvell"),
];
let parts = [
(0x4100c050, "Cortex-A5"),
(0x4100c080, "Cortex-A8"),
(0x4100c090, "Cortex-A9"),
(0x4100c0f0, "Cortex-A15"),
(0x4100c140, "Cortex-R4"),
(0x4100c150, "Cortex-R5"),
(0x4100b360, "ARM1136"),
(0x4100b560, "ARM1156"),
(0x4100b760, "ARM1176"),
(0x4100b020, "ARM11-MPCore"),
(0x41009260, "ARM926"),
(0x41009460, "ARM946"),
(0x41009660, "ARM966"),
(0x510006f0, "Krait"),
(0x510000f0, "Scorpion"),
];
let features = [
(md::ArmElfHwCaps::HWCAP_SWP, "swp"),
(md::ArmElfHwCaps::HWCAP_HALF, "half"),
(md::ArmElfHwCaps::HWCAP_THUMB, "thumb"),
(md::ArmElfHwCaps::HWCAP_26BIT, "26bit"),
(md::ArmElfHwCaps::HWCAP_FAST_MULT, "fastmult"),
(md::ArmElfHwCaps::HWCAP_FPA, "fpa"),
(md::ArmElfHwCaps::HWCAP_VFP, "vfpv2"),
(md::ArmElfHwCaps::HWCAP_EDSP, "edsp"),
(md::ArmElfHwCaps::HWCAP_JAVA, "java"),
(md::ArmElfHwCaps::HWCAP_IWMMXT, "iwmmxt"),
(md::ArmElfHwCaps::HWCAP_CRUNCH, "crunch"),
(md::ArmElfHwCaps::HWCAP_THUMBEE, "thumbee"),
(md::ArmElfHwCaps::HWCAP_NEON, "neon"),
(md::ArmElfHwCaps::HWCAP_VFPv3, "vfpv3"),
(md::ArmElfHwCaps::HWCAP_VFPv3D16, "vfpv3d16"),
(md::ArmElfHwCaps::HWCAP_TLS, "tls"),
(md::ArmElfHwCaps::HWCAP_VFPv4, "vfpv4"),
(md::ArmElfHwCaps::HWCAP_IDIVA, "idiva"),
(md::ArmElfHwCaps::HWCAP_IDIVT, "idivt"),
];
let mut cpu_info = format!("ARMv{}", raw.processor_level);
let cpuid = arm_info.cpuid;
if cpuid != 0 {
let vendor_id = (cpuid >> 24) & 0xff;
let part_id = cpuid & 0xff00fff0;
if let Some(&(_, vendor)) = vendors.iter().find(|&&(id, _)| id == vendor_id) {
write!(&mut cpu_info, " {}", vendor).unwrap();
} else {
write!(&mut cpu_info, " vendor(0x{:x})", vendor_id).unwrap();
}
if let Some(&(_, part)) = parts.iter().find(|&&(id, _)| id == part_id) {
write!(&mut cpu_info, " {}", part).unwrap();
} else {
write!(&mut cpu_info, " part(0x{:x})", part_id).unwrap();
}
}
let elf_hwcaps = md::ArmElfHwCaps::from_bits_truncate(arm_info.elf_hwcaps);
if !elf_hwcaps.is_empty() {
cpu_info.push_str(" features: ");
let mut comma = "";
for &(_, feature) in features
.iter()
.filter(|&&(feature, _)| elf_hwcaps.contains(feature))
{
cpu_info.push_str(comma);
cpu_info.push_str(feature);
comma = ",";
}
}
Some(cpu_info)
}
_ => None,
};
Ok(MinidumpSystemInfo {
raw,
os,
cpu,
csd_version,
cpu_info,
})
}
}
impl MinidumpSystemInfo {
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_SYSTEM_INFO
processor_architecture = {:#x}
processor_level = {}
processor_revision = {:#x}
number_of_processors = {}
product_type = {}
major_version = {}
minor_version = {}
build_number = {}
platform_id = {:#x}
csd_version_rva = {:#x}
suite_mask = {:#x}
(version) = {}{}{} {}
(cpu_info) = {}
",
self.raw.processor_architecture,
self.raw.processor_level,
self.raw.processor_revision,
self.raw.number_of_processors,
self.raw.product_type,
self.raw.major_version,
self.raw.minor_version,
self.raw.build_number,
self.raw.platform_id,
self.raw.csd_version_rva,
self.raw.suite_mask,
self.raw.major_version,
self.raw.minor_version,
self.raw.build_number,
self.csd_version().as_deref().unwrap_or(""),
self.cpu_info().as_deref().unwrap_or(""),
)?;
Ok(())
}
pub fn csd_version(&self) -> Option<Cow<str>> {
self.csd_version.as_deref().map(Cow::Borrowed)
}
pub fn cpu_info(&self) -> Option<Cow<str>> {
self.cpu_info.as_deref().map(Cow::Borrowed)
}
}
macro_rules! misc_accessors {
() => {};
(@defnoflag $name:ident $t:ty [$($variant:ident)+]) => {
#[allow(unreachable_patterns)]
pub fn $name(&self) -> Option<&$t> {
match self {
$(
RawMiscInfo::$variant(ref raw) => Some(&raw.$name),
)+
_ => None,
}
}
};
(@def $name:ident $flag:ident $t:ty [$($variant:ident)+]) => {
#[allow(unreachable_patterns)]
pub fn $name(&self) -> Option<&$t> {
match self {
$(
RawMiscInfo::$variant(ref raw) => if md::MiscInfoFlags::from_bits_truncate(raw.flags1).contains(md::MiscInfoFlags::$flag) { Some(&raw.$name) } else { None },
)+
_ => None,
}
}
};
(1: $name:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@defnoflag $name $t [MiscInfo MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(1: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@def $name $flag $t [MiscInfo MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(2: $name:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@defnoflag $name $t [MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(2: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@def $name $flag $t [MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(3: $name:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@defnoflag $name $t [MiscInfo3 MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(3: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@def $name $flag $t [MiscInfo3 MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(4: $name:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@defnoflag $name $t [MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(4: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@def $name $flag $t [MiscInfo4 MiscInfo5]);
misc_accessors!($($rest)*);
};
(5: $name:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@defnoflag $name $t [MiscInfo5]);
misc_accessors!($($rest)*);
};
(5: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
misc_accessors!(@def $name $flag $t [MiscInfo5]);
misc_accessors!($($rest)*);
};
}
impl RawMiscInfo {
misc_accessors!(
1: size_of_info -> u32,
1: flags1 -> u32,
1: process_id if MINIDUMP_MISC1_PROCESS_ID -> u32,
1: process_create_time if MINIDUMP_MISC1_PROCESS_TIMES -> u32,
1: process_user_time if MINIDUMP_MISC1_PROCESS_TIMES -> u32,
1: process_kernel_time if MINIDUMP_MISC1_PROCESS_TIMES -> u32,
2: processor_max_mhz if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
2: processor_current_mhz if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
2: processor_mhz_limit if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
2: processor_max_idle_state if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
2: processor_current_idle_state if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
3: process_integrity_level if MINIDUMP_MISC3_PROCESS_INTEGRITY -> u32,
3: process_execute_flags if MINIDUMP_MISC3_PROCESS_EXECUTE_FLAGS -> u32,
3: protected_process if MINIDUMP_MISC3_PROTECTED_PROCESS -> u32,
3: time_zone_id if MINIDUMP_MISC3_TIMEZONE -> u32,
3: time_zone if MINIDUMP_MISC3_TIMEZONE -> md::TIME_ZONE_INFORMATION,
4: build_string if MINIDUMP_MISC4_BUILDSTRING -> [u16; 260],
4: dbg_bld_str if MINIDUMP_MISC4_BUILDSTRING -> [u16; 40],
5: xstate_data -> md::XSTATE_CONFIG_FEATURE_MSC_INFO,
5: process_cookie if MINIDUMP_MISC5_PROCESS_COOKIE -> u32,
);
}
impl<'a> MinidumpStream<'a> for MinidumpMiscInfo {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::MiscInfoStream;
fn read(bytes: &[u8], _all: &[u8], endian: scroll::Endian) -> Result<MinidumpMiscInfo, Error> {
macro_rules! do_read {
($(($t:ty, $variant:ident),)+) => {
$(
if bytes.len() >= <$t>::size_with(&endian) {
return Ok(MinidumpMiscInfo {
raw: RawMiscInfo::$variant(bytes.pread_with(0, endian).or(Err(Error::StreamReadFailure))?),
});
}
)+
}
}
do_read!(
(md::MINIDUMP_MISC_INFO_5, MiscInfo5),
(md::MINIDUMP_MISC_INFO_4, MiscInfo4),
(md::MINIDUMP_MISC_INFO_3, MiscInfo3),
(md::MINIDUMP_MISC_INFO_2, MiscInfo2),
(md::MINIDUMP_MISC_INFO, MiscInfo),
);
Err(Error::StreamReadFailure)
}
}
impl MinidumpMiscInfo {
pub fn process_create_time(&self) -> Option<DateTime<Utc>> {
self.raw
.process_create_time()
.map(|t| Utc.timestamp(*t as i64, 0))
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
macro_rules! write_simple_field {
($stream:ident, $field:ident, $format:literal) => {
write!(f, " {:29}= ", stringify!($field))?;
match self.raw.$field() {
Some($field) => {
writeln!(f, $format, $field)?;
}
None => writeln!(f, "(invalid)")?,
}
};
($stream:ident, $field:ident) => {
write_simple_field!($stream, $field, "{}");
};
}
writeln!(f, "MINIDUMP_MISC_INFO")?;
write_simple_field!(f, size_of_info);
write_simple_field!(f, flags1, "{:x}");
write_simple_field!(f, process_id);
write!(f, " process_create_time = ")?;
match self.raw.process_create_time() {
Some(&process_create_time) => {
writeln!(
f,
"{:#x} {}",
process_create_time,
format_time_t(process_create_time),
)?;
}
None => writeln!(f, "(invalid)")?,
}
write_simple_field!(f, process_user_time);
write_simple_field!(f, process_kernel_time);
write_simple_field!(f, processor_max_mhz);
write_simple_field!(f, processor_current_mhz);
write_simple_field!(f, processor_mhz_limit);
write_simple_field!(f, processor_max_idle_state);
write_simple_field!(f, processor_current_idle_state);
write_simple_field!(f, process_integrity_level);
write_simple_field!(f, process_execute_flags, "{:x}");
write_simple_field!(f, protected_process);
write_simple_field!(f, time_zone_id);
write!(f, " time_zone = ")?;
match self.raw.time_zone() {
Some(time_zone) => {
writeln!(f)?;
writeln!(f, " bias = {}", time_zone.bias)?;
writeln!(
f,
" standard_name = {}",
utf16_to_string(&time_zone.standard_name[..])
.unwrap_or_else(|| String::from("(invalid)"))
)?;
writeln!(
f,
" standard_date = {}",
format_system_time(&time_zone.standard_date)
)?;
writeln!(f, " standard_bias = {}", time_zone.standard_bias)?;
writeln!(
f,
" daylight_name = {}",
utf16_to_string(&time_zone.daylight_name[..])
.unwrap_or_else(|| String::from("(invalid)"))
)?;
writeln!(
f,
" daylight_date = {}",
format_system_time(&time_zone.daylight_date)
)?;
writeln!(f, " daylight_bias = {}", time_zone.daylight_bias)?;
}
None => writeln!(f, "(invalid)")?,
}
write!(f, " build_string = ")?;
match self
.raw
.build_string()
.and_then(|string| utf16_to_string(&string[..]))
{
Some(build_string) => writeln!(f, "{}", build_string)?,
None => writeln!(f, "(invalid)")?,
}
write!(f, " dbg_bld_str = ")?;
match self
.raw
.dbg_bld_str()
.and_then(|string| utf16_to_string(&string[..]))
{
Some(dbg_bld_str) => writeln!(f, "{}", dbg_bld_str)?,
None => writeln!(f, "(invalid)")?,
}
write!(f, " xstate_data = ")?;
match self.raw.xstate_data() {
Some(xstate_data) => {
writeln!(f)?;
for (i, feature) in xstate_data.iter() {
if let Some(feature) = md::XstateFeatureIndex::from_index(i) {
write!(f, " feature {:2} - {:22}: ", i, format!("{:?}", feature))?;
} else {
write!(f, " feature {:2} - (unknown) : ", i)?;
}
writeln!(f, " offset {:4}, size {:4}", feature.offset, feature.size)?;
}
}
None => writeln!(f, "(invalid)")?,
}
write_simple_field!(f, process_cookie);
writeln!(f)?;
Ok(())
}
}
impl<'a> MinidumpStream<'a> for MinidumpBreakpadInfo {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::BreakpadInfoStream;
fn read(
bytes: &[u8],
_all: &[u8],
endian: scroll::Endian,
) -> Result<MinidumpBreakpadInfo, Error> {
let raw: md::MINIDUMP_BREAKPAD_INFO = bytes
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
let flags = md::BreakpadInfoValid::from_bits_truncate(raw.validity);
let dump_thread_id = if flags.contains(md::BreakpadInfoValid::DumpThreadId) {
Some(raw.dump_thread_id)
} else {
None
};
let requesting_thread_id = if flags.contains(md::BreakpadInfoValid::RequestingThreadId) {
Some(raw.requesting_thread_id)
} else {
None
};
Ok(MinidumpBreakpadInfo {
raw,
dump_thread_id,
requesting_thread_id,
})
}
}
fn option_or_invalid<T: fmt::LowerHex>(what: &Option<T>) -> Cow<'_, str> {
match *what {
Some(ref val) => Cow::Owned(format!("{:#x}", val)),
None => Cow::Borrowed("(invalid)"),
}
}
impl MinidumpBreakpadInfo {
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_BREAKPAD_INFO
validity = {:#x}
dump_thread_id = {}
requesting_thread_id = {}
",
self.raw.validity,
option_or_invalid(&self.dump_thread_id),
option_or_invalid(&self.requesting_thread_id),
)?;
Ok(())
}
}
impl CrashReason {
fn from_exception(raw: &md::MINIDUMP_EXCEPTION_STREAM, os: Os, cpu: Cpu) -> CrashReason {
use md::{ExceptionCodeLinux, ExceptionCodeMac, ExceptionCodeWindows};
let record = &raw.exception_record;
let info = &record.exception_information;
let exception_code = record.exception_code;
let exception_flags = record.exception_flags;
let mut reason = CrashReason::Unknown(exception_code, exception_flags);
match os {
Os::MacOs | Os::Ios => {
let mac_reason = md::ExceptionCodeMac::from_u32(exception_code);
if let Some(mac_reason) = mac_reason {
reason = CrashReason::MacGeneral(mac_reason, exception_flags);
}
match mac_reason {
Some(ExceptionCodeMac::EXC_BAD_ACCESS) => {
if let Some(ty) =
md::ExceptionCodeMacBadAccessKernType::from_u32(exception_flags)
{
reason = CrashReason::MacBadAccessKern(ty);
} else {
match cpu {
Cpu::Arm64 => {
if let Some(ty) = md::ExceptionCodeMacBadAccessArmType::from_u32(
exception_flags,
) {
reason = CrashReason::MacBadAccessArm(ty);
}
}
Cpu::Ppc => {
if let Some(ty) = md::ExceptionCodeMacBadAccessPpcType::from_u32(
exception_flags,
) {
reason = CrashReason::MacBadAccessPpc(ty);
}
}
Cpu::X86 | Cpu::X86_64 => {
if let Some(ty) = md::ExceptionCodeMacBadAccessX86Type::from_u32(
exception_flags,
) {
reason = CrashReason::MacBadAccessX86(ty);
}
}
_ => {
}
}
}
}
Some(ExceptionCodeMac::EXC_BAD_INSTRUCTION) => match cpu {
Cpu::Arm64 => {
if let Some(ty) =
md::ExceptionCodeMacBadInstructionArmType::from_u32(exception_flags)
{
reason = CrashReason::MacBadInstructionArm(ty);
}
}
Cpu::Ppc => {
if let Some(ty) =
md::ExceptionCodeMacBadInstructionPpcType::from_u32(exception_flags)
{
reason = CrashReason::MacBadInstructionPpc(ty);
}
}
Cpu::X86 | Cpu::X86_64 => {
if let Some(ty) =
md::ExceptionCodeMacBadInstructionX86Type::from_u32(exception_flags)
{
reason = CrashReason::MacBadInstructionX86(ty);
}
}
_ => {
}
},
Some(ExceptionCodeMac::EXC_ARITHMETIC) => match cpu {
Cpu::Ppc => {
if let Some(ty) =
md::ExceptionCodeMacArithmeticPpcType::from_u32(exception_flags)
{
reason = CrashReason::MacArithmeticPpc(ty);
}
}
Cpu::X86 | Cpu::X86_64 => {
if let Some(ty) =
md::ExceptionCodeMacArithmeticX86Type::from_u32(exception_flags)
{
reason = CrashReason::MacArithmeticX86(ty);
}
}
_ => {
}
},
Some(ExceptionCodeMac::EXC_SOFTWARE) => {
if let Some(ty) =
md::ExceptionCodeMacSoftwareType::from_u32(exception_flags)
{
reason = CrashReason::MacSoftware(ty);
}
}
Some(ExceptionCodeMac::EXC_BREAKPOINT) => match cpu {
Cpu::Arm64 => {
if let Some(ty) =
md::ExceptionCodeMacBreakpointArmType::from_u32(exception_flags)
{
reason = CrashReason::MacBreakpointArm(ty);
}
}
Cpu::Ppc => {
if let Some(ty) =
md::ExceptionCodeMacBreakpointPpcType::from_u32(exception_flags)
{
reason = CrashReason::MacBreakpointPpc(ty);
}
}
Cpu::X86 | Cpu::X86_64 => {
if let Some(ty) =
md::ExceptionCodeMacBreakpointX86Type::from_u32(exception_flags)
{
reason = CrashReason::MacBreakpointX86(ty);
}
}
_ => {
}
},
_ => {
}
}
}
Os::Linux | Os::Android => {
let linux_reason = ExceptionCodeLinux::from_u32(exception_code);
if let Some(linux_reason) = linux_reason {
reason = CrashReason::LinuxGeneral(linux_reason, exception_flags);
}
match linux_reason {
Some(ExceptionCodeLinux::SIGILL) => {
if let Some(ty) =
md::ExceptionCodeLinuxSigillKind::from_u32(exception_flags)
{
reason = CrashReason::LinuxSigill(ty);
}
}
Some(ExceptionCodeLinux::SIGFPE) => {
if let Some(ty) =
md::ExceptionCodeLinuxSigfpeKind::from_u32(exception_flags)
{
reason = CrashReason::LinuxSigfpe(ty);
}
}
Some(ExceptionCodeLinux::SIGSEGV) => {
if let Some(ty) =
md::ExceptionCodeLinuxSigsegvKind::from_u32(exception_flags)
{
reason = CrashReason::LinuxSigsegv(ty);
}
}
Some(ExceptionCodeLinux::SIGBUS) => {
if let Some(ty) =
md::ExceptionCodeLinuxSigbusKind::from_u32(exception_flags)
{
reason = CrashReason::LinuxSigbus(ty);
}
}
_ => {
}
}
}
Os::Windows => {
let win_reason = ExceptionCodeWindows::from_u32(exception_code);
if let Some(win_reason) = win_reason {
reason = CrashReason::WindowsGeneral(win_reason);
}
match win_reason {
Some(ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION) => {
if record.number_parameters >= 1 {
if let Some(ty) = md::ExceptionCodeWindowsAccessType::from_u64(info[0])
{
reason = CrashReason::WindowsAccessViolation(ty);
}
}
}
Some(ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR) => {
if record.number_parameters >= 3 {
let nt_status = info[2];
if let Some(ty) =
md::ExceptionCodeWindowsInPageErrorType::from_u64(info[0])
{
reason = CrashReason::WindowsInPageError(ty, nt_status);
}
}
}
Some(ExceptionCodeWindows::STATUS_STACK_BUFFER_OVERRUN) => {
if record.number_parameters >= 1 {
let fast_fail = info[0];
reason = CrashReason::WindowsStackBufferOverrun(fast_fail);
}
}
None => {
reason = CrashReason::WindowsUnknown(exception_code);
}
_ => {
}
}
}
_ => {
}
}
reason
}
}
impl fmt::Display for CrashReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CrashReason::*;
fn write_nt_status(_f: &mut fmt::Formatter<'_>, _nt_status: u64) -> fmt::Result {
Ok(())
}
fn write_fast_fail(_f: &mut fmt::Formatter<'_>, _fast_fail: u64) -> fmt::Result {
Ok(())
}
match *self {
MacGeneral(md::ExceptionCodeMac::SIMULATED, _) => write!(f, "Simulated Exception"),
MacGeneral(ex, flags) => write!(f, "{:?} / 0x{:08x}", ex, flags),
MacBadAccessKern(ex) => write!(f, "EXC_BAD_ACCESS / {:?}", ex),
MacBadAccessArm(ex) => write!(f, "EXC_BAD_ACCESS / {:?}", ex),
MacBadAccessPpc(ex) => write!(f, "EXC_BAD_ACCESS / {:?}", ex),
MacBadAccessX86(ex) => write!(f, "EXC_BAD_ACCESS / {:?}", ex),
MacBadInstructionArm(ex) => write!(f, "EXC_BAD_INSTRUCTION / {:?}", ex),
MacBadInstructionPpc(ex) => write!(f, "EXC_BAD_INSTRUCTION / {:?}", ex),
MacBadInstructionX86(ex) => write!(f, "EXC_BAD_INSTRUCTION / {:?}", ex),
MacArithmeticPpc(ex) => write!(f, "EXC_ARITHMETIC / {:?}", ex),
MacArithmeticX86(ex) => write!(f, "EXC_ARITHMETIC / {:?}", ex),
MacSoftware(ex) => write!(f, "EXC_SOFTWARE / {:?}", ex),
MacBreakpointArm(ex) => write!(f, "EXC_BREAKPOINT / {:?}", ex),
MacBreakpointPpc(ex) => write!(f, "EXC_BREAKPOINT / {:?}", ex),
MacBreakpointX86(ex) => write!(f, "EXC_BREAKPOINT / {:?}", ex),
LinuxGeneral(ex, flags) => write!(f, "{:?} / 0x{:08x}", ex, flags),
LinuxSigill(ex) => write!(f, "SIGILL / {:?}", ex),
LinuxSigbus(ex) => write!(f, "SIGBUS / {:?}", ex),
LinuxSigfpe(ex) => write!(f, "SIGFPE / {:?}", ex),
LinuxSigsegv(ex) => write!(f, "SIGSEGV / {:?}", ex),
WindowsGeneral(md::ExceptionCodeWindows::OUT_OF_MEMORY) => write!(f, "Out of Memory"),
WindowsGeneral(md::ExceptionCodeWindows::UNHANDLED_CPP_EXCEPTION) => {
write!(f, "Unhandled C++ Exception")
}
WindowsGeneral(md::ExceptionCodeWindows::SIMULATED) => write!(f, "Simulated Exception"),
WindowsGeneral(ex) => write!(f, "{:?}", ex),
WindowsAccessViolation(ex) => write!(f, "EXCEPTION_ACCESS_VIOLATION_{:?}", ex),
WindowsInPageError(ex, nt_status) => {
write!(f, "EXCEPTION_IN_PAGE_ERROR_{:?} / ", ex)?;
write_nt_status(f, nt_status)
}
WindowsStackBufferOverrun(fast_fail) => {
write!(f, "EXCEPTION_STACK_BUFFER_OVERRUN / ")?;
write_fast_fail(f, fast_fail)
}
WindowsUnknown(nt_status) => write_nt_status(f, nt_status as u64),
Unknown(code, flags) => write!(f, "unknown 0x{:08} / 0x{:08}", code, flags),
}
}
}
impl<'a> MinidumpStream<'a> for MinidumpException {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::ExceptionStream;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpException, Error> {
let raw: md::MINIDUMP_EXCEPTION_STREAM = bytes
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
let context_data = location_slice(all, &raw.thread_context)?;
let context = MinidumpContext::read(context_data, endian).ok();
let thread_id = raw.thread_id;
Ok(MinidumpException {
raw,
thread_id,
context,
})
}
}
impl MinidumpException {
pub fn get_crash_address(&self, os: Os) -> u64 {
match (
os,
md::ExceptionCodeWindows::from_u32(self.raw.exception_record.exception_code),
) {
(Os::Windows, Some(md::ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION))
| (Os::Windows, Some(md::ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR))
if self.raw.exception_record.number_parameters >= 2 =>
{
self.raw.exception_record.exception_information[1]
}
_ => self.raw.exception_record.exception_address,
}
}
pub fn get_crash_reason(&self, os: Os, cpu: Cpu) -> CrashReason {
CrashReason::from_exception(&self.raw, os, cpu)
}
pub fn get_crashing_thread_id(&self) -> u32 {
self.thread_id
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_EXCEPTION
thread_id = {:#x}
exception_record.exception_code = {:#x}
exception_record.exception_flags = {:#x}
exception_record.exception_record = {:#x}
exception_record.exception_address = {:#x}
exception_record.number_parameters = {}
",
self.thread_id,
self.raw.exception_record.exception_code,
self.raw.exception_record.exception_flags,
self.raw.exception_record.exception_record,
self.raw.exception_record.exception_address,
self.raw.exception_record.number_parameters,
)?;
for i in 0..self.raw.exception_record.number_parameters as usize {
writeln!(
f,
" exception_record.exception_information[{:2}] = {:#x}",
i, self.raw.exception_record.exception_information[i]
)?;
}
write!(
f,
" thread_context.data_size = {}
thread_context.rva = {:#x}
",
self.raw.thread_context.data_size, self.raw.thread_context.rva
)?;
if let Some(ref context) = self.context {
writeln!(f)?;
context.print(f)?;
} else {
write!(
f,
" (no context)
"
)?;
}
Ok(())
}
}
impl<'a> MinidumpStream<'a> for MinidumpAssertion {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::AssertionInfoStream;
fn read(
bytes: &'a [u8],
_all: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpAssertion, Error> {
let raw: md::MINIDUMP_ASSERTION_INFO = bytes
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
Ok(MinidumpAssertion { raw })
}
}
fn utf16_to_string(data: &[u16]) -> Option<String> {
use std::slice;
let len = data.iter().take_while(|c| **c != 0).count();
let s16 = &data[..len];
let bytes = unsafe { slice::from_raw_parts(s16.as_ptr() as *const u8, s16.len() * 2) };
UTF_16LE.decode(bytes, DecoderTrap::Strict).ok()
}
impl MinidumpAssertion {
pub fn expression(&self) -> Option<String> {
utf16_to_string(&self.raw.expression)
}
pub fn function(&self) -> Option<String> {
utf16_to_string(&self.raw.function)
}
pub fn file(&self) -> Option<String> {
utf16_to_string(&self.raw.file)
}
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MDAssertion
expression = {}
function = {}
file = {}
line = {}
type = {}
",
self.expression().unwrap_or_else(String::new),
self.function().unwrap_or_else(String::new),
self.file().unwrap_or_else(String::new),
self.raw.line,
self.raw._type,
)?;
Ok(())
}
}
fn read_string_list(
all: &[u8],
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
endian: scroll::Endian,
) -> Result<Vec<String>, Error> {
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
if data.is_empty() {
return Ok(Vec::new());
}
let mut offset = 0;
let count: u32 = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
let mut strings = Vec::with_capacity(count as usize);
for _ in 0..count {
let rva: md::RVA = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
let string = read_string_utf8(&mut (rva as usize), all, endian)
.or(Err(Error::StreamReadFailure))?
.to_owned();
strings.push(string);
}
Ok(strings)
}
fn read_simple_string_dictionary(
all: &[u8],
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
endian: scroll::Endian,
) -> Result<BTreeMap<String, String>, Error> {
let mut dictionary = BTreeMap::new();
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
if data.is_empty() {
return Ok(dictionary);
}
let mut offset = 0;
let count: u32 = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
for _ in 0..count {
let entry: md::MINIDUMP_SIMPLE_STRING_DICTIONARY_ENTRY = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
let key = read_string_utf8(&mut (entry.key as usize), all, endian)
.or(Err(Error::StreamReadFailure))?;
let value = read_string_utf8(&mut (entry.value as usize), all, endian)
.or(Err(Error::StreamReadFailure))?;
dictionary.insert(key.to_owned(), value.to_owned());
}
Ok(dictionary)
}
fn read_annotation_objects(
all: &[u8],
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
endian: scroll::Endian,
) -> Result<BTreeMap<String, MinidumpAnnotation>, Error> {
let mut dictionary = BTreeMap::new();
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
if data.is_empty() {
return Ok(dictionary);
}
let mut offset = 0;
let count: u32 = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
for _ in 0..count {
let raw: md::MINIDUMP_ANNOTATION = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
let key = read_string_utf8(&mut (raw.name as usize), all, endian)
.or(Err(Error::StreamReadFailure))?;
let value = match raw.ty {
md::MINIDUMP_ANNOTATION::TYPE_INVALID => MinidumpAnnotation::Invalid,
md::MINIDUMP_ANNOTATION::TYPE_STRING => {
let string = read_string_utf8_unterminated(&mut (raw.value as usize), all, endian)
.or(Err(Error::StreamReadFailure))?
.to_owned();
MinidumpAnnotation::String(string)
}
_ if raw.ty >= md::MINIDUMP_ANNOTATION::TYPE_USER_DEFINED => {
MinidumpAnnotation::UserDefined(raw)
}
_ => MinidumpAnnotation::Unsupported(raw),
};
dictionary.insert(key.to_owned(), value);
}
Ok(dictionary)
}
impl MinidumpModuleCrashpadInfo {
pub fn read(
link: md::MINIDUMP_MODULE_CRASHPAD_INFO_LINK,
all: &[u8],
endian: scroll::Endian,
) -> Result<Self, Error> {
let raw: md::MINIDUMP_MODULE_CRASHPAD_INFO = all
.pread_with(link.location.rva as usize, endian)
.or(Err(Error::StreamReadFailure))?;
let list_annotations = read_string_list(all, &raw.list_annotations, endian)?;
let simple_annotations =
read_simple_string_dictionary(all, &raw.simple_annotations, endian)?;
let annotation_objects = read_annotation_objects(all, &raw.annotation_objects, endian)?;
Ok(Self {
raw,
module_index: link.minidump_module_list_index as usize,
list_annotations,
simple_annotations,
annotation_objects,
})
}
}
fn read_crashpad_module_links(
all: &[u8],
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
endian: scroll::Endian,
) -> Result<Vec<MinidumpModuleCrashpadInfo>, Error> {
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
if data.is_empty() {
return Ok(Vec::new());
}
let mut offset = 0;
let count: u32 = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
let mut module_links = Vec::with_capacity(count as usize);
for _ in 0..count {
let link: md::MINIDUMP_MODULE_CRASHPAD_INFO_LINK = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
let info = MinidumpModuleCrashpadInfo::read(link, all, endian)?;
module_links.push(info);
}
Ok(module_links)
}
impl<'a> MinidumpStream<'a> for MinidumpCrashpadInfo {
const STREAM_TYPE: MINIDUMP_STREAM_TYPE = MINIDUMP_STREAM_TYPE::CrashpadInfoStream;
fn read(bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian) -> Result<Self, Error> {
let raw: md::MINIDUMP_CRASHPAD_INFO = bytes
.pread_with(0, endian)
.or(Err(Error::StreamReadFailure))?;
if raw.version == 0 {
return Err(Error::VersionMismatch);
}
let simple_annotations =
read_simple_string_dictionary(all, &raw.simple_annotations, endian)?;
let module_list = read_crashpad_module_links(all, &raw.module_list, endian)?;
Ok(Self {
raw,
simple_annotations,
module_list,
})
}
}
impl MinidumpCrashpadInfo {
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MDRawCrashpadInfo
version = {}
report_id = {}
client_id = {}
",
self.raw.version, self.raw.report_id, self.raw.client_id,
)?;
for (name, value) in &self.simple_annotations {
writeln!(f, " simple_annotations[\"{}\"] = {}", name, value)?;
}
for (index, module) in self.module_list.iter().enumerate() {
writeln!(
f,
" module_list[{}].minidump_module_list_index = {}",
index, module.module_index,
)?;
writeln!(
f,
" module_list[{}].version = {}",
index, module.raw.version,
)?;
for (annotation_index, annotation) in module.list_annotations.iter().enumerate() {
writeln!(
f,
" module_list[{}].list_annotations[{}] = {}",
index, annotation_index, annotation,
)?;
}
for (name, value) in &module.simple_annotations {
writeln!(
f,
" module_list[{}].simple_annotations[\"{}\"] = {}",
index, name, value,
)?;
}
for (name, value) in &module.annotation_objects {
write!(
f,
" module_list[{}].annotation_objects[\"{}\"] = ",
index, name,
)?;
match value {
MinidumpAnnotation::Invalid => writeln!(f, "<invalid>"),
MinidumpAnnotation::String(string) => writeln!(f, "{}", string),
MinidumpAnnotation::UserDefined(_) => writeln!(f, "<user defined>"),
MinidumpAnnotation::Unsupported(_) => writeln!(f, "<unsupported>"),
}?;
}
}
writeln!(f)?;
Ok(())
}
}
impl<'a> Minidump<'a, Mmap> {
pub fn read_path<P>(path: P) -> Result<Minidump<'a, Mmap>, Error>
where
P: AsRef<Path>,
{
let f = File::open(path).or(Err(Error::FileNotFound))?;
let mmap = unsafe { Mmap::map(&f).or(Err(Error::IoError))? };
Minidump::read(mmap)
}
}
impl<'a, T> Minidump<'a, T>
where
T: Deref<Target = [u8]> + 'a,
{
pub fn read(data: T) -> Result<Minidump<'a, T>, Error> {
let mut offset = 0;
let mut endian = LE;
let mut header: md::MINIDUMP_HEADER = data
.gread_with(&mut offset, endian)
.or(Err(Error::MissingHeader))?;
if header.signature != md::MINIDUMP_SIGNATURE {
if header.signature.swap_bytes() != md::MINIDUMP_SIGNATURE {
return Err(Error::HeaderMismatch);
}
endian = BE;
offset = 0;
header = data
.gread_with(&mut offset, endian)
.or(Err(Error::MissingHeader))?;
if header.signature != md::MINIDUMP_SIGNATURE {
return Err(Error::HeaderMismatch);
}
}
if (header.version & 0x0000ffff) != md::MINIDUMP_VERSION {
return Err(Error::VersionMismatch);
}
let mut streams = HashMap::with_capacity(header.stream_count as usize);
offset = header.stream_directory_rva as usize;
for i in 0..header.stream_count {
let dir: md::MINIDUMP_DIRECTORY = data
.gread_with(&mut offset, endian)
.or(Err(Error::MissingDirectory))?;
streams.insert(dir.stream_type, (i, dir));
}
Ok(Minidump {
data,
header,
streams,
endian,
_phantom: PhantomData,
})
}
pub fn get_stream<S>(&'a self) -> Result<S, Error>
where
S: MinidumpStream<'a>,
{
match self.get_raw_stream(S::STREAM_TYPE) {
Err(e) => Err(e),
Ok(bytes) => {
let all_bytes = self.data.deref();
S::read(bytes, all_bytes, self.endian)
}
}
}
pub fn get_raw_stream<S>(&'a self, stream_type: S) -> Result<&'a [u8], Error>
where
S: Into<u32>,
{
match self.streams.get(&stream_type.into()) {
None => Err(Error::StreamNotFound),
Some(&(_, ref dir)) => {
let bytes = self.data.deref();
location_slice(bytes, &dir.location)
}
}
}
pub fn print<W: Write>(&self, f: &mut W) -> io::Result<()> {
fn get_stream_name(stream_type: u32) -> Cow<'static, str> {
if let Some(stream) = MINIDUMP_STREAM_TYPE::from_u32(stream_type) {
Cow::Owned(format!("{:?}", stream))
} else {
Cow::Borrowed("unknown")
}
}
write!(
f,
r#"MDRawHeader
signature = {:#x}
version = {:#x}
stream_count = {}
stream_directory_rva = {:#x}
checksum = {:#x}
time_date_stamp = {:#x} {}
flags = {:#x}
"#,
self.header.signature,
self.header.version,
self.header.stream_count,
self.header.stream_directory_rva,
self.header.checksum,
self.header.time_date_stamp,
format_time_t(self.header.time_date_stamp),
self.header.flags,
)?;
let mut streams = self.streams.iter().collect::<Vec<_>>();
streams.sort_by(|&(&_, &(a, _)), &(&_, &(b, _))| a.cmp(&b));
for &(_, &(i, ref stream)) in streams.iter() {
write!(
f,
r#"mDirectory[{}]
MDRawDirectory
stream_type = {:#x} ({})
location.data_size = {}
location.rva = {:#x}
"#,
i,
stream.stream_type,
get_stream_name(stream.stream_type),
stream.location.data_size,
stream.location.rva
)?;
}
writeln!(f, "Streams:")?;
streams.sort_by(|&(&a, &(_, _)), &(&b, &(_, _))| a.cmp(&b));
for (_, &(i, ref stream)) in streams {
writeln!(
f,
" stream type {:#x} ({}) at index {}",
stream.stream_type,
get_stream_name(stream.stream_type),
i
)?;
}
writeln!(f)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::synth_minidump::{
self, AnnotationValue, CrashpadInfo, DumpString, Memory, MiscFieldsBuildString,
MiscFieldsPowerInfo, MiscFieldsProcessTimes, MiscFieldsTimeZone, MiscInfo5Fields,
MiscStream, Module as SynthModule, ModuleCrashpadInfo, SimpleStream, SynthMinidump, Thread,
UnloadedModule as SynthUnloadedModule, STOCK_VERSION_INFO,
};
use md::GUID;
use std::mem;
use test_assembler::*;
fn read_synth_dump<'a>(dump: SynthMinidump) -> Result<Minidump<'a, Vec<u8>>, Error> {
Minidump::read(dump.finish().unwrap())
}
#[test]
fn test_simple_synth_dump() {
const STREAM_TYPE: u32 = 0x11223344;
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(SimpleStream {
stream_type: STREAM_TYPE,
section: Section::with_endian(Endian::Little).D32(0x55667788),
});
let dump = read_synth_dump(dump).unwrap();
assert_eq!(dump.endian, LE);
assert_eq!(
dump.get_raw_stream(STREAM_TYPE).unwrap(),
&[0x88, 0x77, 0x66, 0x55]
);
assert_eq!(
dump.get_raw_stream(0xaabbccddu32),
Err(Error::StreamNotFound)
);
}
#[test]
fn test_simple_synth_dump_bigendian() {
const STREAM_TYPE: u32 = 0x11223344;
let dump = SynthMinidump::with_endian(Endian::Big).add_stream(SimpleStream {
stream_type: STREAM_TYPE,
section: Section::with_endian(Endian::Big).D32(0x55667788),
});
let dump = read_synth_dump(dump).unwrap();
assert_eq!(dump.endian, BE);
assert_eq!(
dump.get_raw_stream(STREAM_TYPE).unwrap(),
&[0x55, 0x66, 0x77, 0x88]
);
assert_eq!(
dump.get_raw_stream(0xaabbccddu32),
Err(Error::StreamNotFound)
);
}
#[test]
fn test_module_list() {
let name = DumpString::new("single module", Endian::Little);
let cv_record = Section::with_endian(Endian::Little)
.D32(md::CvSignature::Pdb70 as u32) .D32(0xabcd1234)
.D16(0xf00d)
.D16(0xbeef)
.append_bytes(b"\x01\x02\x03\x04\x05\x06\x07\x08")
.D32(1) .append_bytes(b"c:\\foo\\file.pdb\0"); let module = SynthModule::new(
Endian::Little,
0xa90206ca83eb2852,
0xada542bd,
&name,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_module(module)
.add(name)
.add(cv_record);
let dump = read_synth_dump(dump).unwrap();
let module_list = dump.get_stream::<MinidumpModuleList>().unwrap();
let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 1);
assert_eq!(modules[0].base_address(), 0xa90206ca83eb2852);
assert_eq!(modules[0].size(), 0xada542bd);
assert_eq!(modules[0].code_file(), "single module");
assert_eq!(modules[0].code_identifier(), "B1054D2Aada542bd");
assert_eq!(modules[0].debug_file().unwrap(), "c:\\foo\\file.pdb");
assert_eq!(
modules[0].debug_identifier().unwrap(),
"ABCD1234F00DBEEF01020304050607081"
);
}
#[test]
fn test_unloaded_module_list() {
let name = DumpString::new("single module", Endian::Little);
let module = SynthUnloadedModule::new(
Endian::Little,
0xa90206ca83eb2852,
0xada542bd,
&name,
0xb1054d2a,
0x34571371,
);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_unloaded_module(module)
.add(name);
let dump = read_synth_dump(dump).unwrap();
let module_list = dump.get_stream::<MinidumpUnloadedModuleList>().unwrap();
let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 1);
assert_eq!(modules[0].base_address(), 0xa90206ca83eb2852);
assert_eq!(modules[0].size(), 0xada542bd);
assert_eq!(modules[0].code_file(), "single module");
assert_eq!(modules[0].code_identifier(), "B1054D2Aada542bd");
}
#[test]
fn test_module_list_overlap() {
let name1 = DumpString::new("module 1", Endian::Little);
let name2 = DumpString::new("module 2", Endian::Little);
let name3 = DumpString::new("module 3", Endian::Little);
let name4 = DumpString::new("module 4", Endian::Little);
let name5 = DumpString::new("module 5", Endian::Little);
let cv_record = Section::with_endian(Endian::Little)
.D32(md::CvSignature::Pdb70 as u32) .D32(0xabcd1234)
.D16(0xf00d)
.D16(0xbeef)
.append_bytes(b"\x01\x02\x03\x04\x05\x06\x07\x08")
.D32(1) .append_bytes(b"c:\\foo\\file.pdb\0"); let module1 = SynthModule::new(
Endian::Little,
0x100000000,
0x4000,
&name1,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record);
let module2 = SynthModule::new(
Endian::Little,
0x100000000,
0x4000,
&name2,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record);
let module3 = SynthModule::new(
Endian::Little,
0x100000001,
0x4000,
&name3,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record);
let module4 = SynthModule::new(
Endian::Little,
0x100000001,
0x3000,
&name4,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record);
let module5 = SynthModule::new(
Endian::Little,
0x100004000,
0x4000,
&name5,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_module(module1)
.add_module(module2)
.add_module(module3)
.add_module(module4)
.add_module(module5)
.add(name1)
.add(name2)
.add(name3)
.add(name4)
.add(name5)
.add(cv_record);
let dump = read_synth_dump(dump).unwrap();
let module_list = dump.get_stream::<MinidumpModuleList>().unwrap();
let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 5);
assert_eq!(modules[0].base_address(), 0x100000000);
assert_eq!(modules[0].size(), 0x4000);
assert_eq!(modules[0].code_file(), "module 1");
assert_eq!(modules[1].base_address(), 0x100000000);
assert_eq!(modules[1].size(), 0x4000);
assert_eq!(modules[1].code_file(), "module 2");
assert_eq!(modules[2].base_address(), 0x100000001);
assert_eq!(modules[2].size(), 0x4000);
assert_eq!(modules[2].code_file(), "module 3");
assert_eq!(modules[3].base_address(), 0x100000001);
assert_eq!(modules[3].size(), 0x3000);
assert_eq!(modules[3].code_file(), "module 4");
assert_eq!(modules[4].base_address(), 0x100004000);
assert_eq!(modules[4].size(), 0x4000);
assert_eq!(modules[4].code_file(), "module 5");
assert_eq!(module_list.by_addr().count(), 2);
assert_eq!(
module_list
.module_at_address(0x100001000)
.unwrap()
.code_file(),
"module 1"
);
assert_eq!(
module_list
.module_at_address(0x100005000)
.unwrap()
.code_file(),
"module 5"
);
}
#[test]
fn test_memory_list() {
const CONTENTS: &[u8] = b"memory_contents";
let memory = Memory::with_section(
Section::with_endian(Endian::Little).append_bytes(CONTENTS),
0x309d68010bd21b2c,
);
let dump = SynthMinidump::with_endian(Endian::Little).add_memory(memory);
let dump = read_synth_dump(dump).unwrap();
let memory_list = dump.get_stream::<MinidumpMemoryList<'_>>().unwrap();
let regions = memory_list.iter().collect::<Vec<_>>();
assert_eq!(regions.len(), 1);
assert_eq!(regions[0].base_address, 0x309d68010bd21b2c);
assert_eq!(regions[0].size, CONTENTS.len() as u64);
assert_eq!(®ions[0].bytes, &CONTENTS);
}
#[test]
fn test_memory_list_lifetimes() {
const CONTENTS: &[u8] = b"memory_contents";
let memory = Memory::with_section(
Section::with_endian(Endian::Little).append_bytes(CONTENTS),
0x309d68010bd21b2c,
);
let dump = SynthMinidump::with_endian(Endian::Little).add_memory(memory);
let dump = read_synth_dump(dump).unwrap();
let mem_slices: Vec<&[u8]> = {
let mem_list: MinidumpMemoryList<'_> = dump.get_stream().unwrap();
mem_list.iter().map(|mem| mem.bytes).collect()
};
assert_eq!(mem_slices[0], CONTENTS);
}
#[test]
fn test_memory_list_overlap() {
let memory1 = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(0, 0x1000),
0x1000,
);
let memory2 = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(1, 0x1000),
0x1000,
);
let memory3 = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(2, 0x1000),
0x1001,
);
let memory4 = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(3, 0x100),
0x1001,
);
let memory5 = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(4, 0x1000),
0x2000,
);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_memory(memory1)
.add_memory(memory2)
.add_memory(memory3)
.add_memory(memory4)
.add_memory(memory5);
let dump = read_synth_dump(dump).unwrap();
let memory_list = dump.get_stream::<MinidumpMemoryList<'_>>().unwrap();
let regions = memory_list.iter().collect::<Vec<_>>();
assert_eq!(regions.len(), 5);
assert_eq!(regions[0].base_address, 0x1000);
assert_eq!(regions[0].size, 0x1000);
assert_eq!(regions[1].base_address, 0x1000);
assert_eq!(regions[1].size, 0x1000);
assert_eq!(regions[2].base_address, 0x1001);
assert_eq!(regions[2].size, 0x1000);
assert_eq!(regions[3].base_address, 0x1001);
assert_eq!(regions[3].size, 0x100);
assert_eq!(regions[4].base_address, 0x2000);
assert_eq!(regions[4].size, 0x1000);
assert_eq!(memory_list.by_addr().count(), 2);
let m1 = memory_list.memory_at_address(0x1a00).unwrap();
assert_eq!(m1.base_address, 0x1000);
assert_eq!(m1.size, 0x1000);
assert_eq!(m1.bytes, &[0u8; 0x1000][..]);
let m2 = memory_list.memory_at_address(0x2a00).unwrap();
assert_eq!(m2.base_address, 0x2000);
assert_eq!(m2.size, 0x1000);
assert_eq!(m2.bytes, &[4u8; 0x1000][..]);
}
#[test]
fn test_misc_info() {
const PID: u32 = 0x1234abcd;
const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes {
process_create_time: 0xf0f0b0b0,
process_user_time: 0xf030a020,
process_kernel_time: 0xa010b420,
};
let mut misc = MiscStream::new(Endian::Little);
misc.process_id = Some(PID);
misc.process_times = Some(PROCESS_TIMES);
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc);
let dump = read_synth_dump(dump).unwrap();
let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
assert_eq!(misc.raw.process_id(), Some(&PID));
assert_eq!(
misc.process_create_time().unwrap(),
Utc.timestamp(PROCESS_TIMES.process_create_time as i64, 0)
);
assert_eq!(
*misc.raw.process_user_time().unwrap(),
PROCESS_TIMES.process_user_time
);
assert_eq!(
*misc.raw.process_kernel_time().unwrap(),
PROCESS_TIMES.process_kernel_time
);
}
#[test]
fn test_misc_info_large() {
const PID: u32 = 0x1234abcd;
const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes {
process_create_time: 0xf0f0b0b0,
process_user_time: 0xf030a020,
process_kernel_time: 0xa010b420,
};
let mut misc = MiscStream::new(Endian::Little);
misc.process_id = Some(PID);
misc.process_times = Some(PROCESS_TIMES);
misc.pad_to_size = Some(mem::size_of::<md::MINIDUMP_MISC_INFO>() + 32);
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc);
let dump = read_synth_dump(dump).unwrap();
let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
assert_eq!(misc.raw.process_id(), Some(&PID));
assert_eq!(
misc.process_create_time().unwrap(),
Utc.timestamp(PROCESS_TIMES.process_create_time as i64, 0)
);
assert_eq!(
*misc.raw.process_user_time().unwrap(),
PROCESS_TIMES.process_user_time
);
assert_eq!(
*misc.raw.process_kernel_time().unwrap(),
PROCESS_TIMES.process_kernel_time
);
}
fn ascii_string_to_utf16(input: &str) -> Vec<u16> {
input.chars().map(|c| c as u16).collect()
}
#[test]
fn test_misc_info_5() {
const PID: u32 = 0x1234abcd;
const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes {
process_create_time: 0xf0f0b0b0,
process_user_time: 0xf030a020,
process_kernel_time: 0xa010b420,
};
const POWER_INFO: MiscFieldsPowerInfo = MiscFieldsPowerInfo {
processor_max_mhz: 0x45873234,
processor_current_mhz: 0x2134018a,
processor_mhz_limit: 0x3423aead,
processor_max_idle_state: 0x123aef12,
processor_current_idle_state: 0x1205af3a,
};
const PROCESS_INTEGRITY_LEVEL: u32 = 0x35603403;
const PROCESS_EXECUTE_FLAGS: u32 = 0xa4e09da1;
const PROTECTED_PROCESS: u32 = 0x12345678;
let mut standard_name = [0; 32];
let mut daylight_name = [0; 32];
let bare_standard_name = ascii_string_to_utf16("Pacific Standard Time");
let bare_daylight_name = ascii_string_to_utf16("Pacific Daylight Time");
standard_name[..bare_standard_name.len()].copy_from_slice(&bare_standard_name);
daylight_name[..bare_daylight_name.len()].copy_from_slice(&bare_daylight_name);
const TIME_ZONE_ID: u32 = 2;
const BIAS: i32 = 2;
const STANDARD_BIAS: i32 = 1;
const DAYLIGHT_BIAS: i32 = -60;
const STANDARD_DATE: md::SYSTEMTIME = md::SYSTEMTIME {
year: 0,
month: 11,
day_of_week: 2,
day: 1,
hour: 2,
minute: 33,
second: 51,
milliseconds: 123,
};
const DAYLIGHT_DATE: md::SYSTEMTIME = md::SYSTEMTIME {
year: 0,
month: 3,
day_of_week: 4,
day: 2,
hour: 3,
minute: 41,
second: 19,
milliseconds: 512,
};
let time_zone = MiscFieldsTimeZone {
time_zone_id: TIME_ZONE_ID,
time_zone: md::TIME_ZONE_INFORMATION {
bias: BIAS,
standard_bias: STANDARD_BIAS,
daylight_bias: DAYLIGHT_BIAS,
daylight_name,
standard_name,
standard_date: STANDARD_DATE.clone(),
daylight_date: DAYLIGHT_DATE.clone(),
},
};
let mut build_string = [0; 260];
let mut dbg_bld_str = [0; 40];
let bare_build_string = ascii_string_to_utf16("hello");
let bare_dbg_bld_str = ascii_string_to_utf16("world");
build_string[..bare_build_string.len()].copy_from_slice(&bare_build_string);
dbg_bld_str[..bare_dbg_bld_str.len()].copy_from_slice(&bare_dbg_bld_str);
let build_strings = MiscFieldsBuildString {
build_string,
dbg_bld_str,
};
const SIZE_OF_INFO: u32 = mem::size_of::<md::XSTATE_CONFIG_FEATURE_MSC_INFO>() as u32;
const CONTEXT_SIZE: u32 = 0x1234523f;
const PROCESS_COOKIE: u32 = 0x1234dfe0;
const KNOWN_FEATURE_IDX: usize = md::XstateFeatureIndex::LEGACY_SSE as usize;
const UNKNOWN_FEATURE_IDX: usize = 39;
let mut enabled_features = 0;
let mut features = [md::XSTATE_FEATURE::default(); 64];
enabled_features |= 1 << KNOWN_FEATURE_IDX;
features[KNOWN_FEATURE_IDX] = md::XSTATE_FEATURE {
offset: 0,
size: 140,
};
enabled_features |= 1 << UNKNOWN_FEATURE_IDX;
features[UNKNOWN_FEATURE_IDX] = md::XSTATE_FEATURE {
offset: 320,
size: 1100,
};
let misc_5 = MiscInfo5Fields {
xstate_data: md::XSTATE_CONFIG_FEATURE_MSC_INFO {
size_of_info: SIZE_OF_INFO,
context_size: CONTEXT_SIZE,
enabled_features,
features,
},
process_cookie: Some(PROCESS_COOKIE),
};
let mut misc = MiscStream::new(Endian::Little);
misc.process_id = Some(PID);
misc.process_times = Some(PROCESS_TIMES);
misc.power_info = Some(POWER_INFO);
misc.process_integrity_level = Some(PROCESS_INTEGRITY_LEVEL);
misc.protected_process = Some(PROTECTED_PROCESS);
misc.process_execute_flags = Some(PROCESS_EXECUTE_FLAGS);
misc.time_zone = Some(time_zone);
misc.build_strings = Some(build_strings);
misc.misc_5 = Some(misc_5);
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc);
let dump = read_synth_dump(dump).unwrap();
let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
assert_eq!(misc.raw.process_id(), Some(&PID));
assert_eq!(
misc.process_create_time().unwrap(),
Utc.timestamp(PROCESS_TIMES.process_create_time as i64, 0)
);
assert_eq!(
*misc.raw.process_user_time().unwrap(),
PROCESS_TIMES.process_user_time
);
assert_eq!(
*misc.raw.process_kernel_time().unwrap(),
PROCESS_TIMES.process_kernel_time
);
assert_eq!(
*misc.raw.processor_max_mhz().unwrap(),
POWER_INFO.processor_max_mhz,
);
assert_eq!(
*misc.raw.processor_current_mhz().unwrap(),
POWER_INFO.processor_current_mhz,
);
assert_eq!(
*misc.raw.processor_mhz_limit().unwrap(),
POWER_INFO.processor_mhz_limit,
);
assert_eq!(
*misc.raw.processor_max_idle_state().unwrap(),
POWER_INFO.processor_max_idle_state,
);
assert_eq!(
*misc.raw.processor_current_idle_state().unwrap(),
POWER_INFO.processor_current_idle_state,
);
assert_eq!(*misc.raw.time_zone_id().unwrap(), TIME_ZONE_ID);
let time_zone = misc.raw.time_zone().unwrap();
assert_eq!(time_zone.bias, BIAS);
assert_eq!(time_zone.standard_bias, STANDARD_BIAS);
assert_eq!(time_zone.daylight_bias, DAYLIGHT_BIAS);
assert_eq!(time_zone.standard_date, STANDARD_DATE);
assert_eq!(time_zone.daylight_date, DAYLIGHT_DATE);
assert_eq!(time_zone.standard_name, standard_name);
assert_eq!(time_zone.daylight_name, daylight_name);
assert_eq!(*misc.raw.build_string().unwrap(), build_string,);
assert_eq!(*misc.raw.dbg_bld_str().unwrap(), dbg_bld_str,);
assert_eq!(*misc.raw.process_cookie().unwrap(), PROCESS_COOKIE,);
let xstate = misc.raw.xstate_data().unwrap();
assert_eq!(xstate.size_of_info, SIZE_OF_INFO);
assert_eq!(xstate.context_size, CONTEXT_SIZE);
assert_eq!(xstate.enabled_features, enabled_features);
assert_eq!(xstate.features, features);
let mut xstate_iter = xstate.iter();
assert_eq!(
xstate_iter.next().unwrap(),
(KNOWN_FEATURE_IDX, features[KNOWN_FEATURE_IDX]),
);
assert_eq!(
xstate_iter.next().unwrap(),
(UNKNOWN_FEATURE_IDX, features[UNKNOWN_FEATURE_IDX]),
);
assert_eq!(xstate_iter.next(), None);
assert_eq!(xstate_iter.next(), None);
}
#[test]
fn test_elf_build_id() {
let name1 = DumpString::new("module 1", Endian::Little);
const MODULE1_BUILD_ID: &[u8] = &[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
];
let cv_record1 = Section::with_endian(Endian::Little)
.D32(md::CvSignature::Elf as u32) .append_bytes(MODULE1_BUILD_ID);
let module1 = SynthModule::new(
Endian::Little,
0x100000000,
0x4000,
&name1,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record1);
let name2 = DumpString::new("module 2", Endian::Little);
const MODULE2_BUILD_ID: &[u8] = &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
let cv_record2 = Section::with_endian(Endian::Little)
.D32(md::CvSignature::Elf as u32) .append_bytes(MODULE2_BUILD_ID);
let module2 = SynthModule::new(
Endian::Little,
0x200000000,
0x4000,
&name2,
0xb1054d2a,
0x34571371,
Some(&STOCK_VERSION_INFO),
)
.cv_record(&cv_record2);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_module(module1)
.add_module(module2)
.add(name1)
.add(cv_record1)
.add(name2)
.add(cv_record2);
let dump = read_synth_dump(dump).unwrap();
let module_list = dump.get_stream::<MinidumpModuleList>().unwrap();
let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 2);
assert_eq!(modules[0].base_address(), 0x100000000);
assert_eq!(modules[0].code_file(), "module 1");
assert_eq!(
modules[0].code_identifier(),
"000102030405060708090a0b0c0d0e0f1011121314151617"
);
assert_eq!(modules[0].debug_file().unwrap(), "module 1");
assert_eq!(
modules[0].debug_identifier().unwrap(),
"030201000504070608090A0B0C0D0E0F0"
);
assert_eq!(modules[1].base_address(), 0x200000000);
assert_eq!(modules[1].code_file(), "module 2");
assert_eq!(modules[1].code_identifier(), "0001020304050607");
assert_eq!(modules[1].debug_file().unwrap(), "module 2");
assert_eq!(
modules[1].debug_identifier().unwrap(),
"030201000504070600000000000000000"
);
}
#[test]
fn test_thread_list_x86() {
let context = synth_minidump::x86_context(Endian::Little, 0xabcd1234, 0x1010);
let stack = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(0, 0x1000),
0x1000,
);
let thread = Thread::new(Endian::Little, 0x1234, &stack, &context);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_thread(thread)
.add(context)
.add_memory(stack);
let dump = read_synth_dump(dump).unwrap();
let mut thread_list = dump.get_stream::<MinidumpThreadList<'_>>().unwrap();
assert_eq!(thread_list.threads.len(), 1);
let mut thread = thread_list.threads.pop().unwrap();
assert_eq!(thread.raw.thread_id, 0x1234);
let context = thread.context.expect("Should have a thread context");
match context.raw {
MinidumpRawContext::X86(raw) => {
assert_eq!(raw.eip, 0xabcd1234);
assert_eq!(raw.esp, 0x1010);
}
_ => panic!("Got unexpected raw context type!"),
}
let stack = thread.stack.take().expect("Should have stack memory");
assert_eq!(stack.base_address, 0x1000);
assert_eq!(stack.size, 0x1000);
}
#[test]
fn test_thread_list_amd64() {
let context =
synth_minidump::amd64_context(Endian::Little, 0x1234abcd1234abcd, 0x1000000010000000);
let stack = Memory::with_section(
Section::with_endian(Endian::Little).append_repeated(0, 0x1000),
0x1000000010000000,
);
let thread = Thread::new(Endian::Little, 0x1234, &stack, &context);
let dump = SynthMinidump::with_endian(Endian::Little)
.add_thread(thread)
.add(context)
.add_memory(stack);
let dump = read_synth_dump(dump).unwrap();
let mut thread_list = dump.get_stream::<MinidumpThreadList<'_>>().unwrap();
assert_eq!(thread_list.threads.len(), 1);
let mut thread = thread_list.threads.pop().unwrap();
assert_eq!(thread.raw.thread_id, 0x1234);
let context = thread.context.expect("Should have a thread context");
match context.raw {
MinidumpRawContext::Amd64(raw) => {
assert_eq!(raw.rip, 0x1234abcd1234abcd);
assert_eq!(raw.rsp, 0x1000000010000000);
}
_ => panic!("Got unexpected raw context type!"),
}
let stack = thread.stack.take().expect("Should have stack memory");
assert_eq!(stack.base_address, 0x1000000010000000);
assert_eq!(stack.size, 0x1000);
}
#[test]
fn test_crashpad_info_missing() {
let dump = SynthMinidump::with_endian(Endian::Little);
let dump = read_synth_dump(dump).unwrap();
assert!(matches!(
dump.get_stream::<MinidumpCrashpadInfo>(),
Err(Error::StreamNotFound)
));
}
#[test]
fn test_crashpad_info_ids() {
let report_id = GUID {
data1: 1,
data2: 2,
data3: 3,
data4: [4, 5, 6, 7, 8, 9, 10, 11],
};
let client_id = GUID {
data1: 11,
data2: 10,
data3: 9,
data4: [8, 7, 6, 5, 4, 3, 2, 1],
};
let crashpad_info = CrashpadInfo::new(Endian::Little)
.report_id(report_id)
.client_id(client_id);
let dump = SynthMinidump::with_endian(Endian::Little).add_crashpad_info(crashpad_info);
let dump = read_synth_dump(dump).unwrap();
let crashpad_info = dump.get_stream::<MinidumpCrashpadInfo>().unwrap();
assert_eq!(crashpad_info.raw.report_id, report_id);
assert_eq!(crashpad_info.raw.client_id, client_id);
}
#[test]
fn test_crashpad_info_annotations() {
let module = ModuleCrashpadInfo::new(42, Endian::Little)
.add_list_annotation("annotation")
.add_simple_annotation("simple", "module")
.add_annotation_object("string", AnnotationValue::String("value".to_owned()))
.add_annotation_object("invalid", AnnotationValue::Invalid)
.add_annotation_object("custom", AnnotationValue::Custom(0x8001, vec![42]));
let crashpad_info = CrashpadInfo::new(Endian::Little)
.add_module(module)
.add_simple_annotation("simple", "info");
let dump = SynthMinidump::with_endian(Endian::Little).add_crashpad_info(crashpad_info);
let dump = read_synth_dump(dump).unwrap();
let crashpad_info = dump.get_stream::<MinidumpCrashpadInfo>().unwrap();
let module = &crashpad_info.module_list[0];
assert_eq!(crashpad_info.simple_annotations["simple"], "info");
assert_eq!(module.module_index, 42);
assert_eq!(module.list_annotations, vec!["annotation".to_owned()]);
assert_eq!(module.simple_annotations["simple"], "module");
assert_eq!(
module.annotation_objects["string"],
MinidumpAnnotation::String("value".to_owned())
);
assert_eq!(
module.annotation_objects["invalid"],
MinidumpAnnotation::Invalid
);
}
}