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