Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  minidump.rs   Sprache: unbekannt

 
// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
// file at the top-level directory of this distribution.

use debugid::{CodeId, DebugId};
use memmap2::Mmap;
use num_traits::FromPrimitive;
use procfs_core::prelude::*;
use procfs_core::process::{MMPermissions, MemoryMap, MemoryMaps};
use scroll::ctx::{SizeWith, TryFromCtx};
use scroll::{Pread, BE, LE};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::iter;
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::path::Path;
use std::str;
use std::time::{Duration, SystemTime};
use tracing::warn;
use uuid::Uuid;

pub use crate::context::*;
use crate::strings::*;
use crate::system_info::{Cpu, Os, PointerWidth};
use minidump_common::errors::{self as err};
use minidump_common::format::{self as md};
use minidump_common::format::{CvSignature, MINIDUMP_STREAM_TYPE};
use minidump_common::traits::{IntoRangeMapSafe, Module};
use range_map::{Range, RangeMap};
use time::format_description::well_known::Rfc3339;

/// An index into the contents of a minidump.
///
/// The `Minidump` struct represents the parsed header and
/// indices contained at the start of a minidump file. It can be instantiated
/// by calling the [`Minidump::read`][read] or
/// [`Minidump::read_path`][read_path] methods.
///
/// # Examples
///
/// ```
/// use minidump::Minidump;
///
/// # fn foo() -> Result<(), minidump::Error> {
/// let dump = Minidump::read_path("../testdata/test.dmp")?;
/// # Ok(())
/// # }
/// ```
///
/// [read]: struct.Minidump.html#method.read
/// [read_path]: struct.Minidump.html#method.read_path
#[derive(Debug)]
pub struct Minidump<'a, T>
where
    T: Deref<Target = [u8]> + 'a,
{
    data: T,
    /// The raw minidump header from the file.
    pub header: md::MINIDUMP_HEADER,
    streams: BTreeMap<u32, (u32, md::MINIDUMP_DIRECTORY)>,
    system_info: Option<MinidumpSystemInfo>,
    /// The endianness of this minidump file.
    pub endian: scroll::Endian,
    _phantom: PhantomData<&'a [u8]>,
}

/// Errors encountered while reading a `Minidump`.
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
    #[error("File not found")]
    FileNotFound,
    #[error("I/O error")]
    IoError,
    #[error("Missing minidump header (empty minidump?)")]
    MissingHeader,
    #[error("Header mismatch")]
    HeaderMismatch,
    #[error("Minidump version mismatch")]
    VersionMismatch,
    #[error("Missing stream directory (heavily truncated minidump?)")]
    MissingDirectory,
    #[error("Error reading stream")]
    StreamReadFailure,
    #[error("Stream size mismatch: expected {expected} bytes, found {actual} bytes")]
    StreamSizeMismatch { expected: usize, actual: usize },
    #[error("Stream not found")]
    StreamNotFound,
    #[error("Module read failure")]
    ModuleReadFailure,
    #[error("Memory read failure")]
    MemoryReadFailure,
    #[error("Data error")]
    DataError,
    #[error("Error reading CodeView data")]
    CodeViewReadFailure,
    #[error("Uknown element type")]
    UknownElementType,
}

impl Error {
    /// Returns just the name of the error, as a more human-friendly version of
    /// an error-code for error logging.
    pub fn name(&self) -> &'static str {
        match self {
            Error::FileNotFound => "FileNotFound",
            Error::IoError => "IoError",
            Error::MissingHeader => "MissingHeader",
            Error::HeaderMismatch => "HeaderMismatch",
            Error::VersionMismatch => "VersionMismatch",
            Error::MissingDirectory => "MissingDirectory",
            Error::StreamReadFailure => "StreamReadFailure",
            Error::StreamSizeMismatch { .. } => "StreamSizeMismatch",
            Error::StreamNotFound => "StreamNotFound",
            Error::ModuleReadFailure => "ModuleReadFailure",
            Error::MemoryReadFailure => "MemoryReadFailure",
            Error::DataError => "DataError",
            Error::CodeViewReadFailure => "CodeViewReadFailure",
            Error::UknownElementType => "UnknownElementType",
        }
    }
}

/// The fundamental unit of data in a `Minidump`.
pub trait MinidumpStream<'a>: Sized {
    /// The stream type constant used in the `md::MDRawDirectory` entry.
    /// This is usually a [MINIDUMP_STREAM_TYPE][] but it's left as a u32
    /// to allow external projects to add support for their own custom streams.
    const STREAM_TYPE: u32;

    /// Read this `MinidumpStream` type from `bytes`.
    ///
    /// * `bytes` is the contents of this specific stream.
    /// * `all` refers to the full contents of the minidump, for reading auxilliary data
    ///   referred to with `MINIDUMP_LOCATION_DESCRIPTOR`s.
    /// * `system_info` is the preparsed SystemInfo stream, if it exists in the minidump.
    fn read(
        bytes: &'a [u8],
        all: &'a [u8],
        endian: scroll::Endian,
        system_info: Option<&MinidumpSystemInfo>,
    ) -> Result<Self, Error>;
}

/// Provides a unified interface for getting metadata about the process's mapped memory 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<'a> Default for UnifiedMemoryList<'a> {
    fn default() -> Self {
        Self::Memory(Default::default())
    }
}

/// Information about an assertion that caused a crash.
#[derive(Debug)]
pub struct MinidumpAssertion {
    pub raw: md::MINIDUMP_ASSERTION_INFO,
}

/// A typed annotation object.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum MinidumpAnnotation {
    /// An invalid annotation. Reserved for internal use.
    Invalid,
    /// A `NUL`-terminated C-string.
    String(String),
    /// Clients may declare their own custom types.
    UserDefined(md::MINIDUMP_ANNOTATION),
    /// An unsupported annotation from a future crashpad version.
    Unsupported(md::MINIDUMP_ANNOTATION),
}

