1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
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
}
}