// 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 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;
pubusecrate::context::*; usecrate::strings::*; usecrate::system_info::{Cpu, Os, PointerWidth}; use minidump_common::errors::{selfas err}; use minidump_common::format::{selfas 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)] pubstruct Minidump<'a, T> where
T: Deref<Target = [u8]> + 'a,
{
data: T, /// The raw minidump header from the file. pub header: md::MINIDUMP_HEADER,
streams: BTreeMap<u32, (u32, md::MINIDUMP_DIRECTORY)>,
system_info: Option<MinidumpSystemInfo>, /// The endianness of this minidump file. pub endian: scroll::Endian,
_phantom: PhantomData<&'a [u8]>,
}
impl Error { /// Returns just the name of the error, as a more human-friendly version of /// an error-code for error logging. pubfn name(&self) -> &'static str { matchself {
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`. pubtrait 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<Self, Error>;
}
/// 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)] pubenum 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. pubenum 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)] pubstruct MinidumpLinuxMaps<'a> { /// The memory regions, in the order they were stored in the minidump.
regions: Vec<MinidumpLinuxMapInfo<'a>>, /// Map from address range to index in regions. Use /// [`MinidumpLinuxMaps::memory_info_at_address`].
regions_by_addr: RangeMap<u64, usize>,
}
/// A memory mapping entry for the process we are analyzing. #[derive(Debug, Clone, PartialEq, Eq)] pubstruct MinidumpLinuxMapInfo<'a> { pub map: MemoryMap,
_phantom: PhantomData<&'a u8>,
}
#[derive(Debug, Clone)] pubstruct MinidumpMemoryInfoList<'a> { /// The memory regions, in the order they were stored in the minidump.
regions: Vec<MinidumpMemoryInfo<'a>>, /// Map from address range to index in regions. Use /// [`MinidumpMemoryInfoList::memory_info_at_address`].
regions_by_addr: RangeMap<u64, usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)] /// Metadata about a region of memory (whether it is executable, freed, private, and so on). pubstruct 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)] pubenum 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<u8>),
}
/// An executable or shared library loaded in the process at the time the `Minidump` was written. #[derive(Debug, Clone)] pubstruct 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<CodeView>, /// A misc debug record, if one is present. pub misc_info: Option<md::IMAGE_DEBUG_MISC>,
os: Os, /// The parsed DebugId of the module, if one is present.
debug_id: Option<DebugId>,
}
/// A list of `MinidumpModule`s contained in a `Minidump`. #[derive(Debug, Clone)] pubstruct MinidumpModuleList { /// The modules, in the order they were stored in the minidump.
modules: Vec<MinidumpModule>, /// Map from address range to index in modules. Use `MinidumpModuleList::module_at_address`.
modules_by_addr: RangeMap<u64, usize>,
}
/// A mapping of thread ids to their names. #[derive(Debug, Clone, Default)] pubstruct MinidumpThreadNames {
names: BTreeMap<u32, String>,
}
/// 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)] pubstruct 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)] pubstruct MinidumpUnloadedModuleList { /// The modules, in the order they were stored in the minidump.
modules: Vec<MinidumpUnloadedModule>, /// Map from address range to index in modules. /// Use `MinidumpUnloadedModuleList::modules_at_address`.
modules_by_addr: Vec<(Range<u64>, usize)>,
}
/// Contains object-specific information for a handle. Microsoft documentation /// doesn't describe the contents of this type. #[derive(Debug, Clone)] pubstruct MinidumpHandleObjectInformation { pub raw: md::MINIDUMP_HANDLE_OBJECT_INFORMATION, pub info_type: md::MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE,
}
/// Describes the state of an individual system handle at the time the minidump was written. #[derive(Debug, Clone)] pubstruct 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<String>, /// The object name of this handle, if present. /// On Linux this is the file path. pub object_name: Option<String>, /// Object information for this handle, can be empty, platform-specific. pub object_infos: Vec<MinidumpHandleObjectInformation>,
}
/// 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)] pubstruct MinidumpHandleDataStream { pub handles: Vec<MinidumpHandleDescriptor>,
}
/// The state of a thread from the process when the minidump was written. #[derive(Debug)] pubstruct 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<MinidumpMemory<'a>>, /// Saved endianness for lazy parsing.
endian: scroll::Endian,
}
/// A list of `MinidumpThread`s contained in a `Minidump`. #[derive(Debug)] pubstruct MinidumpThreadList<'a> { /// The threads, in the order they were present in the `Minidump`. pub threads: Vec<MinidumpThread<'a>>, /// A map of thread id to index in `threads`.
thread_ids: HashMap<u32, usize>,
}
/// The state of a thread from the process when the minidump was written. #[derive(Debug)] pubstruct 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)] pubstruct MinidumpThreadInfoList { /// The thread info entries, in the order they were present in the `Minidump`. pub thread_infos: Vec<MinidumpThreadInfo>, /// A map of thread id to index in `entries`.
thread_ids: HashMap<u32, usize>,
}
/// Information about the system that generated the minidump. #[derive(Debug, Clone)] pubstruct 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<String>, /// An x86 (not x64!) CPU vendor name that is stored in `raw` but in a way /// that's
cpu_info: Option<String>,
}
/// A region of memory from the process that wrote the minidump. /// This is the underlying generic type for [MinidumpMemory] and [MinidumpMemory64]. #[derive(Clone, Debug)] pubstruct 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. pubtype MinidumpMemory<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>;
/// A large region of memory from the process that wrote the minidump (usually a full dump). pubtype MinidumpMemory64<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>;
/// Provides a unified interface for MinidumpMemory and MinidumpMemory64 #[derive(Debug, Clone, Copy)] pubenum UnifiedMemory<'a, 'mdmp> {
Memory(&'a MinidumpMemory<'mdmp>),
Memory64(&'a MinidumpMemory64<'mdmp>),
}
/// Miscellaneous information about the process that wrote the minidump. #[derive(Debug, Clone)] pubstruct 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)] pubstruct MinidumpBreakpadInfo {
raw: md::MINIDUMP_BREAKPAD_INFO, /// The thread that wrote the minidump. pub dump_thread_id: Option<u32>, /// The thread that requested that a minidump be written. pub requesting_thread_id: Option<u32>,
}
/// The reason for a process crash. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pubenum 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)] pubstruct 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)] pubstruct MinidumpMemoryListBase<'a, Descriptor> { /// The memory regions, in the order they were stored in the minidump.
regions: Vec<MinidumpMemoryBase<'a, Descriptor>>, /// Map from address range to index in regions. Use `MinidumpMemoryList::memory_at_address`.
regions_by_addr: RangeMap<u64, usize>,
}
/// A list of memory regions included in a minidump. pubtype MinidumpMemoryList<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>;
/// A list of large memory regions included in a minidump (usually a full dump). pubtype MinidumpMemory64List<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>;
/// Provides a unified interface for MinidumpMemoryList and MinidumpMemory64List #[derive(Debug)] pubenum UnifiedMemoryList<'a> {
Memory(MinidumpMemoryList<'a>),
Memory64(MinidumpMemory64List<'a>),
} impl<'a> Default for UnifiedMemoryList<'a> { fn default() -> Self { Self::Memory(Default::default())
}
}
/// Information about an assertion that caused a crash. #[derive(Debug)] pubstruct MinidumpAssertion { pub raw: md::MINIDUMP_ASSERTION_INFO,
}
/// A typed annotation object. #[derive(Clone, Debug)] #[non_exhaustive] pubenum 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),
}
/// Additional Crashpad-specific information about a module carried within a minidump file. #[derive(Debug)] pubstruct 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<String>, pub simple_annotations: BTreeMap<String, String>, pub annotation_objects: BTreeMap<String, MinidumpAnnotation>,
}
/// Additional Crashpad-specific information carried within a minidump file. #[derive(Debug)] pubstruct MinidumpCrashpadInfo { pub raw: md::MINIDUMP_CRASHPAD_INFO, pub simple_annotations: BTreeMap<String, String>, pub module_list: Vec<MinidumpModuleCrashpadInfo>,
}
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(|| "<invalid date>".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<String> { let u: u32 = bytes.gread_with(offset, endian).ok()?; let size = u as usize; if size % 2 != 0 || (*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())
}
/// Convert `bytes` with trailing NUL characters to a string fn string_from_bytes_nul(bytes: &[u8]) -> Option<Cow<'_, str>> {
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<String> = 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<CodeView> { 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<DebugId> { 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 = <md::GUID>::size_with(&endian); let guid = if raw.build_id.len() < guid_size { // Pad with zeros. let v: Vec<u8> = raw
.build_id
.iter()
.cloned()
.chain(iter::repeat(0))
.take(guid_size)
.collect();
v.pread_with::<md::GUID>(0, endian).ok()
} else {
raw.build_id.pread_with::<md::GUID>(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))
}
/// Read additional data to construct a `MinidumpModule` from `bytes` using the information /// from the module list in `raw`. pubfn read(
raw: md::MINIDUMP_MODULE,
bytes: &[u8],
endian: scroll::Endian,
system_info: Option<&MinidumpSystemInfo>,
) -> Result<MinidumpModule, Error> { letmut 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));
fn code_identifier(&self) -> Option<CodeId> { matchself.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 ifself.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<Cow<'_, str>> { matchself.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<DebugId> { self.debug_id
} fn version(&self) -> Option<Cow<'_, str>> { ifself.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
}
}
}
/// Read additional data to construct a `MinidumpUnloadedModule` from `bytes` using the information /// from the module list in `raw`. pubfn read(
raw: md::MINIDUMP_UNLOADED_MODULE,
bytes: &[u8],
endian: scroll::Endian,
) -> Result<MinidumpUnloadedModule, Error> { letmut 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`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MINIDUMP_UNLOADED_MODULE
base_of_image = {:#x}
size_of_image = {:#x}
checksum = {:#x}
time_date_stamp = {:#x} {}
module_name_rva = {:#x}
(code_file) = \"{}\"
(code_identifier) = \"{}\" ", self.raw.base_of_image, self.raw.size_of_image, self.raw.checksum, self.raw.time_date_stamp,
format_time_t(self.raw.time_date_stamp), self.raw.module_name_rva, self.code_file(), self.code_identifier().unwrap_or_default(),
)?;
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<CodeId> { // 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<Cow<'_, str>> {
None
} fn debug_identifier(&self) -> Option<DebugId> {
None
} fn version(&self) -> Option<Cow<'_, str>> {
None
}
}
/// Parses X:Y or X=Y lists, skipping any blank/unparseable lines fn linux_list_iter(
bytes: &[u8],
separator: u8,
) -> impl Iterator<Item = (&LinuxOsStr, &LinuxOsStr)> { 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);
let (count, counted_size) = ensure_count_in_bound(
bytes,
u as usize,
<T>::size_with(&endian),
mem::size_of::<u32>(),
)?;
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 letmut raw_entries = Vec::with_capacity(count); for _ in0..count { let raw: T = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
raw_entries.push(raw);
}
Ok(raw_entries)
}
fn read_ex_stream_list<'a, T>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<Vec<T>, Error> where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
T: SizeWith<scroll::Endian>,
{ // 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 = <T>::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 letmut raw_entries = Vec::with_capacity(number_of_entries); for _ in0..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<Self, Error> { letmut offset = 0; let raw_names: Vec<md::MINIDUMP_THREAD_NAME> =
read_stream_list(&mut offset, bytes, endian)?; // read out the actual names letmut names = BTreeMap::new(); for raw_name in raw_names { letmut offset = raw_name.thread_name_rva as usize; // Better to just drop unreadable names individually than the whole stream. iflet 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 MinidumpModuleList { /// Return an empty `MinidumpModuleList`. pubfn new() -> MinidumpModuleList {
MinidumpModuleList {
modules: vec![],
modules_by_addr: RangeMap::new(),
}
} /// Create a `MinidumpModuleList` from a list of `MinidumpModule`s. pubfn from_modules(modules: Vec<MinidumpModule>) -> MinidumpModuleList { let modules_by_addr = modules
.iter()
.enumerate()
.map(|(i, module)| (module.memory_range(), i))
.into_rangemap_safe();
MinidumpModuleList {
modules,
modules_by_addr,
}
}
/// Returns the module corresponding to the main executable. pubfn 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
}
}
/// Iterate over the modules in arbitrary order. pubfn iter(&self) -> impl Iterator<Item = &MinidumpModule> { self.modules.iter()
}
/// Iterate over the modules in order by memory address. pubfn by_addr(&self) -> impl DoubleEndedIterator<Item = &MinidumpModule> { 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`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MinidumpModuleList
module_count = {}
/// Return an iterator of `MinidumpUnloadedModules` whose address range covers `address`. pubfn modules_at_address(
&self,
address: u64,
) -> impl Iterator<Item = &MinidumpUnloadedModule> { // 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. pubfn iter(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> { self.modules.iter()
}
/// Iterate over the modules in order by memory address. pubfn by_addr(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> { 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`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MinidumpUnloadedModuleList
module_count = {}
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<MinidumpUnloadedModuleList, Error> { letmut offset = 0; let raw_modules: Vec<md::MINIDUMP_UNLOADED_MODULE> =
read_ex_stream_list(&mut offset, bytes, endian)?; // read auxiliary data for each module letmut 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)] pubfn $name(&self) -> Option<&$t> { matchself {
$(
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)*);
};
match ctx.fieldsize {
MINIDUMP_HANDLE_DESCRIPTOR_SIZE => { let raw = src.pread_with::<md::MINIDUMP_HANDLE_DESCRIPTOR>(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::<md::MINIDUMP_HANDLE_DESCRIPTOR_2>(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); letmut object_infos = Vec::<MinidumpHandleObjectInformation>::new(); letmut object_info_rva = raw.object_info_rva;
/// Create a `MinidumpHandleDataStream` from a list of `MinidumpHandleDescriptor`s. pubfn from_handles(handles: Vec<MinidumpHandleDescriptor>) -> MinidumpHandleDataStream {
MinidumpHandleDataStream { handles }
}
/// Iterate over the handles in the order contained in the minidump. pubfn iter(&self) -> impl Iterator<Item = &MinidumpHandleDescriptor> { self.handles.iter()
}
let size_of_header = bytes
.gread_with::<u32>(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?; let size_of_descriptor = bytes
.gread_with::<u32>(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?; let number_of_descriptors = bytes
.gread_with::<u32>(&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;
letmut descriptors = Vec::<MinidumpHandleDescriptor>::with_capacity(number_of_entries); for _ in0..number_of_entries { let descriptor: MinidumpHandleDescriptor = bytes
.gread_with(&mut offset, ctx)
.or(Err(Error::StreamReadFailure))?;
descriptors.push(descriptor);
}
impl<'a> MinidumpMemory<'a> { pubfn read(
desc: &md::MINIDUMP_MEMORY_DESCRIPTOR,
data: &'a [u8],
endian: scroll::Endian,
) -> Result<MinidumpMemory<'a>, 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`. pubfn print<T: Write>(&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<'a> MinidumpMemory64<'a> { /// Write a human-readable description of this `MinidumpMemory64` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pubfn print<T: Write>(&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::<T>()` bytes of memory at `addr` from this region. /// /// Return `None` if the requested address range falls out of the bounds /// of this memory region. pubfn get_memory_at_address<T>(&self, addr: u64) -> Option<T> where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
{ let start = addr.checked_sub(self.base_address)? as usize;
/// Create a `MinidumpMemoryListBase` from a list of `MinidumpMemoryBase`s. pubfn from_regions(
regions: Vec<MinidumpMemoryBase<'mdmp, Descriptor>>,
) -> 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. pubfn 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). pubfn iter<'slf>(
&'slf self,
) -> impl Iterator<Item = &'slf MinidumpMemoryBase<'mdmp, Descriptor>> { self.regions.iter()
}
/// Iterate over the memory regions in order by memory address. pubfn by_addr<'slf>(
&'slf self,
) -> impl Iterator<Item = &'slf MinidumpMemoryBase<'mdmp, Descriptor>> { self.regions_by_addr
.ranges_values()
.map(move |&(_, index)| &self.regions[index])
}
}
impl<'mdmp> MinidumpMemoryList<'mdmp> { /// Write a human-readable description of this `MinidumpMemoryList` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pubfn print<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
write!(
f, "MinidumpMemoryList
region_count = {}
impl<'mdmp> MinidumpMemory64List<'mdmp> { /// Write a human-readable description of this `MinidumpMemory64List` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pubfn print<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
write!(
f, "MinidumpMemory64List
region_count = {}
letmut raw_entries = Vec::with_capacity(count); for _ in0..count { let raw: md::MINIDUMP_MEMORY_DESCRIPTOR64 = bytes
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
raw_entries.push(raw);
}
letmut 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)?;
/// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s. pubfn from_regions(regions: Vec<MinidumpMemoryInfo<'mdmp>>) -> 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. pubfn 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). pubfn iter<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpMemoryInfo<'mdmp>> { self.regions.iter()
}
/// Iterate over the memory regions in order by memory address. pubfn by_addr<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpMemoryInfo<'mdmp>> { self.regions_by_addr
.ranges_values()
.map(move |&(_, index)| &self.regions[index])
}
/// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s. pubfn from_regions(regions: Vec<MinidumpLinuxMapInfo<'mdmp>>) -> 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. pubfn 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. pubfn iter<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpLinuxMapInfo<'mdmp>> { self.regions.iter()
}
/// Iterate over the memory regions in order by memory address. pubfn by_addr<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpLinuxMapInfo<'mdmp>> { self.regions_by_addr
.ranges_values()
.map(move |&(_, index)| &self.regions[index])
}
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. pubfn new(
info: Option<MinidumpMemoryInfoList<'a>>,
maps: Option<MinidumpLinuxMaps<'a>>,
) -> Option<Self> { 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. pubfn memory_info_at_address(&self, address: u64) -> Option<UnifiedMemoryInfo> { matchself { 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. pubfn iter(&self) -> impl Iterator<Item = UnifiedMemoryInfo> { // 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. pubfn by_addr(&self) -> impl Iterator<Item = UnifiedMemoryInfo> { 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`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> { matchself { 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. pubfn 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. pubfn 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),* >)? ( & style='color:red'>self $(, $param:ident : $type:ty)* ) -> $ret:ty ; $($rest:tt)* ) => {
$(#[$attr])*
$vis fn $name $(< $($t_param : $t_type),* >)? (&self $(, $param : $type)*) -> $ret { matchself { Self::Info(info) => info.$name($($param),*), Self::Map(map) => map.$name($($param),*),
}
}
pubfn stack_memory<'mem>(
&'mem self,
memory_list: &'mem UnifiedMemoryList<'a>,
) -> Option<UnifiedMemory<'mem, 'a>> { 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`. pubfn print<T: Write>(
&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}
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); iflet 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(); letmut offset = 0; for chunk in stack.bytes().chunks_exact(chunk_size) {
write!(f, " {offset:#010x}: ")?;
match pointer_width {
PointerWidth::Bits32 => { let value = matchself.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 = matchself.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}")?;
}
}
/// 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! pubfn last_error(&self, cpu: Cpu, memory: &UnifiedMemoryList) -> Option<CrashReason> { // 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;
// 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. pubfn 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`. pubfn print<T: Write>(
&self,
f: &mut T,
memory: Option<&UnifiedMemoryList<'a>>,
system: Option<&MinidumpSystemInfo>,
misc: Option<&MinidumpMiscInfo>,
brief: bool,
) -> io::Result<()> {
write!(
f,
r#"MinidumpThreadList
thread_count = {}
/// Get the thread info with id `id` from this thread info list if it exists. pubfn 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`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MinidumpThreadInfoList
thread_info_count = {}
letmut thread_infos = Vec::with_capacity(raw_thread_infos.len()); letmut 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 });
}
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);
letmut 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 => { letmut cpu_info = String::new();
iflet 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))?;
// 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;
// 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 letmut comma = ""; for &(_, feature) in features
.iter()
.filter(|&&(feature, _)| elf_hwcaps.contains(feature))
{
cpu_info.push_str(comma);
cpu_info.push_str(feature);
comma = ",";
}
}
/// 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`. pubfn csd_version(&self) -> Option<Cow<str>> { self.csd_version.as_deref().map(Cow::Borrowed)
}
/// Returns a string describing the cpu's vendor and model. pubfn cpu_info(&self) -> Option<Cow<str>> { 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. pubfn os_parts(&self) -> (String, Option<String>) { 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());
// 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("")); letmut 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::<Vec<&str>>().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)] pubfn $name(&self) -> Option<&$t> { matchself {
$(
RawMiscInfo::$variant(ref raw) => Some(&raw.$name),
)+
_ => None,
}
}
};
(@def $name:ident $flag:ident $t:ty [$($variant:ident)+]) => { #[allow(unreachable_patterns)] pubfn $name(&self) -> Option<&$t> { matchself {
$(
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)*);
};
impl<'a> MinidumpStream<'a> 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<MinidumpMacCrashInfo, Error> { // 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; letmut prev_version = None; letmut 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. iflet 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 = &mut0; 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(); letmut strings = <$strings>::default();
// Read out all the strings we know about for i in0..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.
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
);
} letmut bootargs_offset = raw.bootargs as usize; let bootargs = read_string_utf16(&mut bootargs_offset, all, endian);
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. pubfn iter(&self) -> impl Iterator<Item = (&>'a LinuxOsStr, &'a LinuxOsStr)> {
linux_list_iter(self.data, b':')
}
/// Get the raw bytes of the `/proc/cpuinfo` dump. pubfn 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. pubfn iter(&self) -> impl Iterator<Item = (&>'a LinuxOsStr, &'a LinuxOsStr)> {
linux_list_iter(self.data, b'=')
}
/// Get the raw bytes of the `/proc/self/environ` dump. pubfn 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. pubfn iter(&self) -> impl Iterator<Item = (&>'a LinuxOsStr, &'a LinuxOsStr)> {
linux_list_iter(self.data, b':')
}
/// Get the raw bytes of the `/proc/self/status` dump. pubfn 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. pubfn iter(&self) -> impl Iterator<Item = &'a LinuxOsStr> {
LinuxOsStr::from_bytes(self.data).lines()
}
/// Get the raw bytes of the `/proc/self/limits` dump. pubfn 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. pubfn iter(&self) -> impl Iterator<Item = (&>'a LinuxOsStr, &'a LinuxOsStr)> {
linux_list_iter(self.data, b'=')
}
/// Get the raw bytes of the `/etc/lsb-release` dump. pubfn raw_bytes(&self) -> Cow<'a, [u8]> {
Cow::Borrowed(self.data)
}
}
impl MinidumpBreakpadInfo { /// Write a human-readable description of this `MinidumpBreakpadInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MINIDUMP_BREAKPAD_INFO
validity = {:#x}
dump_thread_id = {}
requesting_thread_id = {}
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. pubfn from_windows_code(exception_code: u32) -> CrashReason { iflet 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. pubfn from_windows_error(error_code: u32) -> CrashReason { iflet Some(err) = err::WinErrorWindows::from_u32(error_code) { Self::WindowsWinError(err)
} elseiflet Some(err) = err::NtStatusWindows::from_u32(error_code) { Self::WindowsNtStatus(err)
} elseiflet Some(err) = Self::from_windows_error_with_facility(error_code) {
err
} else { Self::WindowsUnknown(error_code)
}
}
if (error_code & SEVERITY_MASK) != 0 { // This could be an NTSTATUS or HRESULT code of a specific facility iflet Some(facility) =
err::WinErrorFacilityWindows::from_u32((error_code & FACILITY_MASK) >> 16)
{ iflet Some(error) = err::WinErrorWindows::from_u32(error_code & ERROR_MASK) { return Some(Self::WindowsWinErrorWithFacility(facility, error));
}
}
}
// 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]; iflet 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; iflet 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
}
}
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)?; letmut reason = CrashReason::MacGeneral(mac_reason, exception_flags);
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::*;
// 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"),
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). pubfn context(
&self,
system_info: &MinidumpSystemInfo,
misc: Option<&MinidumpMiscInfo>,
) -> Option<Cow<'a, MinidumpContext>> {
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). pubfn 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)) ifself.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][]. pubfn 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). pubfn 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`. pubfn print<T: Write>(
&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 in0..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
)?; iflet Some(system_info) = system { iflet 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 utf16_to_string(data: &[u16]) -> Option<String> { use std::slice;
let len = data.iter().take_while(|c| **c != 0).count(); let s16 = &data[..len]; let bytes = unsafe { slice::from_raw_parts(s16.as_ptr() as *const u8, s16.len() * 2) };
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. pubfn expression(&self) -> Option<String> {
utf16_to_string(&self.raw.expression)
} /// Get the function name where the assertion happened as a `String` if it exists. pubfn function(&self) -> Option<String> {
utf16_to_string(&self.raw.function)
} /// Get the source file name where the assertion happened as a `String` if it exists. pubfn file(&self) -> Option<String> {
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`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MDAssertion
expression = {}
function = {}
file = {}
line = {} type = {}
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?; if data.is_empty() { return Ok(dictionary);
}
letmut offset = 0;
let count: u32 = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
for _ in0..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)?;
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?; if data.is_empty() { return Ok(dictionary);
}
letmut offset = 0;
let count: u32 = data
.gread_with(&mut offset, endian)
.or(Err(Error::StreamReadFailure))?;
for _ in0..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();
impl MinidumpCrashpadInfo { /// Write a human-readable description of this `MinidumpCrashpadInfo` to `f`. /// /// This is very verbose, it is the format used by `minidump_dump`. pubfn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f, "MDRawCrashpadInfo
version = {}
report_id = {}
client_id = {} ", self.raw.version, self.raw.report_id, self.raw.client_id,
)?;
for (name, value) in &self.simple_annotations {
writeln!(f, " simple_annotations[\"{name}\"] = {value}")?;
}
/// An index into the contents of a memory-mapped minidump. pubtype 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. pubfn read_path<P>(path: P) -> Result<MmapMinidump, Error> where
P: AsRef<Path>,
{ let f = File::open(path).or(Err(Error::FileNotFound))?; let mmap = unsafe { Mmap::map(&f).or(Err(Error::IoError))? };
Minidump::read(mmap)
}
}
/// A stream in the minidump that this implementation can interpret, #[derive(Debug)] pubstruct 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)] pubstruct 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)] pubstruct 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<Target = [u8]> + 'a,
{ /// Read a `Minidump` from the provided `data`. /// /// Typically this will be a `Vec<u8>` or `&[u8]` with the full contents of the minidump, /// but you can also use something like `memmap::Mmap`. pubfn read(data: T) -> Result<Minidump<'a, T>, Error> { letmut offset = 0; letmut endian = LE; letmut 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;
letmut streams = BTreeMap::new(); for i in0..header.stream_count { let dir: md::MINIDUMP_DIRECTORY = data
.gread_with(&mut offset, endian)
.or(Err(Error::MissingDirectory))?; iflet Some((old_idx, old_dir)) = streams.insert(dir.stream_type, (i, dir.clone())) { iflet 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()
})
});
/// 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::<MinidumpSystemInfo>()?; /// let exception = dump.get_stream::<MinidumpException>()?; /// /// // 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::<MinidumpThreadList>() { /// // 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`][] /// pubfn get_stream<S>(&'a self) -> Result<S, Error> where
S: MinidumpStream<'a>,
{ matchself.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. pubfn get_raw_stream(&'a self, stream_type: u32) -> Result<&'a [u8], Error> { matchself.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`][]. pubfn get_memory(&'a self) -> Option<UnifiedMemoryList<'a>> { self.get_stream::<MinidumpMemory64List>()
.map(UnifiedMemoryList::Memory64)
.or_else(|_| { self.get_stream::<MinidumpMemoryList>()
.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. pubfn unimplemented_streams(&self) -> impl Iterator<Item = MinidumpUnimplementedStream> + '_ { 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. pubfn unknown_streams(&self) -> impl Iterator<Item = MinidumpUnknownStream> + '_ { 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. pubfn all_streams(&self) -> impl Iterator<Item = &md::MINIDUMP_DIRECTORY> + '_ { self.streams.iter().map(|(_, (_, stream))| stream)
}
/// Write a verbose description of the `Minidump` to `f`. pubfn print<W: Write>(&self, f: &mut W) -> io::Result<()> { fn get_stream_name(stream_type: u32) -> Cow<'static, str> { iflet Some(stream) = MINIDUMP_STREAM_TYPE::from_u32(stream_type) {
Cow::Owned(format!("{stream:?}"))
} else {
Cow::Borrowed("unknown")
}
}
#[cfg(test)] mod test { usesuper::*; 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::*;
#[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::<MinidumpThreadNames>().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::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 1);
assert_eq!(modules[0].base_address(), 0xa90206ca83eb2852);
assert_eq!(modules[0].size(), 0xada542bd);
assert_eq!(modules[0].code_file(), "single module"); // 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::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 1);
assert_eq!(modules[0].base_address(), 0xa90206ca83eb2852);
assert_eq!(modules[0].size(), 0xada542bd);
assert_eq!(modules[0].code_file(), "single module"); // 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::<MinidumpUnloadedModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 1);
assert_eq!(modules[0].base_address(), 0xa90206ca83eb2852);
assert_eq!(modules[0].size(), 0xada542bd);
assert_eq!(modules[0].code_file(), "single module"); // 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::<MinidumpMemoryInfoList>().ok(); let maps = dump.get_stream::<MinidumpLinuxMaps>().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()) { iflet UnifiedMemoryInfo::Info(info2) = unified {
assert_eq!(info, info2);
} else {
unreachable!();
}
}
#[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:051196511 /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::<MinidumpMemoryInfoList>().ok(); let maps = dump.get_stream::<MinidumpLinuxMaps>().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()) { iflet UnifiedMemoryInfo::Map(info2) = unified {
assert_eq!(info, info2);
} else {
unreachable!();
}
}
let maps = maps.iter().collect::<Vec<_>>();
assert_eq!(maps.len(), 2);
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");
#[test] fn test_memory_list() { const CONTENTS: &[u8] = b"memory_contents"; let memory = Memory::with_section(
Section::with_endian(Endian::Little).append_bytes(CONTENTS), 0x309d68010bd21b2c,
); let dump = SynthMinidump::with_endian(Endian::Little).add_memory(memory); let dump = read_synth_dump(dump).unwrap(); let memory_list = dump.get_stream::<MinidumpMemoryList<'_>>().unwrap(); let regions = memory_list.iter().collect::<Vec<_>>();
assert_eq!(regions.len(), 1);
assert_eq!(regions[0].base_address, 0x309d68010bd21b2c);
assert_eq!(regions[0].size, CONTENTS.len() as u64);
assert_eq!(®ions[0].bytes, &CONTENTS);
}
#[test] fn test_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::<MinidumpMemory64List<'_>>().unwrap(); let regions = memory_list.iter().collect::<Vec<_>>();
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::<MinidumpMemoryList<'_>>().unwrap();
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc); let dump = read_synth_dump(dump).unwrap(); let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
#[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::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
assert_eq!(modules.len(), 2);
assert_eq!(modules[0].base_address(), 0x100000000);
assert_eq!(modules[0].code_file(), "module 1"); // 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::<MinidumpSystemInfo>().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::<MinidumpSystemInfo>().unwrap();
assert_eq!(system_info.os, Os::MacOs);
let module_list = dump.get_stream::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
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::<MinidumpSystemInfo>().unwrap();
assert_eq!(system_info.os, Os::Windows);
let module_list = dump.get_stream::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
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::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
// 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::<MinidumpSystemInfo>().unwrap(); let exception_stream = dump.get_stream::<MinidumpException>().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);
// 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::<MinidumpModuleList>().unwrap(); let modules = module_list.iter().collect::<Vec<_>>();
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::<MinidumpHandleDataStream>()
.expect("The HANDLE_DATA_STREAM must be present"); let handles = handle_data_stream.iter().collect::<Vec<_>>();
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); letmut 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::<MinidumpSystemInfo>().unwrap(); let exception_stream = dump.get_stream::<MinidumpException>().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); letmut 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::<MinidumpSystemInfo>().unwrap(); let exception_stream = dump.get_stream::<MinidumpException>().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); letmut 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::<MinidumpSystemInfo>().unwrap(); let exception_stream = dump.get_stream::<MinidumpException>().unwrap();
assert_eq!(
exception_stream
.get_crash_reason(system_stream.os, system_stream.cpu)
.to_string(), "SIGABRT / SI_TKILL"
);
}
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.312Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.