summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/mac/streams/exception.rs
blob: e594dd8d959e3823972debfad7d4bcf31cdb6766 (plain)
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
use super::*;

use mach2::exception_types as et;

impl MinidumpWriter {
    /// Writes the [`minidump_common::format::MINIDUMP_EXCEPTION_STREAM`] stream.
    ///
    /// This stream is optional on MacOS as a user requested minidump could
    /// choose not to specify the exception information.
    pub(crate) fn write_exception(
        &mut self,
        buffer: &mut DumpBuf,
        dumper: &TaskDumper,
    ) -> Result<MDRawDirectory, WriterError> {
        // This shouldn't fail since we won't be writing this stream if the crash context is
        // not present
        let crash_context = self
            .crash_context
            .as_ref()
            .ok_or(WriterError::NoCrashContext)?;

        let thread_state = dumper.read_thread_state(crash_context.thread).ok();

        let thread_context = if let Some(ts) = &thread_state {
            let mut cpu = Default::default();
            Self::fill_cpu_context(ts, &mut cpu);
            MemoryWriter::alloc_with_val(buffer, cpu)
                .map(|mw| mw.location())
                .ok()
        } else {
            None
        };

        let exception_record = crash_context
            .exception
            .as_ref()
            .map(|exc| {
                let code = exc.code as u64;

                // `EXC_CRASH` exceptions wrap other exceptions, so we want to
                // retrieve the _actual_ exception
                let wrapped_exc = if exc.kind as u32 == et::EXC_CRASH {
                    recover_exc_crash_wrapped_exception(code)
                } else {
                    None
                };

                // For EXC_RESOURCE and EXC_GUARD crashes Crashpad records the
                // uppermost 32 bits of the exception code in the exception flags,
                // as they are the most interesting for those exceptions. Neither
                // of these exceptions can be wrapped by an `EXC_CRASH`
                //
                // EXC_GUARD
                // code:
                // +-------------------+----------------+--------------+
                // |[63:61] guard type | [60:32] flavor | [31:0] target|
                // +-------------------+----------------+--------------+
                //
                // EXC_RESOURCE
                // code:
                // +--------------------------------------------------------+
                // |[63:61] resource type | [60:58] flavor | [57:32] unused |
                // +--------------------------------------------------------+
                let exception_code =
                    if exc.kind as u32 == et::EXC_RESOURCE || exc.kind as u32 == et::EXC_GUARD {
                        (code >> 32) as u32
                    } else if let Some(wrapped) = wrapped_exc {
                        wrapped.code
                    } else {
                        // For all other exceptions types, the value in the code
                        // _should_ never exceed 32 bits, crashpad does an actual
                        // range check here, but since we don't really log anything
                        // else at the moment I'll punt that for now
                        // TODO: log/do something if exc.code > u32::MAX
                        code as u32
                    };

                let exception_kind = if let Some(wrapped) = wrapped_exc {
                    wrapped.kind
                } else {
                    exc.kind
                };

                let exception_address =
                    if exception_kind == et::EXC_BAD_ACCESS && exc.subcode.is_some() {
                        exc.subcode.unwrap_or_default()
                    } else if let Some(ts) = thread_state {
                        ts.pc()
                    } else {
                        0
                    };

                // The naming is confusing here, but it is how it is
                let mut md_exc = MDException {
                    exception_code: exception_kind,
                    exception_flags: exception_code,
                    exception_address,
                    ..Default::default()
                };

                // Now append the (mostly) original information to the "ancillary"
                // exception_information at the end. This allows a minidump parser
                // to recover the full exception information for the crash, rather
                // than only using the (potentially) truncated information we
                // just set in `exception_code` and `exception_flags`
                md_exc.exception_information[0] = exception_kind as u64;
                md_exc.exception_information[1] = code;

                md_exc.number_parameters = if let Some(subcode) = exc.subcode {
                    md_exc.exception_information[2] = subcode;
                    3
                } else {
                    2
                };

                md_exc
            })
            .unwrap_or_default();

        let stream = MDRawExceptionStream {
            thread_id: crash_context.thread,
            exception_record,
            thread_context: thread_context.unwrap_or_default(),
            __align: 0,
        };

        let exc_section = MemoryWriter::<MDRawExceptionStream>::alloc_with_val(buffer, stream)?;

        Ok(MDRawDirectory {
            stream_type: MDStreamType::ExceptionStream as u32,
            location: exc_section.location(),
        })
    }
}

/// [`et::EXC_CRASH`] is a wrapper exception around another exception, but not
/// all exceptions can be wrapped by it, so this function validates that the
/// `EXC_CRASH` is actually valid
#[inline]
fn is_valid_exc_crash(exc_code: u64) -> bool {
    let wrapped = ((exc_code >> 20) & 0xf) as u32;

    !(
        wrapped == et::EXC_CRASH // EXC_CRASH can't wrap another one
        || wrapped == et::EXC_RESOURCE // EXC_RESOURCE would lose information
        || wrapped == et::EXC_GUARD // EXC_GUARD would lose information
        || wrapped == et::EXC_CORPSE_NOTIFY
        // cannot be wrapped
    )
}

/// The details for an exception wrapped by an `EXC_CRASH`
#[derive(Copy, Clone)]
struct WrappedException {
    /// The `EXC_*` that was wrapped
    kind: u32,
    /// The code of the wrapped exception, for all exceptions other than
    /// `EXC_RESOURCE` and `EXC_GUARD` this _should_ never exceed 32 bits, and
    /// is one of the reasons that `EXC_CRASH` cannot wrap those 2 exceptions
    code: u32,
    /// The Unix signal number that the original exception was converted into
    _signal: u8,
}

/// Unwraps an `EXC_CRASH` exception code to the inner exception it wraps.
///
/// Will return `None` if the specified code is wrapping an exception that
/// should not be possible to be wrapped in an `EXC_CRASH`
#[inline]
fn recover_exc_crash_wrapped_exception(code: u64) -> Option<WrappedException> {
    is_valid_exc_crash(code).then(|| WrappedException {
        kind: ((code >> 20) & 0xf) as u32,
        code: (code & 0xfffff) as u32,
        _signal: ((code >> 24) & 0xff) as u8,
    })
}