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
    }
}