use std::{
collections::BTreeMap,
io::{self, Cursor, Seek},
path::Path,
ptr::{self},
};
use binrw::BinRead;
use crate::mapper::MappingFile;
use crate::error::UserDmpError;
use crate::data::{
MINIDUMP_STREAM_TYPE::{self, *},
*,
};
pub type Modules<'a> = BTreeMap<u64, Module<'a>>;
pub type Threads = BTreeMap<u32, Thread>;
pub type Handles = BTreeMap<u64, Handle>;
pub type Memorys<'a> = BTreeMap<u64, Memory<'a>>;
pub type Result<T> = std::result::Result<T, UserDmpError>;
#[derive(Copy, Debug, Clone, Default)]
pub enum Arch {
#[default]
X64,
X86,
}
pub trait MinidumpStream<'a> {
type Output;
fn parse(cursor: &mut Cursor<&'a [u8]>) -> Result<Self::Output>;
}
#[derive(Debug)]
pub struct UserDump<'a> {
pub exception_thread_id: Option<u32>,
pub system: System,
modules: Modules<'a>,
threads: Threads,
memorys: Memorys<'a>,
handles: Handles,
pub mapped_file: MappingFile<'a>,
}
impl<'a> UserDump<'a> {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let mapped_file = MappingFile::new(path)?;
Self::parse(mapped_file)
}
pub fn threads(&self) -> &Threads {
&self.threads
}
pub fn modules(&self) -> &Modules<'_> {
&self.modules
}
pub fn memorys(&self) -> &Memorys<'_> {
&self.memorys
}
pub fn handles(&self) -> &Handles {
&self.handles
}
fn parse_stream<S>(cursor: &mut Cursor<&'a [u8]>) -> Result<S::Output>
where
S: MinidumpStream<'a>,
{
S::parse(cursor)
}
fn parse(mapped_file: MappingFile<'a>) -> Result<Self> {
let mut cursor = mapped_file.cursor();
let header = MINIDUMP_HEADER::read(&mut cursor)?;
if header.Signature != MINIDUMP_SIGNATURE {
return Err(UserDmpError::InvalidSignature);
}
if (header.Flags & DUMP_FLAGS) != 0 {
return Err(UserDmpError::InvalidFlags(header.Flags));
}
cursor.seek(io::SeekFrom::Start(header.StreamDirectoryRva.into()))?;
let mut streams = (0..header.NumberOfStreams)
.filter_map(|_| {
let stream = MINIDUMP_DIRECTORY::read(&mut cursor).ok()?;
(stream.StreamType != UnusedStream as u32).then_some(stream)
})
.collect::<Vec<MINIDUMP_DIRECTORY>>();
streams.sort_by_key(|stream| std::cmp::Reverse(stream.StreamType));
let mut system = System::default();
let mut modules = Modules::new();
let mut threads = Threads::new();
let mut memory_info = Memorys::new();
let mut memory64 = Memorys::new();
let mut handles = Handles::new();
let mut exception_thread_id = None;
for stream in &streams {
cursor.seek(io::SeekFrom::Start(stream.Location.RVA.into()))?;
match MINIDUMP_STREAM_TYPE::try_from(stream.StreamType) {
Ok(SystemInfoStream) => system = Self::parse_stream::<System>(&mut cursor)?,
Ok(ModuleListStream) => modules = Self::parse_stream::<Module>(&mut cursor)?,
Ok(HandleDataStream) => handles = Self::parse_stream::<Handle>(&mut cursor)?,
Ok(ExceptionStream) => exception_thread_id = Some(Self::parser_exception(&mut cursor)?),
Ok(ThreadListStream) => threads = Thread::parse(&mut cursor, &Some(system.processor_architecture))?,
Ok(MemoryInfoListStream) => memory_info = Memory::parser_memory_info(&mut cursor)?,
Ok(Memory64ListStream) => memory64 = Memory::parser_memory64_list(&mut cursor)?,
_ => {}
}
}
let memorys = Memory::merge_memory(memory_info, memory64)?;
Ok(Self {
exception_thread_id,
system,
modules,
threads,
memorys,
handles,
mapped_file,
})
}
fn parser_exception(cursor: &mut Cursor<&'a [u8]>) -> Result<u32> {
let exception = MINIDUMP_EXCEPTION_STREAM::read(cursor)?;
Ok(exception.ThreadId)
}
fn extract_raw_data(cursor: &Cursor<&'a [u8]>, location: MINIDUMP_LOCATION_DESCRIPTOR) -> io::Result<&'a [u8]> {
let rva = location.RVA;
let size = location.DataSize;
let slice = cursor.get_ref();
let (_, tail) = slice.split_at(rva as usize);
Ok(&tail[..size as usize])
}
}
#[derive(Copy, Debug, Clone, Default)]
pub struct System {
pub processor_architecture: Arch,
pub processor_level: u16,
pub processor_revision: u16,
pub number_of_processors: u8,
pub product_type: u8,
pub major_version: u32,
pub minor_version: u32,
pub build_number: u32,
pub platform_id: u32,
}
impl MinidumpStream<'_> for System {
type Output = System;
fn parse(cursor: &mut Cursor<&'_ [u8]>) -> Result<Self::Output> {
let system_info = MINIDUMP_SYSTEM_INFO::read(cursor)?;
Ok(System::from(system_info))
}
}
impl From<MINIDUMP_SYSTEM_INFO> for System {
fn from(info: MINIDUMP_SYSTEM_INFO) -> Self {
Self {
processor_architecture: match info.ProcessorArchitecture {
ARCH_X64 => Arch::X64,
ARCH_X86 => Arch::X86,
_ => panic!("Unsupported architecture: {:x}", info.ProcessorArchitecture),
},
processor_level: info.ProcessorLevel,
processor_revision: info.ProcessorRevision,
number_of_processors: info.NumberOfProcessors,
product_type: info.ProductType,
major_version: info.MajorVersion,
minor_version: info.MinorVersion,
build_number: info.BuildNumber,
platform_id: info.PlatformId,
}
}
}
#[derive(Debug, Clone)]
pub struct Module<'a> {
pub range: std::ops::Range<u64>,
pub checksum: u32,
pub path: std::path::PathBuf,
pub time_date_stamp: u32,
pub cv_record: &'a [u8],
pub misc_record: &'a [u8],
}
impl<'a> Module<'a> {
pub fn new(
module: &MINIDUMP_MODULE,
name: String,
cv_record: &'a [u8],
misc_record: &'a [u8]
) -> Self {
let range = std::ops::Range {
start: module.BaseOfImage,
end: module.BaseOfImage + module.SizeOfImage as u64,
};
if range.is_empty() {
panic!("Problem building the memory range")
}
Self {
range,
checksum: module.CheckSum,
path: name.into(),
time_date_stamp: module.TimeDateStamp,
cv_record,
misc_record,
}
}
pub fn name(&self) -> Option<&str> {
self.path.file_name()?.to_str()
}
pub fn start_addr(&self) -> u64 {
self.range.start
}
pub fn end_addr(&self) -> u64 {
self.range.end - 1
}
pub fn len(&self) -> u64 {
self.range.end - self.range.start
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'a> MinidumpStream<'a> for Module<'a> {
type Output = Modules<'a>;
fn parse(cursor: &mut Cursor<&'a [u8]>) -> Result<Modules<'a>> {
let module_list = MINIDUMP_MODULE_LIST::read(cursor)?;
let modules = module_list
.Modules
.iter()
.map(|module| {
cursor.seek(io::SeekFrom::Start(module.ModuleNameRva.into()))?;
let string = MINIDUMP_STRING::read(cursor)?;
let module_name = String::from_utf16_lossy(&string.Buffer)
.trim_end_matches('\0')
.to_string();
let module = Module::new(module, module_name, &[], &[]);
Ok((module.range.start, module))
})
.collect::<Result<Modules>>()?;
Ok(modules)
}
}
#[derive(Debug)]
pub enum ThreadContext {
X64(Box<CONTEXT_X64>),
X86(Box<CONTEXT_X86>),
}
#[derive(Debug)]
pub struct Thread {
pub thread_id: u32,
pub suspend_count: u32,
pub priority_class: u32,
pub priority: u32,
pub teb: u64,
context: ThreadContext,
}
impl Thread {
fn new(thread: &MINIDUMP_THREAD, context: ThreadContext) -> Self {
Self {
thread_id: thread.ThreadId,
suspend_count: thread.SuspendCount,
priority_class: thread.PriorityClass,
priority: thread.Priority,
teb: thread.Teb,
context,
}
}
pub fn context(&self) -> &ThreadContext {
&self.context
}
fn parse(cursor: &mut Cursor<&[u8]>, arch: &Option<Arch>) -> Result<Threads> {
let thread_list = MINIDUMP_THREAD_LIST::read(cursor)?;
let threads = thread_list
.Threads
.iter()
.map(|thread| {
let context_slice = UserDump::extract_raw_data(cursor, thread.ThreadContext)?;
let context = arch
.as_ref()
.map(|arch| match arch {
Arch::X64 => unsafe {
let ctx = ptr::read_unaligned(context_slice.as_ptr() as *const CONTEXT_X64);
ThreadContext::X64(Box::new(ctx))
},
Arch::X86 => unsafe {
let ctx = ptr::read_unaligned(context_slice.as_ptr() as *const CONTEXT_X86);
ThreadContext::X86(Box::new(ctx))
},
})
.ok_or(UserDmpError::InvalidContext)?;
let thread = Thread::new(thread, context);
Ok((thread.thread_id, thread))
})
.collect::<Result<Threads>>()?;
Ok(threads)
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Memory<'a> {
pub range: std::ops::Range<u64>,
pub allocation_base: u64,
pub allocation_protect: u32,
pub state: u32,
pub protect: u32,
pub type_: u32,
pub data: &'a [u8],
}
impl<'a> Memory<'a> {
fn new(memory: &MINIDUMP_MEMORY_INFO) -> Self {
let range = std::ops::Range {
start: memory.BaseAddress,
end: memory.BaseAddress + memory.RegionSize,
};
if range.is_empty() {
panic!("Problem building the memory range")
}
Self {
range,
allocation_base: memory.AllocationBase,
allocation_protect: memory.AllocationProtect,
state: memory.State,
protect: memory.Protect,
type_: memory.Type,
..Default::default()
}
}
pub fn state(&self) -> &str {
if self.state == 0x10_000 {
return "";
}
match self.state {
0x1_000 => "MEM_COMMIT",
0x2_000 => "MEM_RESERVE",
0x10_000 => "MEM_FREE",
0x8_000 => "MEM_RESET",
0x100_000 => "MEM_TOP_DOWN",
_ => "UNKNOWN",
}
}
pub fn type_memory(&self) -> &str {
match self.type_ {
0x20_000 => "MEM_PRIVATE",
0x40_000 => "MEM_MAPPED",
0x1_000_000 => "MEM_IMAGE",
_ => "UNKNOWN",
}
}
pub fn start_addr(&self) -> u64 {
self.range.start
}
pub fn end_addr(&self) -> u64 {
self.range.end
}
pub fn len(&self) -> u64 {
self.range.end - self.range.start
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn merge_memory(mut memory_info: Memorys<'a>, memory64: Memorys<'a>) -> Result<Memorys<'a>> {
for (address, memory) in memory64 {
memory_info.insert(address, memory);
}
Ok(memory_info)
}
fn parser_memory_info(cursor: &mut Cursor<&'a [u8]>) -> Result<Memorys<'a>> {
let memory_info_list = MINIDUMP_MEMORY_INFO_LIST::read(cursor)?;
let memorys = memory_info_list
.Entries
.iter()
.map(|memory| {
let memory_block = Memory::new(memory);
Ok((memory.BaseAddress, memory_block))
})
.collect::<Result<Memorys>>()?;
Ok(memorys)
}
fn parser_memory64_list(cursor: &mut Cursor<&'a [u8]>) -> Result<Memorys<'a>> {
let memory64_list = MINIDUMP_MEMORY64_LIST::read(cursor)?;
let mut memorys = Memorys::new();
let mut current_rva = memory64_list.BaseRva;
for memory_descriptor in memory64_list.Ranges.iter() {
let range = std::ops::Range {
start: memory_descriptor.StartOfMemoryRange,
end: memory_descriptor.StartOfMemoryRange + memory_descriptor.DataSize,
};
cursor.seek(io::SeekFrom::Start(current_rva))?;
let data = {
let data_slice = &cursor.get_ref()[(current_rva as usize)..];
&data_slice[..(memory_descriptor.DataSize as usize)]
};
let memory = Memory {
range,
allocation_base: 0,
allocation_protect: 0,
state: 0,
protect: 0,
type_: 0,
data,
};
memorys.insert(memory_descriptor.StartOfMemoryRange, memory);
current_rva += memory_descriptor.DataSize;
}
Ok(memorys)
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Handle {
pub handle: u64,
type_name: Option<String>,
object_name: Option<String>,
pub attributes: u32,
pub granted_access: u32,
}
impl Handle {
pub fn new(
type_name: Option<String>,
object_name: Option<String>,
handle: &MINIDUMP_HANDLE_DESCRIPTOR
) -> Self {
Self {
handle: handle.Handle,
type_name,
object_name,
attributes: handle.Attributes,
granted_access: handle.GrantedAccess,
}
}
pub fn handle(&self) -> String {
format!("0x{:x}", self.handle)
}
pub fn type_name(&self) -> Option<&str> {
self.type_name.as_deref()
}
pub fn object_name(&self) -> Option<&str> {
self.object_name.as_deref()
}
}
impl<'a> MinidumpStream<'a> for Handle {
type Output = Handles;
fn parse(cursor: &mut Cursor<&'a [u8]>) -> Result<Self::Output> {
let handle_data = MINIDUMP_HANDLE_DATA_STREAM::read(cursor)?;
let handles = handle_data
.Handles
.iter()
.map(|handle| {
let type_name = if handle.TypeNameRva != 0 {
cursor.seek(io::SeekFrom::Start(handle.TypeNameRva.into()))?;
let string = MINIDUMP_STRING::read(cursor)?;
let name = String::from_utf16_lossy(&string.Buffer)
.trim_end_matches('\0')
.to_string();
Some(name)
} else {
None
};
let object_name = if handle.ObjectNameRva != 0 {
cursor.seek(io::SeekFrom::Start(handle.ObjectNameRva.into()))?;
let string = MINIDUMP_STRING::read(cursor)?;
let name = String::from_utf16_lossy(&string.Buffer)
.trim_end_matches('\0')
.to_string();
Some(name)
} else {
None
};
let handle = Handle::new(type_name, object_name, handle);
Ok((handle.handle, handle))
})
.collect::<Result<Handles>>()?;
Ok(handles)
}
}