|
|
|
|
Quelle minidump.rs
Sprache: unbekannt
|
|
// 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;
pub use crate::context::*;
use crate::strings::*;
use crate::system_info::{Cpu, Os, PointerWidth};
use minidump_common::errors::{self as err};
use minidump_common::format::{self as md};
use minidump_common::format::{CvSignature, MINIDUMP_STREAM_TYPE};
use minidump_common::traits::{IntoRangeMapSafe, Module};
use range_map::{Range, RangeMap};
use time::format_description::well_known::Rfc3339;
/// An index into the contents of a minidump.
///
/// The `Minidump` struct represents the parsed header and
/// indices contained at the start of a minidump file. It can be instantiated
/// by calling the [`Minidump::read`][read] or
/// [`Minidump::read_path`][read_path] methods.
///
/// # Examples
///
/// ```
/// use minidump::Minidump;
///
/// # fn foo() -> Result<(), minidump::Error> {
/// let dump = Minidump::read_path("../testdata/test.dmp")?;
/// # Ok(())
/// # }
/// ```
///
/// [read]: struct.Minidump.html#method.read
/// [read_path]: struct.Minidump.html#method.read_path
#[derive(Debug)]
pub struct Minidump<'a, T>
where
T: Deref<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]>,
}
/// Errors encountered while reading a `Minidump`.
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("File not found")]
FileNotFound,
#[error("I/O error")]
IoError,
#[error("Missing minidump header (empty minidump?)")]
MissingHeader,
#[error("Header mismatch")]
HeaderMismatch,
#[error("Minidump version mismatch")]
VersionMismatch,
#[error("Missing stream directory (heavily truncated minidump?)")]
MissingDirectory,
#[error("Error reading stream")]
StreamReadFailure,
#[error("Stream size mismatch: expected {expected} bytes, found {actual} bytes")]
StreamSizeMismatch { expected: usize, actual: usize },
#[error("Stream not found")]
StreamNotFound,
#[error("Module read failure")]
ModuleReadFailure,
#[error("Memory read failure")]
MemoryReadFailure,
#[error("Data error")]
DataError,
#[error("Error reading CodeView data")]
CodeViewReadFailure,
#[error("Uknown element type")]
UknownElementType,
}
impl Error {
/// Returns just the name of the error, as a more human-friendly version of
/// an error-code for error logging.
pub fn name(&self) -> &'static str {
match self {
Error::FileNotFound => "FileNotFound",
Error::IoError => "IoError",
Error::MissingHeader => "MissingHeader",
Error::HeaderMismatch => "HeaderMismatch",
Error::VersionMismatch => "VersionMismatch",
Error::MissingDirectory => "MissingDirectory",
Error::StreamReadFailure => "StreamReadFailure",
Error::StreamSizeMismatch { .. } => "StreamSizeMismatch",
Error::StreamNotFound => "StreamNotFound",
Error::ModuleReadFailure => "ModuleReadFailure",
Error::MemoryReadFailure => "MemoryReadFailure",
Error::DataError => "DataError",
Error::CodeViewReadFailure => "CodeViewReadFailure",
Error::UknownElementType => "UnknownElementType",
}
}
}
/// The fundamental unit of data in a `Minidump`.
pub trait MinidumpStream<'a>: Sized {
/// The stream type constant used in the `md::MDRawDirectory` entry.
/// This is usually a [MINIDUMP_STREAM_TYPE][] but it's left as a u32
/// to allow external projects to add support for their own custom streams.
const STREAM_TYPE: u32;
/// Read this `MinidumpStream` type from `bytes`.
///
/// * `bytes` is the contents of this specific stream.
/// * `all` refers to the full contents of the minidump, for reading auxilliary data
/// referred to with `MINIDUMP_LOCATION_DESCRIPTOR`s.
/// * `system_info` is the preparsed SystemInfo stream, if it exists in the minidump.
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
system_info: Option<&MinidumpSystemInfo>,
) -> Result<Self, Error>;
}
/// Provides a unified interface for getting metadata about the process's mapped memory regio ns
/// at the time of the crash.
///
/// Currently this is one of [`MinidumpMemoryInfoList`], available in Windows minidumps,
/// or [`MinidumpLinuxMaps`], available in Linux minidumps.
///
/// This allows you to e.g. check whether an address was executable or not without
/// worrying about which platform the crash occured on. If you need to do more
/// specific analysis, you can get the native formats with [`UnifiedMemoryInfoList::info`]
/// and [`UnifiedMemoryInfoList::maps`].
///
/// Currently an enum because there is no situation where you can have both,
/// but this may change if the format evolves. Prefer using this type's methods
/// over pattern matching.
#[derive(Debug, Clone)]
pub enum UnifiedMemoryInfoList<'a> {
Maps(MinidumpLinuxMaps<'a>),
Info(MinidumpMemoryInfoList<'a>),
}
#[derive(Debug, Copy, Clone)]
/// A [`UnifiedMemoryInfoList`] entry, providing metatadata on a region of
/// memory in the crashed process.
pub enum UnifiedMemoryInfo<'a> {
Map(&'a MinidumpLinuxMapInfo<'a>),
Info(&'a MinidumpMemoryInfo<'a>),
}
/// The contents of `/proc/self/maps` for the crashing process.
///
/// This is roughly equivalent in functionality to [`MinidumpMemoryInfoList`].
/// Use [`UnifiedMemoryInfoList`] to handle the two uniformly.
#[derive(Debug, Clone)]
pub struct MinidumpLinuxMaps<'a> {
/// The memory regions, in the order they were stored in the minidump.
regions: Vec<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)]
pub struct MinidumpLinuxMapInfo<'a> {
pub map: MemoryMap,
_phantom: PhantomData<&'a u8>,
}
#[derive(Debug, Clone)]
pub struct MinidumpMemoryInfoList<'a> {
/// The memory regions, in the order they were stored in the minidump.
regions: Vec<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).
pub struct MinidumpMemoryInfo<'a> {
/// The raw value from the minidump.
pub raw: md::MINIDUMP_MEMORY_INFO,
/// The memory protection when the region was initially allocated.
pub allocation_protection: md::MemoryProtection,
/// The state of the pages in the region (whether it is freed or not).
pub state: md::MemoryState,
/// The access protection of the pages in the region.
pub protection: md::MemoryProtection,
/// What kind of memory mapping the pages in this region are.
pub ty: md::MemoryType,
_phantom: PhantomData<&'a u8>,
}
/// CodeView data describes how to locate debug symbols
#[derive(Debug, Clone)]
pub enum CodeView {
/// PDB 2.0 format data in a separate file
Pdb20(md::CV_INFO_PDB20),
/// PDB 7.0 format data in a separate file (most common)
Pdb70(md::CV_INFO_PDB70),
/// Indicates data is in an ELF binary with build ID `build_id`
Elf(md::CV_INFO_ELF),
/// An unknown format containing the raw bytes of data
Unknown(Vec<u8>),
}
/// An executable or shared library loaded in the process at the time the `Minidump` was written.
#[derive(Debug, Clone)]
pub struct MinidumpModule {
/// The `MINIDUMP_MODULE` direct from the minidump file.
pub raw: md::MINIDUMP_MODULE,
/// The module name. This is stored separately in the minidump.
pub name: String,
/// A `CodeView` record, if one is present.
pub codeview_info: Option<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)]
pub struct 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)]
pub struct 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)]
pub struct MinidumpUnloadedModule {
/// The `MINIDUMP_UNLOADED_MODULE` direct from the minidump file.
pub raw: md::MINIDUMP_UNLOADED_MODULE,
/// The module name. This is stored separately in the minidump.
pub name: String,
}
/// A list of `MinidumpUnloadedModule`s contained in a `Minidump`.
#[derive(Debug, Clone)]
pub struct MinidumpUnloadedModuleList {
/// The modules, in the order they were stored in the minidump.
modules: Vec<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)]
pub struct MinidumpHandleObjectInformation {
pub raw: md::MINIDUMP_HANDLE_OBJECT_INFORMATION,
pub info_type: md::MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE,
}
impl fmt::Display for MinidumpHandleObjectInformation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{raw: {:?}, type: {:?}}}", self.raw, self.info_type)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum RawHandleDescriptor {
HandleDescriptor(md::MINIDUMP_HANDLE_DESCRIPTOR),
HandleDescriptor2(md::MINIDUMP_HANDLE_DESCRIPTOR_2),
}
/// Describes the state of an individual system handle at the time the minidump was written.
#[derive(Debug, Clone)]
pub struct MinidumpHandleDescriptor {
/// The `MINIDUMP_HANDKE_DESCRIPTOR` data direct from the minidump file.
pub raw: RawHandleDescriptor,
/// The name of the type of this handle, if present.
pub type_name: Option<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)]
pub struct MinidumpHandleDataStream {
pub handles: Vec<MinidumpHandleDescriptor>,
}
/// The state of a thread from the process when the minidump was written.
#[derive(Debug)]
pub struct MinidumpThread<'a> {
/// The `MINIDUMP_THREAD` direct from the minidump file.
pub raw: md::MINIDUMP_THREAD,
/// The CPU context for the thread, if present.
context: Option<&'a [u8]>,
/// The stack memory for the thread, if present.
stack: Option<MinidumpMemory<'a>>,
/// Saved endianness for lazy parsing.
endian: scroll::Endian,
}
/// A list of `MinidumpThread`s contained in a `Minidump`.
#[derive(Debug)]
pub struct MinidumpThreadList<'a> {
/// The threads, in the order they were present in the `Minidump`.
pub threads: Vec<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)]
pub struct MinidumpThreadInfo {
/// The `MINIDUMP_THREAD_INFO` direct from the minidump file.
pub raw: md::MINIDUMP_THREAD_INFO,
}
/// A list of `MinidumpThread`s contained in a `Minidump`.
#[derive(Debug)]
pub struct MinidumpThreadInfoList {
/// The thread info entries, in the order they were present in the `Minidump`.
pub thread_infos: Vec<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)]
pub struct MinidumpSystemInfo {
/// The `MINIDUMP_SYSTEM_INFO` direct from the minidump
pub raw: md::MINIDUMP_SYSTEM_INFO,
/// The operating system that generated the minidump
pub os: Os,
/// The CPU on which the minidump was generated
pub cpu: Cpu,
/// A string that describes the latest Service Pack installed on the system.
/// If no Service Pack has been installed, the string is empty.
/// This is stored separately in the minidump.
csd_version: Option<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)]
pub struct MinidumpMemoryBase<'a, Descriptor> {
/// The raw `MINIDUMP_MEMORY_DESCRIPTOR` from the minidump.
pub desc: Descriptor,
/// The starting address of this range of memory.
pub base_address: u64,
/// The length of this range of memory.
pub size: u64,
/// The contents of the memory.
pub bytes: &'a [u8],
/// The endianness of the minidump which is used for memory accesses.
pub endian: scroll::Endian,
}
/// A region of memory from the process that wrote the minidump.
pub type MinidumpMemory<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>;
/// A large region of memory from the process that wrote the minidump (usually a full dump).
pub type MinidumpMemory64<'a> = MinidumpMemoryBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>;
/// Provides a unified interface for MinidumpMemory and MinidumpMemory64
#[derive(Debug, Clone, Copy)]
pub enum UnifiedMemory<'a, 'mdmp> {
Memory(&'a MinidumpMemory<'mdmp>),
Memory64(&'a MinidumpMemory64<'mdmp>),
}
#[derive(Debug, Clone)]
pub enum RawMacCrashInfo {
V1(
md::MINIDUMP_MAC_CRASH_INFO_RECORD,
md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS,
),
V4(
md::MINIDUMP_MAC_CRASH_INFO_RECORD_4,
md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_4,
),
V5(
md::MINIDUMP_MAC_CRASH_INFO_RECORD_5,
md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_5,
),
}
#[derive(Debug, Clone)]
pub struct MinidumpMacCrashInfo {
/// The `MINIDUMP_MAC_CRASH_INFO_RECORD` and `MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS`.
pub raw: Vec<RawMacCrashInfo>,
}
#[derive(Debug, Clone)]
pub struct MinidumpMacBootargs {
pub raw: md::MINIDUMP_MAC_BOOTARGS,
pub bootargs: Option<String>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum RawMiscInfo {
MiscInfo(md::MINIDUMP_MISC_INFO),
MiscInfo2(md::MINIDUMP_MISC_INFO_2),
MiscInfo3(md::MINIDUMP_MISC_INFO_3),
MiscInfo4(md::MINIDUMP_MISC_INFO_4),
MiscInfo5(md::MINIDUMP_MISC_INFO_5),
}
/// Miscellaneous information about the process that wrote the minidump.
#[derive(Debug, Clone)]
pub struct MinidumpMiscInfo {
/// The `MINIDUMP_MISC_INFO` struct direct from the minidump.
pub raw: RawMiscInfo,
}
/// Additional information about process state.
///
/// MinidumpBreakpadInfo wraps MINIDUMP_BREAKPAD_INFO, which is an optional stream
/// in a minidump that provides additional information about the process state
/// at the time the minidump was generated.
#[derive(Debug, Clone)]
pub struct MinidumpBreakpadInfo {
raw: md::MINIDUMP_BREAKPAD_INFO,
/// The thread that wrote the minidump.
pub dump_thread_id: Option<u32>,
/// The thread that requested that a minidump be written.
pub requesting_thread_id: Option<u32>,
}
#[derive(Default, Debug)]
/// Interesting values extracted from /etc/lsb-release
pub struct MinidumpLinuxLsbRelease<'a> {
data: &'a [u8],
}
/// Interesting values extracted from /proc/self/environ
#[derive(Default, Debug)]
pub struct MinidumpLinuxEnviron<'a> {
data: &'a [u8],
}
/// Interesting values extracted from /proc/cpuinfo
#[derive(Default, Debug)]
pub struct MinidumpLinuxCpuInfo<'a> {
data: &'a [u8],
}
/// Interesting values extracted from /proc/self/status
#[derive(Default, Debug)]
pub struct MinidumpLinuxProcStatus<'a> {
data: &'a [u8],
}
/// Interesting values extracted from /proc/self/limits
#[derive(Default, Debug)]
pub struct MinidumpLinuxProcLimits<'a> {
data: &'a [u8],
}
/// The reason for a process crash.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CrashReason {
/// A Mac/iOS error code with no other interesting details.
MacGeneral(err::ExceptionCodeMac, u32),
MacBadAccessKern(err::ExceptionCodeMacBadAccessKernType),
MacBadAccessArm(err::ExceptionCodeMacBadAccessArmType),
MacBadAccessPpc(err::ExceptionCodeMacBadAccessPpcType),
MacBadAccessX86(err::ExceptionCodeMacBadAccessX86Type),
MacBadInstructionArm(err::ExceptionCodeMacBadInstructionArmType),
MacBadInstructionPpc(err::ExceptionCodeMacBadInstructionPpcType),
MacBadInstructionX86(err::ExceptionCodeMacBadInstructionX86Type),
MacArithmeticArm(err::ExceptionCodeMacArithmeticArmType),
MacArithmeticPpc(err::ExceptionCodeMacArithmeticPpcType),
MacArithmeticX86(err::ExceptionCodeMacArithmeticX86Type),
MacSoftware(err::ExceptionCodeMacSoftwareType),
MacBreakpointArm(err::ExceptionCodeMacBreakpointArmType),
MacBreakpointPpc(err::ExceptionCodeMacBreakpointPpcType),
MacBreakpointX86(err::ExceptionCodeMacBreakpointX86Type),
MacResource(err::ExceptionCodeMacResourceType, u64, u64),
MacGuard(err::ExceptionCodeMacGuardType, u64, u64),
/// A Linux/Android error code with no other interesting metadata.
LinuxGeneral(err::ExceptionCodeLinux, u32),
LinuxSigill(err::ExceptionCodeLinuxSigillKind),
LinuxSigtrap(err::ExceptionCodeLinuxSigtrapKind),
LinuxSigbus(err::ExceptionCodeLinuxSigbusKind),
LinuxSigfpe(err::ExceptionCodeLinuxSigfpeKind),
LinuxSigsegv(err::ExceptionCodeLinuxSigsegvKind),
LinuxSigsys(err::ExceptionCodeLinuxSigsysKind),
/// A Windows error code with no other interesting metadata.
WindowsGeneral(err::ExceptionCodeWindows),
/// A Windows error from winerror.h.
WindowsWinError(err::WinErrorWindows),
/// A Windows error for a specific facility from winerror.h.
WindowsWinErrorWithFacility(err::WinErrorFacilityWindows, err::WinErrorWindows),
/// A Windows error from ntstatus.h
WindowsNtStatus(err::NtStatusWindows),
/// ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION but with details on the kind of access.
WindowsAccessViolation(err::ExceptionCodeWindowsAccessType),
/// ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR but with details on the kind of access.
/// Second argument is a windows NTSTATUS value.
WindowsInPageError(err::ExceptionCodeWindowsInPageErrorType, u64),
/// ExceptionCodeWindows::EXCEPTION_STACK_BUFFER_OVERRUN with an accompanying
/// windows FAST_FAIL value.
WindowsStackBufferOverrun(u64),
/// A Windows error with no known mapping.
WindowsUnknown(u32),
Unknown(u32, u32),
}
/// Information about the exception that caused the minidump to be generated.
///
/// `MinidumpException` wraps `MINIDUMP_EXCEPTION_STREAM`, which contains information
/// about the exception that caused the minidump to be generated, if the
/// minidump was generated in an exception handler called as a result of an
/// exception. It also provides access to a `MinidumpContext` object, which
/// contains the CPU context for the exception thread at the time the exception
/// occurred.
#[derive(Debug)]
pub struct MinidumpException<'a> {
/// The raw exception information from the minidump stream.
pub raw: md::MINIDUMP_EXCEPTION_STREAM,
/// The thread that encountered this exception.
pub thread_id: u32,
/// If present, the CPU context from the time the thread encountered the exception.
///
/// This should be used in place of the context contained within the thread with id
/// `thread_id`, since it points to the code location where the exception happened,
/// without any exception handling routines that are likely to be on the stack after
/// that point.
context: Option<&'a [u8]>,
/// Saved endianess for lazy parsing.
endian: scroll::Endian,
}
/// A list of memory regions included in a minidump.
/// This is the underlying generic type for [MinidumpMemoryList] and [MinidumpMemory64List].
#[derive(Debug)]
pub struct MinidumpMemoryListBase<'a, Descriptor> {
/// The memory regions, in the order they were stored in the minidump.
regions: Vec<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.
pub type MinidumpMemoryList<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR>;
/// A list of large memory regions included in a minidump (usually a full dump).
pub type MinidumpMemory64List<'a> = MinidumpMemoryListBase<'a, md::MINIDUMP_MEMORY_DESCRIPTOR64>;
/// Provides a unified interface for MinidumpMemoryList and MinidumpMemory64List
#[derive(Debug)]
pub enum UnifiedMemoryList<'a> {
Memory(MinidumpMemoryList<'a>),
Memory64(MinidumpMemory64List<'a>),
}
impl<'a> Default for UnifiedMemoryList<'a> {
fn default() -> Self {
Self::Memory(Default::default())
}
}
/// Information about an assertion that caused a crash.
#[derive(Debug)]
pub struct MinidumpAssertion {
pub raw: md::MINIDUMP_ASSERTION_INFO,
}
/// A typed annotation object.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum MinidumpAnnotation {
/// An invalid annotation. Reserved for internal use.
Invalid,
/// A `NUL`-terminated C-string.
String(String),
/// Clients may declare their own custom types.
UserDefined(md::MINIDUMP_ANNOTATION),
/// An unsupported annotation from a future crashpad version.
Unsupported(md::MINIDUMP_ANNOTATION),
}
impl PartialEq for MinidumpAnnotation {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Invalid, Self::Invalid) => true,
(Self::String(a), Self::String(b)) => a == b,
_ => false,
}
}
}
/// Additional Crashpad-specific information about a module carried within a minidump file.
#[derive(Debug)]
pub struct MinidumpModuleCrashpadInfo {
/// The raw crashpad module extension information.
pub raw: md::MINIDUMP_MODULE_CRASHPAD_INFO,
/// Index of the corresponding module in the `MinidumpModuleList`.
pub module_index: usize,
pub list_annotations: Vec<String>,
pub simple_annotations: BTreeMap<String, String>,
pub annotation_objects: BTreeMap<String, MinidumpAnnotation>,
}
/// Additional Crashpad-specific information carried within a minidump file.
#[derive(Debug)]
pub struct MinidumpCrashpadInfo {
pub raw: md::MINIDUMP_CRASHPAD_INFO,
pub simple_annotations: BTreeMap<String, String>,
pub module_list: Vec<MinidumpModuleCrashpadInfo>,
}
//======================================================
// Implementations
fn format_time_t(t: u32) -> String {
time::OffsetDateTime::from_unix_timestamp(t as i64)
.ok()
.and_then(|datetime| datetime.format(&Rfc3339).ok())
.unwrap_or_default()
}
fn format_system_time(time: &md::SYSTEMTIME) -> String {
// Note this drops the day_of_week field on the ground -- is that fine?
let format_date = || {
use std::convert::TryFrom;
let month = time::Month::try_from(time.month as u8).ok()?;
let date = time::Date::from_calendar_date(time.year as i32, month, time.day as u8).ok()?;
let datetime = date
.with_hms_milli(
time.hour as u8,
time.minute as u8,
time.second as u8,
time.milliseconds,
)
.ok()?
.assume_utc();
datetime.format(&Rfc3339).ok()
};
format_date().unwrap_or_else(|| "<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())
}
#[inline]
fn read_string_utf8_unterminated<'a>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Option<&'a str> {
let length: u32 = bytes.gread_with(offset, endian).ok()?;
let slice = bytes.gread_with(offset, length as usize).ok()?;
std::str::from_utf8(slice).ok()
}
fn read_string_utf8<'a>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Option<&'a str> {
let string = read_string_utf8_unterminated(offset, bytes, endian)?;
match bytes.gread(offset) {
Ok(0u8) => Some(string),
_ => None,
}
}
fn read_cstring_utf8(offset: &mut usize, bytes: &[u8]) -> Option<String> {
let initial_offset = *offset;
loop {
let byte: u8 = bytes.gread(offset).ok()?;
if byte == 0 {
break;
}
}
std::str::from_utf8(&bytes[initial_offset..*offset - 1])
.map(String::from)
.ok()
}
/// Convert `bytes` with trailing NUL characters to a string
fn string_from_bytes_nul(bytes: &[u8]) -> Option<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))
}
impl MinidumpModule {
/// Create a `MinidumpModule` with some basic info.
///
/// Useful for testing.
pub fn new(base: u64, size: u32, name: &str) -> MinidumpModule {
MinidumpModule {
raw: md::MINIDUMP_MODULE {
base_of_image: base,
size_of_image: size,
..md::MINIDUMP_MODULE::default()
},
name: String::from(name),
codeview_info: None,
misc_info: None,
os: Os::Unknown(0),
debug_id: None,
}
}
/// Read additional data to construct a `MinidumpModule` from `bytes` using the information
/// from the module list in `raw`.
pub fn read(
raw: md::MINIDUMP_MODULE,
bytes: &[u8],
endian: scroll::Endian,
system_info: Option<&MinidumpSystemInfo>,
) -> Result<MinidumpModule, Error> {
let mut offset = raw.module_name_rva as usize;
let name =
read_string_utf16(&mut offset, bytes, endian).ok_or(Error::CodeViewReadFailure)?;
let codeview_info = if raw.cv_record.data_size == 0 {
None
} else {
Some(read_codeview(&raw.cv_record, bytes, endian).ok_or(Error::CodeViewReadFailure)?)
};
let os = system_info.map(|info| info.os).unwrap_or(Os::Unknown(0));
let debug_id = codeview_info
.as_ref()
.and_then(|cv| read_debug_id(cv, endian));
Ok(MinidumpModule {
raw,
name,
codeview_info,
misc_info: None,
os,
debug_id,
})
}
/// Write a human-readable description of this `MinidumpModule` to `f`.
///
/// This is very verbose, it is the format used by `minidump_dump`.
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MINIDUMP_MODULE
base_of_image = {:#x}
size_of_image = {:#x}
checksum = {:#x}
time_date_stamp = {:#x} {}
module_name_rva = {:#x}
version_info.signature = {:#x}
version_info.struct_version = {:#x}
version_info.file_version = {:#x}:{:#x}
version_info.product_version = {:#x}:{:#x}
version_info.file_flags_mask = {:#x}
version_info.file_flags = {:#x}
version_info.file_os = {:#x}
version_info.file_type = {:#x}
version_info.file_subtype = {:#x}
version_info.file_date = {:#x}:{:#x}
cv_record.data_size = {}
cv_record.rva = {:#x}
misc_record.data_size = {}
misc_record.rva = {:#x}
(code_file) = \"{}\"
(code_identifier) = \"{}\"
",
self.raw.base_of_image,
self.raw.size_of_image,
self.raw.checksum,
self.raw.time_date_stamp,
format_time_t(self.raw.time_date_stamp),
self.raw.module_name_rva,
self.raw.version_info.signature,
self.raw.version_info.struct_version,
self.raw.version_info.file_version_hi,
self.raw.version_info.file_version_lo,
self.raw.version_info.product_version_hi,
self.raw.version_info.product_version_lo,
self.raw.version_info.file_flags_mask,
self.raw.version_info.file_flags,
self.raw.version_info.file_os,
self.raw.version_info.file_type,
self.raw.version_info.file_subtype,
self.raw.version_info.file_date_hi,
self.raw.version_info.file_date_lo,
self.raw.cv_record.data_size,
self.raw.cv_record.rva,
self.raw.misc_record.data_size,
self.raw.misc_record.rva,
self.code_file(),
self.code_identifier().unwrap_or_default(),
)?;
// Print CodeView data.
match self.codeview_info {
Some(CodeView::Pdb70(ref raw)) => {
let pdb_file_name =
string_from_bytes_nul(&raw.pdb_file_name).unwrap_or(Cow::Borrowed("(invalid)"));
write!(f, " (cv_record).cv_signature = {:#x}
(cv_record).signature = {:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}
(cv_record).age = {}
(cv_record).pdb_file_name = \"{}\"
",
raw.cv_signature,
raw.signature.data1,
raw.signature.data2,
raw.signature.data3,
raw.signature.data4[0],
raw.signature.data4[1],
raw.signature.data4[2],
raw.signature.data4[3],
raw.signature.data4[4],
raw.signature.data4[5],
raw.signature.data4[6],
raw.signature.data4[7],
raw.age,
pdb_file_name,
)?;
}
Some(CodeView::Pdb20(ref raw)) => {
let pdb_file_name =
string_from_bytes_nul(&raw.pdb_file_name).unwrap_or(Cow::Borrowed("(invalid)"));
write!(
f,
" (cv_record).cv_header.signature = {:#x}
(cv_record).cv_header.offset = {:#x}
(cv_record).signature = {:#x} {}
(cv_record).age = {}
(cv_record).pdb_file_name = \"{}\"
",
raw.cv_signature,
raw.cv_offset,
raw.signature,
format_time_t(raw.signature),
raw.age,
pdb_file_name,
)?;
}
Some(CodeView::Elf(ref raw)) => {
// Fibbing about having cv_signature handy here.
write!(
f,
" (cv_record).cv_signature = {:#x}
(cv_record).build_id = {}
",
raw.cv_signature,
bytes_to_hex(&raw.build_id),
)?;
}
Some(CodeView::Unknown(ref bytes)) => {
writeln!(
f,
" (cv_record) = {}",
bytes_to_hex(bytes),
)?;
}
None => {
writeln!(f, " (cv_record) = (null)")?;
}
}
// Print misc record data.
if let Some(ref _misc) = self.misc_info {
//TODO, not terribly important.
writeln!(f, " (misc_record) = (unimplemented)")?;
} else {
writeln!(f, " (misc_record) = (null)")?;
}
// Print remaining data.
write!(
f,
r#" (debug_file) = "{}"
(debug_identifier) = "{}"
(version) = "{}"
"#,
self.debug_file().unwrap_or(Cow::Borrowed("")),
self.debug_identifier().unwrap_or_default(),
self.version().unwrap_or(Cow::Borrowed("")),
)?;
Ok(())
}
fn memory_range(&self) -> Option<Range<u64>> {
if self.size() == 0 {
return None;
}
Some(Range::new(
self.base_address(),
self.base_address().checked_add(self.size())? - 1,
))
}
}
impl Module for MinidumpModule {
fn base_address(&self) -> u64 {
self.raw.base_of_image
}
fn size(&self) -> u64 {
self.raw.size_of_image as u64
}
fn code_file(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.name)
}
fn code_identifier(&self) -> Option<CodeId> {
match self.codeview_info {
Some(CodeView::Pdb70(ref raw)) if matches!(self.os, Os::MacOs | Os::Ios) => {
// MacOs uses PDB70 instead of its own dedicated format.
// See the following issue for a potential MacOs-specific format:
// https://github.com/rust-minidump/rust-minidump/issues/455
Some(CodeId::new(format!("{:#}", raw.signature)))
}
Some(CodeView::Pdb20(_)) | Some(CodeView::Pdb70(_)) => Some(CodeId::new(format!(
"{0:08X}{1:x}",
self.raw.time_date_stamp, self.raw.size_of_image
))),
Some(CodeView::Elf(ref raw)) => {
// Return None instead of sentinel CodeIds for empty
// `build_id`s. Non-executable mapped files like fonts or .jar
// files will usually fall under this case.
if raw.build_id.iter().all(|byte| *byte == 0) {
None
} else {
Some(CodeId::from_binary(&raw.build_id))
}
}
None if self.os == Os::Windows => {
// Fall back to the timestamp + size-based debug-id for Windows.
// Some Module records from Windows have no codeview record, but
// the CodeId generated here is valid and can be looked up on
// the Microsoft symbol server.
// One example might be `wow64cpu.dll` with code-id `378BC3CDa000`.
// This can however lead to "false positive" code-ids for modules
// that have no timestamp, in which case the code-id looks extremely
// low-entropy. The same can happen though if they *do* have a
// codeview record.
Some(CodeId::new(format!(
"{0:08X}{1:x}",
self.raw.time_date_stamp, self.raw.size_of_image
)))
}
// Occasionally things will make it into the module stream that
// shouldn't be there, and so no meaningful CodeId can be found from
// those. One of those things are SysV shared memory segments which
// have no CodeView record.
_ => None,
}
}
fn debug_file(&self) -> Option<Cow<'_, str>> {
match self.codeview_info {
Some(CodeView::Pdb70(ref raw)) => string_from_bytes_nul(&raw.pdb_file_name),
Some(CodeView::Pdb20(ref raw)) => string_from_bytes_nul(&raw.pdb_file_name),
Some(CodeView::Elf(_)) => Some(Cow::Borrowed(&self.name)),
// TODO: support misc record? not really important.
_ => None,
}
}
fn debug_identifier(&self) -> Option<DebugId> {
self.debug_id
}
fn version(&self) -> Option<Cow<'_, str>> {
if self.raw.version_info.signature == md::VS_FFI_SIGNATURE
&& self.raw.version_info.struct_version == md::VS_FFI_STRUCVERSION
{
if matches!(self.os, Os::MacOs | Os::Ios | Os::Windows) {
let ver = format!(
"{}.{}.{}.{}",
self.raw.version_info.file_version_hi >> 16,
self.raw.version_info.file_version_hi & 0xffff,
self.raw.version_info.file_version_lo >> 16,
self.raw.version_info.file_version_lo & 0xffff
);
Some(Cow::Owned(ver))
} else {
// Assume Elf
let ver = format!(
"{}.{}.{}.{}",
self.raw.version_info.file_version_hi,
self.raw.version_info.file_version_lo,
self.raw.version_info.product_version_hi,
self.raw.version_info.product_version_lo
);
Some(Cow::Owned(ver))
}
} else {
None
}
}
}
impl MinidumpUnloadedModule {
/// Create a `MinidumpUnloadedModule` with some basic info.
///
/// Useful for testing.
pub fn new(base: u64, size: u32, name: &str) -> MinidumpUnloadedModule {
MinidumpUnloadedModule {
raw: md::MINIDUMP_UNLOADED_MODULE {
base_of_image: base,
size_of_image: size,
..md::MINIDUMP_UNLOADED_MODULE::default()
},
name: String::from(name),
}
}
/// Read additional data to construct a `MinidumpUnloadedModule` from `bytes` using the information
/// from the module list in `raw`.
pub fn read(
raw: md::MINIDUMP_UNLOADED_MODULE,
bytes: &[u8],
endian: scroll::Endian,
) -> Result<MinidumpUnloadedModule, Error> {
let mut offset = raw.module_name_rva as usize;
let name = read_string_utf16(&mut offset, bytes, endian).ok_or(Error::DataError)?;
Ok(MinidumpUnloadedModule { raw, name })
}
/// Write a human-readable description of this `MinidumpModule` to `f`.
///
/// This is very verbose, it is the format used by `minidump_dump`.
pub fn print<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(),
)?;
Ok(())
}
fn memory_range(&self) -> Option<Range<u64>> {
if self.size() == 0 {
return None;
}
Some(Range::new(
self.base_address(),
self.base_address().checked_add(self.size())? - 1,
))
}
}
impl Module for MinidumpUnloadedModule {
fn base_address(&self) -> u64 {
self.raw.base_of_image
}
fn size(&self) -> u64 {
self.raw.size_of_image as u64
}
fn code_file(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.name)
}
fn code_identifier(&self) -> Option<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);
LinuxOsStr::from_bytes(output)
}
let input = LinuxOsStr::from_bytes(bytes);
input.lines().filter_map(move |line| {
line.split_once(separator)
.map(|(label, val)| (strip_quotes(label), (strip_quotes(val))))
})
}
fn read_stream_list<'a, T>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<Vec<T>, Error>
where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
T: SizeWith<scroll::Endian>,
{
let u: u32 = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
let (count, 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
let mut raw_entries = Vec::with_capacity(count);
for _ in 0..count {
let raw: T = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
raw_entries.push(raw);
}
Ok(raw_entries)
}
fn read_ex_stream_list<'a, T>(
offset: &mut usize,
bytes: &'a [u8],
endian: scroll::Endian,
) -> Result<Vec<T>, Error>
where
T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
T: SizeWith<scroll::Endian>,
{
// 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
let mut raw_entries = Vec::with_capacity(number_of_entries);
for _ in 0..number_of_entries {
let raw: T = bytes
.gread_with(offset, endian)
.or(Err(Error::StreamReadFailure))?;
raw_entries.push(raw);
}
Ok(raw_entries)
}
impl<'a> MinidumpStream<'a> for MinidumpThreadNames {
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ThreadNamesStream as u32;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
_system_info: Option<&MinidumpSystemInfo>,
) -> Result<Self, Error> {
let mut offset = 0;
let raw_names: Vec<md::MINIDUMP_THREAD_NAME> =
read_stream_list(&mut offset, bytes, endian)?;
// read out the actual names
let mut names = BTreeMap::new();
for raw_name in raw_names {
let mut offset = raw_name.thread_name_rva as usize;
// Better to just drop unreadable names individually than the whole stream.
if let Some(name) = read_string_utf16(&mut offset, all, endian) {
names.insert(raw_name.thread_id, name);
} else {
warn!(
"Couldn't read thread name for thread id {}",
raw_name.thread_id
);
}
}
Ok(MinidumpThreadNames { names })
}
}
impl MinidumpThreadNames {
pub fn get_name(&self, thread_id: u32) -> Option<Cow<str>> {
self.names
.get(&thread_id)
.map(|name| Cow::Borrowed(&**name))
}
/// Write a human-readable description of this `MinidumpThreadNames` to `f`.
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MinidumpThreadNames
thread_count = {}
",
self.names.len()
)?;
for (i, (thread_id, name)) in self.names.iter().enumerate() {
writeln!(
f,
"thread_name[{i}]
MINIDUMP_THREAD_NAME
thread_id = {thread_id:#x}
name = \"{name}\"
"
)?;
}
Ok(())
}
}
impl MinidumpModuleList {
/// Return an empty `MinidumpModuleList`.
pub fn new() -> MinidumpModuleList {
MinidumpModuleList {
modules: vec![],
modules_by_addr: RangeMap::new(),
}
}
/// Create a `MinidumpModuleList` from a list of `MinidumpModule`s.
pub fn from_modules(modules: Vec<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.
pub fn main_module(&self) -> Option<&MinidumpModule> {
// The main code module is the first one present in a minidump file's
// MINIDUMP_MODULEList.
if !self.modules.is_empty() {
Some(&self.modules[0])
} else {
None
}
}
/// Return a `MinidumpModule` whose address range covers `address`.
pub fn module_at_address(&self, address: u64) -> Option<&MinidumpModule> {
self.modules_by_addr
.get(address)
.map(|&index| &self.modules[index])
}
/// Iterate over the modules in arbitrary order.
pub fn iter(&self) -> impl Iterator<Item = &MinidumpModule> {
self.modules.iter()
}
/// Iterate over the modules in order by memory address.
pub fn 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`.
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MinidumpModuleList
module_count = {}
",
self.modules.len()
)?;
for (i, module) in self.modules.iter().enumerate() {
writeln!(f, "module[{i}]")?;
module.print(f)?;
}
Ok(())
}
}
impl Default for MinidumpModuleList {
fn default() -> Self {
Self::new()
}
}
impl<'a> MinidumpStream<'a> for MinidumpModuleList {
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ModuleListStream as u32;
fn read(
bytes: &'a [u8],
all: &'a [u8],
endian: scroll::Endian,
system_info: Option<&MinidumpSystemInfo>,
) -> Result<MinidumpModuleList, Error> {
let mut offset = 0;
let raw_modules: Vec<md::MINIDUMP_MODULE> = read_stream_list(&mut offset, bytes, endian)?;
// read auxiliary data for each module
let mut modules = Vec::with_capacity(raw_modules.len());
for (module_index, raw) in raw_modules.into_iter().enumerate() {
if raw.size_of_image == 0 || raw.size_of_image as u64 > (u64::MAX - raw.base_of_image) {
// Bad image size.
tracing::warn!(
module_index,
base = raw.base_of_image,
size = raw.size_of_image,
"bad module image size"
);
continue;
}
modules.push(MinidumpModule::read(raw, all, endian, system_info)?);
}
Ok(MinidumpModuleList::from_modules(modules))
}
}
impl MinidumpUnloadedModuleList {
/// Return an empty `MinidumpModuleList`.
pub fn new() -> MinidumpUnloadedModuleList {
MinidumpUnloadedModuleList {
modules: vec![],
modules_by_addr: vec![],
}
}
/// Create a `MinidumpModuleList` from a list of `MinidumpModule`s.
pub fn from_modules(modules: Vec<MinidumpUnloadedModule>) -> MinidumpUnloadedModuleList {
let mut modules_by_addr = (0..modules.len())
.filter_map(|i| modules[i].memory_range().map(|r| (r, i)))
.collect::<Vec<_>>();
modules_by_addr.sort_by_key(|(range, _idx)| *range);
MinidumpUnloadedModuleList {
modules,
modules_by_addr,
}
}
/// Return an iterator of `MinidumpUnloadedModules` whose address range covers `address`.
pub fn modules_at_address(
&self,
address: u64,
) -> impl Iterator<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.
pub fn iter(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
self.modules.iter()
}
/// Iterate over the modules in order by memory address.
pub fn 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`.
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
write!(
f,
"MinidumpUnloadedModuleList
module_count = {}
",
self.modules.len()
)?;
for (i, module) in self.modules.iter().enumerate() {
writeln!(f, "module[{i}]")?;
module.print(f)?;
}
Ok(())
}
}
impl Default for MinidumpUnloadedModuleList {
fn default() -> Self {
Self::new()
}
}
impl<'a> MinidumpStream<'a> for MinidumpUnloadedModuleList {
const STREAM_TYPE: 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> {
let mut offset = 0;
let raw_modules: Vec<md::MINIDUMP_UNLOADED_MODULE> =
read_ex_stream_list(&mut offset, bytes, endian)?;
// read auxiliary data for each module
let mut modules = Vec::with_capacity(raw_modules.len());
for raw in raw_modules.into_iter() {
if raw.size_of_image == 0 || raw.size_of_image as u64 > (u64::MAX - raw.base_of_image) {
// Bad image size.
// TODO: just drop this module, keep the rest?
return Err(Error::ModuleReadFailure);
}
modules.push(MinidumpUnloadedModule::read(raw, all, endian)?);
}
Ok(MinidumpUnloadedModuleList::from_modules(modules))
}
}
// Generates an accessor for a HANDLE_DESCRIPTOR field with the following syntax:
//
// * VERSION_NUMBER: FIELD_NAME -> FIELD_TYPE
//
// With the following definitions:
//
// * VERSION_NUMBER: The HANDLE_DESCRIPTOR version this field was introduced in
// * FIELD_NAME: The name of the field to read
// * FIELD_TYPE: The type of the field
macro_rules! handle_descriptor_accessors {
() => {};
(@def $name:ident $t:ty [$($variant:ident)+]) => {
#[allow(unreachable_patterns)]
pub fn $name(&self) -> Option<&$t> {
match self {
$(
RawHandleDescriptor::$variant(ref raw) => Some(&raw.$name),
)+
_ => None,
}
}
};
(1: $name:ident -> $t:ty, $($rest:tt)*) => {
handle_descriptor_accessors!(@def $name $t [HandleDescriptor HandleDescriptor2]);
handle_descriptor_accessors!($($rest)*);
};
(2: $name:ident -> $t:ty, $($rest:tt)*) => {
handle_descriptor_accessors!(@def $name $t [HandleDescriptor2]);
handle_descriptor_accessors!($($rest)*);
};
}
impl RawHandleDescriptor {
handle_descriptor_accessors!(
1: handle -> u64,
1: type_name_rva -> md::RVA,
1: object_name_rva -> md::RVA,
1: attributes -> u32,
1: granted_access -> u32,
1: handle_count -> u32,
1: pointer_count -> u32,
2: object_info_rva -> md::RVA,
);
}
impl MinidumpHandleDescriptor {
/// Write a human-readable description.
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
macro_rules! write_simple_field {
($stream:ident, $field:ident, $format:literal) => {
write!(f, " {:18}= ", stringify!($field))?;
match self.raw.$field() {
Some($field) => {
writeln!(f, $format, $field)?;
}
None => writeln!(f, "(invalid)")?,
}
};
($stream:ident, $field:ident) => {
write_simple_field!($stream, $field, "{}");
};
}
writeln!(f, "MINIDUMP_HANDLE_DESCRIPTOR")?;
write_simple_field!(f, handle, "{:#x}");
write_simple_field!(f, type_name_rva, "{:#x}");
write_simple_field!(f, object_name_rva, "{:#x}");
write_simple_field!(f, attributes, "{:#x}");
write_simple_field!(f, granted_access, "{:#x}");
write_simple_field!(f, handle_count);
write_simple_field!(f, pointer_count);
write_simple_field!(f, object_info_rva, "{:#x}");
write!(f, " (type_name) = ")?;
if let Some(type_name) = &self.type_name {
writeln!(f, "{type_name:}")?;
} else {
writeln!(f, "(null)")?;
};
write!(f, " (object_name) = ")?;
if let Some(object_name) = &self.object_name {
writeln!(f, "{object_name:}")?;
} else {
writeln!(f, "(null)")?;
};
if self.object_infos.is_empty() {
writeln!(f, " (object_info) = (null)")?;
} else {
for object_info in &self.object_infos {
writeln!(f, " (object_info) = {object_info:}")?;
}
}
writeln!(f)
}
fn read_string(offset: usize, ctx: HandleDescriptorContext) -> Option<String> {
let mut offset = offset;
if offset != 0 {
read_string_utf16(&mut offset, ctx.bytes, ctx.endianess)
} else {
None
}
}
fn read_object_info(
offset: usize,
ctx: HandleDescriptorContext,
) -> Option<MinidumpHandleObjectInformation> {
if offset != 0 {
ctx.bytes
.pread_with::<md::MINIDUMP_HANDLE_OBJECT_INFORMATION>(offset, ctx.endianess)
.ok()
.map(|raw| MinidumpHandleObjectInformation {
raw: raw.clone(),
info_type: md::MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE::from_u32(raw.info_type)
.unwrap(),
})
} else {
None
}
}
}
#[derive(Copy, Clone)]
struct HandleDescriptorContext<'a> {
bytes: &'a [u8],
fieldsize: u32,
endianess: scroll::Endian,
}
impl<'a> HandleDescriptorContext<'a> {
fn new(
bytes: &'a [u8],
fieldsize: u32,
endianess: scroll::Endian,
) -> HandleDescriptorContext<'a> {
HandleDescriptorContext {
bytes,
fieldsize,
endianess,
}
}
}
impl<'a> TryFromCtx<'a, HandleDescriptorContext<'a>> for MinidumpHandleDescriptor {
type Error = scroll::Error;
fn try_from_ctx(
src: &'a [u8],
ctx: HandleDescriptorContext,
) -> Result<(Self, usize), Self::Error> {
const MINIDUMP_HANDLE_DESCRIPTOR_SIZE: u32 =
mem::size_of::<md::MINIDUMP_HANDLE_DESCRIPTOR>() as u32;
const MINIDUMP_HANDLE_DESCRIPTOR_2_SIZE: u32 =
mem::size_of::<md::MINIDUMP_HANDLE_DESCRIPTOR_2>() as u32;
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 => {
--> --------------------
--> maximum size reached
--> --------------------
[ zur Elbe Produktseite wechseln0.43Quellennavigators
Analyse erneut starten
]
|
2026-04-04
|
|
|
|
|