impl PartialEq for MinidumpAnnotation {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Invalid, Self::Invalid) => true,
            (Self::String(a), Self::String(b)) => a == b,
            _ => false,
        }
    }
}

/// Additional Crashpad-specific information about a module carried within a minidump file.
#[derive(Debug)]
pub struct MinidumpModuleCrashpadInfo {
    /// The raw crashpad module extension information.
    pub raw: md::MINIDUMP_MODULE_CRASHPAD_INFO,
    /// Index of the corresponding module in the `MinidumpModuleList`.
    pub module_index: usize,
    pub list_annotations: Vec<String>,
    pub simple_annotations: BTreeMap<String, String>,
    pub annotation_objects: BTreeMap<String, MinidumpAnnotation>,
}

/// Additional Crashpad-specific information carried within a minidump file.
#[derive(Debug)]
pub struct MinidumpCrashpadInfo {
    pub raw: md::MINIDUMP_CRASHPAD_INFO,
    pub simple_annotations: BTreeMap<String, String>,
    pub module_list: Vec<MinidumpModuleCrashpadInfo>,
}

//======================================================
// Implementations

fn format_time_t(t: u32) -> String {
    time::OffsetDateTime::from_unix_timestamp(t as i64)
        .ok()
        .and_then(|datetime| datetime.format(&Rfc3339).ok())
        .unwrap_or_default()
}

fn format_system_time(time: &md::SYSTEMTIME) -> String {
    // Note this drops the day_of_week field on the ground -- is that fine?
    let format_date = || {
        use std::convert::TryFrom;
        let month = time::Month::try_from(time.month as u8).ok()?;
        let date = time::Date::from_calendar_date(time.year as i32, month, time.day as u8).ok()?;
        let datetime = date
            .with_hms_milli(
                time.hour as u8,
                time.minute as u8,
                time.second as u8,
                time.milliseconds,
            )
            .ok()?
            .assume_utc();
        datetime.format(&Rfc3339).ok()
    };
    format_date().unwrap_or_else(|| "<invalid date>".to_owned())
}

/// Produce a slice of `bytes` corresponding to the offset and size in `loc`, or an
/// `Error` if the data is not fully contained within `bytes`.
fn location_slice<'a>(
    bytes: &'a [u8],
    loc: &md::MINIDUMP_LOCATION_DESCRIPTOR,
) -> Result<&'a [u8], Error> {
    let start = loc.rva as usize;
    start
        .checked_add(loc.data_size as usize)
        .and_then(|end| bytes.get(start..end))
        .ok_or(Error::StreamReadFailure)
}

/// Read a u32 length-prefixed UTF-16 string from `bytes` at `offset`.
fn read_string_utf16(offset: &mut usize, bytes: &[u8], endian: scroll::Endian) -> Option<String> {
    let u: u32 = bytes.gread_with(offset, endian).ok()?;
    let size = u as usize;
    if size % 2 != 0 || (*offset + size) > bytes.len() {
        return None;
    }
    let encoding = match endian {
        scroll::Endian::Little => encoding_rs::UTF_16LE,
        scroll::Endian::Big => encoding_rs::UTF_16BE,
    };

    let s = encoding
        .decode_without_bom_handling_and_without_replacement(&bytes[*offset..*offset + size])?;
    *offset += size;
    Some(s.into())
}

#[inline]
fn read_string_utf8_unterminated<'a>(
    offset: &mut usize,
    bytes: &'a [u8],
    endian: scroll::Endian,
) -> Option<&'a str> {
    let length: u32 = bytes.gread_with(offset, endian).ok()?;
    let slice = bytes.gread_with(offset, length as usize).ok()?;
    std::str::from_utf8(slice).ok()
}

fn read_string_utf8<'a>(
    offset: &mut usize,
    bytes: &'a [u8],
    endian: scroll::Endian,
) -> Option<&'a str> {
    let string = read_string_utf8_unterminated(offset, bytes, endian)?;
    match bytes.gread(offset) {
        Ok(0u8) => Some(string),
        _ => None,
    }
}

fn read_cstring_utf8(offset: &mut usize, bytes: &[u8]) -> Option<String> {
    let initial_offset = *offset;
    loop {
        let byte: u8 = bytes.gread(offset).ok()?;
        if byte == 0 {
            break;
        }
    }
    std::str::from_utf8(&bytes[initial_offset..*offset - 1])
        .map(String::from)
        .ok()
}

/// Convert `bytes` with trailing NUL characters to a string
fn string_from_bytes_nul(bytes: &[u8]) -> Option<Cow<'_, str>> {
    bytes.split(|&b| b == 0).next().map(String::from_utf8_lossy)
}

/// Format `bytes` as a String of hex digits
fn bytes_to_hex(bytes: &[u8]) -> String {
    let hex_bytes: Vec<String> = bytes.iter().map(|b| format!("{b:02x}")).collect();
    hex_bytes.join("")
}

/// Attempt to read a CodeView record from `data` at `location`
fn read_codeview(
    location: &md::MINIDUMP_LOCATION_DESCRIPTOR,
    data: &[u8],
    endian: scroll::Endian,
) -> Option<CodeView> {
    let bytes = location_slice(data, location).ok()?;
    // The CodeView data can be one of a few different formats. Try to read the
    // signature first to figure out what format the data is.
    let signature: u32 = bytes.pread_with(0, endian).ok()?;
    Some(match CvSignature::from_u32(signature) {
        // PDB data has two known versions: the current 7.0 and the older 2.0 version.
        Some(CvSignature::Pdb70) => CodeView::Pdb70(bytes.pread_with(0, endian).ok()?),
        Some(CvSignature::Pdb20) => CodeView::Pdb20(bytes.pread_with(0, endian).ok()?),
        // Breakpad's ELF build ID format.
        Some(CvSignature::Elf) => CodeView::Elf(bytes.pread_with(0, endian).ok()?),
        // Other formats aren't handled, but save the raw bytes.
        _ => CodeView::Unknown(bytes.to_owned()),
    })
}

