// Copyright 2015 Ted Mielczarek. See the COPYRIGHT // file at the top-level directory of this distribution. use debugid::{CodeId, DebugId}; use memmap2::Mmap; use num_traits::FromPrimitive; use procfs_core::prelude::*; use procfs_core::process::{MMPermissions, MemoryMap, MemoryMaps}; use prost::Message; use scroll::ctx::{SizeWith, TryFromCtx}; use scroll::{Pread, BE, LE}; use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::HashMap; use std::convert::TryInto; 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; use std::time::{Duration, SystemTime}; use tracing::warn; use uuid::Uuid; pub use crate::context::*; use crate::stability_report::StabilityReport; use crate::strings::*; use crate::system_info::{Cpu, Os, PointerWidth}; use minidump_common::errors::{self as err}; use minidump_common::format::{self as md}; use minidump_common::format::{CvSignature, MINIDUMP_STREAM_TYPE}; use minidump_common::traits::{IntoRangeMapSafe, Module}; use range_map::{Range, RangeMap}; use time::format_description::well_known::Rfc3339; /// An index into the contents of a minidump. /// /// The `Minidump` struct represents the parsed header and /// indices contained at the start of a minidump file. It can be instantiated /// by calling the [`Minidump::read`][read] or /// [`Minidump::read_path`][read_path] methods. /// /// # Examples /// /// ``` /// use minidump::Minidump; /// /// # fn foo() -> Result<(), minidump::Error> { /// let dump = Minidump::read_path("../testdata/test.dmp")?; /// # Ok(()) /// # } /// ``` /// /// [read]: struct.Minidump.html#method.read /// [read_path]: struct.Minidump.html#method.read_path #[derive(Debug)] pub struct Minidump<'a, T> where T: Deref + 'a, { data: T, /// The raw minidump header from the file. pub header: md::MINIDUMP_HEADER, streams: BTreeMap, system_info: Option, /// The endianness of this minidump file. pub endian: scroll::Endian, _phantom: PhantomData<&'a [u8]>, } /// Errors encountered while reading a `Minidump`. #[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)] pub enum Error { #[error("File not found")] FileNotFound, #[error("I/O error")] IoError, #[error("Missing minidump header (empty minidump?)")] MissingHeader, #[error("Header mismatch")] HeaderMismatch, #[error("Minidump version mismatch")] VersionMismatch, #[error("Missing stream directory (heavily truncated minidump?)")] MissingDirectory, #[error("Error reading stream")] StreamReadFailure, #[error("Stream size mismatch: expected {expected} bytes, found {actual} bytes")] StreamSizeMismatch { expected: usize, actual: usize }, #[error("Stream not found")] StreamNotFound, #[error("Module read failure")] ModuleReadFailure, #[error("Memory read failure")] MemoryReadFailure, #[error("Data error")] DataError, #[error("Error reading CodeView data")] CodeViewReadFailure, #[error("Uknown element type")] UknownElementType, } impl Error { /// Returns just the name of the error, as a more human-friendly version of /// an error-code for error logging. pub fn name(&self) -> &'static str { match self { Error::FileNotFound => "FileNotFound", Error::IoError => "IoError", Error::MissingHeader => "MissingHeader", Error::HeaderMismatch => "HeaderMismatch", Error::VersionMismatch => "VersionMismatch", Error::MissingDirectory => "MissingDirectory", Error::StreamReadFailure => "StreamReadFailure", Error::StreamSizeMismatch { .. } => "StreamSizeMismatch", Error::StreamNotFound => "StreamNotFound", Error::ModuleReadFailure => "ModuleReadFailure", Error::MemoryReadFailure => "MemoryReadFailure", Error::DataError => "DataError", Error::CodeViewReadFailure => "CodeViewReadFailure", Error::UknownElementType => "UnknownElementType", } } } /// The fundamental unit of data in a `Minidump`. pub trait MinidumpStream<'a>: Sized { /// The stream type constant used in the `md::MDRawDirectory` entry. /// This is usually a [MINIDUMP_STREAM_TYPE][] but it's left as a u32 /// to allow external projects to add support for their own custom streams. const STREAM_TYPE: u32; /// Read this `MinidumpStream` type from `bytes`. /// /// * `bytes` is the contents of this specific stream. /// * `all` refers to the full contents of the minidump, for reading auxilliary data /// referred to with `MINIDUMP_LOCATION_DESCRIPTOR`s. /// * `system_info` is the preparsed SystemInfo stream, if it exists in the minidump. fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, system_info: Option<&MinidumpSystemInfo>, ) -> Result; } /// Provides a unified interface for getting metadata about the process's mapped memory regions /// at the time of the crash. /// /// Currently this is one of [`MinidumpMemoryInfoList`], available in Windows minidumps, /// or [`MinidumpLinuxMaps`], available in Linux minidumps. /// /// This allows you to e.g. check whether an address was executable or not without /// worrying about which platform the crash occured on. If you need to do more /// specific analysis, you can get the native formats with [`UnifiedMemoryInfoList::info`] /// and [`UnifiedMemoryInfoList::maps`]. /// /// Currently an enum because there is no situation where you can have both, /// but this may change if the format evolves. Prefer using this type's methods /// over pattern matching. #[derive(Debug, Clone)] pub enum UnifiedMemoryInfoList<'a> { Maps(MinidumpLinuxMaps<'a>), Info(MinidumpMemoryInfoList<'a>), } #[derive(Debug, Copy, Clone)] /// A [`UnifiedMemoryInfoList`] entry, providing metatadata on a region of /// memory in the crashed process. pub enum UnifiedMemoryInfo<'a> { Map(&'a MinidumpLinuxMapInfo<'a>), Info(&'a MinidumpMemoryInfo<'a>), } /// The contents of `/proc/self/maps` for the crashing process. /// /// This is roughly equivalent in functionality to [`MinidumpMemoryInfoList`]. /// Use [`UnifiedMemoryInfoList`] to handle the two uniformly. #[derive(Debug, Clone)] pub struct MinidumpLinuxMaps<'a> { /// The memory regions, in the order they were stored in the minidump. regions: Vec>, /// Map from address range to index in regions. Use /// [`MinidumpLinuxMaps::memory_info_at_address`]. regions_by_addr: RangeMap, } /// A memory mapping entry for the process we are analyzing. #[derive(Debug, Clone, PartialEq, Eq)] pub struct MinidumpLinuxMapInfo<'a> { pub map: MemoryMap, _phantom: PhantomData<&'a u8>, } #[derive(Debug, Clone)] pub struct MinidumpMemoryInfoList<'a> { /// The memory regions, in the order they were stored in the minidump. regions: Vec>, /// Map from address range to index in regions. Use /// [`MinidumpMemoryInfoList::memory_info_at_address`]. regions_by_addr: RangeMap, } #[derive(Debug, Clone, PartialEq, Eq)] /// Metadata about a region of memory (whether it is executable, freed, private, and so on). pub struct MinidumpMemoryInfo<'a> { /// The raw value from the minidump. pub raw: md::MINIDUMP_MEMORY_INFO, /// The memory protection when the region was initially allocated. pub allocation_protection: md::MemoryProtection, /// The state of the pages in the region (whether it is freed or not). pub state: md::MemoryState, /// The access protection of the pages in the region. pub protection: md::MemoryProtection, /// What kind of memory mapping the pages in this region are. pub ty: md::MemoryType, _phantom: PhantomData<&'a u8>, } /// CodeView data describes how to locate debug symbols #[derive(Debug, Clone)] pub enum CodeView { /// PDB 2.0 format data in a separate file Pdb20(md::CV_INFO_PDB20), /// PDB 7.0 format data in a separate file (most common) Pdb70(md::CV_INFO_PDB70), /// Indicates data is in an ELF binary with build ID `build_id` Elf(md::CV_INFO_ELF), /// An unknown format containing the raw bytes of data Unknown(Vec), } /// An executable or shared library loaded in the process at the time the `Minidump` was written. #[derive(Debug, Clone)] pub struct MinidumpModule { /// The `MINIDUMP_MODULE` direct from the minidump file. pub raw: md::MINIDUMP_MODULE, /// The module name. This is stored separately in the minidump. pub name: String, /// A `CodeView` record, if one is present. pub codeview_info: Option, /// A misc debug record, if one is present. pub misc_info: Option, os: Os, /// The parsed DebugId of the module, if one is present. debug_id: Option, } /// A list of `MinidumpModule`s contained in a `Minidump`. #[derive(Debug, Clone)] pub struct MinidumpModuleList { /// The modules, in the order they were stored in the minidump. modules: Vec, /// Map from address range to index in modules. Use `MinidumpModuleList::module_at_address`. modules_by_addr: RangeMap, } /// A mapping of thread ids to their names. #[derive(Debug, Clone, Default)] pub struct MinidumpThreadNames { names: BTreeMap, } /// An executable or shared library that was once loaded into the process, but was unloaded /// by the time the `Minidump` was written. #[derive(Debug, Clone)] pub struct MinidumpUnloadedModule { /// The `MINIDUMP_UNLOADED_MODULE` direct from the minidump file. pub raw: md::MINIDUMP_UNLOADED_MODULE, /// The module name. This is stored separately in the minidump. pub name: String, } /// A list of `MinidumpUnloadedModule`s contained in a `Minidump`. #[derive(Debug, Clone)] pub struct MinidumpUnloadedModuleList { /// The modules, in the order they were stored in the minidump. modules: Vec, /// Map from address range to index in modules. /// Use `MinidumpUnloadedModuleList::modules_at_address`. modules_by_addr: Vec<(Range, usize)>, } /// Contains object-specific information for a handle. Microsoft documentation /// doesn't describe the contents of this type. #[derive(Debug, Clone)] pub struct MinidumpHandleObjectInformation { pub raw: md::MINIDUMP_HANDLE_OBJECT_INFORMATION, pub info_type: md::MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE, } impl fmt::Display for MinidumpHandleObjectInformation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{{raw: {:?}, type: {:?}}}", self.raw, self.info_type) } } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] pub enum RawHandleDescriptor { HandleDescriptor(md::MINIDUMP_HANDLE_DESCRIPTOR), HandleDescriptor2(md::MINIDUMP_HANDLE_DESCRIPTOR_2), } /// Describes the state of an individual system handle at the time the minidump was written. #[derive(Debug, Clone)] pub struct MinidumpHandleDescriptor { /// The `MINIDUMP_HANDKE_DESCRIPTOR` data direct from the minidump file. pub raw: RawHandleDescriptor, /// The name of the type of this handle, if present. pub type_name: Option, /// The object name of this handle, if present. /// On Linux this is the file path. pub object_name: Option, /// Object information for this handle, can be empty, platform-specific. pub object_infos: Vec, } /// A stream holding all the system handles at the time the minidump was written. /// On Linux this is the list of open file descriptors. #[derive(Debug, Clone)] pub struct MinidumpHandleDataStream { pub handles: Vec, } /// The state of a thread from the process when the minidump was written. #[derive(Debug)] pub struct MinidumpThread<'a> { /// The `MINIDUMP_THREAD` direct from the minidump file. pub raw: md::MINIDUMP_THREAD, /// The CPU context for the thread, if present. context: Option<&'a [u8]>, /// The stack memory for the thread, if present. stack: Option>, /// Saved endianness for lazy parsing. endian: scroll::Endian, } /// A list of `MinidumpThread`s contained in a `Minidump`. #[derive(Debug)] pub struct MinidumpThreadList<'a> { /// The threads, in the order they were present in the `Minidump`. pub threads: Vec>, /// A map of thread id to index in `threads`. thread_ids: HashMap, } /// The state of a thread from the process when the minidump was written. #[derive(Debug)] pub struct MinidumpThreadInfo { /// The `MINIDUMP_THREAD_INFO` direct from the minidump file. pub raw: md::MINIDUMP_THREAD_INFO, } /// A list of `MinidumpThread`s contained in a `Minidump`. #[derive(Debug)] pub struct MinidumpThreadInfoList { /// The thread info entries, in the order they were present in the `Minidump`. pub thread_infos: Vec, /// A map of thread id to index in `entries`. thread_ids: HashMap, } /// Information about the system that generated the minidump. #[derive(Debug, Clone)] pub struct MinidumpSystemInfo { /// The `MINIDUMP_SYSTEM_INFO` direct from the minidump pub raw: md::MINIDUMP_SYSTEM_INFO, /// The operating system that generated the minidump pub os: Os, /// The CPU on which the minidump was generated pub cpu: Cpu, /// A string that describes the latest Service Pack installed on the system. /// If no Service Pack has been installed, the string is empty. /// This is stored separately in the minidump. csd_version: Option, /// An x86 (not x64!) CPU vendor name that is stored in `raw` but in a way /// that's cpu_info: Option, } /// A region of memory from the process that wrote the minidump. /// This is the underlying generic type for [MinidumpMemory] and [MinidumpMemory64]. #[derive(Clone, Debug)] pub struct MinidumpMemoryBase<'a, Descriptor> { /// The raw `MINIDUMP_MEMORY_DESCRIPTOR` from the minidump. pub desc: Descriptor, /// The starting address of this range of memory. pub base_address: u64, /// The length of this range of memory. pub size: u64, /// The contents of the memory. pub bytes: &'a [u8], /// The endianness of the minidump which is used for memory accesses. pub endian: scroll::Endian, } /// A region of memory from the process that wrote the minidump. pub type MinidumpMemory<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>; /// A large region of memory from the process that wrote the minidump (usually a full dump). pub type MinidumpMemory64<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>; /// Provides a unified interface for MinidumpMemory and MinidumpMemory64 #[derive(Debug, Clone, Copy)] pub enum UnifiedMemory<'a, 'mdmp> { Memory(&'a MinidumpMemory<'mdmp>), Memory64(&'a MinidumpMemory64<'mdmp>), } #[derive(Debug, Clone)] pub enum RawMacCrashInfo { V1( md::MINIDUMP_MAC_CRASH_INFO_RECORD, md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS, ), V4( md::MINIDUMP_MAC_CRASH_INFO_RECORD_4, md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_4, ), V5( md::MINIDUMP_MAC_CRASH_INFO_RECORD_5, md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_5, ), } #[derive(Debug, Clone)] pub struct MinidumpMacCrashInfo { /// The `MINIDUMP_MAC_CRASH_INFO_RECORD` and `MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS`. pub raw: Vec, } #[derive(Debug, Clone)] pub struct MinidumpMacBootargs { pub raw: md::MINIDUMP_MAC_BOOTARGS, pub bootargs: Option, } #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] 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), } /// Miscellaneous information about the process that wrote the minidump. #[derive(Debug, Clone)] pub struct MinidumpMiscInfo { /// The `MINIDUMP_MISC_INFO` struct direct from the minidump. pub raw: RawMiscInfo, } /// Additional information about process state. /// /// MinidumpBreakpadInfo wraps MINIDUMP_BREAKPAD_INFO, which is an optional stream /// in a minidump that provides additional information about the process state /// at the time the minidump was generated. #[derive(Debug, Clone)] pub struct MinidumpBreakpadInfo { raw: md::MINIDUMP_BREAKPAD_INFO, /// The thread that wrote the minidump. pub dump_thread_id: Option, /// The thread that requested that a minidump be written. pub requesting_thread_id: Option, } #[derive(Default, Debug)] /// Interesting values extracted from /etc/lsb-release pub struct MinidumpLinuxLsbRelease<'a> { data: &'a [u8], } /// Interesting values extracted from /proc/self/environ #[derive(Default, Debug)] pub struct MinidumpLinuxEnviron<'a> { data: &'a [u8], } /// Interesting values extracted from /proc/cpuinfo #[derive(Default, Debug)] pub struct MinidumpLinuxCpuInfo<'a> { data: &'a [u8], } /// Interesting values extracted from /proc/self/status #[derive(Default, Debug)] pub struct MinidumpLinuxProcStatus<'a> { data: &'a [u8], } /// Interesting values extracted from /proc/self/limits #[derive(Default, Debug)] pub struct MinidumpLinuxProcLimits<'a> { data: &'a [u8], } /// Soft errors encountered by minidump-writer during generation #[derive(Default, Debug)] pub struct MinidumpSoftErrors<'a> { json_str: &'a str, } /// The reason for a process crash. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum CrashReason { /// A Mac/iOS error code with no other interesting details. MacGeneral(err::ExceptionCodeMac, u32), MacBadAccessKern(err::ExceptionCodeMacBadAccessKernType), MacBadAccessArm(err::ExceptionCodeMacBadAccessArmType), MacBadAccessPpc(err::ExceptionCodeMacBadAccessPpcType), MacBadAccessX86(err::ExceptionCodeMacBadAccessX86Type), MacBadInstructionArm(err::ExceptionCodeMacBadInstructionArmType), MacBadInstructionPpc(err::ExceptionCodeMacBadInstructionPpcType), MacBadInstructionX86(err::ExceptionCodeMacBadInstructionX86Type), MacArithmeticArm(err::ExceptionCodeMacArithmeticArmType), MacArithmeticPpc(err::ExceptionCodeMacArithmeticPpcType), MacArithmeticX86(err::ExceptionCodeMacArithmeticX86Type), MacSoftware(err::ExceptionCodeMacSoftwareType), MacBreakpointArm(err::ExceptionCodeMacBreakpointArmType), MacBreakpointPpc(err::ExceptionCodeMacBreakpointPpcType), MacBreakpointX86(err::ExceptionCodeMacBreakpointX86Type), MacResource(err::ExceptionCodeMacResourceType, u64, u64), MacGuard(err::ExceptionCodeMacGuardType, u64, u64), /// A Linux/Android error code with no other interesting metadata. LinuxGeneral(err::ExceptionCodeLinux, u32), LinuxSigill(err::ExceptionCodeLinuxSigillKind), LinuxSigtrap(err::ExceptionCodeLinuxSigtrapKind), LinuxSigbus(err::ExceptionCodeLinuxSigbusKind), LinuxSigfpe(err::ExceptionCodeLinuxSigfpeKind), LinuxSigsegv(err::ExceptionCodeLinuxSigsegvKind), LinuxSigsys(err::ExceptionCodeLinuxSigsysKind), /// A Windows error code with no other interesting metadata. WindowsGeneral(err::ExceptionCodeWindows), /// A Windows error from winerror.h. WindowsWinError(err::WinErrorWindows), /// A Windows error for a specific facility from winerror.h. WindowsWinErrorWithFacility(err::WinErrorFacilityWindows, err::WinErrorWindows), /// A Windows error from ntstatus.h WindowsNtStatus(err::NtStatusWindows), /// ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION but with details on the kind of access. WindowsAccessViolation(err::ExceptionCodeWindowsAccessType), /// ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR but with details on the kind of access. /// Second argument is a windows NTSTATUS value. WindowsInPageError(err::ExceptionCodeWindowsInPageErrorType, u64), /// ExceptionCodeWindows::EXCEPTION_STACK_BUFFER_OVERRUN with an accompanying /// windows FAST_FAIL value. WindowsStackBufferOverrun(u64), /// A Windows error with no known mapping. WindowsUnknown(u32), Unknown(u32, u32), } /// Information about the exception that caused the minidump to be generated. /// /// `MinidumpException` wraps `MINIDUMP_EXCEPTION_STREAM`, which contains information /// about the exception that caused the minidump to be generated, if the /// minidump was generated in an exception handler called as a result of an /// exception. It also provides access to a `MinidumpContext` object, which /// contains the CPU context for the exception thread at the time the exception /// occurred. #[derive(Debug)] pub struct MinidumpException<'a> { /// The raw exception information from the minidump stream. pub raw: md::MINIDUMP_EXCEPTION_STREAM, /// The thread that encountered this exception. pub thread_id: u32, /// If present, the CPU context from the time the thread encountered the exception. /// /// This should be used in place of the context contained within the thread with id /// `thread_id`, since it points to the code location where the exception happened, /// without any exception handling routines that are likely to be on the stack after /// that point. context: Option<&'a [u8]>, /// Saved endianess for lazy parsing. endian: scroll::Endian, } /// A list of memory regions included in a minidump. /// This is the underlying generic type for [MinidumpMemoryList] and [MinidumpMemory64List]. #[derive(Debug)] pub struct MinidumpMemoryListBase<'a, Descriptor> { /// The memory regions, in the order they were stored in the minidump. regions: Vec>, /// Map from address range to index in regions. Use `MinidumpMemoryList::memory_at_address`. regions_by_addr: RangeMap, } /// A list of memory regions included in a minidump. pub type MinidumpMemoryList<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>; /// A list of large memory regions included in a minidump (usually a full dump). pub type MinidumpMemory64List<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>; /// Provides a unified interface for MinidumpMemoryList and MinidumpMemory64List #[derive(Debug)] pub enum UnifiedMemoryList<'a> { Memory(MinidumpMemoryList<'a>), Memory64(MinidumpMemory64List<'a>), } impl Default for UnifiedMemoryList<'_> { fn default() -> Self { Self::Memory(Default::default()) } } /// Information about an assertion that caused a crash. #[derive(Debug)] pub struct MinidumpAssertion { pub raw: md::MINIDUMP_ASSERTION_INFO, } /// A typed annotation object. #[derive(Clone, Debug)] #[non_exhaustive] pub enum MinidumpAnnotation { /// An invalid annotation. Reserved for internal use. Invalid, /// A `NUL`-terminated C-string. String(String), /// Clients may declare their own custom types. UserDefined(md::MINIDUMP_ANNOTATION), /// An unsupported annotation from a future crashpad version. 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, } } } /// Additional Crashpad-specific information about a module carried within a minidump file. #[derive(Debug)] pub struct MinidumpModuleCrashpadInfo { /// The raw crashpad module extension information. pub raw: md::MINIDUMP_MODULE_CRASHPAD_INFO, /// Index of the corresponding module in the `MinidumpModuleList`. pub module_index: usize, pub list_annotations: Vec, pub simple_annotations: BTreeMap, pub annotation_objects: BTreeMap, } /// Additional Crashpad-specific information carried within a minidump file. #[derive(Debug)] pub struct MinidumpCrashpadInfo { pub raw: md::MINIDUMP_CRASHPAD_INFO, pub simple_annotations: BTreeMap, pub module_list: Vec, } //====================================================== // Implementations fn format_time_t(t: u32) -> String { time::OffsetDateTime::from_unix_timestamp(t as i64) .ok() .and_then(|datetime| datetime.format(&Rfc3339).ok()) .unwrap_or_default() } fn format_system_time(time: &md::SYSTEMTIME) -> String { // Note this drops the day_of_week field on the ground -- is that fine? let format_date = || { use std::convert::TryFrom; let month = time::Month::try_from(time.month as u8).ok()?; let date = time::Date::from_calendar_date(time.year as i32, month, time.day as u8).ok()?; let datetime = date .with_hms_milli( time.hour as u8, time.minute as u8, time.second as u8, time.milliseconds, ) .ok()? .assume_utc(); datetime.format(&Rfc3339).ok() }; format_date().unwrap_or_else(|| "".to_owned()) } /// Produce a slice of `bytes` corresponding to the offset and size in `loc`, or an /// `Error` if the data is not fully contained within `bytes`. 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) } /// Read a u32 length-prefixed UTF-16 string from `bytes` at `offset`. fn read_string_utf16(offset: &mut usize, bytes: &[u8], endian: scroll::Endian) -> Option { let u: u32 = bytes.gread_with(offset, endian).ok()?; let size = u as usize; if !size.is_multiple_of(2) || (*offset + size) > bytes.len() { return None; } let encoding = match endian { scroll::Endian::Little => encoding_rs::UTF_16LE, scroll::Endian::Big => encoding_rs::UTF_16BE, }; let s = encoding .decode_without_bom_handling_and_without_replacement(&bytes[*offset..*offset + size])?; *offset += size; Some(s.into()) } #[inline] fn read_string_utf8_unterminated<'a>( offset: &mut usize, bytes: &'a [u8], endian: scroll::Endian, ) -> Option<&'a str> { let length: u32 = bytes.gread_with(offset, endian).ok()?; let slice = bytes.gread_with(offset, length as usize).ok()?; std::str::from_utf8(slice).ok() } fn read_string_utf8<'a>( offset: &mut usize, bytes: &'a [u8], endian: scroll::Endian, ) -> Option<&'a str> { let string = read_string_utf8_unterminated(offset, bytes, endian)?; match bytes.gread(offset) { Ok(0u8) => Some(string), _ => None, } } fn read_cstring_utf8(offset: &mut usize, bytes: &[u8]) -> Option { let initial_offset = *offset; loop { let byte: u8 = bytes.gread(offset).ok()?; if byte == 0 { break; } } std::str::from_utf8(&bytes[initial_offset..*offset - 1]) .map(String::from) .ok() } /// Convert `bytes` with trailing NUL characters to a string fn string_from_bytes_nul(bytes: &[u8]) -> Option> { bytes.split(|&b| b == 0).next().map(String::from_utf8_lossy) } /// Format `bytes` as a String of hex digits fn bytes_to_hex(bytes: &[u8]) -> String { let hex_bytes: Vec = bytes.iter().map(|b| format!("{b:02x}")).collect(); hex_bytes.join("") } /// Attempt to read a CodeView record from `data` at `location` fn read_codeview( location: &md::MINIDUMP_LOCATION_DESCRIPTOR, data: &[u8], endian: scroll::Endian, ) -> Option { let bytes = location_slice(data, location).ok()?; // The CodeView data can be one of a few different formats. Try to read the // signature first to figure out what format the data is. let signature: u32 = bytes.pread_with(0, endian).ok()?; Some(match CvSignature::from_u32(signature) { // PDB data has two known versions: the current 7.0 and the older 2.0 version. Some(CvSignature::Pdb70) => CodeView::Pdb70(bytes.pread_with(0, endian).ok()?), Some(CvSignature::Pdb20) => CodeView::Pdb20(bytes.pread_with(0, endian).ok()?), // Breakpad's ELF build ID format. Some(CvSignature::Elf) => CodeView::Elf(bytes.pread_with(0, endian).ok()?), // Other formats aren't handled, but save the raw bytes. _ => CodeView::Unknown(bytes.to_owned()), }) } fn read_debug_id(codeview_info: &CodeView, endian: scroll::Endian) -> Option { match codeview_info { CodeView::Pdb70(ref raw) => { // For macOS, this should be its code ID with the age (0) // appended to the end of it. This makes it identical to debug // IDs for Windows, and is why it doesn't have a special case // here. let uuid = Uuid::from_fields( raw.signature.data1, raw.signature.data2, raw.signature.data3, &raw.signature.data4, ); (!uuid.is_nil()).then(|| DebugId::from_parts(uuid, raw.age)) } CodeView::Pdb20(ref raw) => Some(DebugId::from_pdb20(raw.signature, raw.age)), CodeView::Elf(ref raw) => { // For empty or trivial `build_id`s, we don't want to return a `DebugId`. // This can happen for mapped files that aren't executable, like fonts or .jar files. if raw.build_id.iter().all(|byte| *byte == 0) { return None; } // For backwards-compat (Linux minidumps have historically // been written using PDB70 CodeView info), treat build_id // as if the first 16 bytes were a GUID. let guid_size = ::size_with(&endian); let guid = if raw.build_id.len() < guid_size { // Pad with zeros. let v: Vec = raw .build_id .iter() .cloned() .chain(iter::repeat(0)) .take(guid_size) .collect(); v.pread_with::(0, endian).ok() } else { raw.build_id.pread_with::(0, endian).ok() }; guid.map(|g| Uuid::from_fields(g.data1, g.data2, g.data3, &g.data4)) .map(DebugId::from_uuid) } _ => None, } } /// Checks that the buffer is large enough for the given number of items. /// /// Essentially ensures that `buf.len() >= offset + (number_of_entries * size_of_entry)`. /// Returns `(number_of_entries, expected_size)` on success. fn ensure_count_in_bound( buf: &[u8], number_of_entries: usize, size_of_entry: usize, offset: usize, ) -> Result<(usize, usize), Error> { let expected_size = number_of_entries .checked_mul(size_of_entry) .and_then(|v| v.checked_add(offset)) .ok_or(Error::StreamReadFailure)?; if buf.len() < expected_size { return Err(Error::StreamSizeMismatch { expected: expected_size, actual: buf.len(), }); } Ok((number_of_entries, expected_size)) } impl MinidumpModule { /// Create a `MinidumpModule` with some basic info. /// /// Useful for testing. 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, os: Os::Unknown(0), debug_id: None, } } /// Read additional data to construct a `MinidumpModule` from `bytes` using the information /// from the module list in `raw`. pub fn read( raw: md::MINIDUMP_MODULE, bytes: &[u8], endian: scroll::Endian, system_info: Option<&MinidumpSystemInfo>, ) -> Result { let mut offset = raw.module_name_rva as usize; let name = read_string_utf16(&mut offset, bytes, endian).ok_or(Error::CodeViewReadFailure)?; let codeview_info = if raw.cv_record.data_size == 0 { None } else { Some(read_codeview(&raw.cv_record, bytes, endian).ok_or(Error::CodeViewReadFailure)?) }; let os = system_info.map(|info| info.os).unwrap_or(Os::Unknown(0)); let debug_id = codeview_info .as_ref() .and_then(|cv| read_debug_id(cv, endian)); Ok(MinidumpModule { raw, name, codeview_info, misc_info: None, os, debug_id, }) } /// Write a human-readable description of this `MinidumpModule` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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().unwrap_or_default(), )?; // Print CodeView data. 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)) => { // Fibbing about having cv_signature handy here. 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)")?; } } // Print misc record data. if let Some(ref _misc) = self.misc_info { //TODO, not terribly important. writeln!(f, " (misc_record) = (unimplemented)")?; } else { writeln!(f, " (misc_record) = (null)")?; } // Print remaining data. write!( f, r#" (debug_file) = "{}" (debug_identifier) = "{}" (version) = "{}" "#, self.debug_file().unwrap_or(Cow::Borrowed("")), self.debug_identifier().unwrap_or_default(), self.version().unwrap_or(Cow::Borrowed("")), )?; Ok(()) } fn memory_range(&self) -> Option> { if self.size() == 0 { return None; } Some(Range::new( self.base_address(), self.base_address().checked_add(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) -> Option { match self.codeview_info { Some(CodeView::Pdb70(ref raw)) if matches!(self.os, Os::MacOs | Os::Ios) => { // MacOs uses PDB70 instead of its own dedicated format. // See the following issue for a potential MacOs-specific format: // https://github.com/rust-minidump/rust-minidump/issues/455 Some(CodeId::new(format!("{:#}", raw.signature))) } Some(CodeView::Pdb20(_)) | Some(CodeView::Pdb70(_)) => Some(CodeId::new(format!( "{0:08X}{1:x}", self.raw.time_date_stamp, self.raw.size_of_image ))), Some(CodeView::Elf(ref raw)) => { // Return None instead of sentinel CodeIds for empty // `build_id`s. Non-executable mapped files like fonts or .jar // files will usually fall under this case. if raw.build_id.iter().all(|byte| *byte == 0) { None } else { Some(CodeId::from_binary(&raw.build_id)) } } None if self.os == Os::Windows => { // Fall back to the timestamp + size-based debug-id for Windows. // Some Module records from Windows have no codeview record, but // the CodeId generated here is valid and can be looked up on // the Microsoft symbol server. // One example might be `wow64cpu.dll` with code-id `378BC3CDa000`. // This can however lead to "false positive" code-ids for modules // that have no timestamp, in which case the code-id looks extremely // low-entropy. The same can happen though if they *do* have a // codeview record. Some(CodeId::new(format!( "{0:08X}{1:x}", self.raw.time_date_stamp, self.raw.size_of_image ))) } // Occasionally things will make it into the module stream that // shouldn't be there, and so no meaningful CodeId can be found from // those. One of those things are SysV shared memory segments which // have no CodeView record. _ => None, } } fn debug_file(&self) -> Option> { 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)), // TODO: support misc record? not really important. _ => None, } } fn debug_identifier(&self) -> Option { self.debug_id } fn version(&self) -> Option> { if self.raw.version_info.signature == md::VS_FFI_SIGNATURE && self.raw.version_info.struct_version == md::VS_FFI_STRUCVERSION { if matches!(self.os, Os::MacOs | Os::Ios | Os::Windows) { 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 { // Assume Elf let ver = format!( "{}.{}.{}.{}", 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 ); Some(Cow::Owned(ver)) } } else { None } } } impl MinidumpUnloadedModule { /// Create a `MinidumpUnloadedModule` with some basic info. /// /// Useful for testing. 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), } } /// Read additional data to construct a `MinidumpUnloadedModule` from `bytes` using the information /// from the module list in `raw`. pub fn read( raw: md::MINIDUMP_UNLOADED_MODULE, bytes: &[u8], endian: scroll::Endian, ) -> Result { let mut offset = raw.module_name_rva as usize; let name = read_string_utf16(&mut offset, bytes, endian).ok_or(Error::DataError)?; Ok(MinidumpUnloadedModule { raw, name }) } /// Write a human-readable description of this `MinidumpModule` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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().unwrap_or_default(), )?; Ok(()) } fn memory_range(&self) -> Option> { if self.size() == 0 { return None; } Some(Range::new( self.base_address(), self.base_address().checked_add(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) -> Option { // TODO: This should be returning None if the unloaded module is coming // from a non-Windows minidump. We'll need info about the operating // system, ideally sourced from the SystemInfo to be able to do this. Some(CodeId::new(format!( "{0:08X}{1:x}", self.raw.time_date_stamp, self.raw.size_of_image ))) } fn debug_file(&self) -> Option> { None } fn debug_identifier(&self) -> Option { None } fn version(&self) -> Option> { None } } /// Parses X:Y or X=Y lists, skipping any blank/unparseable lines fn linux_list_iter( bytes: &[u8], separator: u8, ) -> impl Iterator { fn strip_quotes(input: &LinuxOsStr) -> &LinuxOsStr { // Remove any extra surrounding whitespace since formats are inconsistent on this. let input = input.trim_ascii_whitespace(); // Convert `"MyValue"` into `MyValue`, or just return the trimmed input. let output = input .strip_prefix(b"\"") .and_then(|input| input.strip_suffix(b"\"")) .unwrap_or(input); LinuxOsStr::from_bytes(output) } let input = LinuxOsStr::from_bytes(bytes); input.lines().filter_map(move |line| { line.split_once(separator) .map(|(label, val)| (strip_quotes(label), (strip_quotes(val)))) }) } fn read_stream_list<'a, T>( offset: &mut usize, bytes: &'a [u8], endian: scroll::Endian, ) -> Result, Error> where T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>, T: SizeWith, { let u: u32 = bytes .gread_with(offset, endian) .or(Err(Error::StreamReadFailure))?; let (count, counted_size) = ensure_count_in_bound( bytes, u as usize, ::size_with(&endian), mem::size_of::(), )?; match bytes.len() - counted_size { 0 => {} 4 => { // 4 bytes of padding. *offset += 4; } _ => { return Err(Error::StreamSizeMismatch { expected: counted_size, actual: bytes.len(), }); } }; // read count T raw stream entries 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, Error> where T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>, T: SizeWith, { // Some newer list streams have an extended header: // // size_of_header: u32, // size_of_entry: u32, // number_of_entries: u32, // ...entries // In theory this allows the format of the stream to be extended without // us knowing how to handle the new parts. 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 = ::size_with(&endian); if size_of_entry as usize != expected_size_of_entry { // For now, conservatively bail out if entries don't have // the expected size. In theory we can assume entries are // always extended with new trailing fields, and this information // would let us walk over trailing fields we don't know about? // But without an example let's be safe. return Err(Error::StreamReadFailure); } let (number_of_entries, _) = ensure_count_in_bound( bytes, number_of_entries as usize, size_of_entry as usize, size_of_header as usize, )?; let header_padding = match (size_of_header as usize).checked_sub(*offset) { Some(s) => s, None => return Err(Error::StreamReadFailure), }; *offset += header_padding; // read count T raw stream entries let mut raw_entries = Vec::with_capacity(number_of_entries); 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<'a> MinidumpStream<'a> for MinidumpThreadNames { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ThreadNamesStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let mut offset = 0; let raw_names: Vec = read_stream_list(&mut offset, bytes, endian)?; // read out the actual names let mut names = BTreeMap::new(); for raw_name in raw_names { let mut offset = raw_name.thread_name_rva as usize; // Better to just drop unreadable names individually than the whole stream. if let Some(name) = read_string_utf16(&mut offset, all, endian) { names.insert(raw_name.thread_id, name); } else { warn!( "Couldn't read thread name for thread id {}", raw_name.thread_id ); } } Ok(MinidumpThreadNames { names }) } } impl MinidumpThreadNames { pub fn get_name(&self, thread_id: u32) -> Option> { self.names .get(&thread_id) .map(|name| Cow::Borrowed(&**name)) } /// Write a human-readable description of this `MinidumpThreadNames` to `f`. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MinidumpThreadNames thread_count = {} ", self.names.len() )?; for (i, (thread_id, name)) in self.names.iter().enumerate() { writeln!( f, "thread_name[{i}] MINIDUMP_THREAD_NAME thread_id = {thread_id:#x} name = \"{name}\" " )?; } Ok(()) } } impl MinidumpModuleList { /// Return an empty `MinidumpModuleList`. pub fn new() -> MinidumpModuleList { MinidumpModuleList { modules: vec![], modules_by_addr: RangeMap::new(), } } /// Create a `MinidumpModuleList` from a list of `MinidumpModule`s. pub fn from_modules(modules: Vec) -> MinidumpModuleList { let modules_by_addr = modules .iter() .enumerate() .map(|(i, module)| (module.memory_range(), i)) .into_rangemap_safe(); MinidumpModuleList { modules, modules_by_addr, } } /// Returns the module corresponding to the main executable. pub fn main_module(&self) -> Option<&MinidumpModule> { // The main code module is the first one present in a minidump file's // MINIDUMP_MODULEList. if !self.modules.is_empty() { Some(&self.modules[0]) } else { None } } /// Return a `MinidumpModule` whose address range covers `address`. pub fn module_at_address(&self, address: u64) -> Option<&MinidumpModule> { self.modules_by_addr .get(address) .map(|&index| &self.modules[index]) } /// Iterate over the modules in arbitrary order. pub fn iter(&self) -> impl Iterator { self.modules.iter() } /// Iterate over the modules in order by memory address. pub fn by_addr(&self) -> impl DoubleEndedIterator { self.modules_by_addr .ranges_values() .map(move |&(_, index)| &self.modules[index]) } /// Write a human-readable description of this `MinidumpModuleList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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: u32 = MINIDUMP_STREAM_TYPE::ModuleListStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, system_info: Option<&MinidumpSystemInfo>, ) -> Result { let mut offset = 0; let raw_modules: Vec = read_stream_list(&mut offset, bytes, endian)?; // read auxiliary data for each module let mut modules = Vec::with_capacity(raw_modules.len()); for (module_index, raw) in raw_modules.into_iter().enumerate() { if raw.size_of_image == 0 || raw.size_of_image as u64 > (u64::MAX - raw.base_of_image) { // Bad image size. tracing::warn!( module_index, base = raw.base_of_image, size = raw.size_of_image, "bad module image size" ); continue; } modules.push(MinidumpModule::read(raw, all, endian, system_info)?); } Ok(MinidumpModuleList::from_modules(modules)) } } impl MinidumpUnloadedModuleList { /// Return an empty `MinidumpModuleList`. pub fn new() -> MinidumpUnloadedModuleList { MinidumpUnloadedModuleList { modules: vec![], modules_by_addr: vec![], } } /// Create a `MinidumpModuleList` from a list of `MinidumpModule`s. pub fn from_modules(modules: Vec) -> MinidumpUnloadedModuleList { let mut modules_by_addr = (0..modules.len()) .filter_map(|i| modules[i].memory_range().map(|r| (r, i))) .collect::>(); modules_by_addr.sort_by_key(|(range, _idx)| *range); MinidumpUnloadedModuleList { modules, modules_by_addr, } } /// Return an iterator of `MinidumpUnloadedModules` whose address range covers `address`. pub fn modules_at_address( &self, address: u64, ) -> impl Iterator { // We have all of our modules sorted by memory range (base address being the // high-order value), and we need to get the range of values that overlap // with our target address. I'm a bit too tired to work out the exact // combination of binary searches to do this, so let's just use `filter` // for now (unloaded_modules should be a bounded list anyway). self.modules_by_addr .iter() .filter(move |(range, _idx)| range.contains(address)) .map(move |(_range, idx)| &self.modules[*idx]) } /// Iterate over the modules in arbitrary order. pub fn iter(&self) -> impl Iterator { self.modules.iter() } /// Iterate over the modules in order by memory address. pub fn by_addr(&self) -> impl Iterator { self.modules_by_addr .iter() .map(move |&(_, index)| &self.modules[index]) } /// Write a human-readable description of this `MinidumpModuleList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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: u32 = MINIDUMP_STREAM_TYPE::UnloadedModuleListStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let mut offset = 0; let raw_modules: Vec = read_ex_stream_list(&mut offset, bytes, endian)?; // read auxiliary data for each module 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 - raw.base_of_image) { // Bad image size. // TODO: just drop this module, keep the rest? return Err(Error::ModuleReadFailure); } modules.push(MinidumpUnloadedModule::read(raw, all, endian)?); } Ok(MinidumpUnloadedModuleList::from_modules(modules)) } } // Generates an accessor for a HANDLE_DESCRIPTOR field with the following syntax: // // * VERSION_NUMBER: FIELD_NAME -> FIELD_TYPE // // With the following definitions: // // * VERSION_NUMBER: The HANDLE_DESCRIPTOR version this field was introduced in // * FIELD_NAME: The name of the field to read // * FIELD_TYPE: The type of the field macro_rules! handle_descriptor_accessors { () => {}; (@def $name:ident $t:ty [$($variant:ident)+]) => { #[allow(unreachable_patterns)] pub fn $name(&self) -> Option<&$t> { match self { $( RawHandleDescriptor::$variant(ref raw) => Some(&raw.$name), )+ _ => None, } } }; (1: $name:ident -> $t:ty, $($rest:tt)*) => { handle_descriptor_accessors!(@def $name $t [HandleDescriptor HandleDescriptor2]); handle_descriptor_accessors!($($rest)*); }; (2: $name:ident -> $t:ty, $($rest:tt)*) => { handle_descriptor_accessors!(@def $name $t [HandleDescriptor2]); handle_descriptor_accessors!($($rest)*); }; } impl RawHandleDescriptor { handle_descriptor_accessors!( 1: handle -> u64, 1: type_name_rva -> md::RVA, 1: object_name_rva -> md::RVA, 1: attributes -> u32, 1: granted_access -> u32, 1: handle_count -> u32, 1: pointer_count -> u32, 2: object_info_rva -> md::RVA, ); } impl MinidumpHandleDescriptor { /// Write a human-readable description. pub fn print(&self, f: &mut T) -> io::Result<()> { macro_rules! write_simple_field { ($stream:ident, $field:ident, $format:literal) => { write!(f, " {:18}= ", 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_HANDLE_DESCRIPTOR")?; write_simple_field!(f, handle, "{:#x}"); write_simple_field!(f, type_name_rva, "{:#x}"); write_simple_field!(f, object_name_rva, "{:#x}"); write_simple_field!(f, attributes, "{:#x}"); write_simple_field!(f, granted_access, "{:#x}"); write_simple_field!(f, handle_count); write_simple_field!(f, pointer_count); write_simple_field!(f, object_info_rva, "{:#x}"); write!(f, " (type_name) = ")?; if let Some(type_name) = &self.type_name { writeln!(f, "{type_name:}")?; } else { writeln!(f, "(null)")?; }; write!(f, " (object_name) = ")?; if let Some(object_name) = &self.object_name { writeln!(f, "{object_name:}")?; } else { writeln!(f, "(null)")?; }; if self.object_infos.is_empty() { writeln!(f, " (object_info) = (null)")?; } else { for object_info in &self.object_infos { writeln!(f, " (object_info) = {object_info:}")?; } } writeln!(f) } fn read_string(offset: usize, ctx: HandleDescriptorContext) -> Option { let mut offset = offset; if offset != 0 { read_string_utf16(&mut offset, ctx.bytes, ctx.endianess) } else { None } } fn read_object_info( offset: usize, ctx: HandleDescriptorContext, ) -> Option { if offset == 0 { return None; } ctx.bytes .pread_with::(offset, ctx.endianess) .ok() .and_then(|raw| { Some(MinidumpHandleObjectInformation { raw: raw.clone(), info_type: md::MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE::from_u32( raw.info_type, )?, }) }) } } #[derive(Copy, Clone)] struct HandleDescriptorContext<'a> { bytes: &'a [u8], fieldsize: u32, endianess: scroll::Endian, } impl<'a> HandleDescriptorContext<'a> { fn new( bytes: &'a [u8], fieldsize: u32, endianess: scroll::Endian, ) -> HandleDescriptorContext<'a> { HandleDescriptorContext { bytes, fieldsize, endianess, } } } impl<'a> TryFromCtx<'a, HandleDescriptorContext<'a>> for MinidumpHandleDescriptor { type Error = scroll::Error; fn try_from_ctx( src: &'a [u8], ctx: HandleDescriptorContext, ) -> Result<(Self, usize), Self::Error> { const MINIDUMP_HANDLE_DESCRIPTOR_SIZE: u32 = mem::size_of::() as u32; const MINIDUMP_HANDLE_DESCRIPTOR_2_SIZE: u32 = mem::size_of::() as u32; match ctx.fieldsize { MINIDUMP_HANDLE_DESCRIPTOR_SIZE => { let raw = src.pread_with::(0, ctx.endianess)?; let type_name = Self::read_string(raw.type_name_rva as usize, ctx); let object_name = Self::read_string(raw.object_name_rva as usize, ctx); Ok(( MinidumpHandleDescriptor { raw: RawHandleDescriptor::HandleDescriptor(raw), type_name, object_name, object_infos: Vec::new(), }, ctx.fieldsize as usize, )) } MINIDUMP_HANDLE_DESCRIPTOR_2_SIZE => { let raw = src.pread_with::(0, ctx.endianess)?; let type_name = Self::read_string(raw.type_name_rva as usize, ctx); let object_name = Self::read_string(raw.object_name_rva as usize, ctx); let mut object_infos = Vec::::new(); let mut object_info_rva = raw.object_info_rva; while object_info_rva != 0 { if let Some(object_info) = Self::read_object_info(object_info_rva as usize, ctx) { object_info_rva = object_info.raw.next_info_rva; object_infos.push(object_info); } else { break; } } Ok(( MinidumpHandleDescriptor { raw: RawHandleDescriptor::HandleDescriptor2(raw), type_name, object_name, object_infos, }, ctx.fieldsize as usize, )) } _ => Err(scroll::Error::BadInput { size: ctx.fieldsize as usize, msg: "Unknown MINIDUMP_HANDLE_DESCRIPTOR type", }), } } } impl MinidumpHandleDataStream { /// Return an empty `MinidumpHandleDataStream`. pub fn new() -> MinidumpHandleDataStream { MinidumpHandleDataStream { handles: vec![] } } /// Create a `MinidumpHandleDataStream` from a list of `MinidumpHandleDescriptor`s. pub fn from_handles(handles: Vec) -> MinidumpHandleDataStream { MinidumpHandleDataStream { handles } } /// Iterate over the handles in the order contained in the minidump. pub fn iter(&self) -> impl Iterator { self.handles.iter() } /// Write a human-readable description. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MinidumpHandleDataStream handle_count = {} ", self.handles.len() )?; for (i, handle) in self.handles.iter().enumerate() { writeln!(f, "handle[{i}]")?; handle.print(f)?; } Ok(()) } } impl Default for MinidumpHandleDataStream { fn default() -> Self { Self::new() } } impl<'a> MinidumpStream<'a> for MinidumpHandleDataStream { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::HandleDataStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let mut offset = 0; let size_of_header = bytes .gread_with::(&mut offset, endian) .or(Err(Error::StreamReadFailure))?; let size_of_descriptor = bytes .gread_with::(&mut offset, endian) .or(Err(Error::StreamReadFailure))?; let number_of_descriptors = bytes .gread_with::(&mut offset, endian) .or(Err(Error::StreamReadFailure))?; let ctx = HandleDescriptorContext::new(all, size_of_descriptor, endian); let (number_of_entries, _) = ensure_count_in_bound( bytes, number_of_descriptors as usize, size_of_descriptor as usize, size_of_header as usize, )?; // Skip the header offset = size_of_header as usize; let mut descriptors = Vec::::with_capacity(number_of_entries); for _ in 0..number_of_entries { let descriptor: MinidumpHandleDescriptor = bytes .gread_with(&mut offset, ctx) .or(Err(Error::StreamReadFailure))?; descriptors.push(descriptor); } Ok(MinidumpHandleDataStream::from_handles(descriptors)) } } impl<'a> MinidumpMemory<'a> { pub fn read( desc: &md::MINIDUMP_MEMORY_DESCRIPTOR, data: &'a [u8], endian: scroll::Endian, ) -> Result, Error> { if desc.memory.rva == 0 || desc.memory.data_size == 0 { // Windows will sometimes emit null stack RVAs, indicating that // we need to lookup the address in a memory region. It's ok to // emit an error for that here, the thread processing code will // catch it. 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, endian, }) } /// Write a human-readable description of this `MinidumpMemory` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T, brief: bool) -> io::Result<()> { write!( f, "MINIDUMP_MEMORY_DESCRIPTOR start_of_memory_range = {:#x} memory.data_size = {:#x} memory.rva = {:#x} ", self.desc.start_of_memory_range, self.desc.memory.data_size, self.desc.memory.rva, )?; if !brief { writeln!(f, "Memory")?; self.print_contents(f)?; } writeln!(f) } } impl MinidumpMemory64<'_> { /// Write a human-readable description of this `MinidumpMemory64` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T, brief: bool) -> io::Result<()> { write!( f, "MINIDUMP_MEMORY_DESCRIPTOR64 start_of_memory_range = {:#x} memory.data_size = {:#x} ", self.desc.start_of_memory_range, self.desc.data_size, )?; if !brief { writeln!(f, "Memory")?; self.print_contents(f)?; } writeln!(f) } } impl<'a, Descriptor> MinidumpMemoryBase<'a, Descriptor> { /// Get `mem::size_of::()` bytes of memory at `addr` from this region. /// /// Return `None` if the requested address range falls out of the bounds /// of this memory region. pub fn get_memory_at_address(&self, addr: u64) -> Option where T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>, { let start = addr.checked_sub(self.base_address)? as usize; self.bytes.pread_with::(start, self.endian).ok() } /// Write the contents of this `MinidumpMemory` to `f` as a hex string. pub fn print_contents(&self, f: &mut T) -> io::Result<()> { const PARAGRAPH_SIZE: usize = 16; let mut offset = 0; for paragraph in self.bytes.chunks(PARAGRAPH_SIZE) { write!(f, " {offset:08x}: ")?; let mut byte_iter = paragraph.iter().fuse(); for _ in 0..PARAGRAPH_SIZE { if let Some(byte) = byte_iter.next() { write!(f, "{byte:02x} ")?; } else { write!(f, " ")?; } } for &byte in paragraph.iter() { let ascii_char = if !byte.is_ascii() || byte.is_ascii_control() { '.' } else { char::from(byte) }; write!(f, "{ascii_char}")?; } writeln!(f)?; offset += PARAGRAPH_SIZE; } Ok(()) } pub fn memory_range(&self) -> Option> { if self.size == 0 { return None; } Some(Range::new( self.base_address, self.base_address.checked_add(self.size)? - 1, )) } } impl<'a, 'mdmp> UnifiedMemory<'a, 'mdmp> { pub fn get_memory_at_address(&self, addr: u64) -> Option where T: TryFromCtx<'mdmp, scroll::Endian, [u8], Error = scroll::Error>, { match self { UnifiedMemory::Memory(this) => this.get_memory_at_address(addr), UnifiedMemory::Memory64(this) => this.get_memory_at_address(addr), } } pub fn memory_range(&self) -> Option> { match self { UnifiedMemory::Memory(this) => this.memory_range(), UnifiedMemory::Memory64(this) => this.memory_range(), } } pub fn bytes(&self) -> &'a [u8] { match self { UnifiedMemory::Memory(this) => this.bytes, UnifiedMemory::Memory64(this) => this.bytes, } } pub fn base_address(&self) -> u64 { match self { UnifiedMemory::Memory(this) => this.base_address, UnifiedMemory::Memory64(this) => this.base_address, } } pub fn size(&self) -> u64 { match self { UnifiedMemory::Memory(this) => this.size, UnifiedMemory::Memory64(this) => this.size, } } pub fn print_contents(&self, f: &mut T) -> io::Result<()> { match self { UnifiedMemory::Memory(this) => this.print_contents(f), UnifiedMemory::Memory64(this) => this.print_contents(f), } } pub fn print(&self, f: &mut T, brief: bool) -> io::Result<()> { match self { UnifiedMemory::Memory(this) => this.print(f, brief), UnifiedMemory::Memory64(this) => this.print(f, brief), } } } impl<'mdmp, Descriptor> MinidumpMemoryListBase<'mdmp, Descriptor> { /// Return an empty `MinidumpMemoryListBase`. pub fn new() -> MinidumpMemoryListBase<'mdmp, Descriptor> { MinidumpMemoryListBase { regions: vec![], regions_by_addr: RangeMap::new(), } } /// Create a `MinidumpMemoryListBase` from a list of `MinidumpMemoryBase`s. pub fn from_regions( regions: Vec>, ) -> MinidumpMemoryListBase<'mdmp, Descriptor> { let regions_by_addr = regions .iter() .enumerate() .map(|(i, region)| (region.memory_range(), i)) .into_rangemap_safe(); MinidumpMemoryListBase { regions, regions_by_addr, } } /// Return a `MinidumpMemoryBase` containing memory at `address`, if one exists. pub fn memory_at_address( &self, address: u64, ) -> Option<&MinidumpMemoryBase<'mdmp, Descriptor>> { self.regions_by_addr .get(address) .and_then(|&index| self.regions.get(index)) } /// Iterate over the memory regions in the order contained in the minidump. /// /// The iterator returns items of [MinidumpMemoryBase] as `&'slf MinidumpMemoryBase<'mdmp, Descriptor>`. /// That is the lifetime of the item is bound to the lifetime of the iterator itself /// (`'slf`), while the slice inside [MinidumpMemoryBase] pointing at the memory itself has /// the lifetime of the [Minidump] struct ('mdmp). pub fn iter<'slf>( &'slf self, ) -> impl Iterator> { self.regions.iter() } /// Iterate over the memory regions in order by memory address. pub fn by_addr<'slf>( &'slf self, ) -> impl Iterator> { self.regions_by_addr .ranges_values() .map(move |&(_, index)| &self.regions[index]) } } impl MinidumpMemoryList<'_> { /// Write a human-readable description of this `MinidumpMemoryList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T, brief: bool) -> 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, brief)?; } Ok(()) } } impl MinidumpMemory64List<'_> { /// Write a human-readable description of this `MinidumpMemory64List` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T, brief: bool) -> io::Result<()> { write!( f, "MinidumpMemory64List region_count = {} ", self.regions.len() )?; for (i, region) in self.regions.iter().enumerate() { writeln!(f, "region[{i}]")?; region.print(f, brief)?; } Ok(()) } } impl<'mdmp> UnifiedMemoryList<'mdmp> { pub fn memory_at_address<'slf>(&'slf self, address: u64) -> Option> { match self { UnifiedMemoryList::Memory(this) => { this.memory_at_address(address).map(UnifiedMemory::Memory) } UnifiedMemoryList::Memory64(this) => { this.memory_at_address(address).map(UnifiedMemory::Memory64) } } } pub fn iter<'slf>(&'slf self) -> impl Iterator> { let iter1 = if let UnifiedMemoryList::Memory(this) = self { Some(this.iter().map(UnifiedMemory::Memory)) } else { None }; let iter2 = if let UnifiedMemoryList::Memory64(this) = self { Some(this.iter().map(UnifiedMemory::Memory64)) } else { None }; iter1 .into_iter() .flatten() .chain(iter2.into_iter().flatten()) } /// Iterate over the memory regions in order by memory address. pub fn by_addr<'slf>(&'slf self) -> impl Iterator> { let iter1 = if let UnifiedMemoryList::Memory(this) = self { Some(this.by_addr().map(UnifiedMemory::Memory)) } else { None }; let iter2 = if let UnifiedMemoryList::Memory64(this) = self { Some(this.by_addr().map(UnifiedMemory::Memory64)) } else { None }; iter1 .into_iter() .flatten() .chain(iter2.into_iter().flatten()) } pub fn print(&self, f: &mut T, brief: bool) -> io::Result<()> { match self { UnifiedMemoryList::Memory(this) => this.print(f, brief), UnifiedMemoryList::Memory64(this) => this.print(f, brief), } } } impl Default for MinidumpMemoryListBase<'_, Descriptor> { fn default() -> Self { Self::new() } } impl<'a> MinidumpStream<'a> for MinidumpMemoryList<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MemoryListStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { let mut offset = 0; let descriptors: Vec = read_stream_list(&mut offset, bytes, endian)?; // read memory contents for each region let mut regions = Vec::with_capacity(descriptors.len()); for raw in descriptors.into_iter() { if let Ok(memory) = MinidumpMemory::read(&raw, all, endian) { regions.push(memory); } else { // Just skip over corrupt entries and try to limp along. continue; } } Ok(MinidumpMemoryList::from_regions(regions)) } } impl<'a> MinidumpStream<'a> for MinidumpMemory64List<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::Memory64ListStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { let mut offset = 0; let u: u64 = bytes .gread_with(&mut offset, endian) .or(Err(Error::StreamReadFailure))?; let mut rva: u64 = bytes .gread_with(&mut offset, endian) .or(Err(Error::StreamReadFailure))?; let (count, counted_size) = ensure_count_in_bound( bytes, u.try_into().map_err(|_| Error::StreamReadFailure)?, md::MINIDUMP_MEMORY_DESCRIPTOR64::size_with(&endian), offset, )?; if bytes.len() != counted_size { return Err(Error::StreamSizeMismatch { expected: counted_size, actual: bytes.len(), }); } let mut raw_entries = Vec::with_capacity(count); for _ in 0..count { let raw: md::MINIDUMP_MEMORY_DESCRIPTOR64 = bytes .gread_with(&mut offset, endian) .or(Err(Error::StreamReadFailure))?; raw_entries.push(raw); } let mut regions = Vec::with_capacity(raw_entries.len()); for raw in raw_entries { let start = rva; let end = rva .checked_add(raw.data_size) .ok_or(Error::StreamReadFailure)?; let bytes = all .get(start as usize..end as usize) .ok_or(Error::StreamReadFailure)?; regions.push(MinidumpMemory64 { desc: raw, base_address: raw.start_of_memory_range, size: raw.data_size, bytes, endian, }); rva = end; } Ok(MinidumpMemory64List::from_regions(regions)) } } impl<'a> MinidumpStream<'a> for MinidumpMemoryInfoList<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MemoryInfoListStream as u32; fn read( bytes: &'a [u8], _all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { let mut offset = 0; let raw_regions: Vec = read_ex_stream_list(&mut offset, bytes, endian)?; let regions = raw_regions .into_iter() .map(|raw| MinidumpMemoryInfo { allocation_protection: md::MemoryProtection::from_bits_truncate( raw.allocation_protection, ), state: md::MemoryState::from_bits_truncate(raw.state), protection: md::MemoryProtection::from_bits_truncate(raw.protection), ty: md::MemoryType::from_bits_truncate(raw._type), raw, _phantom: PhantomData, }) .collect(); Ok(MinidumpMemoryInfoList::from_regions(regions)) } } impl Default for MinidumpMemoryInfoList<'_> { fn default() -> Self { Self::new() } } impl<'mdmp> MinidumpMemoryInfoList<'mdmp> { /// Return an empty `MinidumpMemoryList`. pub fn new() -> MinidumpMemoryInfoList<'mdmp> { MinidumpMemoryInfoList { regions: vec![], regions_by_addr: RangeMap::new(), } } /// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s. pub fn from_regions(regions: Vec>) -> MinidumpMemoryInfoList<'mdmp> { let regions_by_addr = regions .iter() .enumerate() .map(|(i, region)| (region.memory_range(), i)) .into_rangemap_safe(); MinidumpMemoryInfoList { regions, regions_by_addr, } } /// Return a `MinidumpMemory` containing memory at `address`, if one exists. pub fn memory_info_at_address(&self, address: u64) -> Option<&MinidumpMemoryInfo<'mdmp>> { self.regions_by_addr .get(address) .map(|&index| &self.regions[index]) } /// Iterate over the memory regions in the order contained in the minidump. /// /// The iterator returns items of [MinidumpMemory] as `&'slf MinidumpMemory<'mdmp>`. /// That is the lifetime of the item is bound to the lifetime of the iterator itself /// (`'slf`), while the slice inside [MinidumpMemory] pointing at the memory itself has /// the lifetime of the [Minidump] struct ('mdmp). pub fn iter<'slf>(&'slf self) -> impl Iterator> { self.regions.iter() } /// Iterate over the memory regions in order by memory address. pub fn by_addr<'slf>(&'slf self) -> impl Iterator> { self.regions_by_addr .ranges_values() .map(move |&(_, index)| &self.regions[index]) } /// Write a human-readable description. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MinidumpMemoryInfoList region_count = {} ", self.regions.len() )?; for (i, region) in self.regions.iter().enumerate() { writeln!(f, "region[{i}]")?; region.print(f)?; } Ok(()) } } impl MinidumpMemoryInfo<'_> { /// Write a human-readable description. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MINIDUMP_MEMORY_INFO base_address = {:#x} allocation_base = {:#x} allocation_protection = {:#x} region_size = {:#x} state = {:#x} protection = {:#x} _type = {:#x} ", self.raw.base_address, self.raw.allocation_base, self.allocation_protection, self.raw.region_size, self.state, self.protection, self.ty, )?; writeln!(f) } pub fn memory_range(&self) -> Option> { if self.raw.region_size == 0 { return None; } Some(Range::new( self.raw.base_address, self.raw.base_address.checked_add(self.raw.region_size)? - 1, )) } /// Whether this memory range was readable. pub fn is_readable(&self) -> bool { self.protection.intersects( md::MemoryProtection::PAGE_READONLY | md::MemoryProtection::PAGE_READWRITE | md::MemoryProtection::PAGE_EXECUTE_READ | md::MemoryProtection::PAGE_EXECUTE_READWRITE, ) } /// Whether this memory range was writable. pub fn is_writable(&self) -> bool { self.protection.intersects( md::MemoryProtection::PAGE_READWRITE | md::MemoryProtection::PAGE_WRITECOPY | md::MemoryProtection::PAGE_EXECUTE_READWRITE | md::MemoryProtection::PAGE_EXECUTE_WRITECOPY, ) } /// Whether this memory range was executable. pub fn is_executable(&self) -> bool { self.protection.intersects( md::MemoryProtection::PAGE_EXECUTE | md::MemoryProtection::PAGE_EXECUTE_READ | md::MemoryProtection::PAGE_EXECUTE_READWRITE | md::MemoryProtection::PAGE_EXECUTE_WRITECOPY, ) } } impl<'a> MinidumpStream<'a> for MinidumpLinuxMaps<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxMaps as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { let maps = MemoryMaps::from_read(std::io::Cursor::new(bytes)).map_err(|e| { tracing::warn!("linux memory map read error: {e}"); Error::StreamReadFailure })?; Ok(MinidumpLinuxMaps::from_regions( maps.into_iter() .map(|map| MinidumpLinuxMapInfo { map, _phantom: PhantomData, }) .collect(), )) } } impl Default for MinidumpLinuxMaps<'_> { fn default() -> Self { Self::new() } } impl<'mdmp> MinidumpLinuxMaps<'mdmp> { /// Return an empty `MinidumpMemoryList`. pub fn new() -> Self { Self { regions: vec![], regions_by_addr: RangeMap::new(), } } /// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s. pub fn from_regions(regions: Vec>) -> Self { let regions_by_addr = regions .iter() .enumerate() .map(|(i, region)| (region.memory_range(), i)) .into_rangemap_safe(); Self { regions, regions_by_addr, } } /// Return a `MinidumpMemory` containing memory at `address`, if one exists. pub fn memory_info_at_address(&self, address: u64) -> Option<&MinidumpLinuxMapInfo<'mdmp>> { self.regions_by_addr .get(address) .map(|&index| &self.regions[index]) } /// Iterate over the memory regions in the order contained in the minidump. pub fn iter<'slf>(&'slf self) -> impl Iterator> { self.regions.iter() } /// Iterate over the memory regions in order by memory address. pub fn by_addr<'slf>(&'slf self) -> impl Iterator> { self.regions_by_addr .ranges_values() .map(move |&(_, index)| &self.regions[index]) } /// Write a human-readable description. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MinidumpLinuxMapInfo region_count = {} ", self.regions.len() )?; for (i, region) in self.regions.iter().enumerate() { writeln!(f, "region[{i}]")?; region.print(f)?; } Ok(()) } // Return number of memory mappings pub fn memory_map_count(&self) -> usize { self.regions.len() } } impl MinidumpLinuxMapInfo<'_> { /// Write a human-readable description of this. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MINIDUMP_LINUX_MAP_INFO base_address = {:#x} final_address = {:#x} kind = {:#?} permissions = {} ", self.map.address.0, self.map.address.1, self.map.pathname, self.map.perms.as_str() )?; writeln!(f) } pub fn memory_range(&self) -> Option> { // final address is inclusive afaik if self.map.address.0 > self.map.address.1 { return None; } Some(Range::new(self.map.address.0, self.map.address.1)) } /// Whether this memory range was readable. pub fn is_readable(&self) -> bool { self.map.perms.contains(MMPermissions::READ) } /// Whether this memory range was writable. pub fn is_writable(&self) -> bool { self.map.perms.contains(MMPermissions::WRITE) } /// Whether this memory range was executable. pub fn is_executable(&self) -> bool { self.map.perms.contains(MMPermissions::EXECUTE) } #[cfg(test)] pub fn from_line(bytes: &[u8]) -> Option { let map = MemoryMaps::from_read(std::io::Cursor::new(bytes)) .ok()? .into_iter() .next()?; Some(MinidumpLinuxMapInfo { map, _phantom: PhantomData, }) } } impl Default for UnifiedMemoryInfoList<'_> { fn default() -> Self { Self::Info(MinidumpMemoryInfoList::default()) } } impl<'a> UnifiedMemoryInfoList<'a> { /// Take two potential memory info sources and create an interface that unifies them. /// /// Under normal circumstances a minidump should only contain one of these. /// If both are provided, one will be arbitrarily preferred to attempt to /// make progress. pub fn new( info: Option>, maps: Option>, ) -> Option { match (info, maps) { (Some(info), Some(_maps)) => { warn!("UnifiedMemoryInfoList got both kinds of info! (using InfoList)"); // Just pick one I guess? Some(Self::Info(info)) } (Some(info), None) => Some(Self::Info(info)), (None, Some(maps)) => Some(Self::Maps(maps)), (None, None) => None, } } /// Return a `MinidumpMemory` containing memory at `address`, if one exists. pub fn memory_info_at_address(&self, address: u64) -> Option> { match self { Self::Info(info) => info .memory_info_at_address(address) .map(UnifiedMemoryInfo::Info), Self::Maps(maps) => maps .memory_info_at_address(address) .map(UnifiedMemoryInfo::Map), } } /// Iterate over the memory regions in the order contained in the minidump. pub fn iter(&self) -> impl Iterator> { // Use `flat_map` and `chain` to create a unified stream of the two types // (only one of which will conatin any values). Note that we are using // the fact that `Option` can be iterated (producing 1 to 0 values). let info = self .info() .into_iter() .flat_map(|info| info.iter().map(UnifiedMemoryInfo::Info)); let maps = self .maps() .into_iter() .flat_map(|maps| maps.iter().map(UnifiedMemoryInfo::Map)); info.chain(maps) } /// Iterate over the memory regions in order by memory address. pub fn by_addr(&self) -> impl Iterator> { let info = self .info() .into_iter() .flat_map(|info| info.by_addr().map(UnifiedMemoryInfo::Info)); let maps = self .maps() .into_iter() .flat_map(|maps| maps.by_addr().map(UnifiedMemoryInfo::Map)); info.chain(maps) } /// Write a human-readable description of this `MinidumpMemoryList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T) -> io::Result<()> { match self { Self::Info(info) => info.print(f), Self::Maps(maps) => maps.print(f), } } /// Get the [`MinidumpLinuxMaps`] contained inside, if it exists. /// /// Potentially useful for doing a more refined analysis in specific places. pub fn maps(&self) -> Option<&MinidumpLinuxMaps<'a>> { match &self { Self::Maps(maps) => Some(maps), Self::Info(_) => None, } } /// Get the [`MinidumpMemoryInfoList`] contained inside, if it exists. /// /// Potentially useful for doing a more refined analysis in specific places. pub fn info(&self) -> Option<&MinidumpMemoryInfoList<'a>> { match &self { Self::Maps(_) => None, Self::Info(info) => Some(info), } } } /// Declares functions which will forward to functions of the same name on the inner /// `UnifiedMemoryInfo` members. macro_rules! unified_memory_forward { () => {}; ( $(#[$attr:meta])* $vis:vis fn $name:ident $(< $($t_param:ident : $t_type:path),* >)? ( &self $(, $param:ident : $type:ty)* ) -> $ret:ty ; $($rest:tt)* ) => { $(#[$attr])* $vis fn $name $(< $($t_param : $t_type),* >)? (&self $(, $param : $type)*) -> $ret { match self { Self::Info(info) => info.$name($($param),*), Self::Map(map) => map.$name($($param),*), } } unified_memory_forward!($($rest)*); }; } impl UnifiedMemoryInfo<'_> { unified_memory_forward! { /// Write a human-readable description. pub fn print(&self, f: &mut T) -> io::Result<()>; /// The range of memory this info applies to. pub fn memory_range(&self) -> Option>; /// Whether this memory range was readable. pub fn is_readable(&self) -> bool; /// Whether this memory range was writable. pub fn is_writable(&self) -> bool; /// Whether this memory range was executable. pub fn is_executable(&self) -> bool; } } impl<'a> MinidumpThread<'a> { pub fn context( &self, system_info: &MinidumpSystemInfo, misc: Option<&MinidumpMiscInfo>, ) -> Option> { MinidumpContext::read(self.context?, self.endian, system_info, misc) .ok() .map(Cow::Owned) } pub fn stack_memory<'mem>( &'mem self, memory_list: &'mem UnifiedMemoryList<'a>, ) -> Option> { self.stack.as_ref().map(UnifiedMemory::Memory).or_else(|| { // Sometimes the raw.stack RVA is null/busted, but the start_of_memory_range // value is correct. So if the `read` fails, try resolving start_of_memory_range // with the MinidumpMemoryList. (This seems to specifically be a problem with // Windows minidumps.) let stack_addr = self.raw.stack.start_of_memory_range; let memory = memory_list.memory_at_address(stack_addr)?; Some(memory) }) } /// Write a human-readable description of this `MinidumpThread` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print( &self, f: &mut T, memory: Option<&UnifiedMemoryList<'a>>, system: Option<&MinidumpSystemInfo>, misc: Option<&MinidumpMiscInfo>, brief: bool, ) -> 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(system_info) = system { if let Some(ctx) = self.context(system_info, misc) { ctx.print(f)?; } else { write!(f, " (no context)\n\n")?; } } else { write!(f, " (no context)\n\n")?; } if brief { writeln!(f)?; return Ok(()); } let pointer_width = system.map_or(PointerWidth::Unknown, |info| info.cpu.pointer_width()); // We might not need any memory, so try to limp forward with an empty // MemoryList if we don't have one. let dummy_memory = UnifiedMemoryList::default(); let memory = memory.unwrap_or(&dummy_memory); if let Some(ref stack) = self.stack_memory(memory) { writeln!(f, "Stack")?; // For printing purposes, we'll treat any unknown CPU type as 64-bit let chunk_size: usize = pointer_width.size_in_bytes().unwrap_or(8).into(); let mut offset = 0; for chunk in stack.bytes().chunks_exact(chunk_size) { write!(f, " {offset:#010x}: ")?; match pointer_width { PointerWidth::Bits32 => { let value = match self.endian { scroll::Endian::Little => u32::from_le_bytes(chunk.try_into().unwrap()), scroll::Endian::Big => u32::from_be_bytes(chunk.try_into().unwrap()), }; write!(f, "{value:#010x}")?; } PointerWidth::Unknown | PointerWidth::Bits64 => { let value = match self.endian { scroll::Endian::Little => u64::from_le_bytes(chunk.try_into().unwrap()), scroll::Endian::Big => u64::from_be_bytes(chunk.try_into().unwrap()), }; write!(f, "{value:#018x}")?; } } writeln!(f)?; offset += chunk_size; } } else { writeln!(f, "No stack")?; } writeln!(f)?; Ok(()) } /// Gets the last error code the thread recorded, just like win32's GetLastError. /// /// The value is heuristically converted into a CrashReason because that's our /// general error code handling machinery, even though this may not actually be /// the reason for the crash! pub fn last_error(&self, cpu: Cpu, memory: &UnifiedMemoryList) -> Option { // Early hacky implementation: rather than implementing all the TEB layouts, // just use the fact that we know the value we want is a 13-pointers offset // from the start of the TEB. let teb = self.raw.teb; let pointer_width = cpu.pointer_width().size_in_bytes()? as u64; let offset = pointer_width.checked_mul(13)?; let addr = teb.checked_add(offset)?; let val: u32 = memory .memory_at_address(addr)? .get_memory_at_address(addr)?; Some(CrashReason::from_windows_error(val)) } } impl<'a> MinidumpStream<'a> for MinidumpThreadList<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ThreadListStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { let mut offset = 0; let raw_threads: Vec = 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()); // Defer parsing of this to the `context` method, where we will have access // to other streams that are required to parse a context properly. let context = location_slice(all, &raw.thread_context).ok(); // Try to get the stack memory here, but the `stack_memory` method will // attempt a fallback method with access to other streams. let stack = MinidumpMemory::read(&raw.stack, all, endian).ok(); threads.push(MinidumpThread { raw, context, stack, endian, }); } Ok(MinidumpThreadList { threads, thread_ids, }) } } impl<'a> MinidumpThreadList<'a> { /// Get the thread with id `id` from this thread list if it exists. pub fn get_thread(&self, id: u32) -> Option<&MinidumpThread<'a>> { self.thread_ids.get(&id).map(|&index| &self.threads[index]) } /// Write a human-readable description of this `MinidumpThreadList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print( &self, f: &mut T, memory: Option<&UnifiedMemoryList<'a>>, system: Option<&MinidumpSystemInfo>, misc: Option<&MinidumpMiscInfo>, brief: bool, ) -> 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, memory, system, misc, brief)?; } Ok(()) } } // implement print for MinidumpThreadInfo impl MinidumpThreadInfo { /// Write a human-readable description of this `MinidumpThreadInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, r#"MINIDUMP_THREAD_INFO thread_id = {:#x} dump_flags = {:#x} dump_error = {:#x} exit_status = {:#x} create_time = {:#x} exit_time = {:#x} kernel_time = {:#x} user_time = {:#x} start_address = {:#x} affinity = {:#x} "#, self.raw.thread_id, self.raw.dump_flags, self.raw.dump_error, self.raw.exit_status, self.raw.create_time, self.raw.exit_time, self.raw.kernel_time, self.raw.user_time, self.raw.start_address, self.raw.affinity, )?; Ok(()) } } impl Default for MinidumpThreadInfoList { fn default() -> Self { Self::new() } } impl MinidumpThreadInfoList { /// Return an empty `MinidumpThreadInfoList`. pub fn new() -> MinidumpThreadInfoList { MinidumpThreadInfoList { thread_infos: vec![], thread_ids: HashMap::new(), } } /// Get the thread info with id `id` from this thread info list if it exists. pub fn get_thread_info(&self, id: u32) -> Option<&MinidumpThreadInfo> { self.thread_ids .get(&id) .map(|&index| &self.thread_infos[index]) } /// Write a human-readable description of this `MinidumpModuleList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MinidumpThreadInfoList thread_info_count = {} ", self.thread_infos.len() )?; for (i, thread_info) in self.thread_infos.iter().enumerate() { writeln!(f, "thread info[{i}]")?; thread_info.print(f)?; } Ok(()) } } impl<'a> MinidumpStream<'a> for MinidumpThreadInfoList { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ThreadInfoListStream as u32; fn read( bytes: &'a [u8], _all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let mut offset = 0; let raw_thread_infos: Vec = read_ex_stream_list(&mut offset, bytes, endian)?; let mut thread_infos = Vec::with_capacity(raw_thread_infos.len()); let mut thread_ids = HashMap::with_capacity(raw_thread_infos.len()); for raw in raw_thread_infos.into_iter() { thread_ids.insert(raw.thread_id, thread_infos.len()); thread_infos.push(MinidumpThreadInfo { raw }); } Ok(MinidumpThreadInfoList { thread_infos, thread_ids, }) } } impl MinidumpStream<'_> for MinidumpSystemInfo { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::SystemInfoStream as u32; fn read( bytes: &[u8], all: &[u8], endian: scroll::Endian, system_info: Option<&MinidumpSystemInfo>, ) -> Result { if let Some(system_info) = system_info { return Ok(system_info.clone()); } 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, all, endian); // self.raw.cpu.data is actually a union which we resolve here. let cpu_info = match cpu { Cpu::X86 | Cpu::X86_64 => { let mut cpu_info = String::new(); if let Cpu::X86 = cpu { // The vendor's ID is an ascii string but we need to flatten out the u32's into u8's 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| IntoIterator::into_iter(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))?; // There is no good list of implementer id values, but the following // pages provide some help: // http://comments.gmane.org/gmane.linux.linaro.devel/6903 // http://forum.xda-developers.com/archive/index.php/t-480226.html 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); // Try to extract out known vendor/part names from the cpuid, // falling back to just reporting the raw value. 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({vendor_id:#x})").unwrap(); } if let Some(&(_, part)) = parts.iter().find(|&&(id, _)| id == part_id) { write!(&mut cpu_info, " {part}").unwrap(); } else { write!(&mut cpu_info, " part({part_id:#x})").unwrap(); } } // Report all the known hardware features. let elf_hwcaps = md::ArmElfHwCaps::from_bits_truncate(arm_info.elf_hwcaps); if !elf_hwcaps.is_empty() { cpu_info.push_str(" features: "); // Iterator::intersperse is still unstable, so do it manually 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 { /// Write a human-readable description of this `MinidumpSystemInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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(""), )?; // TODO: cpu info etc Ok(()) } /// If the minidump was generated on: /// - Windows: Returns the the name of the Service Pack. /// - macOS: Returns the product build number. /// - Linux: Returns the contents of `uname -srvmo`. pub fn csd_version(&self) -> Option> { self.csd_version.as_deref().map(Cow::Borrowed) } /// Returns a string describing the cpu's vendor and model. pub fn cpu_info(&self) -> Option> { self.cpu_info.as_deref().map(Cow::Borrowed) } /// Strings identifying the version and build number of the operating /// system. Returns a tuple in the format of (version, build number). This /// may be useful to use if the minidump was created on a Linux machine and /// is an producing empty-ish version number (0.0.0). /// /// Tries to parse the version number from the build if it cannot be found /// in the version string. If the stream already contains a valid version /// number or parsing from the build string fails, this will return what's /// directly stored in the stream. pub fn os_parts(&self) -> (String, Option) { let os_version = format!( "{}.{}.{}", self.raw.major_version, self.raw.minor_version, self.raw.build_number ); let os_build = self .csd_version() .map(|v| v.trim().to_owned()) .filter(|v| !v.is_empty()); if md::PlatformId::from_u32(self.raw.platform_id) != Some(md::PlatformId::Linux) || os_version != "0.0.0" { return (os_version, os_build); } // Try to parse the Linux build string. Breakpad and Crashpad run // `uname -srvmo` to generate it. The string follows this structure: // "Linux [version] [build...] [arch] Linux/GNU" where the Linux/GNU // bit may not always be present. let raw_build = self.csd_version().unwrap_or(Cow::Borrowed("")); let mut parts = raw_build.split(' '); let version = parts.nth(1).unwrap_or("0.0.0"); let _arch_or_os = parts.next_back().unwrap_or_default(); if _arch_or_os == "Linux/GNU" { let _arch = parts.next_back(); } let build = parts.collect::>().join(" "); if version == "0.0.0" { (os_version, os_build) } else { (version.into(), Some(build)) } } } // Generates an accessor for a MISC_INFO field with two possible syntaxes: // // * VERSION_NUMBER: FIELD_NAME -> FIELD_TYPE // * VERSION_NUMBER: FIELD_NAME if FLAG -> FIELD_TYPE // // With the following definitions: // // * VERSION_NUMBER: The MISC_INFO version this field was introduced in // * FIELD_NAME: The name of the field to read // * FLAG: A MiscInfoFlag that defines if this field contains valid data // * FIELD_TYPE: The type of the field 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 { // Fields are grouped by the flag that guards them. 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 MinidumpStream<'_> for MinidumpMiscInfo { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MiscInfoStream as u32; fn read( bytes: &[u8], _all: &[u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { // The misc info has gone through several revisions, so try to read the largest known // struct possible. 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) } } // Generates an accessor for a MAC_CRASH_INFO field with two possible syntaxes: // // * VERSION_NUMBER: FIELD_NAME -> FIELD_TYPE // * VERSION_NUMBER: string FIELD_NAME -> FIELD_TYPE // // With the following definitions: // // * VERSION_NUMBER: The MAC_CRASH_INFO version this field was introduced in // * FIELD_NAME: The name of the field to read // * FIELD_TYPE: The type of the field // // The "string" mode will retrieve the field from the variant's _RECORD_STRINGS // struct, while the other mode will retrieve it from the variant's _RECORD // struct. // // In both cases, None will be yielded if the value is null/empty. macro_rules! mac_crash_accessors { () => {}; (@deffixed $name:ident $t:ty [$($variant:ident)+]) => { #[allow(unreachable_patterns)] pub fn $name(&self) -> Option<&$t> { match self { $( RawMacCrashInfo::$variant(ref fixed, _) => { if fixed.$name == 0 { None } else { Some(&fixed.$name) } }, )+ _ => None, } } }; (@defstrings $name:ident $t:ty [$($variant:ident)+]) => { #[allow(unreachable_patterns)] pub fn $name(&self) -> Option<&$t> { match self { $( RawMacCrashInfo::$variant(_, ref strings) => { if strings.$name.is_empty() { None } else { Some(&*strings.$name) } } )+ _ => None, } } }; (1: $name:ident -> $t:ty, $($rest:tt)*) => { mac_crash_accessors!(@deffixed $name $t [V1 V4 V5]); mac_crash_accessors!($($rest)*); }; (1: string $name:ident -> $t:ty, $($rest:tt)*) => { mac_crash_accessors!(@defstrings $name $t [V1 V4 V5]); mac_crash_accessors!($($rest)*); }; (4: $name:ident -> $t:ty, $($rest:tt)*) => { mac_crash_accessors!(@deffixed $name $t [V4 V5]); mac_crash_accessors!($($rest)*); }; (4: string $name:ident -> $t:ty, $($rest:tt)*) => { mac_crash_accessors!(@defstrings $name $t [V4 V5]); mac_crash_accessors!($($rest)*); }; (5: $name:ident -> $t:ty, $($rest:tt)*) => { mac_crash_accessors!(@deffixed $name $t [V5]); mac_crash_accessors!($($rest)*); }; (5: string $name:ident -> $t:ty, $($rest:tt)*) => { mac_crash_accessors!(@defstrings $name $t [V5]); mac_crash_accessors!($($rest)*); }; } impl RawMacCrashInfo { // Fields are grouped by the flag that guards them. mac_crash_accessors!( 1: version -> u64, 4: thread -> u64, 4: dialog_mode -> u64, 4: string module_path -> str, 4: string message -> str, 4: string signature_string -> str, 4: string backtrace -> str, 4: string message2 -> str, 5: abort_cause -> u64, ); } impl MinidumpStream<'_> for MinidumpMacCrashInfo { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozMacosCrashInfoStream as u32; fn read( bytes: &[u8], all: &[u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { // Get the main header of the stream let header: md::MINIDUMP_MAC_CRASH_INFO = bytes .pread_with(0, endian) .or(Err(Error::StreamReadFailure))?; let strings_offset = header.record_start_size as usize; let mut prev_version = None; let mut infos = Vec::new(); // We use `take` here to better handle a corrupt record_count that is larger than the // maximum supported size. let records = header.records.iter().take(header.record_count as usize); for record_location in records { // Peek the V1 version to get the `version` field let record_slice = location_slice(all, record_location)?; let base: md::MINIDUMP_MAC_CRASH_INFO_RECORD = record_slice .pread_with(0, endian) .or(Err(Error::StreamReadFailure))?; // The V1 version also includes the stream type again, but that's // not really important, so just warn about it and keep going. if base.stream_type != header.stream_type as u64 { warn!( "MozMacosCrashInfoStream records don't have the right stream type? {}", base.stream_type ); } // Make sure every record has the same version, because they have to // share their strings_offset which make heterogeneous records impossible. if let Some(prev_version) = prev_version { if prev_version != base.version { warn!( "MozMacosCrashInfoStream had two different versions ({} != {})", prev_version, base.version ); return Err(Error::VersionMismatch); } } prev_version = Some(base.version); // Now actually read the full record and its strings for the version macro_rules! do_read { ($base_version:expr, $strings_offset:expr, $infos:ident, $(($version:expr, $fixed:ty, $strings:ty, $variant:ident),)+) => {$( if $base_version >= $version { let offset = &mut 0; let fixed: $fixed = record_slice .gread_with(offset, endian) .or(Err(Error::StreamReadFailure))?; // Sanity check that we haven't blown past where the strings start. if *offset > $strings_offset { warn!("MozMacosCrashInfoStream's record_start_size was too small! ({})", $strings_offset); return Err(Error::StreamReadFailure); } // We could be handling a newer version of the format than we know // how to support, so jump to where the strings start, potentially // skipping over some unknown fields. *offset = $strings_offset; let num_strings = <$strings>::num_strings(); let mut strings = <$strings>::default(); // Read out all the strings we know about for i in 0..num_strings { let string = read_cstring_utf8(offset, record_slice) .ok_or(Error::StreamReadFailure)?; strings.set_string(i, string); } // If this is a newer version, there may be some extra variable length // data in this record, but we don't know what it is, so don't try to parse it. infos.push(RawMacCrashInfo::$variant(fixed, strings)); continue; } )+} } do_read!( base.version, strings_offset, infos, ( 5, md::MINIDUMP_MAC_CRASH_INFO_RECORD_5, md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_5, V5 ), ( 4, md::MINIDUMP_MAC_CRASH_INFO_RECORD_4, md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_4, V4 ), ( 1, md::MINIDUMP_MAC_CRASH_INFO_RECORD, md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS, V1 ), ); } Ok(MinidumpMacCrashInfo { raw: infos }) } } impl MinidumpMacCrashInfo { /// Write a human-readable description of this `MinidumpMacCrashInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T) -> io::Result<()> { macro_rules! write_simple_field { ($stream:ident, $field:ident, $idx:ident, $format:literal) => { write!(f, " {:18}= ", stringify!($field))?; match self.raw[$idx].$field() { Some($field) => { writeln!(f, $format, $field)?; } None => writeln!(f)?, } }; ($stream:ident, $field:ident, $idx:ident) => { write_simple_field!($stream, $field, $idx, "{}"); }; } writeln!(f, "MINIDUMP_MAC_CRASH_INFO")?; writeln!(f, " num_records = {}", self.raw.len())?; for i in 0..self.raw.len() { writeln!(f)?; writeln!(f, " RECORD[{i}] ")?; write_simple_field!(f, version, i); write_simple_field!(f, thread, i); write_simple_field!(f, dialog_mode, i, "{:x}"); write_simple_field!(f, module_path, i); write_simple_field!(f, message, i); write_simple_field!(f, signature_string, i); write_simple_field!(f, backtrace, i); write_simple_field!(f, message2, i); write_simple_field!(f, abort_cause, i, "{:x}"); } writeln!(f)?; Ok(()) } } impl MinidumpStream<'_> for MinidumpMacBootargs { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozMacosBootargsStream as u32; fn read( bytes: &[u8], all: &[u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let raw: md::MINIDUMP_MAC_BOOTARGS = bytes .pread_with(0, endian) .or(Err(Error::StreamReadFailure))?; let stream_type = raw.stream_type; if stream_type != Self::STREAM_TYPE { warn!( "MozMacosBootargsStream record doesn't have the right stream type? {}", Self::STREAM_TYPE ); } let mut bootargs_offset = raw.bootargs as usize; let bootargs = read_string_utf16(&mut bootargs_offset, all, endian); Ok(MinidumpMacBootargs { raw, bootargs }) } } impl MinidumpMacBootargs { pub fn print(&self, f: &mut T) -> io::Result<()> { writeln!( f, "mac_boot_args = {}", self.bootargs.as_deref().unwrap_or("") )?; writeln!(f)?; Ok(()) } } impl<'a> MinidumpStream<'a> for MinidumpLinuxLsbRelease<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxLsbRelease as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { Ok(Self { data: bytes }) } } impl<'a> MinidumpStream<'a> for MinidumpLinuxEnviron<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxEnviron as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { Ok(Self { data: bytes }) } } impl<'a> MinidumpStream<'a> for MinidumpLinuxProcStatus<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxProcStatus as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { Ok(Self { data: bytes }) } } impl<'a> MinidumpStream<'a> for MinidumpLinuxProcLimits<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozLinuxLimits as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { Ok(Self { data: bytes }) } } impl<'a> MinidumpStream<'a> for MinidumpSoftErrors<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozSoftErrors as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { let json_str = std::str::from_utf8(bytes).map_err(|_| Error::DataError)?; Ok(Self { json_str }) } } impl AsRef for MinidumpSoftErrors<'_> { fn as_ref(&self) -> &str { self.json_str } } impl<'a> MinidumpStream<'a> for MinidumpLinuxCpuInfo<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxCpuInfo as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result, Error> { Ok(Self { data: bytes }) } } impl<'a> MinidumpLinuxCpuInfo<'a> { /// Get an iterator over the key-value pairs stored in the `/proc/cpuinfo` dump. /// /// Keys and values are `trim`ed of leading/trailing spaces, and if a key /// or value was surrounded by quotes ("like this"), the quotes will be /// stripped. pub fn iter(&self) -> impl Iterator { linux_list_iter(self.data, b':') } /// Get the raw bytes of the `/proc/cpuinfo` dump. pub fn raw_bytes(&self) -> Cow<'a, [u8]> { Cow::Borrowed(self.data) } } impl<'a> MinidumpLinuxEnviron<'a> { /// Get an iterator over the key-value pairs stored in the `/proc/self/environ` dump. /// /// Keys and values are `trim`ed of leading/trailing spaces, and if a key /// or value was surrounded by quotes ("like this"), the quotes will be /// stripped. pub fn iter(&self) -> impl Iterator { linux_list_iter(self.data, b'=') } /// Get the raw bytes of the `/proc/self/environ` dump. pub fn raw_bytes(&self) -> Cow<'a, [u8]> { Cow::Borrowed(self.data) } } impl<'a> MinidumpLinuxProcStatus<'a> { /// Get an iterator over the key-value pairs stored in the `/proc/self/status` dump. /// /// Keys and values are `trim`ed of leading/trailing spaces, and if a key /// or value was surrounded by quotes ("like this"), the quotes will be /// stripped. pub fn iter(&self) -> impl Iterator { linux_list_iter(self.data, b':') } /// Get the raw bytes of the `/proc/self/status` dump. pub fn raw_bytes(&self) -> Cow<'a, [u8]> { Cow::Borrowed(self.data) } } impl<'a> MinidumpLinuxProcLimits<'a> { /// Get an iterator over the key-value pairs stored in the `/proc/self/limits` dump. /// /// Keys and values are `trim`ed of leading/trailing spaces, and if a key /// or value was surrounded by quotes ("like this"), the quotes will be /// stripped. pub fn iter(&self) -> impl Iterator { LinuxOsStr::from_bytes(self.data).lines() } /// Get the raw bytes of the `/proc/self/limits` dump. pub fn raw_bytes(&self) -> Cow<'a, [u8]> { Cow::Borrowed(self.data) } } impl<'a> MinidumpLinuxLsbRelease<'a> { /// Get an iterator over the key-value pairs stored in the `/etc/lsb-release` dump. /// /// Keys and values are `trim`ed of leading/trailing spaces, and if a key /// or value was surrounded by quotes ("like this"), the quotes will be /// stripped. pub fn iter(&self) -> impl Iterator { linux_list_iter(self.data, b'=') } /// Get the raw bytes of the `/etc/lsb-release` dump. pub fn raw_bytes(&self) -> Cow<'a, [u8]> { Cow::Borrowed(self.data) } } fn systemtime_from_timestamp(timestamp: u64) -> Option { SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(timestamp)) } impl MinidumpMiscInfo { pub fn process_create_time(&self) -> Option { self.raw .process_create_time() .and_then(|t| systemtime_from_timestamp(*t as u64)) } /// Write a human-readable description of this `MinidumpMiscInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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) { let feature_name = format!("{feature:?}"); write!(f, " feature {i:2} - {feature_name:22}: ")?; } else { write!(f, " feature {i:2} - (unknown) : ")?; } writeln!(f, " offset {:4}, size {:4}", feature.offset, feature.size)?; } } None => writeln!(f, "(invalid)")?, } write_simple_field!(f, process_cookie); writeln!(f)?; Ok(()) } } impl MinidumpStream<'_> for MinidumpBreakpadInfo { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::BreakpadInfoStream as u32; fn read( bytes: &[u8], _all: &[u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { 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(what: &Option) -> Cow<'_, str> { match *what { Some(ref val) => Cow::Owned(format!("{val:#x}")), None => Cow::Borrowed("(invalid)"), } } impl MinidumpBreakpadInfo { /// Write a human-readable description of this `MinidumpBreakpadInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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 { /// Get a `CrashReason` from a `MINIDUMP_EXCEPTION_STREAM` for a given `Os`. fn from_exception(raw: &md::MINIDUMP_EXCEPTION_STREAM, os: Os, cpu: Cpu) -> CrashReason { let record = &raw.exception_record; let exception_code = record.exception_code; let exception_flags = record.exception_flags; let reason = match os { Os::MacOs | Os::Ios => Self::from_mac_exception(raw, cpu), Os::Linux | Os::Android => Self::from_linux_exception(raw, cpu), Os::Windows => Self::from_windows_exception(raw, cpu), _ => None, }; // Default to a totally generic unknown error reason.unwrap_or(CrashReason::Unknown(exception_code, exception_flags)) } /// Heuristically identifies what kind of windows exception code this is. /// /// Augments [`CrashReason::from_windows_error`] by also including /// `ExceptionCodeWindows`. Appropriate for an actual crash reason. pub fn from_windows_code(exception_code: u32) -> CrashReason { if let Some(err) = err::ExceptionCodeWindows::from_u32(exception_code) { Self::WindowsGeneral(err) } else { Self::from_windows_error(exception_code) } } /// Heuristically identifies what kind of windows error code this is. /// /// Appropriate for things like LastErrorValue() which may be non-fatal. pub fn from_windows_error(error_code: u32) -> CrashReason { if let Some(err) = err::WinErrorWindows::from_u32(error_code) { Self::WindowsWinError(err) } else if let Some(err) = err::NtStatusWindows::from_u32(error_code) { Self::WindowsNtStatus(err) } else if let Some(err) = Self::from_windows_error_with_facility(error_code) { err } else { Self::WindowsUnknown(error_code) } } pub fn from_windows_error_with_facility(error_code: u32) -> Option { static SEVERITY_MASK: u32 = 0xf0000000; static FACILITY_MASK: u32 = 0x0fff0000; static ERROR_MASK: u32 = 0x0000ffff; if (error_code & SEVERITY_MASK) != 0 { // This could be an NTSTATUS or HRESULT code of a specific facility if let Some(facility) = err::WinErrorFacilityWindows::from_u32((error_code & FACILITY_MASK) >> 16) { if let Some(error) = err::WinErrorWindows::from_u32(error_code & ERROR_MASK) { return Some(Self::WindowsWinErrorWithFacility(facility, error)); } } } None } pub fn from_windows_exception( raw: &md::MINIDUMP_EXCEPTION_STREAM, _cpu: Cpu, ) -> Option { use err::ExceptionCodeWindows; let record = &raw.exception_record; let info = &record.exception_information; let exception_code = record.exception_code; let mut reason = CrashReason::from_windows_code(exception_code); // Refine the output for error codes that have more info match reason { CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION) => { // For EXCEPTION_ACCESS_VIOLATION, Windows puts the address that // caused the fault in exception_information[1]. // exception_information[0] is 0 if the violation was caused by // an attempt to read data, 1 if it was an attempt to write data, // and 8 if this was a data execution violation. // This information is useful in addition to the code address, which // will be present in the crash thread's instruction field anyway. if record.number_parameters >= 1 { // NOTE: address := info[1]; if let Some(ty) = err::ExceptionCodeWindowsAccessType::from_u64(info[0]) { reason = CrashReason::WindowsAccessViolation(ty); } } } CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR) => { // For EXCEPTION_IN_PAGE_ERROR, Windows puts the address that // caused the fault in exception_information[1]. // exception_information[0] is 0 if the violation was caused by // an attempt to read data, 1 if it was an attempt to write data, // and 8 if this was a data execution violation. // exception_information[2] contains the underlying NTSTATUS code, // which is the explanation for why this error occured. // This information is useful in addition to the code address, which // will be present in the crash thread's instruction field anyway. if record.number_parameters >= 3 { // NOTE: address := info[1]; // The status code is 32-bits wide, ignore the upper 32 bits let nt_status = info[2] & 0xffff_ffff; if let Some(ty) = err::ExceptionCodeWindowsInPageErrorType::from_u64(info[0]) { reason = CrashReason::WindowsInPageError(ty, nt_status); } } } CrashReason::WindowsNtStatus(err::NtStatusWindows::STATUS_STACK_BUFFER_OVERRUN) => { // STATUS_STACK_BUFFER_OVERRUN are caused by __fastfail() // invocations and the fast-fail code is stored in // exception_information[0]. if record.number_parameters >= 1 { // The status code is 32-bits wide, ignore the upper 32 bits let fast_fail = info[0] & 0xffff_ffff; reason = CrashReason::WindowsStackBufferOverrun(fast_fail); } } _ => { // Do nothing interesting } } Some(reason) } pub fn from_mac_exception( raw: &md::MINIDUMP_EXCEPTION_STREAM, cpu: Cpu, ) -> Option { use err::ExceptionCodeMac; let record = &raw.exception_record; let info = &record.exception_information; let exception_code = record.exception_code; let exception_flags = record.exception_flags; // Default to just directly reporting this reason. let mac_reason = err::ExceptionCodeMac::from_u32(exception_code)?; let mut reason = CrashReason::MacGeneral(mac_reason, exception_flags); // Refine the output for error codes that have more info match mac_reason { ExceptionCodeMac::EXC_BAD_ACCESS => { if let Some(ty) = err::ExceptionCodeMacBadAccessKernType::from_u32(exception_flags) { reason = CrashReason::MacBadAccessKern(ty); } else { match cpu { Cpu::Arm64 => { if let Some(ty) = err::ExceptionCodeMacBadAccessArmType::from_u32(exception_flags) { reason = CrashReason::MacBadAccessArm(ty); } } Cpu::Ppc => { if let Some(ty) = err::ExceptionCodeMacBadAccessPpcType::from_u32(exception_flags) { reason = CrashReason::MacBadAccessPpc(ty); } } Cpu::X86 | Cpu::X86_64 => { if let Some(ty) = err::ExceptionCodeMacBadAccessX86Type::from_u32(exception_flags) { reason = CrashReason::MacBadAccessX86(ty); } } _ => { // Do nothing } } } } ExceptionCodeMac::EXC_BAD_INSTRUCTION => match cpu { Cpu::Arm64 => { if let Some(ty) = err::ExceptionCodeMacBadInstructionArmType::from_u32(exception_flags) { reason = CrashReason::MacBadInstructionArm(ty); } } Cpu::Ppc => { if let Some(ty) = err::ExceptionCodeMacBadInstructionPpcType::from_u32(exception_flags) { reason = CrashReason::MacBadInstructionPpc(ty); } } Cpu::X86 | Cpu::X86_64 => { if let Some(ty) = err::ExceptionCodeMacBadInstructionX86Type::from_u32(exception_flags) { reason = CrashReason::MacBadInstructionX86(ty); } } _ => { // Do nothing } }, ExceptionCodeMac::EXC_ARITHMETIC => match cpu { Cpu::Arm64 => { if let Some(ty) = err::ExceptionCodeMacArithmeticArmType::from_u32(exception_flags) { reason = CrashReason::MacArithmeticArm(ty); } } Cpu::Ppc => { if let Some(ty) = err::ExceptionCodeMacArithmeticPpcType::from_u32(exception_flags) { reason = CrashReason::MacArithmeticPpc(ty); } } Cpu::X86 | Cpu::X86_64 => { if let Some(ty) = err::ExceptionCodeMacArithmeticX86Type::from_u32(exception_flags) { reason = CrashReason::MacArithmeticX86(ty); } } _ => { // Do nothing } }, ExceptionCodeMac::EXC_SOFTWARE => { if let Some(ty) = err::ExceptionCodeMacSoftwareType::from_u32(exception_flags) { reason = CrashReason::MacSoftware(ty); } } ExceptionCodeMac::EXC_BREAKPOINT => match cpu { Cpu::Arm64 => { if let Some(ty) = err::ExceptionCodeMacBreakpointArmType::from_u32(exception_flags) { reason = CrashReason::MacBreakpointArm(ty); } } Cpu::Ppc => { if let Some(ty) = err::ExceptionCodeMacBreakpointPpcType::from_u32(exception_flags) { reason = CrashReason::MacBreakpointPpc(ty); } } Cpu::X86 | Cpu::X86_64 => { if let Some(ty) = err::ExceptionCodeMacBreakpointX86Type::from_u32(exception_flags) { reason = CrashReason::MacBreakpointX86(ty); } } _ => { // Do nothing } }, ExceptionCodeMac::EXC_RESOURCE => { if let Some(ty) = err::ExceptionCodeMacResourceType::from_u32((exception_flags >> 29) & 0x7) { reason = CrashReason::MacResource(ty, info[1], info[2]); } } ExceptionCodeMac::EXC_GUARD => { if let Some(ty) = err::ExceptionCodeMacGuardType::from_u32((exception_flags >> 29) & 0x7) { reason = CrashReason::MacGuard(ty, info[1], info[2]); } } _ => { // Do nothing } } Some(reason) } pub fn from_linux_exception( raw: &md::MINIDUMP_EXCEPTION_STREAM, _cpu: Cpu, ) -> Option { let record = &raw.exception_record; let exception_code = record.exception_code; let exception_flags = record.exception_flags; let linux_reason = err::ExceptionCodeLinux::from_u32(exception_code)?; let mut reason = CrashReason::LinuxGeneral(linux_reason, exception_flags); // Refine the output for error codes that have more info match linux_reason { err::ExceptionCodeLinux::SIGILL => { if let Some(ty) = err::ExceptionCodeLinuxSigillKind::from_u32(exception_flags) { reason = CrashReason::LinuxSigill(ty); } } err::ExceptionCodeLinux::SIGTRAP => { if let Some(ty) = err::ExceptionCodeLinuxSigtrapKind::from_u32(exception_flags) { reason = CrashReason::LinuxSigtrap(ty); } } err::ExceptionCodeLinux::SIGFPE => { if let Some(ty) = err::ExceptionCodeLinuxSigfpeKind::from_u32(exception_flags) { reason = CrashReason::LinuxSigfpe(ty); } } err::ExceptionCodeLinux::SIGSEGV => { if let Some(ty) = err::ExceptionCodeLinuxSigsegvKind::from_u32(exception_flags) { reason = CrashReason::LinuxSigsegv(ty); } } err::ExceptionCodeLinux::SIGBUS => { if let Some(ty) = err::ExceptionCodeLinuxSigbusKind::from_u32(exception_flags) { reason = CrashReason::LinuxSigbus(ty); } } err::ExceptionCodeLinux::SIGSYS => { if let Some(ty) = err::ExceptionCodeLinuxSigsysKind::from_u32(exception_flags) { reason = CrashReason::LinuxSigsys(ty); } } _ => { // No refinements } } Some(reason) } } impl fmt::Display for CrashReason { /// A string describing the crash reason. /// /// This is OS- and possibly CPU-specific. /// For example, "EXCEPTION_ACCESS_VIOLATION" (Windows), /// "EXC_BAD_ACCESS / KERN_INVALID_ADDRESS" (Mac OS X), "SIGSEGV" /// (other Unix). fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use CrashReason::*; fn write_nt_status(f: &mut fmt::Formatter<'_>, raw_nt_status: u64) -> fmt::Result { let nt_status = err::NtStatusWindows::from_u64(raw_nt_status); if let Some(nt_status) = nt_status { write!(f, "{nt_status:?}") } else { write!(f, "{raw_nt_status:#010x}") } } fn write_fast_fail(f: &mut fmt::Formatter<'_>, raw_fast_fail: u64) -> fmt::Result { let fast_fail = err::FastFailCode::from_u64(raw_fast_fail); if let Some(fast_fail) = fast_fail { write!(f, "{fast_fail:?}") } else { write!(f, "{raw_fast_fail:#010x}") } } fn write_exc_resource( f: &mut fmt::Formatter<'_>, ex: err::ExceptionCodeMacResourceType, code: u64, subcode: u64, ) -> fmt::Result { let flavor = (code >> 58) & 0x7; write!(f, "EXC_RESOURCE / {ex:?} / ")?; match ex { err::ExceptionCodeMacResourceType::RESOURCE_TYPE_CPU => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L71-L99 if let Some(cpu_flavor) = err::ExceptionCodeMacResourceCpuFlavor::from_u64(flavor) { let interval = (code >> 7) & 0x1ffffff; let cpu_limit = code & 0x7; let cpu_consumed = subcode & 0x7; write!( f, "{cpu_flavor:?} interval: {interval}s CPU limit: {cpu_limit}% CPU consumed: {cpu_consumed}%" ) } else { write!(f, "{code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacResourceType::RESOURCE_TYPE_WAKEUPS => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L105-L134 if let Some(wakeups_flavor) = err::ExceptionCodeMacResourceWakeupsFlavor::from_u64(flavor) { let interval = (code >> 20) & 0xfffff; let wakeups_permitted = code & 0xfff; let wakeups_observed = subcode & 0xfff; write!( f, "{wakeups_flavor:?} interval: {interval}s wakeups permitted: {wakeups_permitted} wakeups observed: {wakeups_observed}" ) } else { write!(f, "{code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacResourceType::RESOURCE_TYPE_MEMORY => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L139-L162 if let Some(memory_flavor) = err::ExceptionCodeMacResourceMemoryFlavor::from_u64(flavor) { let hwm_limit = code & 0x1fff; write!(f, "{memory_flavor:?} high watermark limit: {hwm_limit}MiB") } else { write!(f, "{code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacResourceType::RESOURCE_TYPE_IO => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L168-L196 if let Some(io_flavor) = err::ExceptionCodeMacResourceIOFlavor::from_u64(flavor) { let interval = (code >> 15) & 0x1ffff; let io_limit = code & 0x7fff; let io_observed = subcode & 0x7fff; write!( f, "{io_flavor:?} interval: {interval}s I/O limit: {io_limit}% I/O observed: {io_observed}%" ) } else { write!(f, "{code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacResourceType::RESOURCE_TYPE_THREADS => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L199-L207 if let Some(threads_flavor) = err::ExceptionCodeMacResourceThreadsFlavor::from_u64(flavor) { let hwm_limit = code & 0x7fff; write!(f, "{threads_flavor:?} high watermark limit: {hwm_limit}") } else { write!(f, "{code:#018x} / {subcode:#018x}") } } } } fn write_exc_guard( f: &mut fmt::Formatter<'_>, ex: err::ExceptionCodeMacGuardType, code: u64, subcode: u64, ) -> fmt::Result { let flavor = (code >> 32) & 0x1fffffff; write!(f, "EXC_GUARD / {ex:?}")?; match ex { err::ExceptionCodeMacGuardType::GUARD_TYPE_NONE => { write!(f, "") } err::ExceptionCodeMacGuardType::GUARD_TYPE_MACH_PORT => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L69-L81 if let Some(mach_port_flavor) = err::ExceptionCodeMacGuardMachPortFlavor::from_u64(flavor) { // FIXME: GUARD_EXC_STRICT_REPLY, GUARD_EXC_MOD_REFS and GUARD_EXC_IMMOVABLE have additional flags defined here: // https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/mach/port.h#L518-L538 // They also encode additional data in the subcode (a mach reply type or a kernel return value), one has to // check Apple's code to figure out each one. Since we haven't encountered them yet in the wild there's no // hurry to decode those. let port_name = code & 0xfffffff; if subcode != 0 { write!( f, " / {mach_port_flavor:?} port name: {port_name} subcode: {subcode}", ) } else { write!(f, " / {mach_port_flavor:?} port name: {port_name}",) } } else { write!(f, " / {code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacGuardType::GUARD_TYPE_FD => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L85-L97 if let Some(fd_flavor) = err::ExceptionCodeMacGuardFDFlavor::from_u64(flavor) { let fd = code & 0xfffffff; write!( f, " / {fd_flavor:?} file descriptor: {fd} guard identifier: {subcode}", ) } else { write!(f, " / {code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacGuardType::GUARD_TYPE_USER => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L101-L113 let namespace = code & 0xffffffff; write!(f, "/ namespace: {namespace} guard identifier: {subcode}",) } err::ExceptionCodeMacGuardType::GUARD_TYPE_VN => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L117-L129 if let Some(vn_flavor) = err::ExceptionCodeMacGuardVNFlavor::from_u64(flavor) { let pid = code & 0xfffffff; write!(f, " / {vn_flavor:?} pid: {pid} guard identifier: {subcode}",) } else { write!(f, " / {code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacGuardType::GUARD_TYPE_VIRT_MEMORY => { // See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L133-L145 if let Some(virt_memory_flavor) = err::ExceptionCodeMacGuardVirtMemoryFlavor::from_u64(flavor) { write!(f, " / {virt_memory_flavor:?} offset: {subcode}") } else { write!(f, " / {code:#018x} / {subcode:#018x}") } } err::ExceptionCodeMacGuardType::GUARD_TYPE_REJECTED_SC => { // See https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/kern/exc_guard.h#L149-L163 if let Some(rejected_sc_flavor) = err::ExceptionCodeMacGuardRejecteSysCallFlavor::from_u64(flavor) { let syscall = subcode; write!(f, " / {rejected_sc_flavor:?} syscall: {syscall}",) } else { write!(f, " / {code:#018x} / {subcode:#018x}") } } } } fn write_signal( f: &mut fmt::Formatter<'_>, ex: err::ExceptionCodeLinux, flags: u32, ) -> fmt::Result { if let Some(si_code) = err::ExceptionCodeLinuxSicode::from_i32(flags as i32) { if si_code == err::ExceptionCodeLinuxSicode::SI_USER { write!(f, "{ex:?}") } else { write!(f, "{ex:?} / {si_code:?}") } } else { write!(f, "{ex:?} / {flags:#010x}") } } // OK this is kinda a gross hack but I *really* don't want // to write out all these strings again, so let's just lean on Debug // repeating the name of the enum variant! match *self { // ======================== Mac/iOS ============================ // These codes get special messages MacGeneral(err::ExceptionCodeMac::SIMULATED, _) => write!(f, "Simulated Exception"), // Thse codes just repeat their names MacGeneral(ex, flags) => write!(f, "{ex:?} / {flags:#010x}"), 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:?}"), MacArithmeticArm(ex) => write!(f, "EXC_ARITHMETIC / {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:?}"), MacResource(ex, code, subcode) => write_exc_resource(f, ex, code, subcode), MacGuard(ex, code, subcode) => write_exc_guard(f, ex, code, subcode), // ===================== Linux/Android ========================= // These codes just repeat their names LinuxGeneral(ex, flags) => write_signal(f, ex, flags), LinuxSigill(ex) => write!(f, "SIGILL / {ex:?}"), LinuxSigtrap(ex) => write!(f, "SIGTRAP / {ex:?}"), LinuxSigbus(ex) => write!(f, "SIGBUS / {ex:?}"), LinuxSigfpe(ex) => write!(f, "SIGFPE / {ex:?}"), LinuxSigsegv(ex) => write!(f, "SIGSEGV / {ex:?}"), LinuxSigsys(ex) => write!(f, "SIGSYS / {ex:?}"), // ======================== Windows ============================= // These codes get special messages WindowsGeneral(err::ExceptionCodeWindows::OUT_OF_MEMORY) => write!(f, "Out of Memory"), WindowsGeneral(err::ExceptionCodeWindows::UNHANDLED_CPP_EXCEPTION) => { write!(f, "Unhandled C++ Exception") } WindowsGeneral(err::ExceptionCodeWindows::SIMULATED) => { write!(f, "Simulated Exception") } // These codes just repeat their names WindowsGeneral(ex) => write!(f, "{ex:?}"), WindowsWinError(winerror) => write!(f, "{winerror:?}"), WindowsWinErrorWithFacility(facility, winerror) => { write!(f, "{facility:?} / {winerror:?}") } WindowsNtStatus(nt_status) => write_nt_status(f, nt_status as _), 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(code) => write!(f, "unknown {code:#010x}"), Unknown(code, flags) => write!(f, "unknown {code:#010x} / {flags:#010x}"), } } } impl<'a> MinidumpStream<'a> for MinidumpException<'a> { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ExceptionStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let raw: md::MINIDUMP_EXCEPTION_STREAM = bytes .pread_with(0, endian) .or(Err(Error::StreamReadFailure))?; let context = location_slice(all, &raw.thread_context).ok(); let thread_id = raw.thread_id; Ok(MinidumpException { raw, thread_id, context, endian, }) } } impl<'a> MinidumpException<'a> { /// Get the cpu context of the crashing (or otherwise minidump-requesting) thread. /// /// CPU contexts are a platform-specific format, so SystemInfo is required /// to reliably parse them. We used to use heuristics to avoid this requirement, /// but this made us too brittle to otherwise-backwards-compatible additions /// to the format. /// /// MiscInfo can contain additional details on the cpu context's format, but /// is optional because those details can be safely ignored (at the cost of /// being unable to parse some very obscure cpu state). pub fn context( &self, system_info: &MinidumpSystemInfo, misc: Option<&MinidumpMiscInfo>, ) -> Option> { MinidumpContext::read(self.context?, self.endian, system_info, misc) .ok() .map(Cow::Owned) } /// Get the address that "caused" the crash. /// /// The meaning of this value depends on the kind of crash this was. /// /// By default, it's the instruction pointer at the time of the crash. /// However, if the crash was caused by an illegal memory access, the /// the address would be the memory address. /// /// So for instance, if you crashed from dereferencing a null pointer, /// the crash_address will be 0 (or close to it, due to offsets). pub fn get_crash_address(&self, os: Os, cpu: Cpu) -> u64 { let addr = match ( os, err::ExceptionCodeWindows::from_u32(self.raw.exception_record.exception_code), ) { (Os::Windows, Some(err::ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION)) | (Os::Windows, Some(err::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, }; // Sometimes on 32-bit these values can be incorrectly sign-extended, // so mask and zero-extend them here. match cpu.pointer_width() { PointerWidth::Bits32 => addr as u32 as u64, _ => addr, } } /// Get the crash reason for an exception. /// /// The returned value reflects our best attempt to recover a /// "native" error for the crashing system based on the OS and /// things like raw error codes. /// /// This is an imperfect process, because OSes may have overlapping /// error types (e.g. WinError and NTSTATUS overlap, so we have to /// pick one arbirarily). /// /// The raw error codes can be extracted from [MinidumpException::raw][]. pub fn get_crash_reason(&self, os: Os, cpu: Cpu) -> CrashReason { CrashReason::from_exception(&self.raw, os, cpu) } /// The id of the thread that caused the crash (or otherwise requested /// the minidump, even if there wasn't actually a crash). pub fn get_crashing_thread_id(&self) -> u32 { self.thread_id } /// Write a human-readable description of this `MinidumpException` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print( &self, f: &mut T, system: Option<&MinidumpSystemInfo>, misc: Option<&MinidumpMiscInfo>, ) -> 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(system_info) = system { if let Some(context) = self.context(system_info, misc) { writeln!(f)?; context.print(f)?; } else { write!( f, " (no context) " )?; } } else { write!( f, " (no context) " )?; } Ok(()) } } impl<'a> MinidumpStream<'a> for MinidumpAssertion { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::AssertionInfoStream as u32; fn read( bytes: &'a [u8], _all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { 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 { 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) }; encoding_rs::UTF_16LE .decode_without_bom_handling_and_without_replacement(bytes) .map(String::from) } impl MinidumpAssertion { /// Get the assertion expression as a `String` if one exists. pub fn expression(&self) -> Option { utf16_to_string(&self.raw.expression) } /// Get the function name where the assertion happened as a `String` if it exists. pub fn function(&self) -> Option { utf16_to_string(&self.raw.function) } /// Get the source file name where the assertion happened as a `String` if it exists. pub fn file(&self) -> Option { utf16_to_string(&self.raw.file) } /// Write a human-readable description of this `MinidumpAssertion` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&self, f: &mut T) -> io::Result<()> { write!( f, "MDAssertion expression = {} function = {} file = {} line = {} type = {} ", self.expression().unwrap_or_default(), self.function().unwrap_or_default(), self.file().unwrap_or_default(), self.raw.line, self.raw._type, )?; Ok(()) } } fn read_string_list( all: &[u8], location: &md::MINIDUMP_LOCATION_DESCRIPTOR, endian: scroll::Endian, ) -> Result, 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 (count, _) = ensure_count_in_bound(all, count as usize, ::size_with(&endian), 0)?; let mut strings = Vec::with_capacity(count); 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) .ok_or(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, 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) .ok_or(Error::StreamReadFailure)?; let value = read_string_utf8(&mut (entry.value as usize), all, endian) .ok_or(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, 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) .ok_or(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) .ok_or(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 { 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, 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 (count, _) = ensure_count_in_bound( all, count as usize, ::size_with(&endian), 0, )?; let mut module_links = Vec::with_capacity(count); 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: u32 = MINIDUMP_STREAM_TYPE::CrashpadInfoStream as u32; fn read( bytes: &'a [u8], all: &'a [u8], endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { let raw: md::MINIDUMP_CRASHPAD_INFO = bytes .pread_with(0, endian) .or(Err(Error::StreamReadFailure))?; if raw.version == 0 { // 0 is an invalid version, but all future versions are compatible with v1. 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 { /// Write a human-readable description of this `MinidumpCrashpadInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pub fn print(&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[{index}].list_annotations[{annotation_index}] = {annotation}", )?; } for (name, value) in &module.simple_annotations { writeln!( f, " module_list[{index}].simple_annotations[\"{name}\"] = {value}", )?; } for (name, value) in &module.annotation_objects { write!( f, " module_list[{index}].annotation_objects[\"{name}\"] = ", )?; match value { MinidumpAnnotation::Invalid => writeln!(f, ""), MinidumpAnnotation::String(string) => writeln!(f, "{string}"), MinidumpAnnotation::UserDefined(_) => writeln!(f, ""), MinidumpAnnotation::Unsupported(_) => writeln!(f, ""), }?; } } writeln!(f)?; Ok(()) } } impl<'a> MinidumpStream<'a> for StabilityReport { const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::StabilityReportStream as u32; fn read( bytes: &'a [u8], _all: &'a [u8], _endian: scroll::Endian, _system_info: Option<&MinidumpSystemInfo>, ) -> Result { StabilityReport::decode(bytes).map_err(|_| Error::DataError) } } impl StabilityReport { pub fn print(&self, f: &mut T) -> std::io::Result<()> { writeln!(f, "StabilityReport")?; for (index, process_state) in self.process_states.iter().enumerate() { if let Some(process_id) = process_state.process_id { writeln!(f, " process_state[{index}].process_id = {process_id}",)?; } if let Some(memory_state) = &process_state.memory_state { if let Some(windows_memory) = &memory_state.windows_memory { if let Some(process_private_usage) = &windows_memory.process_private_usage { writeln!( f, " process_state[{index}].memory_state.process_private_usage = {process_private_usage}", )?; } if let Some(process_peak_workingset_size) = &windows_memory.process_peak_workingset_size { writeln!( f, " process_state[{index}].memory_state.process_peak_workingset_size = {process_peak_workingset_size}", )?; } if let Some(process_peak_pagefile_usage) = &windows_memory.process_peak_pagefile_usage { writeln!( f, " process_state[{index}].memory_state.process_peak_pagefile_usage = {process_peak_pagefile_usage}", )?; } if let Some(process_allocation_attempt) = &windows_memory.process_allocation_attempt { writeln!( f, " process_state[{index}].memory_state.process_allocation_attempt = {process_allocation_attempt}", )?; } } } if let Some(file_system_state) = &process_state.file_system_state { if let Some(posix_file_system_state) = &file_system_state.posix_file_system_state { if let Some(open_file_descriptors) = posix_file_system_state.open_file_descriptors { writeln!( f, " file_system_state.posix_file_system_state.open_file_descriptors = {open_file_descriptors}", )?; } } if let Some(windows_file_system_state) = &file_system_state.windows_file_system_state { if let Some(process_handle_count) = &windows_file_system_state.process_handle_count { writeln!( f, " file_system_state.windows_file_system_state.process_handle_count = {process_handle_count}", )?; } } } } if let Some(system_memory_state) = &self.system_memory_state { if let Some(windows_memory) = &system_memory_state.windows_memory { if let Some(system_commit_limit) = &windows_memory.system_commit_limit { writeln!(f, " system_memory_state.windows_memory.system_commit_limit = {system_commit_limit}",)?; } if let Some(system_commit_remaining) = &windows_memory.system_commit_remaining { writeln!( f, " system_memory_state.windows_memory.system_commit_remaining = {system_commit_remaining}", )?; } if let Some(system_handle_count) = &windows_memory.system_handle_count { writeln!(f, " system_memory_state.windows_memory.system_handle_count = {system_handle_count}",)?; } } } Ok(()) } } /// An index into the contents of a memory-mapped minidump. pub type MmapMinidump = Minidump<'static, Mmap>; impl MmapMinidump { /// Read a `Minidump` from a `Path` to a file on disk. /// /// See [the type definition](Minidump.html) for an example. pub fn read_path

