7417 lines
270 KiB
Rust
7417 lines
270 KiB
Rust
// 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 regions
|
|
/// at the time of the crash.
|
|
///
|
|
/// Currently this is one of [`MinidumpMemoryInfoList`], available in Windows minidumps,
|
|
/// or [`MinidumpLinuxMaps`], available in Linux minidumps.
|
|
///
|
|
/// This allows you to e.g. check whether an address was executable or not without
|
|
/// worrying about which platform the crash occured on. If you need to do more
|
|
/// specific analysis, you can get the native formats with [`UnifiedMemoryInfoList::info`]
|
|
/// and [`UnifiedMemoryInfoList::maps`].
|
|
///
|
|
/// Currently an enum because there is no situation where you can have both,
|
|
/// but this may change if the format evolves. Prefer using this type's methods
|
|
/// over pattern matching.
|
|
#[derive(Debug, Clone)]
|
|
pub enum UnifiedMemoryInfoList<'a> {
|
|
Maps(MinidumpLinuxMaps<'a>),
|
|
Info(MinidumpMemoryInfoList<'a>),
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
/// A [`UnifiedMemoryInfoList`] entry, providing metatadata on a region of
|
|
/// memory in the crashed process.
|
|
pub enum UnifiedMemoryInfo<'a> {
|
|
Map(&'a MinidumpLinuxMapInfo<'a>),
|
|
Info(&'a MinidumpMemoryInfo<'a>),
|
|
}
|
|
|
|
/// The contents of `/proc/self/maps` for the crashing process.
|
|
///
|
|
/// This is roughly equivalent in functionality to [`MinidumpMemoryInfoList`].
|
|
/// Use [`UnifiedMemoryInfoList`] to handle the two uniformly.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MinidumpLinuxMaps<'a> {
|
|
/// The memory regions, in the order they were stored in the minidump.
|
|
regions: Vec<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 Default for UnifiedMemoryList<'_> {
|
|
fn default() -> Self {
|
|
Self::Memory(Default::default())
|
|
}
|
|
}
|
|
|
|
/// Information about an assertion that caused a crash.
|
|
#[derive(Debug)]
|
|
pub struct MinidumpAssertion {
|
|
pub raw: md::MINIDUMP_ASSERTION_INFO,
|
|
}
|
|
|
|
/// A typed annotation object.
|
|
#[derive(Clone, Debug)]
|
|
#[non_exhaustive]
|
|
pub enum MinidumpAnnotation {
|
|
/// An invalid annotation. Reserved for internal use.
|
|
Invalid,
|
|
/// A `NUL`-terminated C-string.
|
|
String(String),
|
|
/// Clients may declare their own custom types.
|
|
UserDefined(md::MINIDUMP_ANNOTATION),
|
|
/// An unsupported annotation from a future crashpad version.
|
|
Unsupported(md::MINIDUMP_ANNOTATION),
|
|
}
|
|
|
|
impl PartialEq for MinidumpAnnotation {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
match (self, other) {
|
|
(Self::Invalid, Self::Invalid) => true,
|
|
(Self::String(a), Self::String(b)) => a == b,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Additional Crashpad-specific information about a module carried within a minidump file.
|
|
#[derive(Debug)]
|
|
pub struct MinidumpModuleCrashpadInfo {
|
|
/// The raw crashpad module extension information.
|
|
pub raw: md::MINIDUMP_MODULE_CRASHPAD_INFO,
|
|
/// Index of the corresponding module in the `MinidumpModuleList`.
|
|
pub module_index: usize,
|
|
pub list_annotations: Vec<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 => {
|
|
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);
|
|
let mut object_infos = Vec::<MinidumpHandleObjectInformation>::new();
|
|
let mut object_info_rva = raw.object_info_rva;
|
|
|
|
while object_info_rva != 0 {
|
|
if let Some(object_info) = Self::read_object_info(object_info_rva as usize, ctx)
|
|
{
|
|
object_info_rva = object_info.raw.next_info_rva;
|
|
object_infos.push(object_info);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ok((
|
|
MinidumpHandleDescriptor {
|
|
raw: RawHandleDescriptor::HandleDescriptor2(raw),
|
|
type_name,
|
|
object_name,
|
|
object_infos,
|
|
},
|
|
ctx.fieldsize as usize,
|
|
))
|
|
}
|
|
_ => Err(scroll::Error::BadInput {
|
|
size: ctx.fieldsize as usize,
|
|
msg: "Unknown MINIDUMP_HANDLE_DESCRIPTOR type",
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MinidumpHandleDataStream {
|
|
/// Return an empty `MinidumpHandleDataStream`.
|
|
pub fn new() -> MinidumpHandleDataStream {
|
|
MinidumpHandleDataStream { handles: vec![] }
|
|
}
|
|
|
|
/// Create a `MinidumpHandleDataStream` from a list of `MinidumpHandleDescriptor`s.
|
|
pub fn from_handles(handles: Vec<MinidumpHandleDescriptor>) -> MinidumpHandleDataStream {
|
|
MinidumpHandleDataStream { handles }
|
|
}
|
|
|
|
/// Iterate over the handles in the order contained in the minidump.
|
|
pub fn iter(&self) -> impl Iterator<Item = &MinidumpHandleDescriptor> {
|
|
self.handles.iter()
|
|
}
|
|
|
|
/// Write a human-readable description.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MinidumpHandleDataStream
|
|
handle_count = {}
|
|
|
|
",
|
|
self.handles.len()
|
|
)?;
|
|
for (i, handle) in self.handles.iter().enumerate() {
|
|
writeln!(f, "handle[{i}]")?;
|
|
handle.print(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Default for MinidumpHandleDataStream {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpHandleDataStream {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::HandleDataStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpHandleDataStream, Error> {
|
|
let mut offset = 0;
|
|
|
|
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;
|
|
|
|
let mut descriptors = Vec::<MinidumpHandleDescriptor>::with_capacity(number_of_entries);
|
|
for _ in 0..number_of_entries {
|
|
let descriptor: MinidumpHandleDescriptor = bytes
|
|
.gread_with(&mut offset, ctx)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
descriptors.push(descriptor);
|
|
}
|
|
|
|
Ok(MinidumpHandleDataStream::from_handles(descriptors))
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpMemory<'a> {
|
|
pub fn read(
|
|
desc: &md::MINIDUMP_MEMORY_DESCRIPTOR,
|
|
data: &'a [u8],
|
|
endian: scroll::Endian,
|
|
) -> Result<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`.
|
|
pub fn 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 MinidumpMemory64<'_> {
|
|
/// Write a human-readable description of this `MinidumpMemory64` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<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.
|
|
pub fn 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;
|
|
|
|
self.bytes.pread_with::<T>(start, self.endian).ok()
|
|
}
|
|
|
|
/// Write the contents of this `MinidumpMemory` to `f` as a hex string.
|
|
pub fn print_contents<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
const PARAGRAPH_SIZE: usize = 16;
|
|
let mut offset = 0;
|
|
for paragraph in self.bytes.chunks(PARAGRAPH_SIZE) {
|
|
write!(f, " {offset:08x}: ")?;
|
|
let mut byte_iter = paragraph.iter().fuse();
|
|
for _ in 0..PARAGRAPH_SIZE {
|
|
if let Some(byte) = byte_iter.next() {
|
|
write!(f, "{byte:02x} ")?;
|
|
} else {
|
|
write!(f, " ")?;
|
|
}
|
|
}
|
|
for &byte in paragraph.iter() {
|
|
let ascii_char = if !byte.is_ascii() || byte.is_ascii_control() {
|
|
'.'
|
|
} else {
|
|
char::from(byte)
|
|
};
|
|
|
|
write!(f, "{ascii_char}")?;
|
|
}
|
|
writeln!(f)?;
|
|
|
|
offset += PARAGRAPH_SIZE;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn memory_range(&self) -> Option<Range<u64>> {
|
|
if self.size == 0 {
|
|
return None;
|
|
}
|
|
Some(Range::new(
|
|
self.base_address,
|
|
self.base_address.checked_add(self.size)? - 1,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<'a, 'mdmp> UnifiedMemory<'a, 'mdmp> {
|
|
pub fn get_memory_at_address<T>(&self, addr: u64) -> Option<T>
|
|
where
|
|
T: TryFromCtx<'mdmp, scroll::Endian, [u8], Error = scroll::Error>,
|
|
{
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.get_memory_at_address(addr),
|
|
UnifiedMemory::Memory64(this) => this.get_memory_at_address(addr),
|
|
}
|
|
}
|
|
|
|
pub fn memory_range(&self) -> Option<Range<u64>> {
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.memory_range(),
|
|
UnifiedMemory::Memory64(this) => this.memory_range(),
|
|
}
|
|
}
|
|
|
|
pub fn bytes(&self) -> &'a [u8] {
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.bytes,
|
|
UnifiedMemory::Memory64(this) => this.bytes,
|
|
}
|
|
}
|
|
|
|
pub fn base_address(&self) -> u64 {
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.base_address,
|
|
UnifiedMemory::Memory64(this) => this.base_address,
|
|
}
|
|
}
|
|
|
|
pub fn size(&self) -> u64 {
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.size,
|
|
UnifiedMemory::Memory64(this) => this.size,
|
|
}
|
|
}
|
|
|
|
pub fn print_contents<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.print_contents(f),
|
|
UnifiedMemory::Memory64(this) => this.print_contents(f),
|
|
}
|
|
}
|
|
|
|
pub fn print<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
|
|
match self {
|
|
UnifiedMemory::Memory(this) => this.print(f, brief),
|
|
UnifiedMemory::Memory64(this) => this.print(f, brief),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'mdmp, Descriptor> MinidumpMemoryListBase<'mdmp, Descriptor> {
|
|
/// Return an empty `MinidumpMemoryListBase`.
|
|
pub fn new() -> MinidumpMemoryListBase<'mdmp, Descriptor> {
|
|
MinidumpMemoryListBase {
|
|
regions: vec![],
|
|
regions_by_addr: RangeMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create a `MinidumpMemoryListBase` from a list of `MinidumpMemoryBase`s.
|
|
pub fn from_regions(
|
|
regions: Vec<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.
|
|
pub fn memory_at_address(
|
|
&self,
|
|
address: u64,
|
|
) -> Option<&MinidumpMemoryBase<'mdmp, Descriptor>> {
|
|
self.regions_by_addr
|
|
.get(address)
|
|
.and_then(|&index| self.regions.get(index))
|
|
}
|
|
|
|
/// Iterate over the memory regions in the order contained in the minidump.
|
|
///
|
|
/// The iterator returns items of [MinidumpMemoryBase] as `&'slf MinidumpMemoryBase<'mdmp, Descriptor>`.
|
|
/// That is the lifetime of the item is bound to the lifetime of the iterator itself
|
|
/// (`'slf`), while the slice inside [MinidumpMemoryBase] pointing at the memory itself has
|
|
/// the lifetime of the [Minidump] struct ('mdmp).
|
|
pub fn iter<'slf>(
|
|
&'slf self,
|
|
) -> impl Iterator<Item = &'slf MinidumpMemoryBase<'mdmp, Descriptor>> {
|
|
self.regions.iter()
|
|
}
|
|
|
|
/// Iterate over the memory regions in order by memory address.
|
|
pub fn 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 MinidumpMemoryList<'_> {
|
|
/// Write a human-readable description of this `MinidumpMemoryList` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MinidumpMemoryList
|
|
region_count = {}
|
|
|
|
",
|
|
self.regions.len()
|
|
)?;
|
|
for (i, region) in self.regions.iter().enumerate() {
|
|
writeln!(f, "region[{i}]")?;
|
|
region.print(f, brief)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MinidumpMemory64List<'_> {
|
|
/// Write a human-readable description of this `MinidumpMemory64List` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MinidumpMemory64List
|
|
region_count = {}
|
|
|
|
",
|
|
self.regions.len()
|
|
)?;
|
|
for (i, region) in self.regions.iter().enumerate() {
|
|
writeln!(f, "region[{i}]")?;
|
|
region.print(f, brief)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'mdmp> UnifiedMemoryList<'mdmp> {
|
|
pub fn memory_at_address<'slf>(&'slf self, address: u64) -> Option<UnifiedMemory<'slf, 'mdmp>> {
|
|
match self {
|
|
UnifiedMemoryList::Memory(this) => {
|
|
this.memory_at_address(address).map(UnifiedMemory::Memory)
|
|
}
|
|
UnifiedMemoryList::Memory64(this) => {
|
|
this.memory_at_address(address).map(UnifiedMemory::Memory64)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn iter<'slf>(&'slf self) -> impl Iterator<Item = UnifiedMemory<'slf, 'mdmp>> {
|
|
let iter1 = if let UnifiedMemoryList::Memory(this) = self {
|
|
Some(this.iter().map(UnifiedMemory::Memory))
|
|
} else {
|
|
None
|
|
};
|
|
let iter2 = if let UnifiedMemoryList::Memory64(this) = self {
|
|
Some(this.iter().map(UnifiedMemory::Memory64))
|
|
} else {
|
|
None
|
|
};
|
|
iter1
|
|
.into_iter()
|
|
.flatten()
|
|
.chain(iter2.into_iter().flatten())
|
|
}
|
|
|
|
/// Iterate over the memory regions in order by memory address.
|
|
pub fn by_addr<'slf>(&'slf self) -> impl Iterator<Item = UnifiedMemory<'slf, 'mdmp>> {
|
|
let iter1 = if let UnifiedMemoryList::Memory(this) = self {
|
|
Some(this.by_addr().map(UnifiedMemory::Memory))
|
|
} else {
|
|
None
|
|
};
|
|
let iter2 = if let UnifiedMemoryList::Memory64(this) = self {
|
|
Some(this.by_addr().map(UnifiedMemory::Memory64))
|
|
} else {
|
|
None
|
|
};
|
|
iter1
|
|
.into_iter()
|
|
.flatten()
|
|
.chain(iter2.into_iter().flatten())
|
|
}
|
|
|
|
pub fn print<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
|
|
match self {
|
|
UnifiedMemoryList::Memory(this) => this.print(f, brief),
|
|
UnifiedMemoryList::Memory64(this) => this.print(f, brief),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Descriptor> Default for MinidumpMemoryListBase<'_, Descriptor> {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpMemoryList<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MemoryListStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpMemoryList<'a>, Error> {
|
|
let mut offset = 0;
|
|
let descriptors: Vec<md::MINIDUMP_MEMORY_DESCRIPTOR> =
|
|
read_stream_list(&mut offset, bytes, endian)?;
|
|
// read memory contents for each region
|
|
let mut regions = Vec::with_capacity(descriptors.len());
|
|
for raw in descriptors.into_iter() {
|
|
if let Ok(memory) = MinidumpMemory::read(&raw, all, endian) {
|
|
regions.push(memory);
|
|
} else {
|
|
// Just skip over corrupt entries and try to limp along.
|
|
continue;
|
|
}
|
|
}
|
|
Ok(MinidumpMemoryList::from_regions(regions))
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpMemory64List<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::Memory64ListStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpMemory64List<'a>, Error> {
|
|
let mut offset = 0;
|
|
let u: u64 = bytes
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let mut rva: u64 = bytes
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let (count, counted_size) = ensure_count_in_bound(
|
|
bytes,
|
|
u.try_into().map_err(|_| Error::StreamReadFailure)?,
|
|
md::MINIDUMP_MEMORY_DESCRIPTOR64::size_with(&endian),
|
|
offset,
|
|
)?;
|
|
|
|
if bytes.len() != counted_size {
|
|
return Err(Error::StreamSizeMismatch {
|
|
expected: counted_size,
|
|
actual: bytes.len(),
|
|
});
|
|
}
|
|
|
|
let mut raw_entries = Vec::with_capacity(count);
|
|
for _ in 0..count {
|
|
let raw: md::MINIDUMP_MEMORY_DESCRIPTOR64 = bytes
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
raw_entries.push(raw);
|
|
}
|
|
|
|
let mut regions = Vec::with_capacity(raw_entries.len());
|
|
for raw in raw_entries {
|
|
let start = rva;
|
|
let end = rva
|
|
.checked_add(raw.data_size)
|
|
.ok_or(Error::StreamReadFailure)?;
|
|
let bytes = all
|
|
.get(start as usize..end as usize)
|
|
.ok_or(Error::StreamReadFailure)?;
|
|
|
|
regions.push(MinidumpMemory64 {
|
|
desc: raw,
|
|
base_address: raw.start_of_memory_range,
|
|
size: raw.data_size,
|
|
bytes,
|
|
endian,
|
|
});
|
|
|
|
rva = end;
|
|
}
|
|
Ok(MinidumpMemory64List::from_regions(regions))
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpMemoryInfoList<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MemoryInfoListStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpMemoryInfoList<'a>, Error> {
|
|
let mut offset = 0;
|
|
let raw_regions: Vec<md::MINIDUMP_MEMORY_INFO> =
|
|
read_ex_stream_list(&mut offset, bytes, endian)?;
|
|
let regions = raw_regions
|
|
.into_iter()
|
|
.map(|raw| MinidumpMemoryInfo {
|
|
allocation_protection: md::MemoryProtection::from_bits_truncate(
|
|
raw.allocation_protection,
|
|
),
|
|
state: md::MemoryState::from_bits_truncate(raw.state),
|
|
protection: md::MemoryProtection::from_bits_truncate(raw.protection),
|
|
ty: md::MemoryType::from_bits_truncate(raw._type),
|
|
raw,
|
|
_phantom: PhantomData,
|
|
})
|
|
.collect();
|
|
Ok(MinidumpMemoryInfoList::from_regions(regions))
|
|
}
|
|
}
|
|
|
|
impl Default for MinidumpMemoryInfoList<'_> {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl<'mdmp> MinidumpMemoryInfoList<'mdmp> {
|
|
/// Return an empty `MinidumpMemoryList`.
|
|
pub fn new() -> MinidumpMemoryInfoList<'mdmp> {
|
|
MinidumpMemoryInfoList {
|
|
regions: vec![],
|
|
regions_by_addr: RangeMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s.
|
|
pub fn from_regions(regions: Vec<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.
|
|
pub fn memory_info_at_address(&self, address: u64) -> Option<&MinidumpMemoryInfo<'mdmp>> {
|
|
self.regions_by_addr
|
|
.get(address)
|
|
.map(|&index| &self.regions[index])
|
|
}
|
|
|
|
/// Iterate over the memory regions in the order contained in the minidump.
|
|
///
|
|
/// The iterator returns items of [MinidumpMemory] as `&'slf MinidumpMemory<'mdmp>`.
|
|
/// That is the lifetime of the item is bound to the lifetime of the iterator itself
|
|
/// (`'slf`), while the slice inside [MinidumpMemory] pointing at the memory itself has
|
|
/// the lifetime of the [Minidump] struct ('mdmp).
|
|
pub fn iter<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpMemoryInfo<'mdmp>> {
|
|
self.regions.iter()
|
|
}
|
|
|
|
/// Iterate over the memory regions in order by memory address.
|
|
pub fn by_addr<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpMemoryInfo<'mdmp>> {
|
|
self.regions_by_addr
|
|
.ranges_values()
|
|
.map(move |&(_, index)| &self.regions[index])
|
|
}
|
|
|
|
/// Write a human-readable description.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MinidumpMemoryInfoList
|
|
region_count = {}
|
|
|
|
",
|
|
self.regions.len()
|
|
)?;
|
|
for (i, region) in self.regions.iter().enumerate() {
|
|
writeln!(f, "region[{i}]")?;
|
|
region.print(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MinidumpMemoryInfo<'_> {
|
|
/// Write a human-readable description.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MINIDUMP_MEMORY_INFO
|
|
base_address = {:#x}
|
|
allocation_base = {:#x}
|
|
allocation_protection = {:#x}
|
|
region_size = {:#x}
|
|
state = {:#x}
|
|
protection = {:#x}
|
|
_type = {:#x}
|
|
",
|
|
self.raw.base_address,
|
|
self.raw.allocation_base,
|
|
self.allocation_protection,
|
|
self.raw.region_size,
|
|
self.state,
|
|
self.protection,
|
|
self.ty,
|
|
)?;
|
|
writeln!(f)
|
|
}
|
|
|
|
pub fn memory_range(&self) -> Option<Range<u64>> {
|
|
if self.raw.region_size == 0 {
|
|
return None;
|
|
}
|
|
Some(Range::new(
|
|
self.raw.base_address,
|
|
self.raw.base_address.checked_add(self.raw.region_size)? - 1,
|
|
))
|
|
}
|
|
|
|
/// Whether this memory range was readable.
|
|
pub fn is_readable(&self) -> bool {
|
|
self.protection.intersects(
|
|
md::MemoryProtection::PAGE_READONLY
|
|
| md::MemoryProtection::PAGE_READWRITE
|
|
| md::MemoryProtection::PAGE_EXECUTE_READ
|
|
| md::MemoryProtection::PAGE_EXECUTE_READWRITE,
|
|
)
|
|
}
|
|
|
|
/// Whether this memory range was writable.
|
|
pub fn is_writable(&self) -> bool {
|
|
self.protection.intersects(
|
|
md::MemoryProtection::PAGE_READWRITE
|
|
| md::MemoryProtection::PAGE_WRITECOPY
|
|
| md::MemoryProtection::PAGE_EXECUTE_READWRITE
|
|
| md::MemoryProtection::PAGE_EXECUTE_WRITECOPY,
|
|
)
|
|
}
|
|
|
|
/// Whether this memory range was executable.
|
|
pub fn is_executable(&self) -> bool {
|
|
self.protection.intersects(
|
|
md::MemoryProtection::PAGE_EXECUTE
|
|
| md::MemoryProtection::PAGE_EXECUTE_READ
|
|
| md::MemoryProtection::PAGE_EXECUTE_READWRITE
|
|
| md::MemoryProtection::PAGE_EXECUTE_WRITECOPY,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpLinuxMaps<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxMaps as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
_endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpLinuxMaps<'a>, Error> {
|
|
let maps = MemoryMaps::from_read(std::io::Cursor::new(bytes)).map_err(|e| {
|
|
tracing::error!("linux memory map read error: {e}");
|
|
Error::StreamReadFailure
|
|
})?;
|
|
|
|
Ok(MinidumpLinuxMaps::from_regions(
|
|
maps.into_iter()
|
|
.map(|map| MinidumpLinuxMapInfo {
|
|
map,
|
|
_phantom: PhantomData,
|
|
})
|
|
.collect(),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl Default for MinidumpLinuxMaps<'_> {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl<'mdmp> MinidumpLinuxMaps<'mdmp> {
|
|
/// Return an empty `MinidumpMemoryList`.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
regions: vec![],
|
|
regions_by_addr: RangeMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create a `MinidumpMemoryList` from a list of `MinidumpMemory`s.
|
|
pub fn from_regions(regions: Vec<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.
|
|
pub fn memory_info_at_address(&self, address: u64) -> Option<&MinidumpLinuxMapInfo<'mdmp>> {
|
|
self.regions_by_addr
|
|
.get(address)
|
|
.map(|&index| &self.regions[index])
|
|
}
|
|
|
|
/// Iterate over the memory regions in the order contained in the minidump.
|
|
pub fn iter<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpLinuxMapInfo<'mdmp>> {
|
|
self.regions.iter()
|
|
}
|
|
|
|
/// Iterate over the memory regions in order by memory address.
|
|
pub fn by_addr<'slf>(&'slf self) -> impl Iterator<Item = &'slf MinidumpLinuxMapInfo<'mdmp>> {
|
|
self.regions_by_addr
|
|
.ranges_values()
|
|
.map(move |&(_, index)| &self.regions[index])
|
|
}
|
|
|
|
/// Write a human-readable description.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MinidumpLinuxMapInfo
|
|
region_count = {}
|
|
|
|
",
|
|
self.regions.len()
|
|
)?;
|
|
for (i, region) in self.regions.iter().enumerate() {
|
|
writeln!(f, "region[{i}]")?;
|
|
region.print(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// Return number of memory mappings
|
|
pub fn memory_map_count(&self) -> usize {
|
|
self.regions.len()
|
|
}
|
|
}
|
|
|
|
impl MinidumpLinuxMapInfo<'_> {
|
|
/// Write a human-readable description of this.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MINIDUMP_LINUX_MAP_INFO
|
|
base_address = {:#x}
|
|
final_address = {:#x}
|
|
kind = {:#?}
|
|
permissions = {}
|
|
",
|
|
self.map.address.0,
|
|
self.map.address.1,
|
|
self.map.pathname,
|
|
self.map.perms.as_str()
|
|
)?;
|
|
writeln!(f)
|
|
}
|
|
|
|
pub fn memory_range(&self) -> Option<Range<u64>> {
|
|
// final address is inclusive afaik
|
|
if self.map.address.0 > self.map.address.1 {
|
|
return None;
|
|
}
|
|
Some(Range::new(self.map.address.0, self.map.address.1))
|
|
}
|
|
|
|
/// Whether this memory range was readable.
|
|
pub fn is_readable(&self) -> bool {
|
|
self.map.perms.contains(MMPermissions::READ)
|
|
}
|
|
|
|
/// Whether this memory range was writable.
|
|
pub fn is_writable(&self) -> bool {
|
|
self.map.perms.contains(MMPermissions::WRITE)
|
|
}
|
|
|
|
/// Whether this memory range was executable.
|
|
pub fn is_executable(&self) -> bool {
|
|
self.map.perms.contains(MMPermissions::EXECUTE)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn from_line(bytes: &[u8]) -> Option<Self> {
|
|
let map = MemoryMaps::from_read(std::io::Cursor::new(bytes))
|
|
.ok()?
|
|
.into_iter()
|
|
.next()?;
|
|
Some(MinidumpLinuxMapInfo {
|
|
map,
|
|
_phantom: PhantomData,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Default for UnifiedMemoryInfoList<'_> {
|
|
fn default() -> Self {
|
|
Self::Info(MinidumpMemoryInfoList::default())
|
|
}
|
|
}
|
|
|
|
impl<'a> UnifiedMemoryInfoList<'a> {
|
|
/// Take two potential memory info sources and create an interface that unifies them.
|
|
///
|
|
/// Under normal circumstances a minidump should only contain one of these.
|
|
/// If both are provided, one will be arbitrarily preferred to attempt to
|
|
/// make progress.
|
|
pub fn new(
|
|
info: Option<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.
|
|
pub fn memory_info_at_address(&self, address: u64) -> Option<UnifiedMemoryInfo> {
|
|
match self {
|
|
Self::Info(info) => info
|
|
.memory_info_at_address(address)
|
|
.map(UnifiedMemoryInfo::Info),
|
|
Self::Maps(maps) => maps
|
|
.memory_info_at_address(address)
|
|
.map(UnifiedMemoryInfo::Map),
|
|
}
|
|
}
|
|
|
|
/// Iterate over the memory regions in the order contained in the minidump.
|
|
pub fn iter(&self) -> impl Iterator<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.
|
|
pub fn 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`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
match self {
|
|
Self::Info(info) => info.print(f),
|
|
Self::Maps(maps) => maps.print(f),
|
|
}
|
|
}
|
|
|
|
/// Get the [`MinidumpLinuxMaps`] contained inside, if it exists.
|
|
///
|
|
/// Potentially useful for doing a more refined analysis in specific places.
|
|
pub fn maps(&self) -> Option<&MinidumpLinuxMaps<'a>> {
|
|
match &self {
|
|
Self::Maps(maps) => Some(maps),
|
|
Self::Info(_) => None,
|
|
}
|
|
}
|
|
|
|
/// Get the [`MinidumpMemoryInfoList`] contained inside, if it exists.
|
|
///
|
|
/// Potentially useful for doing a more refined analysis in specific places.
|
|
pub fn info(&self) -> Option<&MinidumpMemoryInfoList<'a>> {
|
|
match &self {
|
|
Self::Maps(_) => None,
|
|
Self::Info(info) => Some(info),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Declares functions which will forward to functions of the same name on the inner
|
|
/// `UnifiedMemoryInfo` members.
|
|
macro_rules! unified_memory_forward {
|
|
() => {};
|
|
( $(#[$attr:meta])* $vis:vis fn $name:ident $(< $($t_param:ident : $t_type:path),* >)? ( &self $(, $param:ident : $type:ty)* ) -> $ret:ty ; $($rest:tt)* ) => {
|
|
$(#[$attr])*
|
|
$vis fn $name $(< $($t_param : $t_type),* >)? (&self $(, $param : $type)*) -> $ret {
|
|
match self {
|
|
Self::Info(info) => info.$name($($param),*),
|
|
Self::Map(map) => map.$name($($param),*),
|
|
}
|
|
}
|
|
|
|
unified_memory_forward!($($rest)*);
|
|
};
|
|
}
|
|
|
|
impl UnifiedMemoryInfo<'_> {
|
|
unified_memory_forward! {
|
|
/// Write a human-readable description.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()>;
|
|
|
|
/// The range of memory this info applies to.
|
|
pub fn memory_range(&self) -> Option<Range<u64>>;
|
|
|
|
/// Whether this memory range was readable.
|
|
pub fn is_readable(&self) -> bool;
|
|
|
|
/// Whether this memory range was writable.
|
|
pub fn is_writable(&self) -> bool;
|
|
|
|
/// Whether this memory range was executable.
|
|
pub fn is_executable(&self) -> bool;
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpThread<'a> {
|
|
pub fn context(
|
|
&self,
|
|
system_info: &MinidumpSystemInfo,
|
|
misc: Option<&MinidumpMiscInfo>,
|
|
) -> Option<Cow<MinidumpContext>> {
|
|
MinidumpContext::read(self.context?, self.endian, system_info, misc)
|
|
.ok()
|
|
.map(Cow::Owned)
|
|
}
|
|
|
|
pub fn stack_memory<'mem>(
|
|
&'mem self,
|
|
memory_list: &'mem UnifiedMemoryList<'a>,
|
|
) -> Option<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`.
|
|
pub fn 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}
|
|
|
|
"#,
|
|
self.raw.thread_id,
|
|
self.raw.suspend_count,
|
|
self.raw.priority_class,
|
|
self.raw.priority,
|
|
self.raw.teb,
|
|
self.raw.stack.start_of_memory_range,
|
|
self.raw.stack.memory.data_size,
|
|
self.raw.stack.memory.rva,
|
|
self.raw.thread_context.data_size,
|
|
self.raw.thread_context.rva,
|
|
)?;
|
|
if let Some(system_info) = system {
|
|
if let Some(ctx) = self.context(system_info, misc) {
|
|
ctx.print(f)?;
|
|
} else {
|
|
write!(f, " (no context)\n\n")?;
|
|
}
|
|
} else {
|
|
write!(f, " (no context)\n\n")?;
|
|
}
|
|
|
|
if brief {
|
|
writeln!(f)?;
|
|
return Ok(());
|
|
}
|
|
|
|
let pointer_width = system.map_or(PointerWidth::Unknown, |info| info.cpu.pointer_width());
|
|
|
|
// We might not need any memory, so try to limp forward with an empty
|
|
// MemoryList if we don't have one.
|
|
let dummy_memory = UnifiedMemoryList::default();
|
|
let memory = memory.unwrap_or(&dummy_memory);
|
|
if let Some(ref stack) = self.stack_memory(memory) {
|
|
writeln!(f, "Stack")?;
|
|
|
|
// For printing purposes, we'll treat any unknown CPU type as 64-bit
|
|
let chunk_size: usize = pointer_width.size_in_bytes().unwrap_or(8).into();
|
|
let mut offset = 0;
|
|
for chunk in stack.bytes().chunks_exact(chunk_size) {
|
|
write!(f, " {offset:#010x}: ")?;
|
|
|
|
match pointer_width {
|
|
PointerWidth::Bits32 => {
|
|
let value = match self.endian {
|
|
scroll::Endian::Little => u32::from_le_bytes(chunk.try_into().unwrap()),
|
|
scroll::Endian::Big => u32::from_be_bytes(chunk.try_into().unwrap()),
|
|
};
|
|
write!(f, "{value:#010x}")?;
|
|
}
|
|
PointerWidth::Unknown | PointerWidth::Bits64 => {
|
|
let value = match self.endian {
|
|
scroll::Endian::Little => u64::from_le_bytes(chunk.try_into().unwrap()),
|
|
scroll::Endian::Big => u64::from_be_bytes(chunk.try_into().unwrap()),
|
|
};
|
|
write!(f, "{value:#018x}")?;
|
|
}
|
|
}
|
|
|
|
writeln!(f)?;
|
|
|
|
offset += chunk_size;
|
|
}
|
|
} else {
|
|
writeln!(f, "No stack")?;
|
|
}
|
|
writeln!(f)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Gets the last error code the thread recorded, just like win32's GetLastError.
|
|
///
|
|
/// The value is heuristically converted into a CrashReason because that's our
|
|
/// general error code handling machinery, even though this may not actually be
|
|
/// the reason for the crash!
|
|
pub fn last_error(&self, cpu: Cpu, memory: &UnifiedMemoryList) -> Option<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;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpThreadList<'a>, Error> {
|
|
let mut offset = 0;
|
|
let raw_threads: Vec<md::MINIDUMP_THREAD> = read_stream_list(&mut offset, bytes, endian)?;
|
|
let mut threads = Vec::with_capacity(raw_threads.len());
|
|
let mut thread_ids = HashMap::with_capacity(raw_threads.len());
|
|
for raw in raw_threads.into_iter() {
|
|
thread_ids.insert(raw.thread_id, threads.len());
|
|
|
|
// Defer parsing of this to the `context` method, where we will have access
|
|
// to other streams that are required to parse a context properly.
|
|
let context = location_slice(all, &raw.thread_context).ok();
|
|
|
|
// Try to get the stack memory here, but the `stack_memory` method will
|
|
// attempt a fallback method with access to other streams.
|
|
let stack = MinidumpMemory::read(&raw.stack, all, endian).ok();
|
|
threads.push(MinidumpThread {
|
|
raw,
|
|
context,
|
|
stack,
|
|
endian,
|
|
});
|
|
}
|
|
Ok(MinidumpThreadList {
|
|
threads,
|
|
thread_ids,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpThreadList<'a> {
|
|
/// Get the thread with id `id` from this thread list if it exists.
|
|
pub fn get_thread(&self, id: u32) -> Option<&MinidumpThread<'a>> {
|
|
self.thread_ids.get(&id).map(|&index| &self.threads[index])
|
|
}
|
|
|
|
/// Write a human-readable description of this `MinidumpThreadList` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<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 = {}
|
|
|
|
"#,
|
|
self.threads.len()
|
|
)?;
|
|
|
|
for (i, thread) in self.threads.iter().enumerate() {
|
|
writeln!(f, "thread[{i}]")?;
|
|
thread.print(f, memory, system, misc, brief)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// implement print for MinidumpThreadInfo
|
|
impl MinidumpThreadInfo {
|
|
/// Write a human-readable description of this `MinidumpThreadInfo` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
r#"MINIDUMP_THREAD_INFO
|
|
thread_id = {:#x}
|
|
dump_flags = {:#x}
|
|
dump_error = {:#x}
|
|
exit_status = {:#x}
|
|
create_time = {:#x}
|
|
exit_time = {:#x}
|
|
kernel_time = {:#x}
|
|
user_time = {:#x}
|
|
start_address = {:#x}
|
|
affinity = {:#x}
|
|
|
|
"#,
|
|
self.raw.thread_id,
|
|
self.raw.dump_flags,
|
|
self.raw.dump_error,
|
|
self.raw.exit_status,
|
|
self.raw.create_time,
|
|
self.raw.exit_time,
|
|
self.raw.kernel_time,
|
|
self.raw.user_time,
|
|
self.raw.start_address,
|
|
self.raw.affinity,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Default for MinidumpThreadInfoList {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl MinidumpThreadInfoList {
|
|
/// Return an empty `MinidumpThreadInfoList`.
|
|
pub fn new() -> MinidumpThreadInfoList {
|
|
MinidumpThreadInfoList {
|
|
thread_infos: vec![],
|
|
thread_ids: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Get the thread info with id `id` from this thread info list if it exists.
|
|
pub fn get_thread_info(&self, id: u32) -> Option<&MinidumpThreadInfo> {
|
|
self.thread_ids
|
|
.get(&id)
|
|
.map(|&index| &self.thread_infos[index])
|
|
}
|
|
|
|
/// Write a human-readable description of this `MinidumpModuleList` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MinidumpThreadInfoList
|
|
thread_info_count = {}
|
|
|
|
",
|
|
self.thread_infos.len()
|
|
)?;
|
|
for (i, thread_info) in self.thread_infos.iter().enumerate() {
|
|
writeln!(f, "thread info[{}]", i)?;
|
|
thread_info.print(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpThreadInfoList {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ThreadInfoListStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<Self, Error> {
|
|
let mut offset = 0;
|
|
let raw_thread_infos: Vec<md::MINIDUMP_THREAD_INFO> =
|
|
read_ex_stream_list(&mut offset, bytes, endian)?;
|
|
|
|
let mut thread_infos = Vec::with_capacity(raw_thread_infos.len());
|
|
let mut thread_ids = HashMap::with_capacity(raw_thread_infos.len());
|
|
for raw in raw_thread_infos.into_iter() {
|
|
thread_ids.insert(raw.thread_id, thread_infos.len());
|
|
thread_infos.push(MinidumpThreadInfo { raw });
|
|
}
|
|
|
|
Ok(MinidumpThreadInfoList {
|
|
thread_infos,
|
|
thread_ids,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl MinidumpStream<'_> for MinidumpSystemInfo {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::SystemInfoStream as u32;
|
|
|
|
fn read(
|
|
bytes: &[u8],
|
|
all: &[u8],
|
|
endian: scroll::Endian,
|
|
system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpSystemInfo, Error> {
|
|
if let Some(system_info) = system_info {
|
|
return Ok(system_info.clone());
|
|
}
|
|
|
|
use std::fmt::Write;
|
|
|
|
let raw: md::MINIDUMP_SYSTEM_INFO = bytes
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
let os = Os::from_platform_id(raw.platform_id);
|
|
let cpu = Cpu::from_processor_architecture(raw.processor_architecture);
|
|
|
|
let mut csd_offset = raw.csd_version_rva as usize;
|
|
let csd_version = read_string_utf16(&mut csd_offset, all, endian);
|
|
|
|
// self.raw.cpu.data is actually a union which we resolve here.
|
|
let cpu_info = match cpu {
|
|
Cpu::X86 | Cpu::X86_64 => {
|
|
let mut cpu_info = String::new();
|
|
|
|
if let Cpu::X86 = cpu {
|
|
// The vendor's ID is an ascii string but we need to flatten out the u32's into u8's
|
|
let x86_info: md::X86CpuInfo = raw
|
|
.cpu
|
|
.data
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
cpu_info.extend(
|
|
x86_info
|
|
.vendor_id
|
|
.iter()
|
|
.flat_map(|i| IntoIterator::into_iter(i.to_le_bytes()))
|
|
.map(char::from),
|
|
);
|
|
cpu_info.push(' ');
|
|
}
|
|
|
|
write!(
|
|
&mut cpu_info,
|
|
"family {} model {} stepping {}",
|
|
raw.processor_level,
|
|
(raw.processor_revision >> 8) & 0xff,
|
|
raw.processor_revision & 0xff
|
|
)
|
|
.unwrap();
|
|
|
|
Some(cpu_info)
|
|
}
|
|
Cpu::Arm => {
|
|
let arm_info: md::ARMCpuInfo = raw
|
|
.cpu
|
|
.data
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
// There is no good list of implementer id values, but the following
|
|
// pages provide some help:
|
|
// http://comments.gmane.org/gmane.linux.linaro.devel/6903
|
|
// http://forum.xda-developers.com/archive/index.php/t-480226.html
|
|
let vendors = [
|
|
(0x41, "ARM"),
|
|
(0x51, "Qualcomm"),
|
|
(0x56, "Marvell"),
|
|
(0x69, "Intel/Marvell"),
|
|
];
|
|
let parts = [
|
|
(0x4100c050, "Cortex-A5"),
|
|
(0x4100c080, "Cortex-A8"),
|
|
(0x4100c090, "Cortex-A9"),
|
|
(0x4100c0f0, "Cortex-A15"),
|
|
(0x4100c140, "Cortex-R4"),
|
|
(0x4100c150, "Cortex-R5"),
|
|
(0x4100b360, "ARM1136"),
|
|
(0x4100b560, "ARM1156"),
|
|
(0x4100b760, "ARM1176"),
|
|
(0x4100b020, "ARM11-MPCore"),
|
|
(0x41009260, "ARM926"),
|
|
(0x41009460, "ARM946"),
|
|
(0x41009660, "ARM966"),
|
|
(0x510006f0, "Krait"),
|
|
(0x510000f0, "Scorpion"),
|
|
];
|
|
let features = [
|
|
(md::ArmElfHwCaps::HWCAP_SWP, "swp"),
|
|
(md::ArmElfHwCaps::HWCAP_HALF, "half"),
|
|
(md::ArmElfHwCaps::HWCAP_THUMB, "thumb"),
|
|
(md::ArmElfHwCaps::HWCAP_26BIT, "26bit"),
|
|
(md::ArmElfHwCaps::HWCAP_FAST_MULT, "fastmult"),
|
|
(md::ArmElfHwCaps::HWCAP_FPA, "fpa"),
|
|
(md::ArmElfHwCaps::HWCAP_VFP, "vfpv2"),
|
|
(md::ArmElfHwCaps::HWCAP_EDSP, "edsp"),
|
|
(md::ArmElfHwCaps::HWCAP_JAVA, "java"),
|
|
(md::ArmElfHwCaps::HWCAP_IWMMXT, "iwmmxt"),
|
|
(md::ArmElfHwCaps::HWCAP_CRUNCH, "crunch"),
|
|
(md::ArmElfHwCaps::HWCAP_THUMBEE, "thumbee"),
|
|
(md::ArmElfHwCaps::HWCAP_NEON, "neon"),
|
|
(md::ArmElfHwCaps::HWCAP_VFPv3, "vfpv3"),
|
|
(md::ArmElfHwCaps::HWCAP_VFPv3D16, "vfpv3d16"),
|
|
(md::ArmElfHwCaps::HWCAP_TLS, "tls"),
|
|
(md::ArmElfHwCaps::HWCAP_VFPv4, "vfpv4"),
|
|
(md::ArmElfHwCaps::HWCAP_IDIVA, "idiva"),
|
|
(md::ArmElfHwCaps::HWCAP_IDIVT, "idivt"),
|
|
];
|
|
|
|
let mut cpu_info = format!("ARMv{}", raw.processor_level);
|
|
|
|
// Try to extract out known vendor/part names from the cpuid,
|
|
// falling back to just reporting the raw value.
|
|
let cpuid = arm_info.cpuid;
|
|
if cpuid != 0 {
|
|
let vendor_id = (cpuid >> 24) & 0xff;
|
|
let part_id = cpuid & 0xff00fff0;
|
|
|
|
if let Some(&(_, vendor)) = vendors.iter().find(|&&(id, _)| id == vendor_id) {
|
|
write!(&mut cpu_info, " {vendor}").unwrap();
|
|
} else {
|
|
write!(&mut cpu_info, " vendor({vendor_id:#x})").unwrap();
|
|
}
|
|
|
|
if let Some(&(_, part)) = parts.iter().find(|&&(id, _)| id == part_id) {
|
|
write!(&mut cpu_info, " {part}").unwrap();
|
|
} else {
|
|
write!(&mut cpu_info, " part({part_id:#x})").unwrap();
|
|
}
|
|
}
|
|
|
|
// Report all the known hardware features.
|
|
let elf_hwcaps = md::ArmElfHwCaps::from_bits_truncate(arm_info.elf_hwcaps);
|
|
if !elf_hwcaps.is_empty() {
|
|
cpu_info.push_str(" features: ");
|
|
|
|
// Iterator::intersperse is still unstable, so do it manually
|
|
let mut comma = "";
|
|
for &(_, feature) in features
|
|
.iter()
|
|
.filter(|&&(feature, _)| elf_hwcaps.contains(feature))
|
|
{
|
|
cpu_info.push_str(comma);
|
|
cpu_info.push_str(feature);
|
|
comma = ",";
|
|
}
|
|
}
|
|
|
|
Some(cpu_info)
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
Ok(MinidumpSystemInfo {
|
|
raw,
|
|
os,
|
|
cpu,
|
|
csd_version,
|
|
cpu_info,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl MinidumpSystemInfo {
|
|
/// Write a human-readable description of this `MinidumpSystemInfo` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MINIDUMP_SYSTEM_INFO
|
|
processor_architecture = {:#x}
|
|
processor_level = {}
|
|
processor_revision = {:#x}
|
|
number_of_processors = {}
|
|
product_type = {}
|
|
major_version = {}
|
|
minor_version = {}
|
|
build_number = {}
|
|
platform_id = {:#x}
|
|
csd_version_rva = {:#x}
|
|
suite_mask = {:#x}
|
|
(version) = {}.{}.{} {}
|
|
(cpu_info) = {}
|
|
|
|
",
|
|
self.raw.processor_architecture,
|
|
self.raw.processor_level,
|
|
self.raw.processor_revision,
|
|
self.raw.number_of_processors,
|
|
self.raw.product_type,
|
|
self.raw.major_version,
|
|
self.raw.minor_version,
|
|
self.raw.build_number,
|
|
self.raw.platform_id,
|
|
self.raw.csd_version_rva,
|
|
self.raw.suite_mask,
|
|
self.raw.major_version,
|
|
self.raw.minor_version,
|
|
self.raw.build_number,
|
|
self.csd_version().as_deref().unwrap_or(""),
|
|
self.cpu_info().as_deref().unwrap_or(""),
|
|
)?;
|
|
// TODO: cpu info etc
|
|
Ok(())
|
|
}
|
|
|
|
/// If the minidump was generated on:
|
|
/// - Windows: Returns the the name of the Service Pack.
|
|
/// - macOS: Returns the product build number.
|
|
/// - Linux: Returns the contents of `uname -srvmo`.
|
|
pub fn csd_version(&self) -> Option<Cow<str>> {
|
|
self.csd_version.as_deref().map(Cow::Borrowed)
|
|
}
|
|
|
|
/// Returns a string describing the cpu's vendor and model.
|
|
pub fn 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.
|
|
pub fn 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());
|
|
|
|
if md::PlatformId::from_u32(self.raw.platform_id) != Some(md::PlatformId::Linux)
|
|
|| os_version != "0.0.0"
|
|
{
|
|
return (os_version, os_build);
|
|
}
|
|
|
|
// Try to parse the Linux build string. Breakpad and Crashpad run
|
|
// `uname -srvmo` to generate it. The string follows this structure:
|
|
// "Linux [version] [build...] [arch] Linux/GNU" where the Linux/GNU
|
|
// bit may not always be present.
|
|
let raw_build = self.csd_version().unwrap_or(Cow::Borrowed(""));
|
|
let mut parts = raw_build.split(' ');
|
|
let version = parts.nth(1).unwrap_or("0.0.0");
|
|
let _arch_or_os = parts.next_back().unwrap_or_default();
|
|
if _arch_or_os == "Linux/GNU" {
|
|
let _arch = parts.next_back();
|
|
}
|
|
let build = parts.collect::<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)]
|
|
pub fn $name(&self) -> Option<&$t> {
|
|
match self {
|
|
$(
|
|
RawMiscInfo::$variant(ref raw) => Some(&raw.$name),
|
|
)+
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
(@def $name:ident $flag:ident $t:ty [$($variant:ident)+]) => {
|
|
#[allow(unreachable_patterns)]
|
|
pub fn $name(&self) -> Option<&$t> {
|
|
match self {
|
|
$(
|
|
RawMiscInfo::$variant(ref raw) => if md::MiscInfoFlags::from_bits_truncate(raw.flags1).contains(md::MiscInfoFlags::$flag) { Some(&raw.$name) } else { None },
|
|
)+
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
(1: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@defnoflag $name $t [MiscInfo MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
(1: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@def $name $flag $t [MiscInfo MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
|
|
(2: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@defnoflag $name $t [MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
(2: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@def $name $flag $t [MiscInfo2 MiscInfo3 MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
|
|
(3: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@defnoflag $name $t [MiscInfo3 MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
(3: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@def $name $flag $t [MiscInfo3 MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
|
|
(4: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@defnoflag $name $t [MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
(4: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@def $name $flag $t [MiscInfo4 MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
|
|
(5: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@defnoflag $name $t [MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
(5: $name:ident if $flag:ident -> $t:ty, $($rest:tt)*) => {
|
|
misc_accessors!(@def $name $flag $t [MiscInfo5]);
|
|
misc_accessors!($($rest)*);
|
|
};
|
|
}
|
|
|
|
impl RawMiscInfo {
|
|
// Fields are grouped by the flag that guards them.
|
|
misc_accessors!(
|
|
1: size_of_info -> u32,
|
|
1: flags1 -> u32,
|
|
|
|
1: process_id if MINIDUMP_MISC1_PROCESS_ID -> u32,
|
|
|
|
1: process_create_time if MINIDUMP_MISC1_PROCESS_TIMES -> u32,
|
|
1: process_user_time if MINIDUMP_MISC1_PROCESS_TIMES -> u32,
|
|
1: process_kernel_time if MINIDUMP_MISC1_PROCESS_TIMES -> u32,
|
|
|
|
2: processor_max_mhz if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
|
|
2: processor_current_mhz if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
|
|
2: processor_mhz_limit if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
|
|
2: processor_max_idle_state if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
|
|
2: processor_current_idle_state if MINIDUMP_MISC1_PROCESSOR_POWER_INFO -> u32,
|
|
|
|
3: process_integrity_level if MINIDUMP_MISC3_PROCESS_INTEGRITY -> u32,
|
|
|
|
3: process_execute_flags if MINIDUMP_MISC3_PROCESS_EXECUTE_FLAGS -> u32,
|
|
|
|
3: protected_process if MINIDUMP_MISC3_PROTECTED_PROCESS -> u32,
|
|
|
|
3: time_zone_id if MINIDUMP_MISC3_TIMEZONE -> u32,
|
|
3: time_zone if MINIDUMP_MISC3_TIMEZONE -> md::TIME_ZONE_INFORMATION,
|
|
|
|
4: build_string if MINIDUMP_MISC4_BUILDSTRING -> [u16; 260],
|
|
4: dbg_bld_str if MINIDUMP_MISC4_BUILDSTRING -> [u16; 40],
|
|
|
|
5: xstate_data -> md::XSTATE_CONFIG_FEATURE_MSC_INFO,
|
|
|
|
5: process_cookie if MINIDUMP_MISC5_PROCESS_COOKIE -> u32,
|
|
);
|
|
}
|
|
|
|
impl MinidumpStream<'_> for MinidumpMiscInfo {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MiscInfoStream as u32;
|
|
|
|
fn read(
|
|
bytes: &[u8],
|
|
_all: &[u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpMiscInfo, Error> {
|
|
// The misc info has gone through several revisions, so try to read the largest known
|
|
// struct possible.
|
|
macro_rules! do_read {
|
|
($(($t:ty, $variant:ident),)+) => {
|
|
$(
|
|
if bytes.len() >= <$t>::size_with(&endian) {
|
|
return Ok(MinidumpMiscInfo {
|
|
raw: RawMiscInfo::$variant(bytes.pread_with(0, endian).or(Err(Error::StreamReadFailure))?),
|
|
});
|
|
}
|
|
)+
|
|
}
|
|
}
|
|
|
|
do_read!(
|
|
(md::MINIDUMP_MISC_INFO_5, MiscInfo5),
|
|
(md::MINIDUMP_MISC_INFO_4, MiscInfo4),
|
|
(md::MINIDUMP_MISC_INFO_3, MiscInfo3),
|
|
(md::MINIDUMP_MISC_INFO_2, MiscInfo2),
|
|
(md::MINIDUMP_MISC_INFO, MiscInfo),
|
|
);
|
|
Err(Error::StreamReadFailure)
|
|
}
|
|
}
|
|
|
|
// Generates an accessor for a MAC_CRASH_INFO field with two possible syntaxes:
|
|
//
|
|
// * VERSION_NUMBER: FIELD_NAME -> FIELD_TYPE
|
|
// * VERSION_NUMBER: string FIELD_NAME -> FIELD_TYPE
|
|
//
|
|
// With the following definitions:
|
|
//
|
|
// * VERSION_NUMBER: The MAC_CRASH_INFO version this field was introduced in
|
|
// * FIELD_NAME: The name of the field to read
|
|
// * FIELD_TYPE: The type of the field
|
|
//
|
|
// The "string" mode will retrieve the field from the variant's _RECORD_STRINGS
|
|
// struct, while the other mode will retrieve it from the variant's _RECORD
|
|
// struct.
|
|
//
|
|
// In both cases, None will be yielded if the value is null/empty.
|
|
macro_rules! mac_crash_accessors {
|
|
() => {};
|
|
(@deffixed $name:ident $t:ty [$($variant:ident)+]) => {
|
|
#[allow(unreachable_patterns)]
|
|
pub fn $name(&self) -> Option<&$t> {
|
|
match self {
|
|
$(
|
|
RawMacCrashInfo::$variant(ref fixed, _) => {
|
|
if fixed.$name == 0 {
|
|
None
|
|
} else {
|
|
Some(&fixed.$name)
|
|
}
|
|
},
|
|
)+
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
(@defstrings $name:ident $t:ty [$($variant:ident)+]) => {
|
|
#[allow(unreachable_patterns)]
|
|
pub fn $name(&self) -> Option<&$t> {
|
|
match self {
|
|
$(
|
|
RawMacCrashInfo::$variant(_, ref strings) => {
|
|
if strings.$name.is_empty() {
|
|
None
|
|
} else {
|
|
Some(&*strings.$name)
|
|
}
|
|
}
|
|
)+
|
|
_ => None,
|
|
}
|
|
}
|
|
};
|
|
(1: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
mac_crash_accessors!(@deffixed $name $t [V1 V4 V5]);
|
|
mac_crash_accessors!($($rest)*);
|
|
};
|
|
(1: string $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
mac_crash_accessors!(@defstrings $name $t [V1 V4 V5]);
|
|
mac_crash_accessors!($($rest)*);
|
|
};
|
|
(4: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
mac_crash_accessors!(@deffixed $name $t [V4 V5]);
|
|
mac_crash_accessors!($($rest)*);
|
|
};
|
|
(4: string $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
mac_crash_accessors!(@defstrings $name $t [V4 V5]);
|
|
mac_crash_accessors!($($rest)*);
|
|
};
|
|
(5: $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
mac_crash_accessors!(@deffixed $name $t [V5]);
|
|
mac_crash_accessors!($($rest)*);
|
|
};
|
|
(5: string $name:ident -> $t:ty, $($rest:tt)*) => {
|
|
mac_crash_accessors!(@defstrings $name $t [V5]);
|
|
mac_crash_accessors!($($rest)*);
|
|
};
|
|
}
|
|
|
|
impl RawMacCrashInfo {
|
|
// Fields are grouped by the flag that guards them.
|
|
mac_crash_accessors!(
|
|
1: version -> u64,
|
|
|
|
4: thread -> u64,
|
|
4: dialog_mode -> u64,
|
|
|
|
4: string module_path -> str,
|
|
4: string message -> str,
|
|
4: string signature_string -> str,
|
|
4: string backtrace -> str,
|
|
4: string message2 -> str,
|
|
|
|
5: abort_cause -> u64,
|
|
);
|
|
}
|
|
|
|
impl MinidumpStream<'_> for MinidumpMacCrashInfo {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozMacosCrashInfoStream as u32;
|
|
|
|
fn read(
|
|
bytes: &[u8],
|
|
all: &[u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<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;
|
|
let mut prev_version = None;
|
|
let mut infos = Vec::new();
|
|
|
|
// We use `take` here to better handle a corrupt record_count that is larger than the
|
|
// maximum supported size.
|
|
let records = header.records.iter().take(header.record_count as usize);
|
|
|
|
for record_location in records {
|
|
// Peek the V1 version to get the `version` field
|
|
let record_slice = location_slice(all, record_location)?;
|
|
let base: md::MINIDUMP_MAC_CRASH_INFO_RECORD = record_slice
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
// The V1 version also includes the stream type again, but that's
|
|
// not really important, so just warn about it and keep going.
|
|
if base.stream_type != header.stream_type as u64 {
|
|
warn!(
|
|
"MozMacosCrashInfoStream records don't have the right stream type? {}",
|
|
base.stream_type
|
|
);
|
|
}
|
|
|
|
// Make sure every record has the same version, because they have to
|
|
// share their strings_offset which make heterogeneous records impossible.
|
|
if let Some(prev_version) = prev_version {
|
|
if prev_version != base.version {
|
|
warn!(
|
|
"MozMacosCrashInfoStream had two different versions ({} != {})",
|
|
prev_version, base.version
|
|
);
|
|
return Err(Error::VersionMismatch);
|
|
}
|
|
}
|
|
prev_version = Some(base.version);
|
|
|
|
// Now actually read the full record and its strings for the version
|
|
macro_rules! do_read {
|
|
($base_version:expr, $strings_offset:expr, $infos:ident,
|
|
$(($version:expr, $fixed:ty, $strings:ty, $variant:ident),)+) => {$(
|
|
if $base_version >= $version {
|
|
let offset = &mut 0;
|
|
let fixed: $fixed = record_slice
|
|
.gread_with(offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
// Sanity check that we haven't blown past where the strings start.
|
|
if *offset > $strings_offset {
|
|
warn!("MozMacosCrashInfoStream's record_start_size was too small! ({})",
|
|
$strings_offset);
|
|
return Err(Error::StreamReadFailure);
|
|
}
|
|
|
|
// We could be handling a newer version of the format than we know
|
|
// how to support, so jump to where the strings start, potentially
|
|
// skipping over some unknown fields.
|
|
*offset = $strings_offset;
|
|
let num_strings = <$strings>::num_strings();
|
|
let mut strings = <$strings>::default();
|
|
|
|
// Read out all the strings we know about
|
|
for i in 0..num_strings {
|
|
let string = read_cstring_utf8(offset, record_slice)
|
|
.ok_or(Error::StreamReadFailure)?;
|
|
strings.set_string(i, string);
|
|
}
|
|
// If this is a newer version, there may be some extra variable length
|
|
// data in this record, but we don't know what it is, so don't try to parse it.
|
|
|
|
infos.push(RawMacCrashInfo::$variant(fixed, strings));
|
|
continue;
|
|
}
|
|
)+}
|
|
}
|
|
|
|
do_read!(
|
|
base.version,
|
|
strings_offset,
|
|
infos,
|
|
(
|
|
5,
|
|
md::MINIDUMP_MAC_CRASH_INFO_RECORD_5,
|
|
md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_5,
|
|
V5
|
|
),
|
|
(
|
|
4,
|
|
md::MINIDUMP_MAC_CRASH_INFO_RECORD_4,
|
|
md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS_4,
|
|
V4
|
|
),
|
|
(
|
|
1,
|
|
md::MINIDUMP_MAC_CRASH_INFO_RECORD,
|
|
md::MINIDUMP_MAC_CRASH_INFO_RECORD_STRINGS,
|
|
V1
|
|
),
|
|
);
|
|
}
|
|
Ok(MinidumpMacCrashInfo { raw: infos })
|
|
}
|
|
}
|
|
|
|
impl MinidumpMacCrashInfo {
|
|
/// Write a human-readable description of this `MinidumpMacCrashInfo` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
macro_rules! write_simple_field {
|
|
($stream:ident, $field:ident, $idx:ident, $format:literal) => {
|
|
write!(f, " {:18}= ", stringify!($field))?;
|
|
match self.raw[$idx].$field() {
|
|
Some($field) => {
|
|
writeln!(f, $format, $field)?;
|
|
}
|
|
None => writeln!(f)?,
|
|
}
|
|
};
|
|
($stream:ident, $field:ident, $idx:ident) => {
|
|
write_simple_field!($stream, $field, $idx, "{}");
|
|
};
|
|
}
|
|
writeln!(f, "MINIDUMP_MAC_CRASH_INFO")?;
|
|
writeln!(f, " num_records = {}", self.raw.len())?;
|
|
|
|
for i in 0..self.raw.len() {
|
|
writeln!(f)?;
|
|
writeln!(f, " RECORD[{i}] ")?;
|
|
write_simple_field!(f, version, i);
|
|
write_simple_field!(f, thread, i);
|
|
write_simple_field!(f, dialog_mode, i, "{:x}");
|
|
write_simple_field!(f, module_path, i);
|
|
write_simple_field!(f, message, i);
|
|
write_simple_field!(f, signature_string, i);
|
|
write_simple_field!(f, backtrace, i);
|
|
write_simple_field!(f, message2, i);
|
|
write_simple_field!(f, abort_cause, i, "{:x}");
|
|
}
|
|
|
|
writeln!(f)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MinidumpStream<'_> for MinidumpMacBootargs {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozMacosBootargsStream as u32;
|
|
|
|
fn read(
|
|
bytes: &[u8],
|
|
all: &[u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpMacBootargs, Error> {
|
|
let raw: md::MINIDUMP_MAC_BOOTARGS = bytes
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let stream_type = raw.stream_type;
|
|
if stream_type != Self::STREAM_TYPE {
|
|
warn!(
|
|
"MozMacosBootargsStream record doesn't have the right stream type? {}",
|
|
Self::STREAM_TYPE
|
|
);
|
|
}
|
|
let mut bootargs_offset = raw.bootargs as usize;
|
|
let bootargs = read_string_utf16(&mut bootargs_offset, all, endian);
|
|
|
|
Ok(MinidumpMacBootargs { raw, bootargs })
|
|
}
|
|
}
|
|
|
|
impl MinidumpMacBootargs {
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
writeln!(
|
|
f,
|
|
"mac_boot_args = {}",
|
|
self.bootargs.as_deref().unwrap_or("")
|
|
)?;
|
|
writeln!(f)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpLinuxLsbRelease<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxLsbRelease as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
_endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpLinuxLsbRelease<'a>, Error> {
|
|
Ok(Self { data: bytes })
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpLinuxEnviron<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxEnviron as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
_endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpLinuxEnviron<'a>, Error> {
|
|
Ok(Self { data: bytes })
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpLinuxProcStatus<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxProcStatus as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
_endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpLinuxProcStatus<'a>, Error> {
|
|
Ok(Self { data: bytes })
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpLinuxProcLimits<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::MozLinuxLimits as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
_endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpLinuxProcLimits<'a>, Error> {
|
|
Ok(Self { data: bytes })
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpLinuxCpuInfo<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::LinuxCpuInfo as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
_endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpLinuxCpuInfo<'a>, Error> {
|
|
Ok(Self { data: bytes })
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpLinuxCpuInfo<'a> {
|
|
/// Get an iterator over the key-value pairs stored in the `/proc/cpuinfo` dump.
|
|
///
|
|
/// Keys and values are `trim`ed of leading/trailing spaces, and if a key
|
|
/// or value was surrounded by quotes ("like this"), the quotes will be
|
|
/// stripped.
|
|
pub fn iter(&self) -> impl Iterator<Item = (&'a LinuxOsStr, &'a LinuxOsStr)> {
|
|
linux_list_iter(self.data, b':')
|
|
}
|
|
|
|
/// Get the raw bytes of the `/proc/cpuinfo` dump.
|
|
pub fn raw_bytes(&self) -> Cow<'a, [u8]> {
|
|
Cow::Borrowed(self.data)
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpLinuxEnviron<'a> {
|
|
/// Get an iterator over the key-value pairs stored in the `/proc/self/environ` dump.
|
|
///
|
|
/// Keys and values are `trim`ed of leading/trailing spaces, and if a key
|
|
/// or value was surrounded by quotes ("like this"), the quotes will be
|
|
/// stripped.
|
|
pub fn iter(&self) -> impl Iterator<Item = (&'a LinuxOsStr, &'a LinuxOsStr)> {
|
|
linux_list_iter(self.data, b'=')
|
|
}
|
|
|
|
/// Get the raw bytes of the `/proc/self/environ` dump.
|
|
pub fn raw_bytes(&self) -> Cow<'a, [u8]> {
|
|
Cow::Borrowed(self.data)
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpLinuxProcStatus<'a> {
|
|
/// Get an iterator over the key-value pairs stored in the `/proc/self/status` dump.
|
|
///
|
|
/// Keys and values are `trim`ed of leading/trailing spaces, and if a key
|
|
/// or value was surrounded by quotes ("like this"), the quotes will be
|
|
/// stripped.
|
|
pub fn iter(&self) -> impl Iterator<Item = (&'a LinuxOsStr, &'a LinuxOsStr)> {
|
|
linux_list_iter(self.data, b':')
|
|
}
|
|
|
|
/// Get the raw bytes of the `/proc/self/status` dump.
|
|
pub fn raw_bytes(&self) -> Cow<'a, [u8]> {
|
|
Cow::Borrowed(self.data)
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpLinuxProcLimits<'a> {
|
|
/// Get an iterator over the key-value pairs stored in the `/proc/self/limits` dump.
|
|
///
|
|
/// Keys and values are `trim`ed of leading/trailing spaces, and if a key
|
|
/// or value was surrounded by quotes ("like this"), the quotes will be
|
|
/// stripped.
|
|
pub fn iter(&self) -> impl Iterator<Item = &'a LinuxOsStr> {
|
|
LinuxOsStr::from_bytes(self.data).lines()
|
|
}
|
|
|
|
/// Get the raw bytes of the `/proc/self/limits` dump.
|
|
pub fn raw_bytes(&self) -> Cow<'a, [u8]> {
|
|
Cow::Borrowed(self.data)
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpLinuxLsbRelease<'a> {
|
|
/// Get an iterator over the key-value pairs stored in the `/etc/lsb-release` dump.
|
|
///
|
|
/// Keys and values are `trim`ed of leading/trailing spaces, and if a key
|
|
/// or value was surrounded by quotes ("like this"), the quotes will be
|
|
/// stripped.
|
|
pub fn iter(&self) -> impl Iterator<Item = (&'a LinuxOsStr, &'a LinuxOsStr)> {
|
|
linux_list_iter(self.data, b'=')
|
|
}
|
|
|
|
/// Get the raw bytes of the `/etc/lsb-release` dump.
|
|
pub fn raw_bytes(&self) -> Cow<'a, [u8]> {
|
|
Cow::Borrowed(self.data)
|
|
}
|
|
}
|
|
|
|
fn systemtime_from_timestamp(timestamp: u64) -> Option<SystemTime> {
|
|
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(timestamp))
|
|
}
|
|
|
|
impl MinidumpMiscInfo {
|
|
pub fn process_create_time(&self) -> Option<SystemTime> {
|
|
self.raw
|
|
.process_create_time()
|
|
.and_then(|t| systemtime_from_timestamp(*t as u64))
|
|
}
|
|
|
|
/// Write a human-readable description of this `MinidumpMiscInfo` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
macro_rules! write_simple_field {
|
|
($stream:ident, $field:ident, $format:literal) => {
|
|
write!(f, " {:29}= ", stringify!($field))?;
|
|
match self.raw.$field() {
|
|
Some($field) => {
|
|
writeln!(f, $format, $field)?;
|
|
}
|
|
None => writeln!(f, "(invalid)")?,
|
|
}
|
|
};
|
|
($stream:ident, $field:ident) => {
|
|
write_simple_field!($stream, $field, "{}");
|
|
};
|
|
}
|
|
writeln!(f, "MINIDUMP_MISC_INFO")?;
|
|
|
|
write_simple_field!(f, size_of_info);
|
|
write_simple_field!(f, flags1, "{:x}");
|
|
write_simple_field!(f, process_id);
|
|
write!(f, " process_create_time = ")?;
|
|
match self.raw.process_create_time() {
|
|
Some(&process_create_time) => {
|
|
writeln!(
|
|
f,
|
|
"{:#x} {}",
|
|
process_create_time,
|
|
format_time_t(process_create_time),
|
|
)?;
|
|
}
|
|
None => writeln!(f, "(invalid)")?,
|
|
}
|
|
write_simple_field!(f, process_user_time);
|
|
write_simple_field!(f, process_kernel_time);
|
|
|
|
write_simple_field!(f, processor_max_mhz);
|
|
write_simple_field!(f, processor_current_mhz);
|
|
write_simple_field!(f, processor_mhz_limit);
|
|
write_simple_field!(f, processor_max_idle_state);
|
|
write_simple_field!(f, processor_current_idle_state);
|
|
|
|
write_simple_field!(f, process_integrity_level);
|
|
write_simple_field!(f, process_execute_flags, "{:x}");
|
|
write_simple_field!(f, protected_process);
|
|
write_simple_field!(f, time_zone_id);
|
|
|
|
write!(f, " time_zone = ")?;
|
|
match self.raw.time_zone() {
|
|
Some(time_zone) => {
|
|
writeln!(f)?;
|
|
writeln!(f, " bias = {}", time_zone.bias)?;
|
|
writeln!(
|
|
f,
|
|
" standard_name = {}",
|
|
utf16_to_string(&time_zone.standard_name[..])
|
|
.unwrap_or_else(|| String::from("(invalid)"))
|
|
)?;
|
|
writeln!(
|
|
f,
|
|
" standard_date = {}",
|
|
format_system_time(&time_zone.standard_date)
|
|
)?;
|
|
writeln!(f, " standard_bias = {}", time_zone.standard_bias)?;
|
|
writeln!(
|
|
f,
|
|
" daylight_name = {}",
|
|
utf16_to_string(&time_zone.daylight_name[..])
|
|
.unwrap_or_else(|| String::from("(invalid)"))
|
|
)?;
|
|
writeln!(
|
|
f,
|
|
" daylight_date = {}",
|
|
format_system_time(&time_zone.daylight_date)
|
|
)?;
|
|
writeln!(f, " daylight_bias = {}", time_zone.daylight_bias)?;
|
|
}
|
|
None => writeln!(f, "(invalid)")?,
|
|
}
|
|
|
|
write!(f, " build_string = ")?;
|
|
match self
|
|
.raw
|
|
.build_string()
|
|
.and_then(|string| utf16_to_string(&string[..]))
|
|
{
|
|
Some(build_string) => writeln!(f, "{build_string}")?,
|
|
None => writeln!(f, "(invalid)")?,
|
|
}
|
|
write!(f, " dbg_bld_str = ")?;
|
|
match self
|
|
.raw
|
|
.dbg_bld_str()
|
|
.and_then(|string| utf16_to_string(&string[..]))
|
|
{
|
|
Some(dbg_bld_str) => writeln!(f, "{dbg_bld_str}")?,
|
|
None => writeln!(f, "(invalid)")?,
|
|
}
|
|
|
|
write!(f, " xstate_data = ")?;
|
|
match self.raw.xstate_data() {
|
|
Some(xstate_data) => {
|
|
writeln!(f)?;
|
|
for (i, feature) in xstate_data.iter() {
|
|
if let Some(feature) = md::XstateFeatureIndex::from_index(i) {
|
|
let feature_name = format!("{feature:?}");
|
|
write!(f, " feature {i:2} - {feature_name:22}: ")?;
|
|
} else {
|
|
write!(f, " feature {i:2} - (unknown) : ")?;
|
|
}
|
|
writeln!(f, " offset {:4}, size {:4}", feature.offset, feature.size)?;
|
|
}
|
|
}
|
|
None => writeln!(f, "(invalid)")?,
|
|
}
|
|
|
|
write_simple_field!(f, process_cookie);
|
|
writeln!(f)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl MinidumpStream<'_> for MinidumpBreakpadInfo {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::BreakpadInfoStream as u32;
|
|
|
|
fn read(
|
|
bytes: &[u8],
|
|
_all: &[u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpBreakpadInfo, Error> {
|
|
let raw: md::MINIDUMP_BREAKPAD_INFO = bytes
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
let flags = md::BreakpadInfoValid::from_bits_truncate(raw.validity);
|
|
let dump_thread_id = if flags.contains(md::BreakpadInfoValid::DumpThreadId) {
|
|
Some(raw.dump_thread_id)
|
|
} else {
|
|
None
|
|
};
|
|
let requesting_thread_id = if flags.contains(md::BreakpadInfoValid::RequestingThreadId) {
|
|
Some(raw.requesting_thread_id)
|
|
} else {
|
|
None
|
|
};
|
|
Ok(MinidumpBreakpadInfo {
|
|
raw,
|
|
dump_thread_id,
|
|
requesting_thread_id,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn option_or_invalid<T: fmt::LowerHex>(what: &Option<T>) -> Cow<'_, str> {
|
|
match *what {
|
|
Some(ref val) => Cow::Owned(format!("{val:#x}")),
|
|
None => Cow::Borrowed("(invalid)"),
|
|
}
|
|
}
|
|
|
|
impl MinidumpBreakpadInfo {
|
|
/// Write a human-readable description of this `MinidumpBreakpadInfo` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MINIDUMP_BREAKPAD_INFO
|
|
validity = {:#x}
|
|
dump_thread_id = {}
|
|
requesting_thread_id = {}
|
|
|
|
",
|
|
self.raw.validity,
|
|
option_or_invalid(&self.dump_thread_id),
|
|
option_or_invalid(&self.requesting_thread_id),
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl CrashReason {
|
|
/// Get a `CrashReason` from a `MINIDUMP_EXCEPTION_STREAM` for a given `Os`.
|
|
fn from_exception(raw: &md::MINIDUMP_EXCEPTION_STREAM, os: Os, cpu: Cpu) -> CrashReason {
|
|
let record = &raw.exception_record;
|
|
let exception_code = record.exception_code;
|
|
let exception_flags = record.exception_flags;
|
|
|
|
let reason = match os {
|
|
Os::MacOs | Os::Ios => Self::from_mac_exception(raw, cpu),
|
|
Os::Linux | Os::Android => Self::from_linux_exception(raw, cpu),
|
|
Os::Windows => Self::from_windows_exception(raw, cpu),
|
|
_ => None,
|
|
};
|
|
|
|
// Default to a totally generic unknown error
|
|
reason.unwrap_or(CrashReason::Unknown(exception_code, exception_flags))
|
|
}
|
|
|
|
/// Heuristically identifies what kind of windows exception code this is.
|
|
///
|
|
/// Augments [`CrashReason::from_windows_error`] by also including
|
|
/// `ExceptionCodeWindows`. Appropriate for an actual crash reason.
|
|
pub fn from_windows_code(exception_code: u32) -> CrashReason {
|
|
if let Some(err) = err::ExceptionCodeWindows::from_u32(exception_code) {
|
|
Self::WindowsGeneral(err)
|
|
} else {
|
|
Self::from_windows_error(exception_code)
|
|
}
|
|
}
|
|
|
|
/// Heuristically identifies what kind of windows error code this is.
|
|
///
|
|
/// Appropriate for things like LastErrorValue() which may be non-fatal.
|
|
pub fn from_windows_error(error_code: u32) -> CrashReason {
|
|
if let Some(err) = err::WinErrorWindows::from_u32(error_code) {
|
|
Self::WindowsWinError(err)
|
|
} else if let Some(err) = err::NtStatusWindows::from_u32(error_code) {
|
|
Self::WindowsNtStatus(err)
|
|
} else if let Some(err) = Self::from_windows_error_with_facility(error_code) {
|
|
err
|
|
} else {
|
|
Self::WindowsUnknown(error_code)
|
|
}
|
|
}
|
|
|
|
pub fn from_windows_error_with_facility(error_code: u32) -> Option<CrashReason> {
|
|
static SEVERITY_MASK: u32 = 0xf0000000;
|
|
static FACILITY_MASK: u32 = 0x0fff0000;
|
|
static ERROR_MASK: u32 = 0x0000ffff;
|
|
|
|
if (error_code & SEVERITY_MASK) != 0 {
|
|
// This could be an NTSTATUS or HRESULT code of a specific facility
|
|
if let Some(facility) =
|
|
err::WinErrorFacilityWindows::from_u32((error_code & FACILITY_MASK) >> 16)
|
|
{
|
|
if let Some(error) = err::WinErrorWindows::from_u32(error_code & ERROR_MASK) {
|
|
return Some(Self::WindowsWinErrorWithFacility(facility, error));
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn from_windows_exception(
|
|
raw: &md::MINIDUMP_EXCEPTION_STREAM,
|
|
_cpu: Cpu,
|
|
) -> Option<CrashReason> {
|
|
use err::ExceptionCodeWindows;
|
|
|
|
let record = &raw.exception_record;
|
|
let info = &record.exception_information;
|
|
let exception_code = record.exception_code;
|
|
|
|
let mut reason = CrashReason::from_windows_code(exception_code);
|
|
|
|
// Refine the output for error codes that have more info
|
|
match reason {
|
|
CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION) => {
|
|
// For EXCEPTION_ACCESS_VIOLATION, Windows puts the address that
|
|
// caused the fault in exception_information[1].
|
|
// exception_information[0] is 0 if the violation was caused by
|
|
// an attempt to read data, 1 if it was an attempt to write data,
|
|
// and 8 if this was a data execution violation.
|
|
// This information is useful in addition to the code address, which
|
|
// will be present in the crash thread's instruction field anyway.
|
|
if record.number_parameters >= 1 {
|
|
// NOTE: address := info[1];
|
|
if let Some(ty) = err::ExceptionCodeWindowsAccessType::from_u64(info[0]) {
|
|
reason = CrashReason::WindowsAccessViolation(ty);
|
|
}
|
|
}
|
|
}
|
|
CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR) => {
|
|
// For EXCEPTION_IN_PAGE_ERROR, Windows puts the address that
|
|
// caused the fault in exception_information[1].
|
|
// exception_information[0] is 0 if the violation was caused by
|
|
// an attempt to read data, 1 if it was an attempt to write data,
|
|
// and 8 if this was a data execution violation.
|
|
// exception_information[2] contains the underlying NTSTATUS code,
|
|
// which is the explanation for why this error occured.
|
|
// This information is useful in addition to the code address, which
|
|
// will be present in the crash thread's instruction field anyway.
|
|
if record.number_parameters >= 3 {
|
|
// NOTE: address := info[1];
|
|
// The status code is 32-bits wide, ignore the upper 32 bits
|
|
let nt_status = info[2] & 0xffff_ffff;
|
|
if let Some(ty) = err::ExceptionCodeWindowsInPageErrorType::from_u64(info[0]) {
|
|
reason = CrashReason::WindowsInPageError(ty, nt_status);
|
|
}
|
|
}
|
|
}
|
|
CrashReason::WindowsNtStatus(err::NtStatusWindows::STATUS_STACK_BUFFER_OVERRUN) => {
|
|
// STATUS_STACK_BUFFER_OVERRUN are caused by __fastfail()
|
|
// invocations and the fast-fail code is stored in
|
|
// exception_information[0].
|
|
if record.number_parameters >= 1 {
|
|
// The status code is 32-bits wide, ignore the upper 32 bits
|
|
let fast_fail = info[0] & 0xffff_ffff;
|
|
reason = CrashReason::WindowsStackBufferOverrun(fast_fail);
|
|
}
|
|
}
|
|
_ => {
|
|
// Do nothing interesting
|
|
}
|
|
}
|
|
|
|
Some(reason)
|
|
}
|
|
|
|
pub fn from_mac_exception(
|
|
raw: &md::MINIDUMP_EXCEPTION_STREAM,
|
|
cpu: Cpu,
|
|
) -> Option<CrashReason> {
|
|
use err::ExceptionCodeMac;
|
|
|
|
let record = &raw.exception_record;
|
|
let info = &record.exception_information;
|
|
let exception_code = record.exception_code;
|
|
let exception_flags = record.exception_flags;
|
|
|
|
// Default to just directly reporting this reason.
|
|
let mac_reason = err::ExceptionCodeMac::from_u32(exception_code)?;
|
|
let mut reason = CrashReason::MacGeneral(mac_reason, exception_flags);
|
|
|
|
// Refine the output for error codes that have more info
|
|
match mac_reason {
|
|
ExceptionCodeMac::EXC_BAD_ACCESS => {
|
|
if let Some(ty) = err::ExceptionCodeMacBadAccessKernType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadAccessKern(ty);
|
|
} else {
|
|
match cpu {
|
|
Cpu::Arm64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBadAccessArmType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadAccessArm(ty);
|
|
}
|
|
}
|
|
Cpu::Ppc => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBadAccessPpcType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadAccessPpc(ty);
|
|
}
|
|
}
|
|
Cpu::X86 | Cpu::X86_64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBadAccessX86Type::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadAccessX86(ty);
|
|
}
|
|
}
|
|
_ => {
|
|
// Do nothing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ExceptionCodeMac::EXC_BAD_INSTRUCTION => match cpu {
|
|
Cpu::Arm64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBadInstructionArmType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadInstructionArm(ty);
|
|
}
|
|
}
|
|
Cpu::Ppc => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBadInstructionPpcType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadInstructionPpc(ty);
|
|
}
|
|
}
|
|
Cpu::X86 | Cpu::X86_64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBadInstructionX86Type::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBadInstructionX86(ty);
|
|
}
|
|
}
|
|
_ => {
|
|
// Do nothing
|
|
}
|
|
},
|
|
ExceptionCodeMac::EXC_ARITHMETIC => match cpu {
|
|
Cpu::Arm64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacArithmeticArmType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacArithmeticArm(ty);
|
|
}
|
|
}
|
|
Cpu::Ppc => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacArithmeticPpcType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacArithmeticPpc(ty);
|
|
}
|
|
}
|
|
Cpu::X86 | Cpu::X86_64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacArithmeticX86Type::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacArithmeticX86(ty);
|
|
}
|
|
}
|
|
_ => {
|
|
// Do nothing
|
|
}
|
|
},
|
|
ExceptionCodeMac::EXC_SOFTWARE => {
|
|
if let Some(ty) = err::ExceptionCodeMacSoftwareType::from_u32(exception_flags) {
|
|
reason = CrashReason::MacSoftware(ty);
|
|
}
|
|
}
|
|
ExceptionCodeMac::EXC_BREAKPOINT => match cpu {
|
|
Cpu::Arm64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBreakpointArmType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBreakpointArm(ty);
|
|
}
|
|
}
|
|
Cpu::Ppc => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBreakpointPpcType::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBreakpointPpc(ty);
|
|
}
|
|
}
|
|
Cpu::X86 | Cpu::X86_64 => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacBreakpointX86Type::from_u32(exception_flags)
|
|
{
|
|
reason = CrashReason::MacBreakpointX86(ty);
|
|
}
|
|
}
|
|
_ => {
|
|
// Do nothing
|
|
}
|
|
},
|
|
ExceptionCodeMac::EXC_RESOURCE => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacResourceType::from_u32((exception_flags >> 29) & 0x7)
|
|
{
|
|
reason = CrashReason::MacResource(ty, info[1], info[2]);
|
|
}
|
|
}
|
|
ExceptionCodeMac::EXC_GUARD => {
|
|
if let Some(ty) =
|
|
err::ExceptionCodeMacGuardType::from_u32((exception_flags >> 29) & 0x7)
|
|
{
|
|
reason = CrashReason::MacGuard(ty, info[1], info[2]);
|
|
}
|
|
}
|
|
_ => {
|
|
// Do nothing
|
|
}
|
|
}
|
|
Some(reason)
|
|
}
|
|
|
|
pub fn from_linux_exception(
|
|
raw: &md::MINIDUMP_EXCEPTION_STREAM,
|
|
_cpu: Cpu,
|
|
) -> Option<CrashReason> {
|
|
let record = &raw.exception_record;
|
|
let exception_code = record.exception_code;
|
|
let exception_flags = record.exception_flags;
|
|
|
|
let linux_reason = err::ExceptionCodeLinux::from_u32(exception_code)?;
|
|
let mut reason = CrashReason::LinuxGeneral(linux_reason, exception_flags);
|
|
// Refine the output for error codes that have more info
|
|
match linux_reason {
|
|
err::ExceptionCodeLinux::SIGILL => {
|
|
if let Some(ty) = err::ExceptionCodeLinuxSigillKind::from_u32(exception_flags) {
|
|
reason = CrashReason::LinuxSigill(ty);
|
|
}
|
|
}
|
|
err::ExceptionCodeLinux::SIGTRAP => {
|
|
if let Some(ty) = err::ExceptionCodeLinuxSigtrapKind::from_u32(exception_flags) {
|
|
reason = CrashReason::LinuxSigtrap(ty);
|
|
}
|
|
}
|
|
err::ExceptionCodeLinux::SIGFPE => {
|
|
if let Some(ty) = err::ExceptionCodeLinuxSigfpeKind::from_u32(exception_flags) {
|
|
reason = CrashReason::LinuxSigfpe(ty);
|
|
}
|
|
}
|
|
err::ExceptionCodeLinux::SIGSEGV => {
|
|
if let Some(ty) = err::ExceptionCodeLinuxSigsegvKind::from_u32(exception_flags) {
|
|
reason = CrashReason::LinuxSigsegv(ty);
|
|
}
|
|
}
|
|
err::ExceptionCodeLinux::SIGBUS => {
|
|
if let Some(ty) = err::ExceptionCodeLinuxSigbusKind::from_u32(exception_flags) {
|
|
reason = CrashReason::LinuxSigbus(ty);
|
|
}
|
|
}
|
|
err::ExceptionCodeLinux::SIGSYS => {
|
|
if let Some(ty) = err::ExceptionCodeLinuxSigsysKind::from_u32(exception_flags) {
|
|
reason = CrashReason::LinuxSigsys(ty);
|
|
}
|
|
}
|
|
_ => {
|
|
// No refinements
|
|
}
|
|
}
|
|
Some(reason)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for CrashReason {
|
|
/// A string describing the crash reason.
|
|
///
|
|
/// This is OS- and possibly CPU-specific.
|
|
/// For example, "EXCEPTION_ACCESS_VIOLATION" (Windows),
|
|
/// "EXC_BAD_ACCESS / KERN_INVALID_ADDRESS" (Mac OS X), "SIGSEGV"
|
|
/// (other Unix).
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
use CrashReason::*;
|
|
|
|
fn write_nt_status(f: &mut fmt::Formatter<'_>, raw_nt_status: u64) -> fmt::Result {
|
|
let nt_status = err::NtStatusWindows::from_u64(raw_nt_status);
|
|
if let Some(nt_status) = nt_status {
|
|
write!(f, "{nt_status:?}")
|
|
} else {
|
|
write!(f, "{raw_nt_status:#010x}")
|
|
}
|
|
}
|
|
|
|
fn write_fast_fail(f: &mut fmt::Formatter<'_>, raw_fast_fail: u64) -> fmt::Result {
|
|
let fast_fail = err::FastFailCode::from_u64(raw_fast_fail);
|
|
if let Some(fast_fail) = fast_fail {
|
|
write!(f, "{fast_fail:?}")
|
|
} else {
|
|
write!(f, "{raw_fast_fail:#010x}")
|
|
}
|
|
}
|
|
|
|
fn write_exc_resource(
|
|
f: &mut fmt::Formatter<'_>,
|
|
ex: err::ExceptionCodeMacResourceType,
|
|
code: u64,
|
|
subcode: u64,
|
|
) -> fmt::Result {
|
|
let flavor = (code >> 58) & 0x7;
|
|
write!(f, "EXC_RESOURCE / {ex:?} / ")?;
|
|
match ex {
|
|
err::ExceptionCodeMacResourceType::RESOURCE_TYPE_CPU => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L71-L99
|
|
if let Some(cpu_flavor) =
|
|
err::ExceptionCodeMacResourceCpuFlavor::from_u64(flavor)
|
|
{
|
|
let interval = (code >> 7) & 0x1ffffff;
|
|
let cpu_limit = code & 0x7;
|
|
let cpu_consumed = subcode & 0x7;
|
|
write!(
|
|
f,
|
|
"{cpu_flavor:?} interval: {interval}s CPU limit: {cpu_limit}% CPU consumed: {cpu_consumed}%"
|
|
)
|
|
} else {
|
|
write!(f, "{code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacResourceType::RESOURCE_TYPE_WAKEUPS => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L105-L134
|
|
if let Some(wakeups_flavor) =
|
|
err::ExceptionCodeMacResourceWakeupsFlavor::from_u64(flavor)
|
|
{
|
|
let interval = (code >> 20) & 0xfffff;
|
|
let wakeups_permitted = code & 0xfff;
|
|
let wakeups_observed = subcode & 0xfff;
|
|
write!(
|
|
f,
|
|
"{wakeups_flavor:?} interval: {interval}s wakeups permitted: {wakeups_permitted} wakeups observed: {wakeups_observed}"
|
|
)
|
|
} else {
|
|
write!(f, "{code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacResourceType::RESOURCE_TYPE_MEMORY => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L139-L162
|
|
if let Some(memory_flavor) =
|
|
err::ExceptionCodeMacResourceMemoryFlavor::from_u64(flavor)
|
|
{
|
|
let hwm_limit = code & 0x1fff;
|
|
write!(f, "{memory_flavor:?} high watermark limit: {hwm_limit}MiB")
|
|
} else {
|
|
write!(f, "{code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacResourceType::RESOURCE_TYPE_IO => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L168-L196
|
|
if let Some(io_flavor) = err::ExceptionCodeMacResourceIOFlavor::from_u64(flavor)
|
|
{
|
|
let interval = (code >> 15) & 0x1ffff;
|
|
let io_limit = code & 0x7fff;
|
|
let io_observed = subcode & 0x7fff;
|
|
write!(
|
|
f,
|
|
"{io_flavor:?} interval: {interval}s I/O limit: {io_limit}% I/O observed: {io_observed}%"
|
|
)
|
|
} else {
|
|
write!(f, "{code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacResourceType::RESOURCE_TYPE_THREADS => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_resource.h#L199-L207
|
|
if let Some(threads_flavor) =
|
|
err::ExceptionCodeMacResourceThreadsFlavor::from_u64(flavor)
|
|
{
|
|
let hwm_limit = code & 0x7fff;
|
|
write!(f, "{threads_flavor:?} high watermark limit: {hwm_limit}")
|
|
} else {
|
|
write!(f, "{code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_exc_guard(
|
|
f: &mut fmt::Formatter<'_>,
|
|
ex: err::ExceptionCodeMacGuardType,
|
|
code: u64,
|
|
subcode: u64,
|
|
) -> fmt::Result {
|
|
let flavor = (code >> 32) & 0x1fffffff;
|
|
write!(f, "EXC_GUARD / {ex:?}")?;
|
|
match ex {
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_NONE => {
|
|
write!(f, "")
|
|
}
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_MACH_PORT => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L69-L81
|
|
if let Some(mach_port_flavor) =
|
|
err::ExceptionCodeMacGuardMachPortFlavor::from_u64(flavor)
|
|
{
|
|
// FIXME: GUARD_EXC_STRICT_REPLY, GUARD_EXC_MOD_REFS and GUARD_EXC_IMMOVABLE have additional flags defined here:
|
|
// https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/mach/port.h#L518-L538
|
|
// They also encode additional data in the subcode (a mach reply type or a kernel return value), one has to
|
|
// check Apple's code to figure out each one. Since we haven't encountered them yet in the wild there's no
|
|
// hurry to decode those.
|
|
let port_name = code & 0xfffffff;
|
|
if subcode != 0 {
|
|
write!(
|
|
f,
|
|
" / {mach_port_flavor:?} port name: {port_name} subcode: {subcode}",
|
|
)
|
|
} else {
|
|
write!(f, " / {mach_port_flavor:?} port name: {port_name}",)
|
|
}
|
|
} else {
|
|
write!(f, " / {code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_FD => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L85-L97
|
|
if let Some(fd_flavor) = err::ExceptionCodeMacGuardFDFlavor::from_u64(flavor) {
|
|
let fd = code & 0xfffffff;
|
|
write!(
|
|
f,
|
|
" / {fd_flavor:?} file descriptor: {fd} guard identifier: {subcode}",
|
|
)
|
|
} else {
|
|
write!(f, " / {code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_USER => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L101-L113
|
|
let namespace = code & 0xffffffff;
|
|
write!(f, "/ namespace: {namespace} guard identifier: {subcode}",)
|
|
}
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_VN => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L117-L129
|
|
if let Some(vn_flavor) = err::ExceptionCodeMacGuardVNFlavor::from_u64(flavor) {
|
|
let pid = code & 0xfffffff;
|
|
write!(f, " / {vn_flavor:?} pid: {pid} guard identifier: {subcode}",)
|
|
} else {
|
|
write!(f, " / {code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_VIRT_MEMORY => {
|
|
// See https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/kern/exc_guard.h#L133-L145
|
|
if let Some(virt_memory_flavor) =
|
|
err::ExceptionCodeMacGuardVirtMemoryFlavor::from_u64(flavor)
|
|
{
|
|
write!(f, " / {virt_memory_flavor:?} offset: {subcode}")
|
|
} else {
|
|
write!(f, " / {code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
err::ExceptionCodeMacGuardType::GUARD_TYPE_REJECTED_SC => {
|
|
// See https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/kern/exc_guard.h#L149-L163
|
|
if let Some(rejected_sc_flavor) =
|
|
err::ExceptionCodeMacGuardRejecteSysCallFlavor::from_u64(flavor)
|
|
{
|
|
let syscall = subcode;
|
|
write!(f, " / {rejected_sc_flavor:?} syscall: {syscall}",)
|
|
} else {
|
|
write!(f, " / {code:#018x} / {subcode:#018x}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_signal(
|
|
f: &mut fmt::Formatter<'_>,
|
|
ex: err::ExceptionCodeLinux,
|
|
flags: u32,
|
|
) -> fmt::Result {
|
|
if let Some(si_code) = err::ExceptionCodeLinuxSicode::from_i32(flags as i32) {
|
|
if si_code == err::ExceptionCodeLinuxSicode::SI_USER {
|
|
write!(f, "{ex:?}")
|
|
} else {
|
|
write!(f, "{ex:?} / {si_code:?}")
|
|
}
|
|
} else {
|
|
write!(f, "{ex:?} / {flags:#010x}")
|
|
}
|
|
}
|
|
|
|
// OK this is kinda a gross hack but I *really* don't want
|
|
// to write out all these strings again, so let's just lean on Debug
|
|
// repeating the name of the enum variant!
|
|
match *self {
|
|
// ======================== Mac/iOS ============================
|
|
|
|
// These codes get special messages
|
|
MacGeneral(err::ExceptionCodeMac::SIMULATED, _) => write!(f, "Simulated Exception"),
|
|
|
|
// Thse codes just repeat their names
|
|
MacGeneral(ex, flags) => write!(f, "{ex:?} / {flags:#010x}"),
|
|
MacBadAccessKern(ex) => write!(f, "EXC_BAD_ACCESS / {ex:?}"),
|
|
MacBadAccessArm(ex) => write!(f, "EXC_BAD_ACCESS / {ex:?}"),
|
|
MacBadAccessPpc(ex) => write!(f, "EXC_BAD_ACCESS / {ex:?}"),
|
|
MacBadAccessX86(ex) => write!(f, "EXC_BAD_ACCESS / {ex:?}"),
|
|
MacBadInstructionArm(ex) => write!(f, "EXC_BAD_INSTRUCTION / {ex:?}"),
|
|
MacBadInstructionPpc(ex) => write!(f, "EXC_BAD_INSTRUCTION / {ex:?}"),
|
|
MacBadInstructionX86(ex) => write!(f, "EXC_BAD_INSTRUCTION / {ex:?}"),
|
|
MacArithmeticArm(ex) => write!(f, "EXC_ARITHMETIC / {ex:?}"),
|
|
MacArithmeticPpc(ex) => write!(f, "EXC_ARITHMETIC / {ex:?}"),
|
|
MacArithmeticX86(ex) => write!(f, "EXC_ARITHMETIC / {ex:?}"),
|
|
MacSoftware(ex) => write!(f, "EXC_SOFTWARE / {ex:?}"),
|
|
MacBreakpointArm(ex) => write!(f, "EXC_BREAKPOINT / {ex:?}"),
|
|
MacBreakpointPpc(ex) => write!(f, "EXC_BREAKPOINT / {ex:?}"),
|
|
MacBreakpointX86(ex) => write!(f, "EXC_BREAKPOINT / {ex:?}"),
|
|
MacResource(ex, code, subcode) => write_exc_resource(f, ex, code, subcode),
|
|
MacGuard(ex, code, subcode) => write_exc_guard(f, ex, code, subcode),
|
|
|
|
// ===================== Linux/Android =========================
|
|
|
|
// These codes just repeat their names
|
|
LinuxGeneral(ex, flags) => write_signal(f, ex, flags),
|
|
LinuxSigill(ex) => write!(f, "SIGILL / {ex:?}"),
|
|
LinuxSigtrap(ex) => write!(f, "SIGTRAP / {ex:?}"),
|
|
LinuxSigbus(ex) => write!(f, "SIGBUS / {ex:?}"),
|
|
LinuxSigfpe(ex) => write!(f, "SIGFPE / {ex:?}"),
|
|
LinuxSigsegv(ex) => write!(f, "SIGSEGV / {ex:?}"),
|
|
LinuxSigsys(ex) => write!(f, "SIGSYS / {ex:?}"),
|
|
|
|
// ======================== Windows =============================
|
|
|
|
// These codes get special messages
|
|
WindowsGeneral(err::ExceptionCodeWindows::OUT_OF_MEMORY) => write!(f, "Out of Memory"),
|
|
WindowsGeneral(err::ExceptionCodeWindows::UNHANDLED_CPP_EXCEPTION) => {
|
|
write!(f, "Unhandled C++ Exception")
|
|
}
|
|
WindowsGeneral(err::ExceptionCodeWindows::SIMULATED) => {
|
|
write!(f, "Simulated Exception")
|
|
}
|
|
// These codes just repeat their names
|
|
WindowsGeneral(ex) => write!(f, "{ex:?}"),
|
|
WindowsWinError(winerror) => write!(f, "{winerror:?}"),
|
|
WindowsWinErrorWithFacility(facility, winerror) => {
|
|
write!(f, "{facility:?} / {winerror:?}")
|
|
}
|
|
WindowsNtStatus(nt_status) => write_nt_status(f, nt_status as _),
|
|
WindowsAccessViolation(ex) => write!(f, "EXCEPTION_ACCESS_VIOLATION_{ex:?}"),
|
|
WindowsInPageError(ex, nt_status) => {
|
|
write!(f, "EXCEPTION_IN_PAGE_ERROR_{ex:?} / ")?;
|
|
write_nt_status(f, nt_status)
|
|
}
|
|
WindowsStackBufferOverrun(fast_fail) => {
|
|
write!(f, "EXCEPTION_STACK_BUFFER_OVERRUN / ")?;
|
|
write_fast_fail(f, fast_fail)
|
|
}
|
|
WindowsUnknown(code) => write!(f, "unknown {code:#010x}"),
|
|
|
|
Unknown(code, flags) => write!(f, "unknown {code:#010x} / {flags:#010x}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpException<'a> {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ExceptionStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<Self, Error> {
|
|
let raw: md::MINIDUMP_EXCEPTION_STREAM = bytes
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
let context = location_slice(all, &raw.thread_context).ok();
|
|
let thread_id = raw.thread_id;
|
|
Ok(MinidumpException {
|
|
raw,
|
|
thread_id,
|
|
context,
|
|
endian,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpException<'a> {
|
|
/// Get the cpu context of the crashing (or otherwise minidump-requesting) thread.
|
|
///
|
|
/// CPU contexts are a platform-specific format, so SystemInfo is required
|
|
/// to reliably parse them. We used to use heuristics to avoid this requirement,
|
|
/// but this made us too brittle to otherwise-backwards-compatible additions
|
|
/// to the format.
|
|
///
|
|
/// MiscInfo can contain additional details on the cpu context's format, but
|
|
/// is optional because those details can be safely ignored (at the cost of
|
|
/// being unable to parse some very obscure cpu state).
|
|
pub fn context(
|
|
&self,
|
|
system_info: &MinidumpSystemInfo,
|
|
misc: Option<&MinidumpMiscInfo>,
|
|
) -> Option<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).
|
|
pub fn get_crash_address(&self, os: Os, cpu: Cpu) -> u64 {
|
|
let addr = match (
|
|
os,
|
|
err::ExceptionCodeWindows::from_u32(self.raw.exception_record.exception_code),
|
|
) {
|
|
(Os::Windows, Some(err::ExceptionCodeWindows::EXCEPTION_ACCESS_VIOLATION))
|
|
| (Os::Windows, Some(err::ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR))
|
|
if self.raw.exception_record.number_parameters >= 2 =>
|
|
{
|
|
self.raw.exception_record.exception_information[1]
|
|
}
|
|
_ => self.raw.exception_record.exception_address,
|
|
};
|
|
|
|
// Sometimes on 32-bit these values can be incorrectly sign-extended,
|
|
// so mask and zero-extend them here.
|
|
match cpu.pointer_width() {
|
|
PointerWidth::Bits32 => addr as u32 as u64,
|
|
_ => addr,
|
|
}
|
|
}
|
|
|
|
/// Get the crash reason for an exception.
|
|
///
|
|
/// The returned value reflects our best attempt to recover a
|
|
/// "native" error for the crashing system based on the OS and
|
|
/// things like raw error codes.
|
|
///
|
|
/// This is an imperfect process, because OSes may have overlapping
|
|
/// error types (e.g. WinError and NTSTATUS overlap, so we have to
|
|
/// pick one arbirarily).
|
|
///
|
|
/// The raw error codes can be extracted from [MinidumpException::raw][].
|
|
pub fn get_crash_reason(&self, os: Os, cpu: Cpu) -> CrashReason {
|
|
CrashReason::from_exception(&self.raw, os, cpu)
|
|
}
|
|
|
|
/// The id of the thread that caused the crash (or otherwise requested
|
|
/// the minidump, even if there wasn't actually a crash).
|
|
pub fn get_crashing_thread_id(&self) -> u32 {
|
|
self.thread_id
|
|
}
|
|
|
|
/// Write a human-readable description of this `MinidumpException` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<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 in 0..self.raw.exception_record.number_parameters as usize {
|
|
writeln!(
|
|
f,
|
|
" exception_record.exception_information[{:2}] = {:#x}",
|
|
i, self.raw.exception_record.exception_information[i]
|
|
)?;
|
|
}
|
|
write!(
|
|
f,
|
|
" thread_context.data_size = {}
|
|
thread_context.rva = {:#x}
|
|
",
|
|
self.raw.thread_context.data_size, self.raw.thread_context.rva
|
|
)?;
|
|
if let Some(system_info) = system {
|
|
if let Some(context) = self.context(system_info, misc) {
|
|
writeln!(f)?;
|
|
context.print(f)?;
|
|
} else {
|
|
write!(
|
|
f,
|
|
" (no context)
|
|
|
|
"
|
|
)?;
|
|
}
|
|
} else {
|
|
write!(
|
|
f,
|
|
" (no context)
|
|
|
|
"
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpAssertion {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::AssertionInfoStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
_all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<MinidumpAssertion, Error> {
|
|
let raw: md::MINIDUMP_ASSERTION_INFO = bytes
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
Ok(MinidumpAssertion { raw })
|
|
}
|
|
}
|
|
|
|
fn utf16_to_string(data: &[u16]) -> Option<String> {
|
|
use std::slice;
|
|
|
|
let len = data.iter().take_while(|c| **c != 0).count();
|
|
let s16 = &data[..len];
|
|
let bytes = unsafe { slice::from_raw_parts(s16.as_ptr() as *const u8, s16.len() * 2) };
|
|
encoding_rs::UTF_16LE
|
|
.decode_without_bom_handling_and_without_replacement(bytes)
|
|
.map(String::from)
|
|
}
|
|
|
|
impl MinidumpAssertion {
|
|
/// Get the assertion expression as a `String` if one exists.
|
|
pub fn expression(&self) -> Option<String> {
|
|
utf16_to_string(&self.raw.expression)
|
|
}
|
|
/// Get the function name where the assertion happened as a `String` if it exists.
|
|
pub fn function(&self) -> Option<String> {
|
|
utf16_to_string(&self.raw.function)
|
|
}
|
|
/// Get the source file name where the assertion happened as a `String` if it exists.
|
|
pub fn file(&self) -> Option<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`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MDAssertion
|
|
expression = {}
|
|
function = {}
|
|
file = {}
|
|
line = {}
|
|
type = {}
|
|
|
|
",
|
|
self.expression().unwrap_or_default(),
|
|
self.function().unwrap_or_default(),
|
|
self.file().unwrap_or_default(),
|
|
self.raw.line,
|
|
self.raw._type,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn read_string_list(
|
|
all: &[u8],
|
|
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
endian: scroll::Endian,
|
|
) -> Result<Vec<String>, Error> {
|
|
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
|
|
if data.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let mut offset = 0;
|
|
|
|
let count: u32 = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let (count, _) = ensure_count_in_bound(all, count as usize, <md::RVA>::size_with(&endian), 0)?;
|
|
|
|
let mut strings = Vec::with_capacity(count);
|
|
for _ in 0..count {
|
|
let rva: md::RVA = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let string = read_string_utf8(&mut (rva as usize), all, endian)
|
|
.ok_or(Error::StreamReadFailure)?
|
|
.to_owned();
|
|
|
|
strings.push(string);
|
|
}
|
|
|
|
Ok(strings)
|
|
}
|
|
|
|
fn read_simple_string_dictionary(
|
|
all: &[u8],
|
|
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
endian: scroll::Endian,
|
|
) -> Result<BTreeMap<String, String>, Error> {
|
|
let mut dictionary = BTreeMap::new();
|
|
|
|
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
|
|
if data.is_empty() {
|
|
return Ok(dictionary);
|
|
}
|
|
|
|
let mut offset = 0;
|
|
|
|
let count: u32 = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
for _ in 0..count {
|
|
let entry: md::MINIDUMP_SIMPLE_STRING_DICTIONARY_ENTRY = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let key = read_string_utf8(&mut (entry.key as usize), all, endian)
|
|
.ok_or(Error::StreamReadFailure)?;
|
|
let value = read_string_utf8(&mut (entry.value as usize), all, endian)
|
|
.ok_or(Error::StreamReadFailure)?;
|
|
|
|
dictionary.insert(key.to_owned(), value.to_owned());
|
|
}
|
|
|
|
Ok(dictionary)
|
|
}
|
|
|
|
fn read_annotation_objects(
|
|
all: &[u8],
|
|
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
endian: scroll::Endian,
|
|
) -> Result<BTreeMap<String, MinidumpAnnotation>, Error> {
|
|
let mut dictionary = BTreeMap::new();
|
|
|
|
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
|
|
if data.is_empty() {
|
|
return Ok(dictionary);
|
|
}
|
|
|
|
let mut offset = 0;
|
|
|
|
let count: u32 = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
for _ in 0..count {
|
|
let raw: md::MINIDUMP_ANNOTATION = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let key = read_string_utf8(&mut (raw.name as usize), all, endian)
|
|
.ok_or(Error::StreamReadFailure)?;
|
|
|
|
let value = match raw.ty {
|
|
md::MINIDUMP_ANNOTATION::TYPE_INVALID => MinidumpAnnotation::Invalid,
|
|
md::MINIDUMP_ANNOTATION::TYPE_STRING => {
|
|
let string = read_string_utf8_unterminated(&mut (raw.value as usize), all, endian)
|
|
.ok_or(Error::StreamReadFailure)?
|
|
.to_owned();
|
|
|
|
MinidumpAnnotation::String(string)
|
|
}
|
|
_ if raw.ty >= md::MINIDUMP_ANNOTATION::TYPE_USER_DEFINED => {
|
|
MinidumpAnnotation::UserDefined(raw)
|
|
}
|
|
_ => MinidumpAnnotation::Unsupported(raw),
|
|
};
|
|
|
|
dictionary.insert(key.to_owned(), value);
|
|
}
|
|
|
|
Ok(dictionary)
|
|
}
|
|
|
|
impl MinidumpModuleCrashpadInfo {
|
|
pub fn read(
|
|
link: md::MINIDUMP_MODULE_CRASHPAD_INFO_LINK,
|
|
all: &[u8],
|
|
endian: scroll::Endian,
|
|
) -> Result<Self, Error> {
|
|
let raw: md::MINIDUMP_MODULE_CRASHPAD_INFO = all
|
|
.pread_with(link.location.rva as usize, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let list_annotations = read_string_list(all, &raw.list_annotations, endian)?;
|
|
let simple_annotations =
|
|
read_simple_string_dictionary(all, &raw.simple_annotations, endian)?;
|
|
let annotation_objects = read_annotation_objects(all, &raw.annotation_objects, endian)?;
|
|
|
|
Ok(Self {
|
|
raw,
|
|
module_index: link.minidump_module_list_index as usize,
|
|
list_annotations,
|
|
simple_annotations,
|
|
annotation_objects,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn read_crashpad_module_links(
|
|
all: &[u8],
|
|
location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
endian: scroll::Endian,
|
|
) -> Result<Vec<MinidumpModuleCrashpadInfo>, Error> {
|
|
let data = location_slice(all, location).or(Err(Error::StreamReadFailure))?;
|
|
if data.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let mut offset = 0;
|
|
|
|
let count: u32 = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let (count, _) = ensure_count_in_bound(
|
|
all,
|
|
count as usize,
|
|
<md::MINIDUMP_MODULE_CRASHPAD_INFO_LINK>::size_with(&endian),
|
|
0,
|
|
)?;
|
|
|
|
let mut module_links = Vec::with_capacity(count);
|
|
for _ in 0..count {
|
|
let link: md::MINIDUMP_MODULE_CRASHPAD_INFO_LINK = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
let info = MinidumpModuleCrashpadInfo::read(link, all, endian)?;
|
|
module_links.push(info);
|
|
}
|
|
|
|
Ok(module_links)
|
|
}
|
|
|
|
impl<'a> MinidumpStream<'a> for MinidumpCrashpadInfo {
|
|
const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::CrashpadInfoStream as u32;
|
|
|
|
fn read(
|
|
bytes: &'a [u8],
|
|
all: &'a [u8],
|
|
endian: scroll::Endian,
|
|
_system_info: Option<&MinidumpSystemInfo>,
|
|
) -> Result<Self, Error> {
|
|
let raw: md::MINIDUMP_CRASHPAD_INFO = bytes
|
|
.pread_with(0, endian)
|
|
.or(Err(Error::StreamReadFailure))?;
|
|
|
|
if raw.version == 0 {
|
|
// 0 is an invalid version, but all future versions are compatible with v1.
|
|
return Err(Error::VersionMismatch);
|
|
}
|
|
|
|
let simple_annotations =
|
|
read_simple_string_dictionary(all, &raw.simple_annotations, endian)?;
|
|
|
|
let module_list = read_crashpad_module_links(all, &raw.module_list, endian)?;
|
|
|
|
Ok(Self {
|
|
raw,
|
|
simple_annotations,
|
|
module_list,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl MinidumpCrashpadInfo {
|
|
/// Write a human-readable description of this `MinidumpCrashpadInfo` to `f`.
|
|
///
|
|
/// This is very verbose, it is the format used by `minidump_dump`.
|
|
pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
|
|
write!(
|
|
f,
|
|
"MDRawCrashpadInfo
|
|
version = {}
|
|
report_id = {}
|
|
client_id = {}
|
|
",
|
|
self.raw.version, self.raw.report_id, self.raw.client_id,
|
|
)?;
|
|
|
|
for (name, value) in &self.simple_annotations {
|
|
writeln!(f, " simple_annotations[\"{name}\"] = {value}")?;
|
|
}
|
|
|
|
for (index, module) in self.module_list.iter().enumerate() {
|
|
writeln!(
|
|
f,
|
|
" module_list[{}].minidump_module_list_index = {}",
|
|
index, module.module_index,
|
|
)?;
|
|
writeln!(
|
|
f,
|
|
" module_list[{}].version = {}",
|
|
index, module.raw.version,
|
|
)?;
|
|
|
|
for (annotation_index, annotation) in module.list_annotations.iter().enumerate() {
|
|
writeln!(
|
|
f,
|
|
" module_list[{index}].list_annotations[{annotation_index}] = {annotation}",
|
|
)?;
|
|
}
|
|
|
|
for (name, value) in &module.simple_annotations {
|
|
writeln!(
|
|
f,
|
|
" module_list[{index}].simple_annotations[\"{name}\"] = {value}",
|
|
)?;
|
|
}
|
|
|
|
for (name, value) in &module.annotation_objects {
|
|
write!(
|
|
f,
|
|
" module_list[{index}].annotation_objects[\"{name}\"] = ",
|
|
)?;
|
|
|
|
match value {
|
|
MinidumpAnnotation::Invalid => writeln!(f, "<invalid>"),
|
|
MinidumpAnnotation::String(string) => writeln!(f, "{string}"),
|
|
MinidumpAnnotation::UserDefined(_) => writeln!(f, "<user defined>"),
|
|
MinidumpAnnotation::Unsupported(_) => writeln!(f, "<unsupported>"),
|
|
}?;
|
|
}
|
|
}
|
|
|
|
writeln!(f)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// An index into the contents of a memory-mapped minidump.
|
|
pub type MmapMinidump = Minidump<'static, Mmap>;
|
|
|
|
impl MmapMinidump {
|
|
/// Read a `Minidump` from a `Path` to a file on disk.
|
|
///
|
|
/// See [the type definition](Minidump.html) for an example.
|
|
pub fn read_path<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)]
|
|
pub struct MinidumpImplementedStream {
|
|
pub stream_type: MINIDUMP_STREAM_TYPE,
|
|
pub location: md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
pub vendor: &'static str,
|
|
}
|
|
|
|
/// A stream in the minidump that this implementation has no knowledge of.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MinidumpUnknownStream {
|
|
pub stream_type: u32,
|
|
pub location: md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
pub vendor: &'static str,
|
|
}
|
|
|
|
/// A stream in the minidump that this implementation is aware of but doesn't
|
|
/// yet support.
|
|
#[derive(Debug, Clone)]
|
|
pub struct MinidumpUnimplementedStream {
|
|
pub stream_type: MINIDUMP_STREAM_TYPE,
|
|
pub location: md::MINIDUMP_LOCATION_DESCRIPTOR,
|
|
pub vendor: &'static str,
|
|
}
|
|
|
|
impl<'a, T> Minidump<'a, T>
|
|
where
|
|
T: Deref<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`.
|
|
pub fn read(data: T) -> Result<Minidump<'a, T>, Error> {
|
|
let mut offset = 0;
|
|
let mut endian = LE;
|
|
let mut header: md::MINIDUMP_HEADER = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::MissingHeader))?;
|
|
if header.signature != md::MINIDUMP_SIGNATURE {
|
|
if header.signature.swap_bytes() != md::MINIDUMP_SIGNATURE {
|
|
return Err(Error::HeaderMismatch);
|
|
}
|
|
// Try again with big-endian.
|
|
endian = BE;
|
|
offset = 0;
|
|
header = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::MissingHeader))?;
|
|
if header.signature != md::MINIDUMP_SIGNATURE {
|
|
return Err(Error::HeaderMismatch);
|
|
}
|
|
}
|
|
if (header.version & 0x0000ffff) != md::MINIDUMP_VERSION {
|
|
return Err(Error::VersionMismatch);
|
|
}
|
|
|
|
offset = header.stream_directory_rva as usize;
|
|
|
|
let mut streams = BTreeMap::new();
|
|
for i in 0..header.stream_count {
|
|
let dir: md::MINIDUMP_DIRECTORY = data
|
|
.gread_with(&mut offset, endian)
|
|
.or(Err(Error::MissingDirectory))?;
|
|
if let Some((old_idx, old_dir)) = streams.insert(dir.stream_type, (i, dir.clone())) {
|
|
if let Some(known_stream_type) = MINIDUMP_STREAM_TYPE::from_u32(dir.stream_type) {
|
|
if !(known_stream_type == MINIDUMP_STREAM_TYPE::UnusedStream
|
|
&& old_dir.location.data_size == 0
|
|
&& dir.location.data_size == 0)
|
|
{
|
|
warn!("Minidump contains multiple streams of type {} ({:?}) at indices {} ({} bytes) and {} ({} bytes) (using {})",
|
|
dir.stream_type,
|
|
known_stream_type,
|
|
old_idx,
|
|
old_dir.location.data_size,
|
|
i,
|
|
dir.location.data_size,
|
|
i,
|
|
);
|
|
}
|
|
} else {
|
|
warn!("Minidump contains multiple streams of unknown type {} at indices {} ({} bytes) and {} ({} bytes) (using {})",
|
|
dir.stream_type,
|
|
old_idx,
|
|
old_dir.location.data_size,
|
|
i,
|
|
dir.location.data_size,
|
|
i,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
let system_info = streams
|
|
.get(&MinidumpSystemInfo::STREAM_TYPE)
|
|
.and_then(|(_, dir)| {
|
|
location_slice(data.deref(), &dir.location)
|
|
.ok()
|
|
.and_then(|bytes| {
|
|
let all_bytes = data.deref();
|
|
MinidumpSystemInfo::read(bytes, all_bytes, endian, None).ok()
|
|
})
|
|
});
|
|
|
|
Ok(Minidump {
|
|
data,
|
|
header,
|
|
streams,
|
|
endian,
|
|
system_info,
|
|
_phantom: PhantomData,
|
|
})
|
|
}
|
|
|
|
/// Read and parse the specified [`MinidumpStream`][] `S` from the Minidump, if it exists.
|
|
///
|
|
/// Because Minidump Streams can have totally different formats and meanings, the only
|
|
/// way to coherently access one is by specifying a static type that provides an
|
|
/// interpretation and interface of that format.
|
|
///
|
|
/// As such, typical usage of this interface is to just statically request every
|
|
/// stream your care about. Depending on what analysis you're trying to perform, you may:
|
|
///
|
|
/// * Consider it an error for a stream to be missing (using `?` or `unwrap`)
|
|
/// * Branch on the presence of stream to conditionally refine your analysis
|
|
/// * Use a stream's `Default` implementation to make progress (with `unwrap_or_default`)
|
|
///
|
|
/// ```
|
|
/// use minidump::*;
|
|
///
|
|
/// fn main() -> Result<(), Error> {
|
|
/// // Read the minidump from a file
|
|
/// let mut dump = minidump::Minidump::read_path("../testdata/test.dmp")?;
|
|
///
|
|
/// // Statically request (and require) several streams we care about:
|
|
/// let system_info = dump.get_stream::<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`][]
|
|
///
|
|
pub fn get_stream<S>(&'a self) -> Result<S, Error>
|
|
where
|
|
S: MinidumpStream<'a>,
|
|
{
|
|
match self.get_raw_stream(S::STREAM_TYPE) {
|
|
Err(e) => Err(e),
|
|
Ok(bytes) => {
|
|
let all_bytes = self.data.deref();
|
|
S::read(bytes, all_bytes, self.endian, self.system_info.as_ref())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get a stream of raw data from the minidump.
|
|
///
|
|
/// This can be used to get the contents of arbitrary minidump streams.
|
|
/// For streams of known types you almost certainly want to use
|
|
/// [`Minidump::get_stream`][] instead.
|
|
///
|
|
/// Note that the lifetime of the returned stream is bound to the lifetime of the this
|
|
/// `Minidump` struct itself and not to the lifetime of the data backing this minidump.
|
|
/// This is a consequence of how this struct relies on [Deref] to access the data.
|
|
pub fn get_raw_stream(&'a self, stream_type: u32) -> Result<&'a [u8], Error> {
|
|
match self.streams.get(&stream_type) {
|
|
None => Err(Error::StreamNotFound),
|
|
Some((_, dir)) => {
|
|
let bytes = self.data.deref();
|
|
location_slice(bytes, &dir.location)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get whichever of the two MemoryLists are available in the minidump,
|
|
/// preferring [`MinidumpMemory64List`][].
|
|
pub fn get_memory(&'a self) -> Option<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.
|
|
pub fn 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.
|
|
pub fn 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.
|
|
pub fn all_streams(&self) -> impl Iterator<Item = &md::MINIDUMP_DIRECTORY> + '_ {
|
|
self.streams.iter().map(|(_, (_, stream))| stream)
|
|
}
|
|
|
|
/// Write a verbose description of the `Minidump` to `f`.
|
|
pub fn print<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
|
fn get_stream_name(stream_type: u32) -> Cow<'static, str> {
|
|
if let Some(stream) = MINIDUMP_STREAM_TYPE::from_u32(stream_type) {
|
|
Cow::Owned(format!("{stream:?}"))
|
|
} else {
|
|
Cow::Borrowed("unknown")
|
|
}
|
|
}
|
|
|
|
write!(
|
|
f,
|
|
r#"MDRawHeader
|
|
signature = {:#x}
|
|
version = {:#x}
|
|
stream_count = {}
|
|
stream_directory_rva = {:#x}
|
|
checksum = {:#x}
|
|
time_date_stamp = {:#x} {}
|
|
flags = {:#x}
|
|
|
|
"#,
|
|
self.header.signature,
|
|
self.header.version,
|
|
self.header.stream_count,
|
|
self.header.stream_directory_rva,
|
|
self.header.checksum,
|
|
self.header.time_date_stamp,
|
|
format_time_t(self.header.time_date_stamp),
|
|
self.header.flags,
|
|
)?;
|
|
let mut streams = self.streams.iter().collect::<Vec<_>>();
|
|
streams.sort_by(|&(&_, &(a, _)), &(&_, &(b, _))| a.cmp(&b));
|
|
for &(_, &(i, ref stream)) in streams.iter() {
|
|
write!(
|
|
f,
|
|
r#"mDirectory[{}]
|
|
MDRawDirectory
|
|
stream_type = {:#x} ({})
|
|
location.data_size = {}
|
|
location.rva = {:#x}
|
|
|
|
"#,
|
|
i,
|
|
stream.stream_type,
|
|
get_stream_name(stream.stream_type),
|
|
stream.location.data_size,
|
|
stream.location.rva
|
|
)?;
|
|
}
|
|
writeln!(f, "Streams:")?;
|
|
streams.sort_by(|&(&a, &(_, _)), &(&b, &(_, _))| a.cmp(&b));
|
|
for (_, &(i, ref stream)) in streams {
|
|
writeln!(
|
|
f,
|
|
" stream type {:#x} ({}) at index {}",
|
|
stream.stream_type,
|
|
get_stream_name(stream.stream_type),
|
|
i
|
|
)?;
|
|
}
|
|
writeln!(f)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn stream_vendor(stream_type: u32) -> &'static str {
|
|
if stream_type <= MINIDUMP_STREAM_TYPE::LastReservedStream as u32 {
|
|
"Official"
|
|
} else {
|
|
match stream_type & 0xFFFF0000 {
|
|
0x4767_0000 => "Google Extension",
|
|
0x4d7a_0000 => "Mozilla Extension",
|
|
_ => "Unknown Extension",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use md::GUID;
|
|
use minidump_common::{
|
|
errors::NtStatusWindows,
|
|
format::{PlatformId, ProcessorArchitecture},
|
|
};
|
|
use minidump_synth::{
|
|
AnnotationValue, CrashpadInfo, DumpString, Exception,
|
|
HandleDescriptor as SynthHandleDescriptor, Memory, MemoryInfo as SynthMemoryInfo,
|
|
MiscFieldsBuildString, MiscFieldsPowerInfo, MiscFieldsProcessTimes, MiscFieldsTimeZone,
|
|
MiscInfo5Fields, MiscStream, Module as SynthModule, ModuleCrashpadInfo, SimpleStream,
|
|
SynthMinidump, SystemInfo, Thread, ThreadName, UnloadedModule as SynthUnloadedModule,
|
|
STOCK_VERSION_INFO,
|
|
};
|
|
use test_assembler::*;
|
|
|
|
fn read_synth_dump<'a>(dump: SynthMinidump) -> Result<Minidump<'a, Vec<u8>>, Error> {
|
|
Minidump::read(dump.finish().unwrap())
|
|
}
|
|
|
|
#[ctor::ctor]
|
|
fn init_logger() {
|
|
env_logger::builder().is_test(true).init();
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_synth_dump() {
|
|
const STREAM_TYPE: u32 = 0x11223344;
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(SimpleStream {
|
|
stream_type: STREAM_TYPE,
|
|
section: Section::with_endian(Endian::Little).D32(0x55667788),
|
|
});
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
assert_eq!(dump.endian, LE);
|
|
assert_eq!(
|
|
dump.get_raw_stream(STREAM_TYPE).unwrap(),
|
|
&[0x88, 0x77, 0x66, 0x55]
|
|
);
|
|
|
|
assert_eq!(
|
|
dump.get_raw_stream(0xaabbccddu32),
|
|
Err(Error::StreamNotFound)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_synth_dump_bigendian() {
|
|
const STREAM_TYPE: u32 = 0x11223344;
|
|
let dump = SynthMinidump::with_endian(Endian::Big).add_stream(SimpleStream {
|
|
stream_type: STREAM_TYPE,
|
|
section: Section::with_endian(Endian::Big).D32(0x55667788),
|
|
});
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
assert_eq!(dump.endian, BE);
|
|
assert_eq!(
|
|
dump.get_raw_stream(STREAM_TYPE).unwrap(),
|
|
&[0x55, 0x66, 0x77, 0x88]
|
|
);
|
|
|
|
assert_eq!(
|
|
dump.get_raw_stream(0xaabbccddu32),
|
|
Err(Error::StreamNotFound)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_thread_names() {
|
|
let good_thread_id = 17;
|
|
let corrupt_thread_id = 123;
|
|
|
|
let good_name = DumpString::new("MyCoolThread", Endian::Little);
|
|
// No corrupt name, will dangle
|
|
|
|
let good_thread_name_entry =
|
|
ThreadName::new(Endian::Little, good_thread_id, Some(&good_name));
|
|
let corrupt_thread_name_entry = ThreadName::new(Endian::Little, corrupt_thread_id, None);
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_thread_name(good_thread_name_entry)
|
|
.add_thread_name(corrupt_thread_name_entry)
|
|
.add(good_name);
|
|
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let thread_names = dump.get_stream::<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()) {
|
|
if let UnifiedMemoryInfo::Info(info2) = unified {
|
|
assert_eq!(info, info2);
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
}
|
|
|
|
let infos = info_list.iter().collect::<Vec<_>>();
|
|
|
|
assert_eq!(infos.len(), 2);
|
|
|
|
assert_eq!(infos[0].raw.base_address, 0xa90206ca83eb2852);
|
|
assert_eq!(infos[0].raw.allocation_base, 0xa802064a83eb2752);
|
|
assert_eq!(
|
|
infos[0].raw.allocation_protection,
|
|
info1_alloc_protection.bits()
|
|
);
|
|
assert_eq!(infos[0].raw.region_size, 0xf80e064a93eb2356);
|
|
assert_eq!(infos[0].raw.state, info1_state.bits());
|
|
assert_eq!(infos[0].raw.protection, info1_protection.bits());
|
|
assert_eq!(infos[0].raw._type, info1_ty.bits());
|
|
|
|
assert_eq!(infos[0].allocation_protection, info1_alloc_protection);
|
|
assert_eq!(infos[0].protection, info1_protection);
|
|
assert_eq!(infos[0].state, info1_state);
|
|
assert_eq!(infos[0].ty, info1_ty);
|
|
assert!(infos[0].is_executable());
|
|
|
|
assert_eq!(infos[1].raw.base_address, 0xd70206ca83eb2852);
|
|
assert_eq!(infos[1].raw.allocation_base, 0xb802064383eb2752);
|
|
assert_eq!(
|
|
infos[1].raw.allocation_protection,
|
|
info2_alloc_protection.bits()
|
|
);
|
|
assert_eq!(infos[1].raw.region_size, 0xe80e064a93eb2356);
|
|
assert_eq!(infos[1].raw.state, info2_state.bits());
|
|
assert_eq!(infos[1].raw.protection, info2_protection.bits());
|
|
assert_eq!(infos[1].raw._type, info2_ty.bits());
|
|
|
|
assert_eq!(infos[1].allocation_protection, info2_alloc_protection);
|
|
assert_eq!(infos[1].protection, info2_protection);
|
|
assert_eq!(infos[1].state, info2_state);
|
|
assert_eq!(infos[1].ty, info2_ty);
|
|
assert!(!infos[1].is_executable());
|
|
}
|
|
|
|
#[test]
|
|
fn test_linux_maps() {
|
|
use procfs_core::process::{MMPermissions, MMapPath};
|
|
|
|
// TODO: is it okay to give up wonky whitespace support?
|
|
let input =
|
|
b"a90206ca83eb2852-b90206ca83eb3852 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so\n\
|
|
c70206ca83eb2852-de0206ca83eb2852 -w-s 10bac9000 fd:05 1196511 /usr/lib64/libtdb2.so (deleted)";
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little).set_linux_maps(input);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
|
|
// Read both kinds of info to test this path on UnifiedMemoryInfo
|
|
let info_list = dump.get_stream::<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()) {
|
|
if let UnifiedMemoryInfo::Map(info2) = unified {
|
|
assert_eq!(info, info2);
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
}
|
|
|
|
let maps = maps.iter().collect::<Vec<_>>();
|
|
assert_eq!(maps.len(), 2);
|
|
|
|
assert_eq!(maps[0].map.address.0, 0xa90206ca83eb2852);
|
|
assert_eq!(maps[0].map.address.1, 0xb90206ca83eb3852);
|
|
assert_eq!(
|
|
maps[0].map.pathname,
|
|
MMapPath::Path("/usr/lib64/libtdb1.so".into())
|
|
);
|
|
assert!(
|
|
maps[0].map.perms
|
|
== MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::PRIVATE
|
|
);
|
|
|
|
assert_eq!(maps[1].map.address.0, 0xc70206ca83eb2852);
|
|
assert_eq!(maps[1].map.address.1, 0xde0206ca83eb2852);
|
|
assert_eq!(
|
|
maps[1].map.pathname,
|
|
MMapPath::Path("/usr/lib64/libtdb2.so (deleted)".into())
|
|
);
|
|
assert!(maps[1].map.perms == MMPermissions::WRITE | MMPermissions::SHARED);
|
|
|
|
let mut unified_infos = unified_info.by_addr();
|
|
|
|
assert!(matches!(unified_infos.next(), Some(UnifiedMemoryInfo::Map(m)) if m == maps[0]));
|
|
assert!(matches!(unified_infos.next(), Some(UnifiedMemoryInfo::Map(m)) if m == maps[1]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_linux_map_parse() {
|
|
use procfs_core::process::{MMPermissions, MMapPath::*};
|
|
let parse = |input| MinidumpLinuxMapInfo::from_line(input).unwrap();
|
|
let maybe_parse = MinidumpLinuxMapInfo::from_line;
|
|
|
|
// TODO: is it okay to give up wonky whitespace support?
|
|
|
|
{
|
|
// Normal file
|
|
let map = parse(b"10a00-10b00 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Path("/usr/lib64/libtdb1.so".into()));
|
|
|
|
assert!(
|
|
map.map.perms
|
|
== MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::PRIVATE
|
|
);
|
|
assert!(map.is_readable());
|
|
assert!(map.is_executable());
|
|
}
|
|
|
|
{
|
|
// Deleted file (also some whitespace in the file name)
|
|
let map = parse(b"ffffffffff600000-ffffffffff601000 -wxs 10bac9000 fd:05 1196511 /usr/lib64/ libtdb1.so (deleted)");
|
|
|
|
assert_eq!(map.map.address.0, 0xffffffffff600000);
|
|
assert_eq!(map.map.address.1, 0xffffffffff601000);
|
|
assert_eq!(
|
|
map.memory_range(),
|
|
Some(Range::new(0xffffffffff600000, 0xffffffffff601000))
|
|
);
|
|
assert_eq!(
|
|
map.map.pathname,
|
|
Path("/usr/lib64/ libtdb1.so (deleted)".into())
|
|
);
|
|
assert!(
|
|
map.map.perms
|
|
== MMPermissions::WRITE | MMPermissions::EXECUTE | MMPermissions::SHARED
|
|
);
|
|
assert!(map.is_writable());
|
|
assert!(map.is_executable());
|
|
}
|
|
|
|
{
|
|
// Stack
|
|
let map = parse(b"10a00-10b00 ------- 10bac9000 fd:05 1196511 [stack]");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Stack);
|
|
assert!(map.map.perms == MMPermissions::NONE);
|
|
}
|
|
|
|
{
|
|
// Stack with tid
|
|
let map = parse(b"10a00-10b00 ------- 10bac9000 fd:05 1196511 [stack:1234567]");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, TStack(1234567));
|
|
assert!(map.map.perms == MMPermissions::NONE);
|
|
}
|
|
|
|
{
|
|
// Heap
|
|
let map = parse(b"10a00-10b00 -- 10bac9000 fd:05 1196511 [heap]");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Heap);
|
|
assert!(map.map.perms == MMPermissions::NONE);
|
|
}
|
|
|
|
{
|
|
// Vdso
|
|
let map = parse(b"10a00-10b00 r-wx- 10bac9000 fd:05 1196511 [vdso]");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Vdso);
|
|
assert!(
|
|
map.map.perms
|
|
== MMPermissions::READ | MMPermissions::WRITE | MMPermissions::EXECUTE
|
|
);
|
|
}
|
|
|
|
{
|
|
// Unknown Special
|
|
let map = parse(b"10a00-10b00 r-wx- 10bac9000 fd:05 1196511 [asdfasd]");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Other("asdfasd".into()));
|
|
assert!(
|
|
map.map.perms
|
|
== MMPermissions::READ | MMPermissions::WRITE | MMPermissions::EXECUTE
|
|
);
|
|
}
|
|
|
|
{
|
|
// Anonymous
|
|
let map = parse(b"10a00-10b00 -r- 10bac9000 fd:05 1196511 ");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Anonymous);
|
|
assert!(map.map.perms == MMPermissions::READ);
|
|
}
|
|
|
|
/*
|
|
{
|
|
// Truncated defaults to anonymous
|
|
let map = parse(b"10a00-10b00");
|
|
|
|
assert_eq!(map.map.address.0, 0x10a00);
|
|
assert_eq!(map.map.address.1, 0x10b00);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0x10a00, 0x10b00)));
|
|
assert_eq!(map.map.pathname, Anonymous);
|
|
assert!(map.map.perms == MMPermissions::NONE);
|
|
}
|
|
*/
|
|
|
|
{
|
|
// Reversed ranges result in None for memory_range()
|
|
let map = parse(b"fffff-10000 -r- 10bac9000 fd:05 1196511 ");
|
|
|
|
assert_eq!(map.map.address.0, 0xfffff);
|
|
assert_eq!(map.map.address.1, 0x10000);
|
|
assert_eq!(map.memory_range(), None);
|
|
}
|
|
|
|
{
|
|
// Equal ranges are valid
|
|
let map = parse(b"fffff-fffff --- 10bac9000 fd:05 1196511 ");
|
|
|
|
assert_eq!(map.map.address.0, 0xfffff);
|
|
assert_eq!(map.map.address.1, 0xfffff);
|
|
assert_eq!(map.memory_range(), Some(Range::new(0xfffff, 0xfffff)));
|
|
}
|
|
|
|
{
|
|
// blank line
|
|
assert!(maybe_parse(b"").is_none());
|
|
assert!(maybe_parse(b" ").is_none());
|
|
}
|
|
|
|
{
|
|
// bad addresses
|
|
let map = maybe_parse(b"-10b00 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so");
|
|
assert!(map.is_none());
|
|
|
|
let map = maybe_parse(b"10b00- r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so");
|
|
assert!(map.is_none());
|
|
|
|
let map = maybe_parse(b"10b00 r-xp 10bac9000 fd:05 1196511 /usr/lib64/libtdb1.so");
|
|
assert!(map.is_none());
|
|
}
|
|
|
|
{
|
|
// bad [stack:<tid>]
|
|
let map = maybe_parse(b"10a00-10b00 r-xp 10bac9000 fd:05 1196511 [stack:]");
|
|
assert!(map.is_none());
|
|
|
|
let map = maybe_parse(b"10a00-10b00 r-xp 10bac9000 fd:05 1196511 [stack:a10]");
|
|
assert!(map.is_none());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_module_list_overlap() {
|
|
let name1 = DumpString::new("module 1", Endian::Little);
|
|
let name2 = DumpString::new("module 2", Endian::Little);
|
|
let name3 = DumpString::new("module 3", Endian::Little);
|
|
let name4 = DumpString::new("module 4", Endian::Little);
|
|
let name5 = DumpString::new("module 5", Endian::Little);
|
|
let cv_record = Section::with_endian(Endian::Little)
|
|
.D32(md::CvSignature::Pdb70 as u32) // signature
|
|
// signature, a GUID
|
|
.D32(0xabcd1234)
|
|
.D16(0xf00d)
|
|
.D16(0xbeef)
|
|
.append_bytes(b"\x01\x02\x03\x04\x05\x06\x07\x08")
|
|
.D32(1) // age
|
|
.append_bytes(b"c:\\foo\\file.pdb\0"); // pdb_file_name
|
|
let module1 = SynthModule::new(
|
|
Endian::Little,
|
|
0x100000000,
|
|
0x4000,
|
|
&name1,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record);
|
|
// module2 overlaps module1 exactly
|
|
let module2 = SynthModule::new(
|
|
Endian::Little,
|
|
0x100000000,
|
|
0x4000,
|
|
&name2,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record);
|
|
// module3 overlaps module1 partially
|
|
let module3 = SynthModule::new(
|
|
Endian::Little,
|
|
0x100000001,
|
|
0x4000,
|
|
&name3,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record);
|
|
// module4 is fully contained within module1
|
|
let module4 = SynthModule::new(
|
|
Endian::Little,
|
|
0x100000001,
|
|
0x3000,
|
|
&name4,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record);
|
|
// module5 is cool, though.
|
|
let module5 = SynthModule::new(
|
|
Endian::Little,
|
|
0x100004000,
|
|
0x4000,
|
|
&name5,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record);
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_module(module1)
|
|
.add_module(module2)
|
|
.add_module(module3)
|
|
.add_module(module4)
|
|
.add_module(module5)
|
|
.add(name1)
|
|
.add(name2)
|
|
.add(name3)
|
|
.add(name4)
|
|
.add(name5)
|
|
.add(cv_record);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let module_list = dump.get_stream::<MinidumpModuleList>().unwrap();
|
|
let modules = module_list.iter().collect::<Vec<_>>();
|
|
assert_eq!(modules.len(), 5);
|
|
assert_eq!(modules[0].base_address(), 0x100000000);
|
|
assert_eq!(modules[0].size(), 0x4000);
|
|
assert_eq!(modules[0].code_file(), "module 1");
|
|
assert_eq!(modules[1].base_address(), 0x100000000);
|
|
assert_eq!(modules[1].size(), 0x4000);
|
|
assert_eq!(modules[1].code_file(), "module 2");
|
|
assert_eq!(modules[2].base_address(), 0x100000001);
|
|
assert_eq!(modules[2].size(), 0x4000);
|
|
assert_eq!(modules[2].code_file(), "module 3");
|
|
assert_eq!(modules[3].base_address(), 0x100000001);
|
|
assert_eq!(modules[3].size(), 0x3000);
|
|
assert_eq!(modules[3].code_file(), "module 4");
|
|
assert_eq!(modules[4].base_address(), 0x100004000);
|
|
assert_eq!(modules[4].size(), 0x4000);
|
|
assert_eq!(modules[4].code_file(), "module 5");
|
|
|
|
// module_at_address should discard overlapping modules.
|
|
assert_eq!(module_list.by_addr().count(), 2);
|
|
assert_eq!(
|
|
module_list
|
|
.module_at_address(0x100001000)
|
|
.unwrap()
|
|
.code_file(),
|
|
"module 1"
|
|
);
|
|
assert_eq!(
|
|
module_list
|
|
.module_at_address(0x100005000)
|
|
.unwrap()
|
|
.code_file(),
|
|
"module 5"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_list() {
|
|
const CONTENTS: &[u8] = b"memory_contents";
|
|
let memory = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_bytes(CONTENTS),
|
|
0x309d68010bd21b2c,
|
|
);
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_memory(memory);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let memory_list = dump.get_stream::<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();
|
|
|
|
assert!(memory_list.memory_at_address(u64::MAX).is_none());
|
|
assert_eq!(memory_list.regions.len(), 1);
|
|
assert!(memory_list.regions[0].memory_range().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_list_overlap() {
|
|
let memory1 = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(0, 0x1000),
|
|
0x1000,
|
|
);
|
|
// memory2 overlaps memory1 exactly
|
|
let memory2 = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(1, 0x1000),
|
|
0x1000,
|
|
);
|
|
// memory3 overlaps memory1 partially
|
|
let memory3 = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(2, 0x1000),
|
|
0x1001,
|
|
);
|
|
// memory4 is fully contained within memory1
|
|
let memory4 = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(3, 0x100),
|
|
0x1001,
|
|
);
|
|
// memory5 is cool, though.
|
|
let memory5 = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(4, 0x1000),
|
|
0x2000,
|
|
);
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_memory(memory1)
|
|
.add_memory(memory2)
|
|
.add_memory(memory3)
|
|
.add_memory(memory4)
|
|
.add_memory(memory5);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let memory_list = dump.get_stream::<MinidumpMemoryList<'_>>().unwrap();
|
|
let regions = memory_list.iter().collect::<Vec<_>>();
|
|
assert_eq!(regions.len(), 5);
|
|
assert_eq!(regions[0].base_address, 0x1000);
|
|
assert_eq!(regions[0].size, 0x1000);
|
|
assert_eq!(regions[1].base_address, 0x1000);
|
|
assert_eq!(regions[1].size, 0x1000);
|
|
assert_eq!(regions[2].base_address, 0x1001);
|
|
assert_eq!(regions[2].size, 0x1000);
|
|
assert_eq!(regions[3].base_address, 0x1001);
|
|
assert_eq!(regions[3].size, 0x100);
|
|
assert_eq!(regions[4].base_address, 0x2000);
|
|
assert_eq!(regions[4].size, 0x1000);
|
|
|
|
// memory_at_address should discard overlapping regions.
|
|
assert_eq!(memory_list.by_addr().count(), 2);
|
|
let m1 = memory_list.memory_at_address(0x1a00).unwrap();
|
|
assert_eq!(m1.base_address, 0x1000);
|
|
assert_eq!(m1.size, 0x1000);
|
|
assert_eq!(m1.bytes, &[0u8; 0x1000][..]);
|
|
let m2 = memory_list.memory_at_address(0x2a00).unwrap();
|
|
assert_eq!(m2.base_address, 0x2000);
|
|
assert_eq!(m2.size, 0x1000);
|
|
assert_eq!(m2.bytes, &[4u8; 0x1000][..]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_misc_info() {
|
|
const PID: u32 = 0x1234abcd;
|
|
const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes {
|
|
process_create_time: 0xf0f0b0b0,
|
|
process_user_time: 0xf030a020,
|
|
process_kernel_time: 0xa010b420,
|
|
};
|
|
|
|
let mut misc = MiscStream::new(Endian::Little);
|
|
misc.process_id = Some(PID);
|
|
misc.process_times = Some(PROCESS_TIMES);
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
|
|
assert_eq!(misc.raw.process_id(), Some(&PID));
|
|
assert_eq!(
|
|
misc.process_create_time().unwrap(),
|
|
systemtime_from_timestamp(PROCESS_TIMES.process_create_time as u64).unwrap()
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.process_user_time().unwrap(),
|
|
PROCESS_TIMES.process_user_time
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.process_kernel_time().unwrap(),
|
|
PROCESS_TIMES.process_kernel_time
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_misc_info_large() {
|
|
const PID: u32 = 0x1234abcd;
|
|
const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes {
|
|
process_create_time: 0xf0f0b0b0,
|
|
process_user_time: 0xf030a020,
|
|
process_kernel_time: 0xa010b420,
|
|
};
|
|
let mut misc = MiscStream::new(Endian::Little);
|
|
misc.process_id = Some(PID);
|
|
misc.process_times = Some(PROCESS_TIMES);
|
|
// Make it larger.
|
|
misc.pad_to_size = Some(mem::size_of::<md::MINIDUMP_MISC_INFO>() + 32);
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
|
|
assert_eq!(misc.raw.process_id(), Some(&PID));
|
|
assert_eq!(
|
|
misc.process_create_time().unwrap(),
|
|
systemtime_from_timestamp(PROCESS_TIMES.process_create_time as u64).unwrap(),
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.process_user_time().unwrap(),
|
|
PROCESS_TIMES.process_user_time
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.process_kernel_time().unwrap(),
|
|
PROCESS_TIMES.process_kernel_time
|
|
);
|
|
}
|
|
|
|
fn ascii_string_to_utf16(input: &str) -> Vec<u16> {
|
|
input.chars().map(|c| c as u16).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn test_misc_info_5() {
|
|
// MISC_INFO fields
|
|
const PID: u32 = 0x1234abcd;
|
|
const PROCESS_TIMES: MiscFieldsProcessTimes = MiscFieldsProcessTimes {
|
|
process_create_time: 0xf0f0b0b0,
|
|
process_user_time: 0xf030a020,
|
|
process_kernel_time: 0xa010b420,
|
|
};
|
|
|
|
// MISC_INFO_2 fields
|
|
const POWER_INFO: MiscFieldsPowerInfo = MiscFieldsPowerInfo {
|
|
processor_max_mhz: 0x45873234,
|
|
processor_current_mhz: 0x2134018a,
|
|
processor_mhz_limit: 0x3423aead,
|
|
processor_max_idle_state: 0x123aef12,
|
|
processor_current_idle_state: 0x1205af3a,
|
|
};
|
|
|
|
// MISC_INFO_3 fields
|
|
const PROCESS_INTEGRITY_LEVEL: u32 = 0x35603403;
|
|
const PROCESS_EXECUTE_FLAGS: u32 = 0xa4e09da1;
|
|
const PROTECTED_PROCESS: u32 = 0x12345678;
|
|
|
|
let mut standard_name = [0; 32];
|
|
let mut daylight_name = [0; 32];
|
|
let bare_standard_name = ascii_string_to_utf16("Pacific Standard Time");
|
|
let bare_daylight_name = ascii_string_to_utf16("Pacific Daylight Time");
|
|
standard_name[..bare_standard_name.len()].copy_from_slice(&bare_standard_name);
|
|
daylight_name[..bare_daylight_name.len()].copy_from_slice(&bare_daylight_name);
|
|
|
|
const TIME_ZONE_ID: u32 = 2;
|
|
const BIAS: i32 = 2;
|
|
const STANDARD_BIAS: i32 = 1;
|
|
const DAYLIGHT_BIAS: i32 = -60;
|
|
const STANDARD_DATE: md::SYSTEMTIME = md::SYSTEMTIME {
|
|
year: 0,
|
|
month: 11,
|
|
day_of_week: 2,
|
|
day: 1,
|
|
hour: 2,
|
|
minute: 33,
|
|
second: 51,
|
|
milliseconds: 123,
|
|
};
|
|
const DAYLIGHT_DATE: md::SYSTEMTIME = md::SYSTEMTIME {
|
|
year: 0,
|
|
month: 3,
|
|
day_of_week: 4,
|
|
day: 2,
|
|
hour: 3,
|
|
minute: 41,
|
|
second: 19,
|
|
milliseconds: 512,
|
|
};
|
|
|
|
let time_zone = MiscFieldsTimeZone {
|
|
time_zone_id: TIME_ZONE_ID,
|
|
time_zone: md::TIME_ZONE_INFORMATION {
|
|
bias: BIAS,
|
|
standard_bias: STANDARD_BIAS,
|
|
daylight_bias: DAYLIGHT_BIAS,
|
|
daylight_name,
|
|
standard_name,
|
|
standard_date: STANDARD_DATE.clone(),
|
|
daylight_date: DAYLIGHT_DATE.clone(),
|
|
},
|
|
};
|
|
|
|
// MISC_INFO_4 fields
|
|
let mut build_string = [0; 260];
|
|
let mut dbg_bld_str = [0; 40];
|
|
let bare_build_string = ascii_string_to_utf16("hello");
|
|
let bare_dbg_bld_str = ascii_string_to_utf16("world");
|
|
build_string[..bare_build_string.len()].copy_from_slice(&bare_build_string);
|
|
dbg_bld_str[..bare_dbg_bld_str.len()].copy_from_slice(&bare_dbg_bld_str);
|
|
|
|
let build_strings = MiscFieldsBuildString {
|
|
build_string,
|
|
dbg_bld_str,
|
|
};
|
|
|
|
// MISC_INFO_5 fields
|
|
const SIZE_OF_INFO: u32 = mem::size_of::<md::XSTATE_CONFIG_FEATURE_MSC_INFO>() as u32;
|
|
const CONTEXT_SIZE: u32 = 0x1234523f;
|
|
const PROCESS_COOKIE: u32 = 0x1234dfe0;
|
|
const KNOWN_FEATURE_IDX: usize = md::XstateFeatureIndex::LEGACY_SSE as usize;
|
|
const UNKNOWN_FEATURE_IDX: usize = 39;
|
|
let mut enabled_features = 0;
|
|
let mut features = [md::XSTATE_FEATURE::default(); 64];
|
|
// One known feature and one unknown feature.
|
|
enabled_features |= 1 << KNOWN_FEATURE_IDX;
|
|
features[KNOWN_FEATURE_IDX] = md::XSTATE_FEATURE {
|
|
offset: 0,
|
|
size: 140,
|
|
};
|
|
enabled_features |= 1 << UNKNOWN_FEATURE_IDX;
|
|
features[UNKNOWN_FEATURE_IDX] = md::XSTATE_FEATURE {
|
|
offset: 320,
|
|
size: 1100,
|
|
};
|
|
let misc_5 = MiscInfo5Fields {
|
|
xstate_data: md::XSTATE_CONFIG_FEATURE_MSC_INFO {
|
|
size_of_info: SIZE_OF_INFO,
|
|
context_size: CONTEXT_SIZE,
|
|
enabled_features,
|
|
features,
|
|
},
|
|
process_cookie: Some(PROCESS_COOKIE),
|
|
};
|
|
|
|
let mut misc = MiscStream::new(Endian::Little);
|
|
misc.process_id = Some(PID);
|
|
misc.process_times = Some(PROCESS_TIMES);
|
|
misc.power_info = Some(POWER_INFO);
|
|
misc.process_integrity_level = Some(PROCESS_INTEGRITY_LEVEL);
|
|
misc.protected_process = Some(PROTECTED_PROCESS);
|
|
misc.process_execute_flags = Some(PROCESS_EXECUTE_FLAGS);
|
|
misc.time_zone = Some(time_zone);
|
|
misc.build_strings = Some(build_strings);
|
|
misc.misc_5 = Some(misc_5);
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_stream(misc);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let misc = dump.get_stream::<MinidumpMiscInfo>().unwrap();
|
|
|
|
// MISC_INFO fields
|
|
assert_eq!(misc.raw.process_id(), Some(&PID));
|
|
assert_eq!(
|
|
misc.process_create_time().unwrap(),
|
|
systemtime_from_timestamp(PROCESS_TIMES.process_create_time as u64).unwrap()
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.process_user_time().unwrap(),
|
|
PROCESS_TIMES.process_user_time
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.process_kernel_time().unwrap(),
|
|
PROCESS_TIMES.process_kernel_time
|
|
);
|
|
|
|
// MISC_INFO_2 fields
|
|
assert_eq!(
|
|
*misc.raw.processor_max_mhz().unwrap(),
|
|
POWER_INFO.processor_max_mhz,
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.processor_current_mhz().unwrap(),
|
|
POWER_INFO.processor_current_mhz,
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.processor_mhz_limit().unwrap(),
|
|
POWER_INFO.processor_mhz_limit,
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.processor_max_idle_state().unwrap(),
|
|
POWER_INFO.processor_max_idle_state,
|
|
);
|
|
assert_eq!(
|
|
*misc.raw.processor_current_idle_state().unwrap(),
|
|
POWER_INFO.processor_current_idle_state,
|
|
);
|
|
|
|
// MISC_INFO_3 fields
|
|
assert_eq!(*misc.raw.time_zone_id().unwrap(), TIME_ZONE_ID);
|
|
let time_zone = misc.raw.time_zone().unwrap();
|
|
assert_eq!(time_zone.bias, BIAS);
|
|
assert_eq!(time_zone.standard_bias, STANDARD_BIAS);
|
|
assert_eq!(time_zone.daylight_bias, DAYLIGHT_BIAS);
|
|
assert_eq!(time_zone.standard_date, STANDARD_DATE);
|
|
assert_eq!(time_zone.daylight_date, DAYLIGHT_DATE);
|
|
assert_eq!(time_zone.standard_name, standard_name);
|
|
assert_eq!(time_zone.daylight_name, daylight_name);
|
|
|
|
// MISC_INFO_4 fields
|
|
assert_eq!(*misc.raw.build_string().unwrap(), build_string,);
|
|
assert_eq!(*misc.raw.dbg_bld_str().unwrap(), dbg_bld_str,);
|
|
|
|
// MISC_INFO_5 fields
|
|
assert_eq!(*misc.raw.process_cookie().unwrap(), PROCESS_COOKIE,);
|
|
|
|
let xstate = misc.raw.xstate_data().unwrap();
|
|
assert_eq!(xstate.size_of_info, SIZE_OF_INFO);
|
|
assert_eq!(xstate.context_size, CONTEXT_SIZE);
|
|
assert_eq!(xstate.enabled_features, enabled_features);
|
|
assert_eq!(xstate.features, features);
|
|
|
|
let mut xstate_iter = xstate.iter();
|
|
assert_eq!(
|
|
xstate_iter.next().unwrap(),
|
|
(KNOWN_FEATURE_IDX, features[KNOWN_FEATURE_IDX]),
|
|
);
|
|
assert_eq!(
|
|
xstate_iter.next().unwrap(),
|
|
(UNKNOWN_FEATURE_IDX, features[UNKNOWN_FEATURE_IDX]),
|
|
);
|
|
assert_eq!(xstate_iter.next(), None);
|
|
assert_eq!(xstate_iter.next(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_elf_build_id() {
|
|
// Add a module with a long ELF build id
|
|
let name1 = DumpString::new("module 1", Endian::Little);
|
|
const MODULE1_BUILD_ID: &[u8] = &[
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
|
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
];
|
|
let cv_record1 = Section::with_endian(Endian::Little)
|
|
.D32(md::CvSignature::Elf as u32) // signature
|
|
.append_bytes(MODULE1_BUILD_ID);
|
|
let module1 = SynthModule::new(
|
|
Endian::Little,
|
|
0x100000000,
|
|
0x4000,
|
|
&name1,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record1);
|
|
// Add a module with a short ELF build id
|
|
let name2 = DumpString::new("module 2", Endian::Little);
|
|
const MODULE2_BUILD_ID: &[u8] = &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
|
|
let cv_record2 = Section::with_endian(Endian::Little)
|
|
.D32(md::CvSignature::Elf as u32) // signature
|
|
.append_bytes(MODULE2_BUILD_ID);
|
|
let module2 = SynthModule::new(
|
|
Endian::Little,
|
|
0x200000000,
|
|
0x4000,
|
|
&name2,
|
|
0xb1054d2a,
|
|
0x34571371,
|
|
Some(&STOCK_VERSION_INFO),
|
|
)
|
|
.cv_record(&cv_record2);
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_module(module1)
|
|
.add_module(module2)
|
|
.add(name1)
|
|
.add(cv_record1)
|
|
.add(name2)
|
|
.add(cv_record2);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let module_list = dump.get_stream::<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<_>>();
|
|
|
|
assert!(modules[0].debug_identifier().is_none());
|
|
assert!(modules[1].debug_identifier().is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_thread_list_x86() {
|
|
let context = minidump_synth::x86_context(Endian::Little, 0xabcd1234, 0x1010);
|
|
let stack = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(0, 0x1000),
|
|
0x1000,
|
|
);
|
|
let arch = md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_INTEL as u16;
|
|
let system_info = SystemInfo::new(Endian::Little).set_processor_architecture(arch);
|
|
let thread = Thread::new(Endian::Little, 0x1234, &stack, &context);
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_thread(thread)
|
|
.add(context)
|
|
.add_memory(stack)
|
|
.add_system_info(system_info);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let mut thread_list = dump.get_stream::<MinidumpThreadList<'_>>().unwrap();
|
|
let system_info = dump.get_stream::<MinidumpSystemInfo>().unwrap();
|
|
let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
|
|
assert_eq!(thread_list.threads.len(), 1);
|
|
let mut thread = thread_list.threads.pop().unwrap();
|
|
assert_eq!(thread.raw.thread_id, 0x1234);
|
|
let context = thread
|
|
.context(&system_info, misc_info.as_ref())
|
|
.expect("Should have a thread context");
|
|
match &context.raw {
|
|
MinidumpRawContext::X86(raw) => {
|
|
assert_eq!(raw.eip, 0xabcd1234);
|
|
assert_eq!(raw.esp, 0x1010);
|
|
}
|
|
_ => panic!("Got unexpected raw context type!"),
|
|
}
|
|
let stack = thread.stack.take().expect("Should have stack memory");
|
|
assert_eq!(stack.base_address, 0x1000);
|
|
assert_eq!(stack.size, 0x1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_thread_list_amd64() {
|
|
let context =
|
|
minidump_synth::amd64_context(Endian::Little, 0x1234abcd1234abcd, 0x1000000010000000);
|
|
let stack = Memory::with_section(
|
|
Section::with_endian(Endian::Little).append_repeated(0, 0x1000),
|
|
0x1000000010000000,
|
|
);
|
|
let arch = md::ProcessorArchitecture::PROCESSOR_ARCHITECTURE_AMD64 as u16;
|
|
let system_info = SystemInfo::new(Endian::Little).set_processor_architecture(arch);
|
|
let thread = Thread::new(Endian::Little, 0x1234, &stack, &context);
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_thread(thread)
|
|
.add(context)
|
|
.add_memory(stack)
|
|
.add_system_info(system_info);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let mut thread_list = dump.get_stream::<MinidumpThreadList<'_>>().unwrap();
|
|
let system_info = dump.get_stream::<MinidumpSystemInfo>().unwrap();
|
|
let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
|
|
assert_eq!(thread_list.threads.len(), 1);
|
|
let mut thread = thread_list.threads.pop().unwrap();
|
|
assert_eq!(thread.raw.thread_id, 0x1234);
|
|
let context = thread
|
|
.context(&system_info, misc_info.as_ref())
|
|
.expect("Should have a thread context");
|
|
match &context.raw {
|
|
MinidumpRawContext::Amd64(raw) => {
|
|
assert_eq!(raw.rip, 0x1234abcd1234abcd);
|
|
assert_eq!(raw.rsp, 0x1000000010000000);
|
|
}
|
|
_ => panic!("Got unexpected raw context type!"),
|
|
}
|
|
let stack = thread.stack.take().expect("Should have stack memory");
|
|
assert_eq!(stack.base_address, 0x1000000010000000);
|
|
assert_eq!(stack.size, 0x1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_crashpad_info_missing() {
|
|
let dump = SynthMinidump::with_endian(Endian::Little);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
|
|
assert!(matches!(
|
|
dump.get_stream::<MinidumpCrashpadInfo>(),
|
|
Err(Error::StreamNotFound)
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn test_crashpad_info_ids() {
|
|
let report_id = GUID {
|
|
data1: 1,
|
|
data2: 2,
|
|
data3: 3,
|
|
data4: [4, 5, 6, 7, 8, 9, 10, 11],
|
|
};
|
|
|
|
let client_id = GUID {
|
|
data1: 11,
|
|
data2: 10,
|
|
data3: 9,
|
|
data4: [8, 7, 6, 5, 4, 3, 2, 1],
|
|
};
|
|
|
|
let crashpad_info = CrashpadInfo::new(Endian::Little)
|
|
.report_id(report_id)
|
|
.client_id(client_id);
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_crashpad_info(crashpad_info);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
|
|
let crashpad_info = dump.get_stream::<MinidumpCrashpadInfo>().unwrap();
|
|
|
|
assert_eq!(crashpad_info.raw.report_id, report_id);
|
|
assert_eq!(crashpad_info.raw.client_id, client_id);
|
|
}
|
|
|
|
#[test]
|
|
fn test_crashpad_info_annotations() {
|
|
let module = ModuleCrashpadInfo::new(42, Endian::Little)
|
|
.add_list_annotation("annotation")
|
|
.add_simple_annotation("simple", "module")
|
|
.add_annotation_object("string", AnnotationValue::String("value".to_owned()))
|
|
.add_annotation_object("invalid", AnnotationValue::Invalid)
|
|
.add_annotation_object("custom", AnnotationValue::Custom(0x8001, vec![42]));
|
|
|
|
let crashpad_info = CrashpadInfo::new(Endian::Little)
|
|
.add_module(module)
|
|
.add_simple_annotation("simple", "info");
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little).add_crashpad_info(crashpad_info);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
|
|
let crashpad_info = dump.get_stream::<MinidumpCrashpadInfo>().unwrap();
|
|
let module = &crashpad_info.module_list[0];
|
|
|
|
assert_eq!(crashpad_info.simple_annotations["simple"], "info");
|
|
assert_eq!(module.module_index, 42);
|
|
assert_eq!(module.list_annotations, vec!["annotation".to_owned()]);
|
|
assert_eq!(module.simple_annotations["simple"], "module");
|
|
assert_eq!(
|
|
module.annotation_objects["string"],
|
|
MinidumpAnnotation::String("value".to_owned())
|
|
);
|
|
assert_eq!(
|
|
module.annotation_objects["invalid"],
|
|
MinidumpAnnotation::Invalid
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_exception_x86() {
|
|
// Defaults to x86
|
|
let system_info = SystemInfo::new(Endian::Little);
|
|
|
|
let mut exception = Exception::new(Endian::Little);
|
|
|
|
// Check that we clear the erroneous high bits for 32-bit
|
|
exception.exception_record.exception_address = 0xf0e1_d2c3_b4a5_9687;
|
|
// FIXME: test other fields too
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_system_info(system_info)
|
|
.add_exception(exception);
|
|
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
|
|
let system_stream = dump.get_stream::<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);
|
|
|
|
let mut exception = Exception::new(Endian::Little);
|
|
|
|
// Check that we don't truncate this on 64-bit
|
|
exception.exception_record.exception_address = 0xf0e1_d2c3_b4a5_9687;
|
|
// FIXME: test other fields too
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_system_info(system_info)
|
|
.add_exception(exception);
|
|
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
|
|
let system_stream = dump.get_stream::<MinidumpSystemInfo>().unwrap();
|
|
let exception_stream = dump.get_stream::<MinidumpException>().unwrap();
|
|
assert_eq!(
|
|
exception_stream.get_crash_address(system_stream.os, system_stream.cpu),
|
|
0xf0e1_d2c3_b4a5_9687
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fuzzed_oom() {
|
|
// https://github.com/rust-minidump/rust-minidump/issues/381
|
|
let data = b"MDMP\x93\xa7\x00\x00\x00\xffffdYfffff@\n\nfp\n\xbb\xff\xff\xff\n\xff\n";
|
|
assert!(Minidump::read(data.as_ref()).is_err());
|
|
|
|
// https://github.com/getsentry/symbolic/issues/478
|
|
let data = b"MDMP\x93\xa7\x00\x00\r\x00\x00\x00 \xff\xff\xff\xff\xff\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
|
assert!(Minidump::read(data.as_ref()).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_module() {
|
|
let name = DumpString::new("/SYSV00000000 (deleted)", Endian::Little);
|
|
let module = SynthModule::new(
|
|
Endian::Little,
|
|
0x7f602915e000,
|
|
0x26000,
|
|
&name,
|
|
0x0,
|
|
0x0,
|
|
// All of these are completely zeroed out in the wild.
|
|
Some(&md::VS_FIXEDFILEINFO {
|
|
signature: 0,
|
|
struct_version: 0,
|
|
file_version_hi: 0,
|
|
file_version_lo: 0,
|
|
product_version_hi: 0,
|
|
product_version_lo: 0,
|
|
file_flags_mask: 0,
|
|
file_flags: 0,
|
|
file_os: 0,
|
|
file_type: 0,
|
|
file_subtype: 0,
|
|
file_date_hi: 0,
|
|
file_date_lo: 0,
|
|
}),
|
|
);
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_module(module)
|
|
.add(name);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let module_list = dump.get_stream::<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);
|
|
let mut exception = Exception::new(Endian::Little);
|
|
exception.exception_record.exception_code =
|
|
err::ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR as u32;
|
|
exception.exception_record.exception_address = address;
|
|
exception.exception_record.number_parameters = 3;
|
|
exception.exception_record.exception_information[0] =
|
|
err::ExceptionCodeWindowsInPageErrorType::WRITE as u64;
|
|
exception.exception_record.exception_information[1] = address;
|
|
exception.exception_record.exception_information[2] =
|
|
err::NtStatusWindows::STATUS_DISK_FULL as u64;
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_system_info(amd64_system_info)
|
|
.add_exception(exception);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let system_stream = dump.get_stream::<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);
|
|
let mut exception = Exception::new(Endian::Little);
|
|
exception.exception_record.exception_code =
|
|
err::ExceptionCodeWindows::EXCEPTION_IN_PAGE_ERROR as u32;
|
|
exception.exception_record.exception_address = address;
|
|
exception.exception_record.number_parameters = 3;
|
|
exception.exception_record.exception_information[0] =
|
|
err::ExceptionCodeWindowsInPageErrorType::WRITE as u64;
|
|
exception.exception_record.exception_information[1] = address;
|
|
// Sign extend the error code like 32-bit windbg.dll does
|
|
exception.exception_record.exception_information[2] =
|
|
0xffff_ffff_0000_0000 | (err::NtStatusWindows::STATUS_DISK_FULL as u64);
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_system_info(x86_system_info)
|
|
.add_exception(exception);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let system_stream = dump.get_stream::<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);
|
|
let mut exception = Exception::new(Endian::Little);
|
|
exception.exception_record.exception_code = err::ExceptionCodeLinux::SIGABRT as u32;
|
|
exception.exception_record.exception_flags = err::ExceptionCodeLinuxSicode::SI_TKILL as u32;
|
|
|
|
let dump = SynthMinidump::with_endian(Endian::Little)
|
|
.add_system_info(amd64_system_info)
|
|
.add_exception(exception);
|
|
let dump = read_synth_dump(dump).unwrap();
|
|
let system_stream = dump.get_stream::<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"
|
|
);
|
|
}
|
|
}
|