summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/mac/minidump_writer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minidump-writer/src/mac/minidump_writer.rs')
-rw-r--r--third_party/rust/minidump-writer/src/mac/minidump_writer.rs187
1 files changed, 187 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/src/mac/minidump_writer.rs b/third_party/rust/minidump-writer/src/mac/minidump_writer.rs
new file mode 100644
index 0000000000..b05662fd21
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/mac/minidump_writer.rs
@@ -0,0 +1,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
+ }
+}