fn read_debug_id(codeview_info: &CodeView, endian: scroll::Endian) -> Option<DebugId> {
    match codeview_info {
        CodeView::Pdb70(ref raw) => {
            // For macOS, this should be its code ID with the age (0)
            // appended to the end of it. This makes it identical to debug
            // IDs for Windows, and is why it doesn't have a special case
            // here.
            let uuid = Uuid::from_fields(
                raw.signature.data1,
                raw.signature.data2,
                raw.signature.data3,
                &raw.signature.data4,
            );
            (!uuid.is_nil()).then(|| DebugId::from_parts(uuid, raw.age))
        }
        CodeView::Pdb20(ref raw) => Some(DebugId::from_pdb20(raw.signature, raw.age)),
        CodeView::Elf(ref raw) => {
            // For empty or trivial `build_id`s, we don't want to return a `DebugId`.
            // This can happen for mapped files that aren't executable, like fonts or .jar files.
            if raw.build_id.iter().all(|byte| *byte == 0) {
                return None;
            }

            // For backwards-compat (Linux minidumps have historically
            // been written using PDB70 CodeView info), treat build_id
            // as if the first 16 bytes were a GUID.
            let guid_size = <md::GUID>::size_with(&endian);
            let guid = if raw.build_id.len() < guid_size {
                // Pad with zeros.
                let v: Vec<u8> = raw
                    .build_id
                    .iter()
                    .cloned()
                    .chain(iter::repeat(0))
                    .take(guid_size)
                    .collect();
                v.pread_with::<md::GUID>(0, endian).ok()
            } else {
                raw.build_id.pread_with::<md::GUID>(0, endian).ok()
            };
            guid.map(|g| Uuid::from_fields(g.data1, g.data2, g.data3, &g.data4))
                .map(DebugId::from_uuid)
        }
        _ => None,
    }
}

/// Checks that the buffer is large enough for the given number of items.
///
/// Essentially ensures that `buf.len() >= offset + (number_of_entries * size_of_entry)`.
/// Returns `(number_of_entries, expected_size)` on success.
fn ensure_count_in_bound(
    buf: &[u8],
    number_of_entries: usize,
    size_of_entry: usize,
    offset: usize,
) -> Result<(usize, usize), Error> {
    let expected_size = number_of_entries
        .checked_mul(size_of_entry)
        .and_then(|v| v.checked_add(offset))
        .ok_or(Error::StreamReadFailure)?;
    if buf.len() < expected_size {
        return Err(Error::StreamSizeMismatch {
            expected: expected_size,
            actual: buf.len(),
        });
    }
    Ok((number_of_entries, expected_size))
}

impl MinidumpModule {
    /// Create a `MinidumpModule` with some basic info.
    ///
    /// Useful for testing.
    pub fn new(base: u64, size: u32, name: &str) -> MinidumpModule {
        MinidumpModule {
            raw: md::MINIDUMP_MODULE {
                base_of_image: base,
                size_of_image: size,
                ..md::MINIDUMP_MODULE::default()
            },
            name: String::from(name),
            codeview_info: None,
            misc_info: None,
            os: Os::Unknown(0),
            debug_id: None,
        }
    }

    /// Read additional data to construct a `MinidumpModule` from `bytes` using the information
    /// from the module list in `raw`.
    pub fn read(
        raw: md::MINIDUMP_MODULE,
        bytes: &[u8],
        endian: scroll::Endian,
        system_info: Option<&MinidumpSystemInfo>,
    ) -> Result<MinidumpModule, Error> {
        let mut offset = raw.module_name_rva as usize;
        let name =
            read_string_utf16(&mut offset, bytes, endian).ok_or(Error::CodeViewReadFailure)?;
        let codeview_info = if raw.cv_record.data_size == 0 {
            None
        } else {
            Some(read_codeview(&raw.cv_record, bytes, endian).ok_or(Error::CodeViewReadFailure)?)
        };

        let os = system_info.map(|info| info.os).unwrap_or(Os::Unknown(0));

        let debug_id = codeview_info
            .as_ref()
            .and_then(|cv| read_debug_id(cv, endian));

        Ok(MinidumpModule {
            raw,
            name,
            codeview_info,
            misc_info: None,
            os,
            debug_id,
        })
    }