(path: P) -> Result where P: AsRef, { let f = File::open(path).or(Err(Error::FileNotFound))?; let mmap = unsafe { Mmap::map(&f).or(Err(Error::IoError))? }; Minidump::read(mmap) } } /// A stream in the minidump that this implementation can interpret, #[derive(Debug)] pub struct MinidumpImplementedStream { pub stream_type: MINIDUMP_STREAM_TYPE, pub location: md::MINIDUMP_LOCATION_DESCRIPTOR, pub vendor: &'static str, } /// A stream in the minidump that this implementation has no knowledge of. #[derive(Debug, Clone)] pub struct MinidumpUnknownStream { pub stream_type: u32, pub location: md::MINIDUMP_LOCATION_DESCRIPTOR, pub vendor: &'static str, } /// A stream in the minidump that this implementation is aware of but doesn't /// yet support. #[derive(Debug, Clone)] pub struct MinidumpUnimplementedStream { pub stream_type: MINIDUMP_STREAM_TYPE, pub location: md::MINIDUMP_LOCATION_DESCRIPTOR, pub vendor: &'static str, } impl<'a, T> Minidump<'a, T> where T: Deref + 'a, { /// Read a `Minidump` from the provided `data`. /// /// Typically this will be a `Vec` or `&[u8]` with the full contents of the minidump, /// but you can also use something like `memmap::Mmap`. pub fn read(data: T) -> Result, 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); } // Try again with big-endian. 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); } offset = header.stream_directory_rva as usize; let mut streams = BTreeMap::new(); for i in 0..header.stream_count { let dir: md::MINIDUMP_DIRECTORY = data .gread_with(&mut offset, endian) .or(Err(Error::MissingDirectory))?; if let Some((old_idx, old_dir)) = streams.insert(dir.stream_type, (i, dir.clone())) { if let Some(known_stream_type) = MINIDUMP_STREAM_TYPE::from_u32(dir.stream_type) { if !(known_stream_type == MINIDUMP_STREAM_TYPE::UnusedStream && old_dir.location.data_size == 0 && dir.location.data_size == 0) { warn!("Minidump contains multiple streams of type {} ({:?}) at indices {} ({} bytes) and {} ({} bytes) (using {})", dir.stream_type, known_stream_type, old_idx, old_dir.location.data_size, i, dir.location.data_size, i, ); } } else { warn!("Minidump contains multiple streams of unknown type {} at indices {} ({} bytes) and {} ({} bytes) (using {})", dir.stream_type, old_idx, old_dir.location.data_size, i, dir.location.data_size, i, ); } } } let system_info = streams .get(&MinidumpSystemInfo::STREAM_TYPE) .and_then(|(_, dir)| { location_slice(data.deref(), &dir.location) .ok() .and_then(|bytes| { let all_bytes = data.deref(); MinidumpSystemInfo::read(bytes, all_bytes, endian, None).ok() }) }); Ok(Minidump { data, header, streams, endian, system_info, _phantom: PhantomData, }) } /// Read and parse the specified [`MinidumpStream`][] `S` from the Minidump, if it exists. /// /// Because Minidump Streams can have totally different formats and meanings, the only /// way to coherently access one is by specifying a static type that provides an /// interpretation and interface of that format. /// /// As such, typical usage of this interface is to just statically request every /// stream your care about. Depending on what analysis you're trying to perform, you may: /// /// * Consider it an error for a stream to be missing (using `?` or `unwrap`) /// * Branch on the presence of stream to conditionally refine your analysis /// * Use a stream's `Default` implementation to make progress (with `unwrap_or_default`) /// /// ``` /// use minidump::*; /// /// fn main() -> Result<(), Error> { /// // Read the minidump from a file /// let mut dump = minidump::Minidump::read_path("../testdata/test.dmp")?; /// /// // Statically request (and require) several streams we care about: /// let system_info = dump.get_stream::()?; /// let exception = dump.get_stream::()?; /// /// // Combine the contents of the streams to perform more refined analysis /// let crash_reason = exception.get_crash_reason(system_info.os, system_info.cpu); /// /// // Conditionally analyze a stream /// if let Ok(threads) = dump.get_stream::() { /// // Use `Default` to try to make some progress when a stream is missing. /// // This is especially natural for MinidumpMemoryList because /// // everything needs to handle memory lookups failing anyway. /// let mem = dump.get_memory().unwrap_or_default(); /// /// for thread in &threads.threads { /// let stack = thread.stack_memory(&mem); /// // ... /// } /// } /// /// Ok(()) /// } /// ``` /// /// Some streams are impossible to fully parse/interpret without the contents /// of other streams (for instance, many things require [`MinidumpSystemInfo`][] to interpret /// hardware-specific details). As a result, some parsing of the stream may be /// further deferred to methods on the Stream type where those dependencies can be provided /// (e.g. [`MinidumpException::get_crash_reason`][]). /// /// Note that the lifetime of the returned stream is bound to the lifetime of the /// `Minidump` struct itself and not to the lifetime of the data backing this minidump. /// This is a consequence of how this struct relies on [`Deref`][] to access the data. /// /// ## Currently Supported Streams /// /// * [`MinidumpAssertion`][] /// * [`MinidumpBreakpadInfo`][] /// * [`MinidumpCrashpadInfo`][] /// * [`MinidumpException`][] /// * [`MinidumpLinuxCpuInfo`][] /// * [`MinidumpLinuxEnviron`][] /// * [`MinidumpLinuxLsbRelease`][] /// * [`MinidumpLinuxMaps`][] /// * [`MinidumpLinuxProcStatus`][] /// * [`MinidumpMacCrashInfo`][] /// * [`MinidumpMacBootargs`][] /// * [`MinidumpMemoryList`][] /// * [`MinidumpMemory64List`][] /// * [`MinidumpMemoryInfoList`][] /// * [`MinidumpMiscInfo`][] /// * [`MinidumpModuleList`][] /// * [`MinidumpSystemInfo`][] /// * [`MinidumpThreadList`][] /// * [`MinidumpThreadNames`][] /// * [`MinidumpUnloadedModuleList`][] /// * [`MinidumpHandleDataStream`][] /// pub fn get_stream(&'a self) -> Result 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, self.system_info.as_ref()) } } } /// Get a stream of raw data from the minidump. /// /// This can be used to get the contents of arbitrary minidump streams. /// For streams of known types you almost certainly want to use /// [`Minidump::get_stream`][] instead. /// /// Note that the lifetime of the returned stream is bound to the lifetime of the this /// `Minidump` struct itself and not to the lifetime of the data backing this minidump. /// This is a consequence of how this struct relies on [Deref] to access the data. pub fn get_raw_stream(&'a self, stream_type: u32) -> Result<&'a [u8], Error> { match self.streams.get(&stream_type) { None => Err(Error::StreamNotFound), Some((_, dir)) => { let bytes = self.data.deref(); location_slice(bytes, &dir.location) } } } /// Get whichever of the two MemoryLists are available in the minidump, /// preferring [`MinidumpMemory64List`][]. pub fn get_memory(&'a self) -> Option> { self.get_stream::() .map(UnifiedMemoryList::Memory64) .or_else(|_| { self.get_stream::() .map(UnifiedMemoryList::Memory) }) .ok() } /// A listing of all the streams in the Minidump that this library is *aware* of, /// but has no further analysis for. /// /// If there are multiple copies of the same stream type (which should not happen for /// well-formed Minidumps), then only one of them will be yielded, arbitrarily. pub fn unimplemented_streams(&self) -> impl Iterator + '_ { static UNIMPLEMENTED_STREAMS: [MINIDUMP_STREAM_TYPE; 30] = [ // Presumably will never have an implementation: MINIDUMP_STREAM_TYPE::UnusedStream, MINIDUMP_STREAM_TYPE::ReservedStream0, MINIDUMP_STREAM_TYPE::ReservedStream1, MINIDUMP_STREAM_TYPE::LastReservedStream, // Presumably should be implemented: MINIDUMP_STREAM_TYPE::ThreadExListStream, MINIDUMP_STREAM_TYPE::CommentStreamA, MINIDUMP_STREAM_TYPE::CommentStreamW, MINIDUMP_STREAM_TYPE::FunctionTable, MINIDUMP_STREAM_TYPE::HandleOperationListStream, MINIDUMP_STREAM_TYPE::TokenStream, MINIDUMP_STREAM_TYPE::JavaScriptDataStream, MINIDUMP_STREAM_TYPE::SystemMemoryInfoStream, MINIDUMP_STREAM_TYPE::ProcessVmCountersStream, MINIDUMP_STREAM_TYPE::IptTraceStream, // Windows CE streams, very unlikely to be found in the wild. // Their contents are documented here: https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms939618(v=msdn.10) MINIDUMP_STREAM_TYPE::ceStreamNull, MINIDUMP_STREAM_TYPE::ceStreamSystemInfo, MINIDUMP_STREAM_TYPE::ceStreamException, MINIDUMP_STREAM_TYPE::ceStreamModuleList, MINIDUMP_STREAM_TYPE::ceStreamProcessList, MINIDUMP_STREAM_TYPE::ceStreamThreadList, MINIDUMP_STREAM_TYPE::ceStreamThreadContextList, MINIDUMP_STREAM_TYPE::ceStreamThreadCallStackList, MINIDUMP_STREAM_TYPE::ceStreamMemoryVirtualList, MINIDUMP_STREAM_TYPE::ceStreamMemoryPhysicalList, MINIDUMP_STREAM_TYPE::ceStreamBucketParameters, MINIDUMP_STREAM_TYPE::ceStreamProcessModuleMap, MINIDUMP_STREAM_TYPE::ceStreamDiagnosisList, // non-standard streams (should also be implemented): MINIDUMP_STREAM_TYPE::LinuxCmdLine, MINIDUMP_STREAM_TYPE::LinuxAuxv, MINIDUMP_STREAM_TYPE::LinuxDsoDebug, ]; self.streams.iter().filter_map(|(_, (_, stream))| { MINIDUMP_STREAM_TYPE::from_u32(stream.stream_type).and_then(|stream_type| { if UNIMPLEMENTED_STREAMS.contains(&stream_type) { return Some(MinidumpUnimplementedStream { stream_type, location: stream.location, vendor: stream_vendor(stream.stream_type), }); } None }) }) } /// A listing of all the streams in the Minidump that this library has no knowledge of. /// /// If there are multiple copies of the same stream (which should not happen for /// well-formed Minidumps), then only one of them will be yielded, arbitrarily. pub fn unknown_streams(&self) -> impl Iterator + '_ { self.streams.iter().filter_map(|(_, (_, stream))| { if MINIDUMP_STREAM_TYPE::from_u32(stream.stream_type).is_none() { return Some(MinidumpUnknownStream { stream_type: stream.stream_type, location: stream.location, vendor: stream_vendor(stream.stream_type), }); } None }) } /// A listing of all the streams in the Minidump. /// /// If there are multiple copies of the same stream (which should not happen for /// well-formed Minidumps), then only one of them will be yielded, arbitrarily. pub fn all_streams(&self) -> impl Iterator + '_ { self.streams.iter().map(|(_, (_, stream))| stream) } /// Write a verbose description of the `Minidump` to `f`. pub fn print(&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::>(); 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(()) } } fn stream_vendor(stream_type: u32) -> &'static str { if stream_type <= MINIDUMP_STREAM_TYPE::LastReservedStream as u32 { "Official" } else { match stream_type & 0xFFFF0000 { 0x4767_0000 => "Google Extension", 0x4d7a_0000 => "Mozilla Extension", _ => "Unknown Extension", } } } #[cfg(test)] mod test { use super::*; use md::GUID; use minidump_common::{ errors::NtStatusWindows, format::{PlatformId, ProcessorArchitecture}, }; use minidump_synth::{ AnnotationValue, CrashpadInfo, DumpString, Exception, HandleDescriptor as SynthHandleDescriptor, Memory, MemoryInfo as SynthMemoryInfo, MiscFieldsBuildString, MiscFieldsPowerInfo, MiscFieldsProcessTimes, MiscFieldsTimeZone, MiscInfo5Fields, MiscStream, Module as SynthModule, ModuleCrashpadInfo, SimpleStream, SynthMinidump, SystemInfo, Thread, ThreadName, UnloadedModule as SynthUnloadedModule, STOCK_VERSION_INFO, }; use test_assembler::*; fn read_synth_dump<'a>(dump: SynthMinidump) -> Result>, Error> { Minidump::read(dump.finish().unwrap()) } #[ctor::ctor] fn init_logger() { env_logger::builder().is_test(true).init(); } #[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_thread_names() { let good_thread_id = 17; let corrupt_thread_id = 123; let good_name = DumpString::new("MyCoolThread", Endian::Little); // No corrupt name, will dangle let good_thread_name_entry = ThreadName::new(Endian::Little, good_thread_id, Some(&good_name)); let corrupt_thread_name_entry = ThreadName::new(Endian::Little, corrupt_thread_id, None); let dump = SynthMinidump::with_endian(Endian::Little) .add_thread_name(good_thread_name_entry) .add_thread_name(corrupt_thread_name_entry) .add(good_name); let dump = read_synth_dump(dump).unwrap(); let thread_names = dump.get_stream::().unwrap(); assert_eq!(thread_names.names.len(), 1); assert_eq!( &*thread_names.get_name(good_thread_id).unwrap(), "MyCoolThread" ); assert_eq!(thread_names.get_name(corrupt_thread_id), None); } #[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) // signature // signature, a GUID .D32(0xabcd1234) .D16(0xf00d) .D16(0xbeef) .append_bytes(b"\x01\x02\x03\x04\x05\x06\x07\x08") .D32(1) // age .append_bytes(b"c:\\foo\\file.pdb\0"); // pdb_file_name 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::().unwrap(); let modules = module_list.iter().collect::>(); 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"); // time_date_stamp and size_of_image concatenated assert_eq!( modules[0].code_identifier().unwrap(), CodeId::new("B1054D2Aada542bd".to_string()) ); assert_eq!(modules[0].debug_file().unwrap(), "c:\\foo\\file.pdb"); assert_eq!( modules[0].debug_identifier().unwrap(), DebugId::from_breakpad("ABCD1234F00DBEEF01020304050607081").unwrap() ); } #[test] fn test_module_list_pdb20() { let name = DumpString::new("single module", Endian::Little); let cv_record = Section::with_endian(Endian::Little) .D32(md::CvSignature::Pdb20 as u32) // cv_signature .D32(0x0) // cv_offset .D32(0xabcd1234) // signature .D32(1) // age .append_bytes(b"c:\\foo\\file.pdb\0"); // pdb_file_name 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::().unwrap(); let modules = module_list.iter().collect::>(); 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"); // time_date_stamp and size_of_image concatenated assert_eq!( modules[0].code_identifier().unwrap(), CodeId::new("B1054D2Aada542bd".to_string()) ); assert_eq!(modules[0].debug_file().unwrap(), "c:\\foo\\file.pdb"); assert_eq!( modules[0].debug_identifier().unwrap(), DebugId::from_pdb20(0xabcd1234, 1) ); } #[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::().unwrap(); let modules = module_list.iter().collect::>(); 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"); // time_date_stamp and size_of_image concatenated assert_eq!( modules[0].code_identifier().unwrap(), CodeId::new("B1054D2Aada542bd".to_string()) ); } #[test] fn test_memory_info() { let info1_alloc_protection = md::MemoryProtection::PAGE_GUARD; let info1_protection = md::MemoryProtection::PAGE_EXECUTE_READ; let info1_state = md::MemoryState::MEM_FREE; let info1_ty = md::MemoryType::MEM_MAPPED; let info1 = SynthMemoryInfo::new( Endian::Little, 0xa90206ca83eb2852, 0xa802064a83eb2752, info1_alloc_protection.bits(), 0xf80e064a93eb2356, info1_state.bits(), info1_protection.bits(), info1_ty.bits(), ); let info2_alloc_protection = md::MemoryProtection::PAGE_EXECUTE_READ; let info2_protection = md::MemoryProtection::PAGE_READONLY; let info2_state = md::MemoryState::MEM_COMMIT; let info2_ty = md::MemoryType::MEM_PRIVATE; let info2 = SynthMemoryInfo::new( Endian::Little, 0xd70206ca83eb2852, 0xb802064383eb2752, info2_alloc_protection.bits(), 0xe80e064a93eb2356, info2_state.bits(), info2_protection.bits(), info2_ty.bits(), ); let dump = SynthMinidump::with_endian(Endian::Little) .add_memory_info(info1) .add_memory_info(info2); let dump = read_synth_dump(dump).unwrap(); // Read both kinds of info to test this path on UnifiedMemoryInfo let info_list = dump.get_stream::().ok(); let maps = dump.get_stream::().ok(); assert!(info_list.is_some()); assert!(maps.is_none()); let unified_info = UnifiedMemoryInfoList::new(info_list, maps).unwrap(); let info_list = unified_info.info().unwrap(); assert!(unified_info.maps().is_none()); // Assert that unified and the info_list agree for (info, unified) in info_list.iter().zip(unified_info.iter()) { if let UnifiedMemoryInfo::Info(info2) = unified { assert_eq!(info, info2); } else { unreachable!(); } } let infos = info_list.iter().collect::>(); assert_eq!(infos.len(), 2); assert_eq!(infos[0].raw.base_address, 0xa90206ca83eb2852); assert_eq!(infos[0].raw.allocation_base, 0xa802064a83eb2752); assert_eq!( infos[0].raw.allocation_protection, info1_alloc_protection.bits() ); assert_eq!(infos[0].raw.region_size, 0xf80e064a93eb2356); assert_eq!(infos[0].raw.state, info1_state.bits()); assert_eq!(infos[0].raw.protection, info1_protection.bits()); assert_eq!(infos[0].raw._type, info1_ty.bits()); assert_eq!(infos[0].allocation_protection, info1_alloc_protection); assert_eq!(infos[0].protection, info1_protection); assert_eq!(infos[0].state, info1_state); assert_eq!(infos[0].ty, info1_ty); assert!(infos[0].is_executable()); assert_eq!(infos[1].raw.base_address, 0xd70206ca83eb2852); assert_eq!(infos[1].raw.allocation_base, 0xb802064383eb2752); assert_eq!( infos[1].raw.allocation_protection, info2_alloc_protection.bits() ); assert_eq!(infos[1].raw.region_size, 0xe80e064a93eb2356); assert_eq!(infos[1].raw.state, info2_state.bits()); assert_eq!(infos[1].raw.protection, info2_protection.bits()); assert_eq!(infos[1].raw._type, info2_ty.bits()); assert_eq!(infos[1].allocation_protection, info2_alloc_protection); assert_eq!(infos[1].protection, info2_protection); assert_eq!(infos[1].state, info2_state); assert_eq!(infos[1].ty, info2_ty); assert!(!infos[1].is_executable()); } #[test] fn test_linux_maps() { use procfs_core::process::{MMPermissions, MMapPath}; // TODO: is it okay to give up wonky whitespace support? let input = b"a90206ca83eb2852-b90206ca83eb3852 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so\n\ c70206ca83eb2852-de0206ca83eb2852 -w-s 10bac9000 fd:05 1196511 /usr/lib64/libtdb2.so (deleted)"; let dump = SynthMinidump::with_endian(Endian::Little).set_linux_maps(input); let dump = read_synth_dump(dump).unwrap(); // Read both kinds of info to test this path on UnifiedMemoryInfo let info_list = dump.get_stream::().ok(); let maps = dump.get_stream::().unwrap(); assert!(info_list.is_none()); let unified_info = UnifiedMemoryInfoList::new(info_list, Some(maps)).unwrap(); let maps = unified_info.maps().unwrap(); assert!(unified_info.info().is_none()); // Assert that unified and the maps agree for (info, unified) in maps.iter().zip(unified_info.iter()) { if let UnifiedMemoryInfo::Map(info2) = unified { assert_eq!(info, info2); } else { unreachable!(); } } let maps = maps.iter().collect::>(); assert_eq!(maps.len(), 2); assert_eq!(maps[0].map.address.0, 0xa90206ca83eb2852); assert_eq!(maps[0].map.address.1, 0xb90206ca83eb3852); assert_eq!( maps[0].map.pathname, MMapPath::Path("/usr/lib64/libtdb1.so".into()) ); assert!( maps[0].map.perms == MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::PRIVATE ); assert_eq!(maps[1].map.address.0, 0xc70206ca83eb2852); assert_eq!(maps[1].map.address.1, 0xde0206ca83eb2852); assert_eq!( maps[1].map.pathname, MMapPath::Path("/usr/lib64/libtdb2.so (deleted)".into()) ); assert!(maps[1].map.perms == MMPermissions::WRITE | MMPermissions::SHARED); let mut unified_infos = unified_info.by_addr(); assert!(matches!(unified_infos.next(), Some(UnifiedMemoryInfo::Map(m)) if m == maps[0])); assert!(matches!(unified_infos.next(), Some(UnifiedMemoryInfo::Map(m)) if m == maps[1])); } #[test] fn test_linux_map_parse() { use procfs_core::process::{MMPermissions, MMapPath::*}; let parse = |input| MinidumpLinuxMapInfo::from_line(input).unwrap(); let maybe_parse = MinidumpLinuxMapInfo::from_line; // TODO: is it okay to give up wonky whitespace support? { // Normal file let map = parse(b"10a00-10b00 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Path("/usr/lib64/libtdb1.so".into())); assert!( map.map.perms == MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::PRIVATE ); assert!(map.is_readable()); assert!(map.is_executable()); } { // Deleted file (also some whitespace in the file name) let map = parse(b"ffffffffff600000-ffffffffff601000 -wxs 10bac9000 fd:05 1196511 /usr/lib64/ libtdb1.so (deleted)"); assert_eq!(map.map.address.0, 0xffffffffff600000); assert_eq!(map.map.address.1, 0xffffffffff601000); assert_eq!( map.memory_range(), Some(Range::new(0xffffffffff600000, 0xffffffffff601000)) ); assert_eq!( map.map.pathname, Path("/usr/lib64/ libtdb1.so (deleted)".into()) ); assert!( map.map.perms == MMPermissions::WRITE | MMPermissions::EXECUTE | MMPermissions::SHARED ); assert!(map.is_writable()); assert!(map.is_executable()); } { // Stack let map = parse(b"10a00-10b00 ------- 10bac9000 fd:05 1196511 [stack]"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Stack); assert!(map.map.perms == MMPermissions::NONE); } { // Stack with tid let map = parse(b"10a00-10b00 ------- 10bac9000 fd:05 1196511 [stack:1234567]"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, TStack(1234567)); assert!(map.map.perms == MMPermissions::NONE); } { // Heap let map = parse(b"10a00-10b00 -- 10bac9000 fd:05 1196511 [heap]"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Heap); assert!(map.map.perms == MMPermissions::NONE); } { // Vdso let map = parse(b"10a00-10b00 r-wx- 10bac9000 fd:05 1196511 [vdso]"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Vdso); assert!( map.map.perms == MMPermissions::READ | MMPermissions::WRITE | MMPermissions::EXECUTE ); } { // Unknown Special let map = parse(b"10a00-10b00 r-wx- 10bac9000 fd:05 1196511 [asdfasd]"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Other("asdfasd".into())); assert!( map.map.perms == MMPermissions::READ | MMPermissions::WRITE | MMPermissions::EXECUTE ); } { // Anonymous let map = parse(b"10a00-10b00 -r- 10bac9000 fd:05 1196511 "); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Anonymous); assert!(map.map.perms == MMPermissions::READ); } /* { // Truncated defaults to anonymous let map = parse(b"10a00-10b00"); assert_eq!(map.map.address.0, 0x10a00); assert_eq!(map.map.address.1, 0x10b00); assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00))); assert_eq!(map.map.pathname, Anonymous); assert!(map.map.perms == MMPermissions::NONE); } */ { // Reversed ranges result in None for memory_range() let map = parse(b"fffff-10000 -r- 10bac9000 fd:05 1196511 "); assert_eq!(map.map.address.0, 0xfffff); assert_eq!(map.map.address.1, 0x10000); assert_eq!(map.memory_range(), None); } { // Equal ranges are valid let map = parse(b"fffff-fffff --- 10bac9000 fd:05 1196511 "); assert_eq!(map.map.address.0, 0xfffff); assert_eq!(map.map.address.1, 0xfffff); assert_eq!(map.memory_range(), Some(Range::new(0xfffff, 0xfffff))); } { // blank line assert!(maybe_parse(b"").is_none()); assert!(maybe_parse(b" ").is_none()); } { // bad addresses let map = maybe_parse(b"-10b00 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so"); assert!(map.is_none()); let map = maybe_parse(b"10b00- r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so"); assert!(map.is_none()); let map = maybe_parse(b"10b00 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so"); assert!(map.is_none()); } { // bad [stack:] let map = maybe_parse(b"10a00-10b00 r-xp 10bac9000 fd:05 1196511 [stack:]"); assert!(map.is_none()); let map = maybe_parse(b"10a00-10b00 r-xp 10bac9000 fd:05 1196511 [stack:a10]"); assert!(map.is_none()); } } #[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) // signature // signature, a GUID .D32(0xabcd1234) .D16(0xf00d) .D16(0xbeef) .append_bytes(b"\x01\x02\x03\x04\x05\x06\x07\x08") .D32(1) // age .append_bytes(b"c:\\foo\\file.pdb\0"); // pdb_file_name let module1 = SynthModule::new( Endian::Little, 0x100000000, 0x4000, &name1, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record); // module2 overlaps module1 exactly let module2 = SynthModule::new( Endian::Little, 0x100000000, 0x4000, &name2, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record); // module3 overlaps module1 partially let module3 = SynthModule::new( Endian::Little, 0x100000001, 0x4000, &name3, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record); // module4 is fully contained within module1 let module4 = SynthModule::new( Endian::Little, 0x100000001, 0x3000, &name4, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record); // module5 is cool, though. 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::().unwrap(); let modules = module_list.iter().collect::>(); 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"); // module_at_address should discard overlapping modules. 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::>().unwrap(); let regions = memory_list.iter().collect::>(); 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_memory64_list() { const CONTENTS0: &[u8] = b"memory_contents"; const CONTENTS1: &[u8] = b"another_block"; let memory0 = Memory::with_section( Section::with_endian(Endian::Little).append_bytes(CONTENTS0), 0x309d68010bd21b2c, ); let memory1 = Memory::with_section( Section::with_endian(Endian::Little).append_bytes(CONTENTS1), 0x1234, ); let dump = SynthMinidump::with_endian(Endian::Little) .add_memory64(memory0) .add_memory64(memory1); let dump = read_synth_dump(dump).unwrap(); let memory_list = dump.get_stream::>().unwrap(); let regions = memory_list.iter().collect::>(); assert_eq!(regions.len(), 2); assert_eq!(regions[0].base_address, 0x309d68010bd21b2c); assert_eq!(regions[0].size, CONTENTS0.len() as u64); assert_eq!(®ions[0].bytes, &CONTENTS0); assert_eq!(regions[1].base_address, 0x1234); assert_eq!(regions[1].size, CONTENTS1.len() as u64); assert_eq!(®ions[1].bytes, &CONTENTS1); } #[test] fn test_memory_list_lifetimes() { // A memory list should not own any of the minidump data. 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_overflow() { let memory1 = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(0, 2), u64::MAX, ); let dump = SynthMinidump::with_endian(Endian::Little).add_memory(memory1); let dump = read_synth_dump(dump).unwrap(); let memory_list = dump.get_stream::>().unwrap(); assert!(memory_list.memory_at_address(u64::MAX).is_none()); assert_eq!(memory_list.regions.len(), 1); assert!(memory_list.regions[0].memory_range().is_none()); } #[test] fn test_memory_list_overlap() { let memory1 = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(0, 0x1000), 0x1000, ); // memory2 overlaps memory1 exactly let memory2 = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(1, 0x1000), 0x1000, ); // memory3 overlaps memory1 partially let memory3 = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(2, 0x1000), 0x1001, ); // memory4 is fully contained within memory1 let memory4 = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(3, 0x100), 0x1001, ); // memory5 is cool, though. 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::>().unwrap(); let regions = memory_list.iter().collect::>(); 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); // memory_at_address should discard overlapping regions. 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::().unwrap(); assert_eq!(misc.raw.process_id(), Some(&PID)); assert_eq!( misc.process_create_time().unwrap(), systemtime_from_timestamp(PROCESS_TIMES.process_create_time as u64).unwrap() ); 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); // Make it larger. misc.pad_to_size = Some(mem::size_of::() + 32); let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc); let dump = read_synth_dump(dump).unwrap(); let misc = dump.get_stream::().unwrap(); assert_eq!(misc.raw.process_id(), Some(&PID)); assert_eq!( misc.process_create_time().unwrap(), systemtime_from_timestamp(PROCESS_TIMES.process_create_time as u64).unwrap(), ); 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 { input.chars().map(|c| c as u16).collect() } #[test] fn test_misc_info_5() { // MISC_INFO fields const PID: u32 = 0x1234abcd; const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes { process_create_time: 0xf0f0b0b0, process_user_time: 0xf030a020, process_kernel_time: 0xa010b420, }; // MISC_INFO_2 fields 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, }; // MISC_INFO_3 fields 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(), }, }; // MISC_INFO_4 fields 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, }; // MISC_INFO_5 fields const SIZE_OF_INFO: u32 = mem::size_of::() 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]; // One known feature and one unknown feature. 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::().unwrap(); // MISC_INFO fields assert_eq!(misc.raw.process_id(), Some(&PID)); assert_eq!( misc.process_create_time().unwrap(), systemtime_from_timestamp(PROCESS_TIMES.process_create_time as u64).unwrap() ); 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 ); // MISC_INFO_2 fields 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, ); // MISC_INFO_3 fields 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); // MISC_INFO_4 fields assert_eq!(*misc.raw.build_string().unwrap(), build_string,); assert_eq!(*misc.raw.dbg_bld_str().unwrap(), dbg_bld_str,); // MISC_INFO_5 fields 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() { // Add a module with a long 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) // signature .append_bytes(MODULE1_BUILD_ID); let module1 = SynthModule::new( Endian::Little, 0x100000000, 0x4000, &name1, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record1); // Add a module with a short ELF build id 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) // signature .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::().unwrap(); let modules = module_list.iter().collect::>(); assert_eq!(modules.len(), 2); assert_eq!(modules[0].base_address(), 0x100000000); assert_eq!(modules[0].code_file(), "module 1"); // The full build ID. assert_eq!( modules[0].code_identifier().unwrap(), CodeId::new("000102030405060708090a0b0c0d0e0f1011121314151617".to_string()) ); assert_eq!(modules[0].debug_file().unwrap(), "module 1"); // The first 16 bytes of the build ID interpreted as a GUID. assert_eq!( modules[0].debug_identifier().unwrap(), DebugId::from_breakpad("030201000504070608090A0B0C0D0E0F0").unwrap() ); assert_eq!(modules[1].base_address(), 0x200000000); assert_eq!(modules[1].code_file(), "module 2"); // The full build ID. assert_eq!( modules[1].code_identifier().unwrap(), CodeId::new("0001020304050607".to_string()) ); assert_eq!(modules[1].debug_file().unwrap(), "module 2"); // The first 16 bytes of the build ID interpreted as a GUID, padded with // zeroes in this case. assert_eq!( modules[1].debug_identifier().unwrap(), DebugId::from_breakpad("030201000504070600000000000000000").unwrap() ); } #[test] fn test_os() { let dump = SynthMinidump::with_endian(Endian::Little).add_system_info( SystemInfo::new(Endian::Little).set_platform_id(PlatformId::MacOs as u32), ); let dump = read_synth_dump(dump).unwrap(); let system_info = dump.get_stream::().unwrap(); assert_eq!(system_info.os, Os::MacOs); } #[test] fn test_macos_ids() { let name = DumpString::new("macos module", Endian::Little); let cv_record = Section::with_endian(Endian::Little) // signature .D32(md::CvSignature::Pdb70 as u32) // signature, a GUID .D32(0xaabbccdd) .D16(0xeeff) .D16(0x0011) .append_bytes(b"\x22\x33\x44\x55\x66\x77\x88\x99") // age, breakpad writes 0 .D32(0) // pdb_file_name .append_bytes(b"helpivecrashed.dylib\0"); let module = SynthModule::new( Endian::Little, 0x100000000, 0x4000, &name, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record); let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info( SystemInfo::new(Endian::Little).set_platform_id(PlatformId::MacOs as u32), ) .add_module(module) .add(name) .add(cv_record); let dump = read_synth_dump(dump).unwrap(); let system_info = dump.get_stream::().unwrap(); assert_eq!(system_info.os, Os::MacOs); let module_list = dump.get_stream::().unwrap(); let modules = module_list.iter().collect::>(); assert_eq!(modules.len(), 1); // should be the uuid stored in cv record assert_eq!( modules[0].code_identifier().unwrap(), CodeId::new("AABBCCDDEEFF00112233445566778899".to_owned()) ); // should match code identifier, but with the age appended to it assert_eq!( modules[0].debug_identifier().unwrap(), DebugId::from_breakpad("AABBCCDDEEFF001122334455667788990").unwrap() ); assert_eq!(modules[0].code_file(), "macos module"); assert_eq!(modules[0].debug_file().unwrap(), "helpivecrashed.dylib"); } #[test] fn test_windows_code_id_no_cv() { let name = DumpString::new("windows module", Endian::Little); let module = SynthModule::new( Endian::Little, 0x100000000, 0x4000, // size of image &name, 0xb105_4d2a, // datetime 0x34571371, Some(&STOCK_VERSION_INFO), ); let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info( SystemInfo::new(Endian::Little) .set_platform_id(PlatformId::VER_PLATFORM_WIN32_NT as u32), ) .add_module(module) .add(name); let dump = read_synth_dump(dump).unwrap(); let system_info = dump.get_stream::().unwrap(); assert_eq!(system_info.os, Os::Windows); let module_list = dump.get_stream::().unwrap(); let modules = module_list.iter().collect::>(); assert_eq!(modules.len(), 1); // should match datetime + size of image assert_eq!( modules[0].code_identifier().unwrap(), CodeId::new("B1054D2A4000".to_owned()) ); } #[test] fn test_null_id() { // Add a module with an ELF build id of nothing but zeros let name1 = DumpString::new("module 1", Endian::Little); const MODULE1_BUILD_ID: &[u8] = &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; let cv_record1 = Section::with_endian(Endian::Little) .D32(md::CvSignature::Elf as u32) // signature .append_bytes(MODULE1_BUILD_ID); let module1 = SynthModule::new( Endian::Little, 0x100000000, 0x4000, &name1, 0xb1054d2a, 0x34571371, Some(&STOCK_VERSION_INFO), ) .cv_record(&cv_record1); // Add a module with a PDB70 build id of nothing but zeros let name2 = DumpString::new("module 2", Endian::Little); let cv_record2 = Section::with_endian(Endian::Little) // signature .D32(md::CvSignature::Pdb70 as u32) // signature, a GUID .D32(0x0) .D16(0x0) .D16(0x0) .append_bytes(b"\0\0\0\0\0\0\0\0") // age, breakpad writes 0 .D32(0) // pdb_file_name .append_bytes(b"\0"); let module2 = SynthModule::new( Endian::Little, 0x100000000, 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::().unwrap(); let modules = module_list.iter().collect::>(); assert!(modules[0].debug_identifier().is_none()); assert!(modules[1].debug_identifier().is_none()); } #[test] fn test_thread_list_x86() { let context = minidump_synth::x86_context(Endian::Little, 0xabcd1234, 0x1010); let stack = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(0, 0x1000), 0x1000, ); let arch = md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_INTEL as u16; let system_info = SystemInfo::new(Endian::Little).set_processor_architecture(arch); let thread = Thread::new(Endian::Little, 0x1234, &stack, &context); let dump = SynthMinidump::with_endian(Endian::Little) .add_thread(thread) .add(context) .add_memory(stack) .add_system_info(system_info); let dump = read_synth_dump(dump).unwrap(); let mut thread_list = dump.get_stream::>().unwrap(); let system_info = dump.get_stream::().unwrap(); let misc_info = dump.get_stream::().ok(); 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(&system_info, misc_info.as_ref()) .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 = minidump_synth::amd64_context(Endian::Little, 0x1234abcd1234abcd, 0x1000000010000000); let stack = Memory::with_section( Section::with_endian(Endian::Little).append_repeated(0, 0x1000), 0x1000000010000000, ); let arch = md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_AMD64 as u16; let system_info = SystemInfo::new(Endian::Little).set_processor_architecture(arch); let thread = Thread::new(Endian::Little, 0x1234, &stack, &context); let dump = SynthMinidump::with_endian(Endian::Little) .add_thread(thread) .add(context) .add_memory(stack) .add_system_info(system_info); let dump = read_synth_dump(dump).unwrap(); let mut thread_list = dump.get_stream::>().unwrap(); let system_info = dump.get_stream::().unwrap(); let misc_info = dump.get_stream::().ok(); 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(&system_info, misc_info.as_ref()) .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::(), 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::().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::().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 ); } #[test] fn test_stability_report() { let stability_report_stream = SimpleStream { stream_type: MINIDUMP_STREAM_TYPE::StabilityReportStream as u32, // This data was copied from a real stability report. section: Section::with_endian(Endian::Little).append_bytes(&[ 18, 24, 24, 236, 118, 34, 12, 10, 10, 8, 143, 84, 16, 198, 160, 1, 24, 154, 84, 58, 5, 18, 3, 8, 174, 2, 58, 16, 10, 14, 8, 159, 222, 139, 3, 16, 213, 203, 215, 1, 24, 225, 161, 7, ]), }; let dump = SynthMinidump::with_endian(Endian::Little).add_stream(stability_report_stream); let dump = read_synth_dump(dump).unwrap(); let stability_report = dump.get_stream::().unwrap(); let process_states = stability_report.process_states.first().unwrap(); assert_eq!(process_states.process_id, Some(15212)); assert_eq!( process_states.memory_state, Some(crate::process_state::MemoryState { windows_memory: Some(crate::process_state::memory_state::WindowsMemory { process_peak_pagefile_usage: Some(10778), process_peak_workingset_size: Some(20550), process_private_usage: Some(10767), process_allocation_attempt: None, }) }) ); assert_eq!( process_states.file_system_state, Some(crate::process_state::FileSystemState { posix_file_system_state: None, windows_file_system_state: Some( crate::process_state::file_system_state::WindowsFileSystemState { process_handle_count: Some(302), } ) }) ); assert_eq!( stability_report.system_memory_state, Some(crate::SystemMemoryState { windows_memory: Some(crate::system_memory_state::WindowsMemory { system_commit_limit: Some(6483743), system_commit_remaining: Some(3532245), system_handle_count: Some(119009), }) }) ); } #[test] fn test_exception_x86() { // Defaults to x86 let system_info = SystemInfo::new(Endian::Little); let mut exception = Exception::new(Endian::Little); // Check that we clear the erroneous high bits for 32-bit exception.exception_record.exception_address = 0xf0e1_d2c3_b4a5_9687; // FIXME: test other fields too let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info(system_info) .add_exception(exception); let dump = read_synth_dump(dump).unwrap(); let system_stream = dump.get_stream::().unwrap(); let exception_stream = dump.get_stream::().unwrap(); assert_eq!( exception_stream.get_crash_address(system_stream.os, system_stream.cpu), 0xb4a5_9687 ); } #[test] fn test_exception_x64() { // Defaults to x86 let system_info = SystemInfo::new(Endian::Little) .set_processor_architecture(ProcessorArchitecture::PROCESSOR_ARCHITECTURE_AMD64 as u16); let mut exception = Exception::new(Endian::Little); // Check that we don't truncate this on 64-bit exception.exception_record.exception_address = 0xf0e1_d2c3_b4a5_9687; // FIXME: test other fields too let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info(system_info) .add_exception(exception); let dump = read_synth_dump(dump).unwrap(); let system_stream = dump.get_stream::().unwrap(); let exception_stream = dump.get_stream::().unwrap(); assert_eq!( exception_stream.get_crash_address(system_stream.os, system_stream.cpu), 0xf0e1_d2c3_b4a5_9687 ); } #[test] fn test_fuzzed_oom() { // https://github.com/rust-minidump/rust-minidump/issues/381 let data = b"MDMP\x93\xa7\x00\x00\x00\xffffdYfffff@\n\nfp\n\xbb\xff\xff\xff\n\xff\n"; assert!(Minidump::read(data.as_ref()).is_err()); // https://github.com/getsentry/symbolic/issues/478 let data = b"MDMP\x93\xa7\x00\x00\r\x00\x00\x00 \xff\xff\xff\xff\xff\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; assert!(Minidump::read(data.as_ref()).is_err()); } #[test] fn test_empty_module() { let name = DumpString::new("/SYSV00000000 (deleted)", Endian::Little); let module = SynthModule::new( Endian::Little, 0x7f602915e000, 0x26000, &name, 0x0, 0x0, // All of these are completely zeroed out in the wild. Some(&md::VS_FIXEDFILEINFO { signature: 0, struct_version: 0, file_version_hi: 0, file_version_lo: 0, product_version_hi: 0, product_version_lo: 0, file_flags_mask: 0, file_flags: 0, file_os: 0, file_type: 0, file_subtype: 0, file_date_hi: 0, file_date_lo: 0, }), ); let dump = SynthMinidump::with_endian(Endian::Little) .add_module(module) .add(name); let dump = read_synth_dump(dump).unwrap(); let module_list = dump.get_stream::().unwrap(); let modules = module_list.iter().collect::>(); assert_eq!(modules.len(), 1); assert_eq!(modules[0].code_identifier(), None); assert_eq!(modules[0].debug_identifier(), None); assert_eq!(modules[0].code_file(), "/SYSV00000000 (deleted)"); assert_eq!(modules[0].debug_file(), None); assert_eq!(modules[0].raw.base_of_image, 0x7f602915e000); assert_eq!(modules[0].raw.size_of_image, 0x26000); } #[test] fn test_handle_data_stream() { const HANDLE_VALUE: u64 = 123; const TYPE_NAME: &str = "This is a type name"; const OBJECT_NAME: &str = "And this is an object name"; let type_name = DumpString::new(TYPE_NAME, Endian::Little); let object_name = DumpString::new(OBJECT_NAME, Endian::Little); let handle = SynthHandleDescriptor::new( Endian::Little, HANDLE_VALUE, Some(&type_name), Some(&object_name), 0xf00ff00f, 0xcafecafe, 0xcacacaca, 0xbeefbeef, ); let dump = SynthMinidump::with_endian(Endian::Little) .add_handle_descriptor(handle) .add(type_name) .add(object_name); let dump = read_synth_dump(dump).unwrap(); let handle_data_stream = dump .get_stream::() .expect("The HANDLE_DATA_STREAM must be present"); let handles = handle_data_stream.iter().collect::>(); assert_eq!(handles.len(), 1); assert_eq!( handles[0] .raw .handle() .expect("The `handle` field must be present"), &HANDLE_VALUE ); assert_eq!( handles[0] .type_name .as_ref() .expect("The `type_name` field must be populated"), TYPE_NAME ); assert_eq!( handles[0] .object_name .as_ref() .expect("The `object_name` field must be populated"), OBJECT_NAME ); } #[test] fn test_windows_status_code() { let address = 0x1234_5678_u64; let amd64_system_info = SystemInfo::new(Endian::Little) .set_processor_architecture(ProcessorArchitecture::PROCESSOR_ARCHITECTURE_AMD64 as u16) .set_platform_id(PlatformId::VER_PLATFORM_WIN32_NT as u32); let mut exception = Exception::new(Endian::Little); exception.exception_record.exception_code = err::ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR as u32; exception.exception_record.exception_address = address; exception.exception_record.number_parameters = 3; exception.exception_record.exception_information[0] = err::ExceptionCodeWindowsInPageErrorType::WRITE as u64; exception.exception_record.exception_information[1] = address; exception.exception_record.exception_information[2] = err::NtStatusWindows::STATUS_DISK_FULL as u64; let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info(amd64_system_info) .add_exception(exception); let dump = read_synth_dump(dump).unwrap(); let system_stream = dump.get_stream::().unwrap(); let exception_stream = dump.get_stream::().unwrap(); assert_eq!( exception_stream.get_crash_reason(system_stream.os, system_stream.cpu), CrashReason::WindowsInPageError( err::ExceptionCodeWindowsInPageErrorType::WRITE, NtStatusWindows::STATUS_DISK_FULL as u64 ) ); // Let's try again but for 32-bit x86 let x86_system_info = SystemInfo::new(Endian::Little) .set_processor_architecture(ProcessorArchitecture::PROCESSOR_ARCHITECTURE_INTEL as u16) .set_platform_id(PlatformId::VER_PLATFORM_WIN32_NT as u32); let mut exception = Exception::new(Endian::Little); exception.exception_record.exception_code = err::ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR as u32; exception.exception_record.exception_address = address; exception.exception_record.number_parameters = 3; exception.exception_record.exception_information[0] = err::ExceptionCodeWindowsInPageErrorType::WRITE as u64; exception.exception_record.exception_information[1] = address; // Sign extend the error code like 32-bit windbg.dll does exception.exception_record.exception_information[2] = 0xffff_ffff_0000_0000 | (err::NtStatusWindows::STATUS_DISK_FULL as u64); let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info(x86_system_info) .add_exception(exception); let dump = read_synth_dump(dump).unwrap(); let system_stream = dump.get_stream::().unwrap(); let exception_stream = dump.get_stream::().unwrap(); assert_eq!( exception_stream.get_crash_reason(system_stream.os, system_stream.cpu), CrashReason::WindowsInPageError( err::ExceptionCodeWindowsInPageErrorType::WRITE, NtStatusWindows::STATUS_DISK_FULL as u64 ) ); } #[test] fn test_linux_abort_si_code() { let amd64_system_info = SystemInfo::new(Endian::Little) .set_processor_architecture(ProcessorArchitecture::PROCESSOR_ARCHITECTURE_AMD64 as u16) .set_platform_id(PlatformId::Linux as u32); let mut exception = Exception::new(Endian::Little); exception.exception_record.exception_code = err::ExceptionCodeLinux::SIGABRT as u32; exception.exception_record.exception_flags = err::ExceptionCodeLinuxSicode::SI_TKILL as u32; let dump = SynthMinidump::with_endian(Endian::Little) .add_system_info(amd64_system_info) .add_exception(exception); let dump = read_synth_dump(dump).unwrap(); let system_stream = dump.get_stream::().unwrap(); let exception_stream = dump.get_stream::().unwrap(); assert_eq!( exception_stream .get_crash_reason(system_stream.os, system_stream.cpu) .to_string(), "SIGABRT / SI_TKILL" ); } }