feat(crash): Breakpad-style minidumps on macOS and Windows
Ghostty links sentry-native with the breakpad backend on every platform (SharedDeps.zig), so it writes minidumps on macOS and Windows as well. - macOS: a Mach exception port handler thread (structured like Breakpad's exception_handler) catches EXC_BAD_ACCESS/BAD_INSTRUCTION/ ARITHMETIC/BREAKPOINT, suspends other threads, reads the crashed thread state and writes a minidump via minidump-writer as the event.minidump attachment, then restores the previous ports so the BSD signal and default termination follow. The signal path remains the fallback (and handles SIGABRT); a watchdog alarm bounds dumping. - Windows: SetUnhandledExceptionFilter hands off to a pre-started dump thread using minidump-writer's dump_crash_context. - The screen-file open action decodes the escaped path to raw OS bytes (also for the C action passed to embedders). macOS/Windows code is compile-checked (clippy -D warnings) only; the shared Mach message decoding and minidump attachment logic have Linux unit tests. Platform matrix in docs/rewrite/notes/crash-CR.md. Test Plan: - cargo test --workspace (3348 passed) - just check-macos; just check-macos x86_64-apple-darwin; just check-windows
This commit is contained in:
1 parent
41e0b9cb68
commit
b1ad29fa97
12 files changed
+914
-34
No files matched your search
@@ -27,7 +27,7 @@ use std::{
|
||||
use crate::{
|
||||
context,
|
||||
dir::{self, REPORT_EXTENSION},
|
||||
envelope::{Attachment, Envelope, Item, ObjectMap},
|
||||
envelope::{Attachment, Item},
|
||||
signal,
|
||||
};
|
||||
|
||||
@@ -97,8 +97,7 @@ pub(crate) fn state() -> Option<&'static State> {
|
||||
///
|
||||
/// Call from one thread; [`deinit`] should be called from the same thread.
|
||||
pub fn init(opts: &Options) -> Result<(), InitError> {
|
||||
// Not supported on Windows currently, doesn't build (same as Zig).
|
||||
if !ENABLED || cfg!(windows) {
|
||||
if !ENABLED {
|
||||
return Ok(());
|
||||
}
|
||||
install(opts)
|
||||
@@ -357,6 +356,20 @@ fn write_report(
|
||||
value: &str,
|
||||
mechanism: &str,
|
||||
backtrace: Option<&str>,
|
||||
) -> io::Result<PathBuf> {
|
||||
write_report_ext(st, kind, value, mechanism, None, backtrace, None)
|
||||
}
|
||||
|
||||
/// [`write_report`] plus the native crash context and a minidump attachment
|
||||
/// (`event.minidump`), used by the macOS and Windows exception handlers.
|
||||
pub(crate) fn write_report_ext(
|
||||
st: &State,
|
||||
kind: &str,
|
||||
value: &str,
|
||||
mechanism: &str,
|
||||
native: Option<&crate::native::Native>,
|
||||
backtrace: Option<&str>,
|
||||
minidump: Option<&[u8]>,
|
||||
) -> io::Result<PathBuf> {
|
||||
let mut raw = [0u8; 16];
|
||||
signal::random_bytes(&mut raw);
|
||||
@@ -371,7 +384,7 @@ fn write_report(
|
||||
kind,
|
||||
value,
|
||||
mechanism,
|
||||
native: None,
|
||||
native,
|
||||
};
|
||||
|
||||
let mut event = String::new();
|
||||
@@ -380,14 +393,20 @@ fn write_report(
|
||||
write_envelope_head(&mut head, &crash, event.len()).map_err(io::Error::other)?;
|
||||
let mut data = head.into_bytes();
|
||||
data.extend_from_slice(event.as_bytes());
|
||||
let mut attachments: Vec<Attachment> = Vec::new();
|
||||
if let Some(bt) = backtrace {
|
||||
let mut a = Attachment::new("backtrace.txt", bt.as_bytes().to_vec());
|
||||
a.attachment_type = Some("event.attachment".into());
|
||||
let mut env = Envelope {
|
||||
headers: ObjectMap::new(),
|
||||
items: vec![Item::Attachment(a)],
|
||||
};
|
||||
let enc = env.items[0].encode().clone();
|
||||
attachments.push(a);
|
||||
}
|
||||
if let Some(md) = minidump {
|
||||
let mut a = Attachment::new("minidump.dmp", md.to_vec());
|
||||
a.attachment_type = Some("event.minidump".into());
|
||||
attachments.push(a);
|
||||
}
|
||||
for a in attachments {
|
||||
let mut item = Item::Attachment(a);
|
||||
let enc = item.encode().clone();
|
||||
data.push(b'\n');
|
||||
let mut h = enc.headers;
|
||||
h.insert("length".into(), enc.payload.len().into());
|
||||
@@ -416,7 +435,7 @@ fn write_report(
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use super::*;
|
||||
use crate::envelope::ItemType;
|
||||
use crate::envelope::{Envelope, ItemType};
|
||||
|
||||
#[test]
|
||||
fn civil_dates() {
|
||||
@@ -495,4 +514,25 @@ mod tests {
|
||||
};
|
||||
assert_eq!(b"bt\nline", a.payload.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_with_minidump_attachment_parses_back() {
|
||||
let t = tempfile::tempdir().unwrap();
|
||||
let st = State {
|
||||
dir: t.path().join("c").as_os_str().as_encoded_bytes().to_vec(),
|
||||
prefix: "\"release\":\"1\",\"environment\":\"production\",\"tags\":{".into(),
|
||||
};
|
||||
let md = b"MDMP\0\x01\xff binary".to_vec();
|
||||
let path =
|
||||
write_report_ext(&st, "EXC_BAD_ACCESS", "x", "mach", None, None, Some(&md)).unwrap();
|
||||
let mut env = Envelope::parse_slice(&std::fs::read(path).unwrap()).unwrap();
|
||||
assert_eq!(2, env.items.len());
|
||||
env.items[1].decode().unwrap();
|
||||
let Item::Attachment(a) = &env.items[1] else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!("minidump.dmp", a.filename);
|
||||
assert_eq!(Some("event.minidump"), a.attachment_type.as_deref());
|
||||
assert_eq!(md, a.payload);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user