    /// Write a human-readable description of this `MinidumpModule` to `f`.
    ///
    /// This is very verbose, it is the format used by `minidump_dump`.
    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
        write!(
            f,
            "MINIDUMP_MODULE
  base_of_image                   = {:#x}
  size_of_image                   = {:#x}
  checksum                        = {:#x}
  time_date_stamp                 = {:#x} {}
  module_name_rva                 = {:#x}
  version_info.signature          = {:#x}
  version_info.struct_version     = {:#x}
  version_info.file_version       = {:#x}:{:#x}
  version_info.product_version    = {:#x}:{:#x}
  version_info.file_flags_mask    = {:#x}
  version_info.file_flags         = {:#x}
  version_info.file_os            = {:#x}
  version_info.file_type          = {:#x}
  version_info.file_subtype       = {:#x}
  version_info.file_date          = {:#x}:{:#x}
  cv_record.data_size             = {}
  cv_record.rva                   = {:#x}
  misc_record.data_size           = {}
  misc_record.rva                 = {:#x}
  (code_file)                     = \"{}\"
  (code_identifier)               = \"{}\"
",
            self.raw.base_of_image,
            self.raw.size_of_image,
            self.raw.checksum,
            self.raw.time_date_stamp,
            format_time_t(self.raw.time_date_stamp),
            self.raw.module_name_rva,
            self.raw.version_info.signature,
            self.raw.version_info.struct_version,
            self.raw.version_info.file_version_hi,
            self.raw.version_info.file_version_lo,
            self.raw.version_info.product_version_hi,
            self.raw.version_info.product_version_lo,
            self.raw.version_info.file_flags_mask,
            self.raw.version_info.file_flags,
            self.raw.version_info.file_os,
            self.raw.version_info.file_type,
            self.raw.version_info.file_subtype,
            self.raw.version_info.file_date_hi,
            self.raw.version_info.file_date_lo,
            self.raw.cv_record.data_size,
            self.raw.cv_record.rva,
            self.raw.misc_record.data_size,
            self.raw.misc_record.rva,
            self.code_file(),
            self.code_identifier().unwrap_or_default(),
        )?;
        // Print CodeView data.
        match self.codeview_info {
            Some(CodeView::Pdb70(ref raw)) => {
                let pdb_file_name =
                    string_from_bytes_nul(&raw.pdb_file_name).unwrap_or(Cow::Borrowed("(invalid)"));
                write!(f, "  (cv_record).cv_signature        = {:#x}
  (cv_record).signature           = {:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}
  (cv_record).age                 = {}
  (cv_record).pdb_file_name       = \"{}\"
",
                       raw.cv_signature,
                       raw.signature.data1,
                       raw.signature.data2,
                       raw.signature.data3,
                       raw.signature.data4[0],
                       raw.signature.data4[1],
                       raw.signature.data4[2],
                       raw.signature.data4[3],
                       raw.signature.data4[4],
                       raw.signature.data4[5],
                       raw.signature.data4[6],
                       raw.signature.data4[7],
                       raw.age,
                       pdb_file_name,
                )?;
            }
            Some(CodeView::Pdb20(ref raw)) => {
                let pdb_file_name =
                    string_from_bytes_nul(&raw.pdb_file_name).unwrap_or(Cow::Borrowed("(invalid)"));
                write!(
                    f,
                    "  (cv_record).cv_header.signature = {:#x}
  (cv_record).cv_header.offset    = {:#x}
  (cv_record).signature           = {:#x} {}
  (cv_record).age                 = {}
  (cv_record).pdb_file_name       = \"{}\"
",
                    raw.cv_signature,
                    raw.cv_offset,
                    raw.signature,
                    format_time_t(raw.signature),
                    raw.age,
                    pdb_file_name,
                )?;
            }
            Some(CodeView::Elf(ref raw)) => {
                // Fibbing about having cv_signature handy here.
                write!(
                    f,
                    "  (cv_record).cv_signature        = {:#x}
  (cv_record).build_id            = {}
",
                    raw.cv_signature,
                    bytes_to_hex(&raw.build_id),
                )?;
            }
            Some(CodeView::Unknown(ref bytes)) => {
                writeln!(
                    f,
                    "  (cv_record)                     = {}",
                    bytes_to_hex(bytes),
                )?;
            }
            None => {
                writeln!(f, "  (cv_record)                     = (null)")?;
            }
        }

        // Print misc record data.
        if let Some(ref _misc) = self.misc_info {
            //TODO, not terribly important.
            writeln!(f, "  (misc_record)                   = (unimplemented)")?;
        } else {
            writeln!(f, "  (misc_record)                   = (null)")?;
        }

        // Print remaining data.
        write!(
            f,
            r#"  (debug_file)                    = "{}"
  (debug_identifier)              = "{}"
  (version)                       = "{}"

"#,
            self.debug_file().unwrap_or(Cow::Borrowed("")),
            self.debug_identifier().unwrap_or_default(),
            self.version().unwrap_or(Cow::Borrowed("")),
        )?;
        Ok(())
    }

    fn memory_range(&self) -> Option<Range<u64>> {
        if self.size() == 0 {
            return None;
        }
        Some(Range::new(
            self.base_address(),
            self.base_address().checked_add(self.size())? - 1,
        ))
    }
}

impl Module for MinidumpModule {
    fn base_address(&self) -> u64 {
        self.raw.base_of_image
    }
    fn size(&self) -> u64 {
        self.raw.size_of_image as u64
    }
    fn code_file(&self) -> Cow<'_, str> {
        Cow::Borrowed(&self.name)
    }

    fn code_identifier(&self) -> Option<CodeId> {
        match self.codeview_info {
            Some(CodeView::Pdb70(ref raw)) if matches!(self.os, Os::MacOs | Os::Ios) => {
                // MacOs uses PDB70 instead of its own dedicated format.
                // See the following issue for a potential MacOs-specific format:
                // https://github.com/rust-minidump/rust-minidump/issues/455
                Some(CodeId::new(format!("{:#}", raw.signature)))
            }
            Some(CodeView::Pdb20(_)) | Some(CodeView::Pdb70(_)) => Some(CodeId::new(format!(
                "{0:08X}{1:x}",
                self.raw.time_date_stamp, self.raw.size_of_image
            ))),
            Some(CodeView::Elf(ref raw)) => {
                // Return None instead of sentinel CodeIds for empty
                // `build_id`s. Non-executable mapped files like fonts or .jar
                // files will usually fall under this case.
                if raw.build_id.iter().all(|byte| *byte == 0) {
                    None
                } else {
                    Some(CodeId::from_binary(&raw.build_id))
                }
            }
            None if self.os == Os::Windows => {
                // Fall back to the timestamp + size-based debug-id for Windows.
                // Some Module records from Windows have no codeview record, but
                // the CodeId generated here is valid and can be looked up on
                // the Microsoft symbol server.
                // One example might be `wow64cpu.dll` with code-id `378BC3CDa000`.
                // This can however lead to "false positive" code-ids for modules
                // that have no timestamp, in which case the code-id looks extremely
                // low-entropy. The same can happen though if they *do* have a
                // codeview record.
                Some(CodeId::new(format!(
                    "{0:08X}{1:x}",
                    self.raw.time_date_stamp, self.raw.size_of_image
                )))
            }
            // Occasionally things will make it into the module stream that
            // shouldn't be there, and so no meaningful CodeId can be found from
            // those. One of those things are SysV shared memory segments which
            // have no CodeView record.
            _ => None,
        }
    }
    fn debug_file(&self) -> Option<Cow<'_, str>> {
        match self.codeview_info {
            Some(CodeView::Pdb70(ref raw)) => string_from_bytes_nul(&raw.pdb_file_name),
            Some(CodeView::Pdb20(ref raw)) => string_from_bytes_nul(&raw.pdb_file_name),
            Some(CodeView::Elf(_)) => Some(Cow::Borrowed(&self.name)),
            // TODO: support misc record? not really important.
            _ => None,
        }
    }
    fn debug_identifier(&self) -> Option<DebugId> {
        self.debug_id
    }
    fn version(&self) -> Option<Cow<'_, str>> {
        if self.raw.version_info.signature == md::VS_FFI_SIGNATURE
            && self.raw.version_info.struct_version == md::VS_FFI_STRUCVERSION
        {
            if matches!(self.os, Os::MacOs | Os::Ios | Os::Windows) {
                let ver = format!(
                    "{}.{}.{}.{}",
                    self.raw.version_info.file_version_hi >> 16,
                    self.raw.version_info.file_version_hi & 0xffff,
                    self.raw.version_info.file_version_lo >> 16,
                    self.raw.version_info.file_version_lo & 0xffff
                );
                Some(Cow::Owned(ver))
            } else {
                // Assume Elf
                let ver = format!(
                    "{}.{}.{}.{}",
                    self.raw.version_info.file_version_hi,
                    self.raw.version_info.file_version_lo,
                    self.raw.version_info.product_version_hi,
                    self.raw.version_info.product_version_lo
                );
                Some(Cow::Owned(ver))
            }
        } else {
            None
        }
    }
}

impl MinidumpUnloadedModule {
    /// Create a `MinidumpUnloadedModule` with some basic info.
    ///
    /// Useful for testing.
    pub fn new(base: u64, size: u32, name: &str) -> MinidumpUnloadedModule {
        MinidumpUnloadedModule {
            raw: md::MINIDUMP_UNLOADED_MODULE {
                base_of_image: base,
                size_of_image: size,
                ..md::MINIDUMP_UNLOADED_MODULE::default()
            },
            name: String::from(name),
        }
    }

    /// Read additional data to construct a `MinidumpUnloadedModule` from `bytes` using the information
    /// from the module list in `raw`.
    pub fn read(
        raw: md::MINIDUMP_UNLOADED_MODULE,
        bytes: &[u8],
        endian: scroll::Endian,
    ) -> Result<MinidumpUnloadedModule, Error> {
        let mut offset = raw.module_name_rva as usize;
        let name = read_string_utf16(&mut offset, bytes, endian).ok_or(Error::DataError)?;
        Ok(MinidumpUnloadedModule { raw, name })
    }

    /// Write a human-readable description of this `MinidumpModule` to `f`.
    ///
    /// This is very verbose, it is the format used by `minidump_dump`.
    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
        write!(
            f,
            "MINIDUMP_UNLOADED_MODULE
  base_of_image                   = {:#x}
  size_of_image                   = {:#x}
  checksum                        = {:#x}
  time_date_stamp                 = {:#x} {}
  module_name_rva                 = {:#x}
  (code_file)                     = \"{}\"
  (code_identifier)               = \"{}\"
",
            self.raw.base_of_image,
            self.raw.size_of_image,
            self.raw.checksum,
            self.raw.time_date_stamp,
            format_time_t(self.raw.time_date_stamp),
            self.raw.module_name_rva,
            self.code_file(),
            self.code_identifier().unwrap_or_default(),
        )?;

        Ok(())
    }

    fn memory_range(&self) -> Option<Range<u64>> {
        if self.size() == 0 {
            return None;
        }
        Some(Range::new(
            self.base_address(),
            self.base_address().checked_add(self.size())? - 1,
        ))
    }
}

impl Module for MinidumpUnloadedModule {
    fn base_address(&self) -> u64 {
        self.raw.base_of_image
    }
    fn size(&self) -> u64 {
        self.raw.size_of_image as u64
    }
    fn code_file(&self) -> Cow<'_, str> {
        Cow::Borrowed(&self.name)
    }
    fn code_identifier(&self) -> Option<CodeId> {
        // TODO: This should be returning None if the unloaded module is coming
        // from a non-Windows minidump. We'll need info about the operating
        // system, ideally sourced from the SystemInfo to be able to do this.
        Some(CodeId::new(format!(
            "{0:08X}{1:x}",
            self.raw.time_date_stamp, self.raw.size_of_image
        )))
    }
    fn debug_file(&self) -> Option<Cow<'_, str>> {
        None
    }
    fn debug_identifier(&self) -> Option<DebugId> {
        None
    }
    fn version(&self) -> Option<Cow<'_, str>> {
        None
    }
}

/// Parses X:Y or X=Y lists, skipping any blank/unparseable lines
fn linux_list_iter(
    bytes: &[u8],
    separator: u8,
) -> impl Iterator<Item = (&LinuxOsStr, &LinuxOsStr)> {
    fn strip_quotes(input: &LinuxOsStr) -> &LinuxOsStr {
        // Remove any extra surrounding whitespace since formats are inconsistent on this.
        let input = input.trim_ascii_whitespace();

        // Convert `"MyValue"` into `MyValue`, or just return the trimmed input.
        let output = input
            .strip_prefix(b"\"")
            .and_then(|input| input.strip_suffix(b"\""))
            .unwrap_or(input);

        LinuxOsStr::from_bytes(output)
    }

    let input = LinuxOsStr::from_bytes(bytes);
    input.lines().filter_map(move |line| {
        line.split_once(separator)
            .map(|(label, val)| (strip_quotes(label), (strip_quotes(val))))
    })
}

fn read_stream_list<'a, T>(
    offset: &mut usize,
    bytes: &'a [u8],
    endian: scroll::Endian,
) -> Result<Vec<T>, Error>
where
    T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
    T: SizeWith<scroll::Endian>,
{
    let u: u32 = bytes
        .gread_with(offset, endian)
        .or(Err(Error::StreamReadFailure))?;

    let (count, counted_size) = ensure_count_in_bound(
        bytes,
        u as usize,
        <T>::size_with(&endian),
        mem::size_of::<u32>(),
    )?;

    match bytes.len() - counted_size {
        0 => {}
        4 => {
            // 4 bytes of padding.
            *offset += 4;
        }
        _ => {
            return Err(Error::StreamSizeMismatch {
                expected: counted_size,
                actual: bytes.len(),
            });
        }
    };
    // read count T raw stream entries
    let mut raw_entries = Vec::with_capacity(count);
    for _ in 0..count {
        let raw: T = bytes
            .gread_with(offset, endian)
            .or(Err(Error::StreamReadFailure))?;
        raw_entries.push(raw);
    }
    Ok(raw_entries)
}

fn read_ex_stream_list<'a, T>(
    offset: &mut usize,
    bytes: &'a [u8],
    endian: scroll::Endian,
) -> Result<Vec<T>, Error>
where
    T: TryFromCtx<'a, scroll::Endian, [u8], Error = scroll::Error>,
    T: SizeWith<scroll::Endian>,
{
    // Some newer list streams have an extended header:
    //
    // size_of_header: u32,
    // size_of_entry: u32,
    // number_of_entries: u32,
    // ...entries

    // In theory this allows the format of the stream to be extended without
    // us knowing how to handle the new parts.

    let size_of_header: u32 = bytes
        .gread_with(offset, endian)
        .or(Err(Error::StreamReadFailure))?;

    let size_of_entry: u32 = bytes
        .gread_with(offset, endian)
        .or(Err(Error::StreamReadFailure))?;

    let number_of_entries: u32 = bytes
        .gread_with(offset, endian)
        .or(Err(Error::StreamReadFailure))?;

    let expected_size_of_entry = <T>::size_with(&endian);

    if size_of_entry as usize != expected_size_of_entry {
        // For now, conservatively bail out if entries don't have
        // the expected size. In theory we can assume entries are
        // always extended with new trailing fields, and this information
        // would let us walk over trailing fields we don't know about?
        // But without an example let's be safe.
        return Err(Error::StreamReadFailure);
    }

    let (number_of_entries, _) = ensure_count_in_bound(
        bytes,
        number_of_entries as usize,
        size_of_entry as usize,
        size_of_header as usize,
    )?;

    let header_padding = match (size_of_header as usize).checked_sub(*offset) {
        Some(s) => s,
        None => return Err(Error::StreamReadFailure),
    };
    *offset += header_padding;

    // read count T raw stream entries
    let mut raw_entries = Vec::with_capacity(number_of_entries);
    for _ in 0..number_of_entries {
        let raw: T = bytes
            .gread_with(offset, endian)
            .or(Err(Error::StreamReadFailure))?;
        raw_entries.push(raw);
    }
    Ok(raw_entries)
}

impl<'a> MinidumpStream<'a> for MinidumpThreadNames {
    const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ThreadNamesStream as u32;

    fn read(
        bytes: &'a [u8],
        all: &'a [u8],
        endian: scroll::Endian,
        _system_info: Option<&MinidumpSystemInfo>,
    ) -> Result<Self, Error> {
        let mut offset = 0;
        let raw_names: Vec<md::MINIDUMP_THREAD_NAME> =
            read_stream_list(&mut offset, bytes, endian)?;
        // read out the actual names
        let mut names = BTreeMap::new();
        for raw_name in raw_names {
            let mut offset = raw_name.thread_name_rva as usize;
            // Better to just drop unreadable names individually than the whole stream.
            if let Some(name) = read_string_utf16(&mut offset, all, endian) {
                names.insert(raw_name.thread_id, name);
            } else {
                warn!(
                    "Couldn't read thread name for thread id {}",
                    raw_name.thread_id
                );
            }
        }
        Ok(MinidumpThreadNames { names })
    }
}

impl MinidumpThreadNames {
    pub fn get_name(&self, thread_id: u32) -> Option<Cow<str>> {
        self.names
            .get(&thread_id)
            .map(|name| Cow::Borrowed(&**name))
    }

    /// Write a human-readable description of this `MinidumpThreadNames` to `f`.
    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
        write!(
            f,
            "MinidumpThreadNames
  thread_count = {}

",
            self.names.len()
        )?;
        for (i, (thread_id, name)) in self.names.iter().enumerate() {
            writeln!(
                f,
                "thread_name[{i}]
MINIDUMP_THREAD_NAME
  thread_id = {thread_id:#x}
  name      = \"{name}\"
"
            )?;
        }

        Ok(())
    }
}

impl MinidumpModuleList {
    /// Return an empty `MinidumpModuleList`.
    pub fn new() -> MinidumpModuleList {
        MinidumpModuleList {
            modules: vec![],
            modules_by_addr: RangeMap::new(),
        }
    }
    /// Create a `MinidumpModuleList` from a list of `MinidumpModule`s.
    pub fn from_modules(modules: Vec<MinidumpModule>) -> MinidumpModuleList {
        let modules_by_addr = modules
            .iter()
            .enumerate()
            .map(|(i, module)| (module.memory_range(), i))
            .into_rangemap_safe();
        MinidumpModuleList {
            modules,
            modules_by_addr,
        }
    }

    /// Returns the module corresponding to the main executable.
    pub fn main_module(&self) -> Option<&MinidumpModule> {
        // The main code module is the first one present in a minidump file's
        // MINIDUMP_MODULEList.
        if !self.modules.is_empty() {
            Some(&self.modules[0])
        } else {
            None
        }
    }

    /// Return a `MinidumpModule` whose address range covers `address`.
    pub fn module_at_address(&self, address: u64) -> Option<&MinidumpModule> {
        self.modules_by_addr
            .get(address)
            .map(|&index| &self.modules[index])
    }

    /// Iterate over the modules in arbitrary order.
    pub fn iter(&self) -> impl Iterator<Item = &MinidumpModule> {
        self.modules.iter()
    }

    /// Iterate over the modules in order by memory address.
    pub fn by_addr(&self) -> impl DoubleEndedIterator<Item = &MinidumpModule> {
        self.modules_by_addr
            .ranges_values()
            .map(move |&(_, index)| &self.modules[index])
    }

    /// Write a human-readable description of this `MinidumpModuleList` to `f`.
    ///
    /// This is very verbose, it is the format used by `minidump_dump`.
    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
        write!(
            f,
            "MinidumpModuleList
  module_count = {}

",
            self.modules.len()
        )?;
        for (i, module) in self.modules.iter().enumerate() {
            writeln!(f, "module[{i}]")?;
            module.print(f)?;
        }
        Ok(())
    }
}

impl Default for MinidumpModuleList {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> MinidumpStream<'a> for MinidumpModuleList {
    const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::ModuleListStream as u32;

    fn read(
        bytes: &'a [u8],
        all: &'a [u8],
        endian: scroll::Endian,
        system_info: Option<&MinidumpSystemInfo>,
    ) -> Result<MinidumpModuleList, Error> {
        let mut offset = 0;
        let raw_modules: Vec<md::MINIDUMP_MODULE> = read_stream_list(&mut offset, bytes, endian)?;
        // read auxiliary data for each module
        let mut modules = Vec::with_capacity(raw_modules.len());
        for (module_index, raw) in raw_modules.into_iter().enumerate() {
            if raw.size_of_image == 0 || raw.size_of_image as u64 > (u64::MAX - raw.base_of_image) {
                // Bad image size.
                tracing::warn!(
                    module_index,
                    base = raw.base_of_image,
                    size = raw.size_of_image,
                    "bad module image size"
                );
                continue;
            }
            modules.push(MinidumpModule::read(raw, all, endian, system_info)?);
        }
        Ok(MinidumpModuleList::from_modules(modules))
    }
}

impl MinidumpUnloadedModuleList {
    /// Return an empty `MinidumpModuleList`.
    pub fn new() -> MinidumpUnloadedModuleList {
        MinidumpUnloadedModuleList {
            modules: vec![],
            modules_by_addr: vec![],
        }
    }
    /// Create a `MinidumpModuleList` from a list of `MinidumpModule`s.
    pub fn from_modules(modules: Vec<MinidumpUnloadedModule>) -> MinidumpUnloadedModuleList {
        let mut modules_by_addr = (0..modules.len())
            .filter_map(|i| modules[i].memory_range().map(|r| (r, i)))
            .collect::<Vec<_>>();

        modules_by_addr.sort_by_key(|(range, _idx)| *range);

        MinidumpUnloadedModuleList {
            modules,
            modules_by_addr,
        }
    }

    /// Return an iterator of `MinidumpUnloadedModules` whose address range covers `address`.
    pub fn modules_at_address(
        &self,
        address: u64,
    ) -> impl Iterator<Item = &MinidumpUnloadedModule> {
        // We have all of our modules sorted by memory range (base address being the
        // high-order value), and we need to get the range of values that overlap
        // with our target address. I'm a bit too tired to work out the exact
        // combination of binary searches to do this, so let's just use `filter`
        // for now (unloaded_modules should be a bounded list anyway).
        self.modules_by_addr
            .iter()
            .filter(move |(range, _idx)| range.contains(address))
            .map(move |(_range, idx)| &self.modules[*idx])
    }

    /// Iterate over the modules in arbitrary order.
    pub fn iter(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
        self.modules.iter()
    }

    /// Iterate over the modules in order by memory address.
    pub fn by_addr(&self) -> impl Iterator<Item = &MinidumpUnloadedModule> {
        self.modules_by_addr
            .iter()
            .map(move |&(_, index)| &self.modules[index])
    }

    /// Write a human-readable description of this `MinidumpModuleList` to `f`.
    ///
    /// This is very verbose, it is the format used by `minidump_dump`.
    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
        write!(
            f,
            "MinidumpUnloadedModuleList
  module_count = {}

",
            self.modules.len()
        )?;
        for (i, module) in self.modules.iter().enumerate() {
            writeln!(f, "module[{i}]")?;
            module.print(f)?;
        }
        Ok(())
    }
}

impl Default for MinidumpUnloadedModuleList {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> MinidumpStream<'a> for MinidumpUnloadedModuleList {
    const STREAM_TYPE: u32 = MINIDUMP_STREAM_TYPE::UnloadedModuleListStream as u32;

    fn read(
        bytes: &'a [u8],
        all: &'a [u8],
        endian: scroll::Endian,
        _system_info: Option<&MinidumpSystemInfo>,
    ) -> Result<MinidumpUnloadedModuleList, Error> {
        let mut offset = 0;
        let raw_modules: Vec<md::MINIDUMP_UNLOADED_MODULE> =
            read_ex_stream_list(&mut offset, bytes, endian)?;
        // read auxiliary data for each module
        let mut modules = Vec::with_capacity(raw_modules.len());
        for raw in raw_modules.into_iter() {
            if raw.size_of_image == 0 || raw.size_of_image as u64 > (u64::MAX - raw.base_of_image) {
                // Bad image size.
                // TODO: just drop this module, keep the rest?
                return Err(Error::ModuleReadFailure);
            }
            modules.push(MinidumpUnloadedModule::read(raw, all, endian)?);
        }
        Ok(MinidumpUnloadedModuleList::from_modules(modules))
    }
}

// Generates an accessor for a HANDLE_DESCRIPTOR field with the following syntax:
//
// * VERSION_NUMBER: FIELD_NAME -> FIELD_TYPE
//
// With the following definitions:
//
// * VERSION_NUMBER: The HANDLE_DESCRIPTOR version this field was introduced in
// * FIELD_NAME: The name of the field to read
// * FIELD_TYPE: The type of the field
macro_rules! handle_descriptor_accessors {
    () => {};
    (@def $name:ident $t:ty [$($variant:ident)+]) => {
        #[allow(unreachable_patterns)]
        pub fn $name(&self) -> Option<&$t> {
            match self {
                $(
                    RawHandleDescriptor::$variant(ref raw) => Some(&raw.$name),
                )+
                _ => None,
            }
        }
    };
    (1: $name:ident -> $t:ty, $($rest:tt)*) => {
        handle_descriptor_accessors!(@def $name $t [HandleDescriptor HandleDescriptor2]);
        handle_descriptor_accessors!($($rest)*);
    };

    (2: $name:ident -> $t:ty, $($rest:tt)*) => {
        handle_descriptor_accessors!(@def $name $t [HandleDescriptor2]);
        handle_descriptor_accessors!($($rest)*);
    };
}

impl RawHandleDescriptor {
    handle_descriptor_accessors!(
        1: handle -> u64,
        1: type_name_rva -> md::RVA,
        1: object_name_rva -> md::RVA,
        1: attributes -> u32,
        1: granted_access -> u32,
        1: handle_count -> u32,
        1: pointer_count -> u32,
        2: object_info_rva -> md::RVA,
    );
}

impl MinidumpHandleDescriptor {
    /// Write a human-readable description.
    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
        macro_rules! write_simple_field {
            ($stream:ident, $field:ident, $format:literal) => {
                write!(f, "  {:18}= ", stringify!($field))?;
                match self.raw.$field() {
                    Some($field) => {
                        writeln!(f, $format, $field)?;
                    }
                    None => writeln!(f, "(invalid)")?,
                }
            };
            ($stream:ident, $field:ident) => {
                write_simple_field!($stream, $field, "{}");
            };
        }

        writeln!(f, "MINIDUMP_HANDLE_DESCRIPTOR")?;
        write_simple_field!(f, handle, "{:#x}");
        write_simple_field!(f, type_name_rva, "{:#x}");
        write_simple_field!(f, object_name_rva, "{:#x}");
        write_simple_field!(f, attributes, "{:#x}");
        write_simple_field!(f, granted_access, "{:#x}");
        write_simple_field!(f, handle_count);
        write_simple_field!(f, pointer_count);
        write_simple_field!(f, object_info_rva, "{:#x}");
        write!(f, "  (type_name)       = ")?;
        if let Some(type_name) = &self.type_name {
            writeln!(f, "{type_name:}")?;
        } else {
            writeln!(f, "(null)")?;
        };
        write!(f, "  (object_name)     = ")?;
        if let Some(object_name) = &self.object_name {
            writeln!(f, "{object_name:}")?;
        } else {
            writeln!(f, "(null)")?;
        };
        if self.object_infos.is_empty() {
            writeln!(f, "  (object_info)     = (null)")?;
        } else {
            for object_info in &self.object_infos {
                writeln!(f, "  (object_info)     = {object_info:}")?;
            }
        }
        writeln!(f)
    }

    fn read_string(offset: usize, ctx: HandleDescriptorContext) -> Option<String> {
        let mut offset = offset;
        if offset != 0 {
            read_string_utf16(&mut offset, ctx.bytes, ctx.endianess)
        } else {
            None
        }
    }

    fn read_object_info(
        offset: usize,
        ctx: HandleDescriptorContext,
    ) -> Option<MinidumpHandleObjectInformation> {
        if offset != 0 {
            ctx.bytes
                .pread_with::<md::MINIDUMP_HANDLE_OBJECT_INFORMATION>(offset, ctx.endianess)
                .ok()
                .map(|raw| MinidumpHandleObjectInformation {
                    raw: raw.clone(),
                    info_type: md::MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE::from_u32(raw.info_type)
                        .unwrap(),
                })
        } else {
            None
        }
    }
}

#[derive(Copy, Clone)]
struct HandleDescriptorContext<'a> {
    bytes: &'a [u8],
    fieldsize: u32,
    endianess: scroll::Endian,
}

impl<'a> HandleDescriptorContext<'a> {
    fn new(
        bytes: &'a [u8],
        fieldsize: u32,
        endianess: scroll::Endian,
    ) -> HandleDescriptorContext<'a> {
        HandleDescriptorContext {
            bytes,
            fieldsize,
            endianess,
        }
    }
}

impl<'a> TryFromCtx<'a, HandleDescriptorContext<'a>> for MinidumpHandleDescriptor {
    type Error = scroll::Error;

    fn try_from_ctx(
        src: &'a [u8],
        ctx: HandleDescriptorContext,
    ) -> Result<(Self, usize), Self::Error> {
        const MINIDUMP_HANDLE_DESCRIPTOR_SIZE: u32 =
            mem::size_of::<md::MINIDUMP_HANDLE_DESCRIPTOR>() as u32;
        const MINIDUMP_HANDLE_DESCRIPTOR_2_SIZE: u32 =
            mem::size_of::<md::MINIDUMP_HANDLE_DESCRIPTOR_2>() as u32;

        match ctx.fieldsize {
            MINIDUMP_HANDLE_DESCRIPTOR_SIZE => {
                let raw = src.pread_with::<md::MINIDUMP_HANDLE_DESCRIPTOR>(0, ctx.endianess)?;
                let type_name = Self::read_string(raw.type_name_rva as usize, ctx);
                let object_name = Self::read_string(raw.object_name_rva as usize, ctx);
                Ok((
                    MinidumpHandleDescriptor {
                        raw: RawHandleDescriptor::HandleDescriptor(raw),
                        type_name,
                        object_name,
                        object_infos: Vec::new(),
                    },
                    ctx.fieldsize as usize,
                ))
            }
            MINIDUMP_HANDLE_DESCRIPTOR_2_SIZE => {
--> --------------------

--> maximum size reached

--> --------------------

[ zur Elbe Produktseite wechseln0.43Quellennavigators  Analyse erneut starten  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge