Source code

Revision control

Copy as Markdown

Other Tools

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<T> = std::result::Result<T, WriterError>;
pub struct MinidumpWriter {
/// The crash context as captured by an exception handler
pub(crate) crash_context: Option<crash_context::CrashContext>,
/// 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<MDMemoryDescriptor>,
/// 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<task_t>, handler_thread: Option<thread_t>) -> 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<Vec<u8>> {
let writers = {
#[allow(clippy::type_complexity)]
let mut writers: Vec<
Box<dyn FnMut(&mut Self, &mut DumpBuf, &TaskDumper) -> Result<MDRawDirectory>>,
> = 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::<MDRawHeader>::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<Self::Item> {
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
}
}