use crate::{ dir_section::{DirSection, DumpBuf}, mac::{errors::WriterError, task_dumper::TaskDumper}, mem_writer::*, minidump_format::{self, MDMemoryDescriptor, MDRawDirectory, MDRawHeader}, }; use std::io::{Seek, Write}; pub use mach2::mach_types::{task_t, thread_t}; type Result = std::result::Result; pub struct MinidumpWriter { /// The crash context as captured by an exception handler pub(crate) crash_context: Option, /// List of raw blocks of memory we've written into the stream. These are /// referenced by other streams (eg thread list) pub(crate) memory_blocks: Vec, /// The task being dumped pub(crate) task: task_t, /// The handler thread, so it can be ignored/deprioritized pub(crate) handler_thread: thread_t, } impl MinidumpWriter { /// Creates a minidump writer for the specified mach task (process) and /// handler thread. If not specified, defaults to the current task and thread. /// /// ``` /// use minidump_writer::{minidump_writer::MinidumpWriter, mach2}; /// /// // Note that this is the same as specifying `None` for both the task and /// // handler thread, this is just meant to illustrate how you can setup /// // a MinidumpWriter manually instead of using a `CrashContext` /// // SAFETY: syscalls /// let mdw = unsafe { /// MinidumpWriter::new( /// Some(mach2::traps::mach_task_self()), /// Some(mach2::mach_init::mach_thread_self()), /// ) /// }; /// ``` pub fn new(task: Option, handler_thread: Option) -> Self { Self { crash_context: None, memory_blocks: Vec::new(), task: task.unwrap_or_else(|| { // SAFETY: syscall unsafe { mach2::traps::mach_task_self() } }), handler_thread: handler_thread.unwrap_or_else(|| { // SAFETY: syscall unsafe { mach2::mach_init::mach_thread_self() } }), } } /// Creates a minidump writer with the specified crash context, presumably /// for another task pub fn with_crash_context(crash_context: crash_context::CrashContext) -> Self { let task = crash_context.task; let handler_thread = crash_context.handler_thread; Self { crash_context: Some(crash_context), memory_blocks: Vec::new(), task, handler_thread, } } /// Writes a minidump to the specified destination, returning the raw minidump /// contents upon success pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result> { let writers = { #[allow(clippy::type_complexity)] let mut writers: Vec< Box Result>, > = vec![ Box::new(|mw, buffer, dumper| mw.write_thread_list(buffer, dumper)), Box::new(|mw, buffer, dumper| mw.write_memory_list(buffer, dumper)), Box::new(|mw, buffer, dumper| mw.write_system_info(buffer, dumper)), Box::new(|mw, buffer, dumper| mw.write_module_list(buffer, dumper)), Box::new(|mw, buffer, dumper| mw.write_misc_info(buffer, dumper)), Box::new(|mw, buffer, dumper| mw.write_breakpad_info(buffer, dumper)), Box::new(|mw, buffer, dumper| mw.write_thread_names(buffer, dumper)), ]; // Exception stream needs to be the last entry in this array as it may // be omitted in the case where the minidump is written without an // exception. if self .crash_context .as_ref() .and_then(|cc| cc.exception.as_ref()) .is_some() { writers.push(Box::new(|mw, buffer, dumper| { mw.write_exception(buffer, dumper) })); } writers }; let num_writers = writers.len() as u32; let mut buffer = Buffer::with_capacity(0); let mut header_section = MemoryWriter::::alloc(&mut buffer)?; let mut dir_section = DirSection::new(&mut buffer, num_writers, destination)?; let header = MDRawHeader { signature: minidump_format::MD_HEADER_SIGNATURE, version: minidump_format::MD_HEADER_VERSION, stream_count: num_writers, stream_directory_rva: dir_section.position(), checksum: 0, /* Can be 0. In fact, that's all that's * been found in minidump files. */ time_date_stamp: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as flags: 0, }; header_section.set_value(&mut buffer, header)?; // Ensure the header gets flushed. If we crash somewhere below, // we should have a mostly-intact dump dir_section.write_to_file(&mut buffer, None)?; let dumper = super::task_dumper::TaskDumper::new(self.task); for mut writer in writers { let dirent = writer(self, &mut buffer, &dumper)?; dir_section.write_to_file(&mut buffer, Some(dirent))?; } Ok(buffer.into()) } /// Retrieves the list of active threads in the target process, except /// the handler thread if it is known, to simplify dump analysis #[inline] pub(crate) fn threads(&self, dumper: &TaskDumper) -> ActiveThreads { ActiveThreads { threads: dumper.read_threads().unwrap_or_default(), handler_thread: self.handler_thread, i: 0, } } } pub(crate) struct ActiveThreads { threads: &'static [u32], handler_thread: u32, i: usize, } impl ActiveThreads { #[inline] pub(crate) fn len(&self) -> usize { let mut len = self.threads.len(); if self.handler_thread != mach2::port::MACH_PORT_NULL { len -= 1; } len } } impl Iterator for ActiveThreads { type Item = u32; fn next(&mut self) -> Option { while self.i < self.threads.len() { let i = self.i; self.i += 1; if self.threads[i] != self.handler_thread { return Some(self.threads[i]); } } None } }