fix: close the tail items from the targeted re-audit
Resolves the 13 rows still partial in docs/rewrite/notes/review-rv4- codex.md (details in final-Z5.md): - Escaped config strings are decoded to raw bytes at every OS boundary: Flatpak cwd/argv to D-Bus, screen-file paths, the default config template, GHOSTTY_RESOURCES_DIR/TERMINFO/shell integration/ bindtextdomain consumers, custom shader loading, list-themes hyperlinks. - osbytes escapes literal U+FDD0 on non-Unix too (bijective everywhere); the GTK paste dialog only decodes text that was actually encoded. - shadertoy: whole-array uses of iChannelTime rebuild a plain float[4] from the std140-padded members (naga-validated, MSL offsets checked). - libghostty-vt: the OSC parser holds caller-allocated storage inline and command payloads borrow from it, so ghostty_osc_end performs no global allocations (verified from Rust and C). - crash: aarch64 Linux minidump context fixed; macOS fatal signals record siginfo, registers and a fault-safe stack walk. - tripwire: selectionString map allocation-failure cleanup test ported. - libghostty-vt emits the versioned soname libghostty-vt.so.0 at build time (no patchelf needed at install). Test Plan: - cargo test --workspace (3342 passed) - just check-macos; just check-macos x86_64-apple-darwin; just check-windows; just check-wasm - cargo check --target aarch64-unknown-linux-gnu -p ghostty-crash
This commit is contained in:
1 parent
bc297c823b
commit
184cdd5c62
49 files changed
+1785
-557
No files matched your search
@@ -0,0 +1,135 @@
|
||||
//! Darwin glue for [`crate::native`]: fill the native crash context from the
|
||||
//! `siginfo_t` and `ucontext_t` passed to the fatal-signal handler.
|
||||
//!
|
||||
//! The handler runs on the faulting thread, so the `ucontext` holds the
|
||||
//! thread state at the fault (the equivalent of `thread_get_state` on the
|
||||
//! crashed thread that breakpad's Mach exception handler does). Memory is read
|
||||
//! with `mach_vm_read_overwrite`, which returns an error instead of faulting
|
||||
//! for unmapped addresses, so walking a corrupted stack is safe.
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use crate::native::Native;
|
||||
|
||||
/// `struct __darwin_ucontext` (`<sys/_types/_ucontext.h>`, 64-bit).
|
||||
#[repr(C)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
struct Ucontext {
|
||||
uc_onstack: libc::c_int,
|
||||
uc_sigmask: u32,
|
||||
uc_stack: libc::stack_t,
|
||||
uc_link: *mut Ucontext,
|
||||
uc_mcsize: usize,
|
||||
/// `struct __darwin_mcontext64 *`: exception state (16 bytes on both
|
||||
/// x86_64 and arm64) followed by the thread state.
|
||||
uc_mcontext: *const u8,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const X_NAMES: [&str; 29] = [
|
||||
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14",
|
||||
"x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27",
|
||||
"x28",
|
||||
];
|
||||
|
||||
/// Size of `__darwin_x86_exception_state64` / `__darwin_arm_exception_state64`,
|
||||
/// which precedes the thread state in `__darwin_mcontext64`.
|
||||
const EXCEPTION_STATE_SIZE: usize = 16;
|
||||
|
||||
fn read_mem(addr: u64) -> Option<[u8; 16]> {
|
||||
let mut buf = [0u8; 16];
|
||||
let mut out: mach2::vm_types::mach_vm_size_t = 0;
|
||||
// SAFETY: `buf` is a valid 16 byte destination; the call validates the
|
||||
// source address and fails instead of faulting.
|
||||
let kr = unsafe {
|
||||
mach2::vm::mach_vm_read_overwrite(
|
||||
mach2::traps::mach_task_self(),
|
||||
addr,
|
||||
16,
|
||||
buf.as_mut_ptr() as mach2::vm_types::mach_vm_address_t,
|
||||
&raw mut out,
|
||||
)
|
||||
};
|
||||
(kr == mach2::kern_return::KERN_SUCCESS && out == 16).then_some(buf)
|
||||
}
|
||||
|
||||
/// Fill `n` from the handler arguments.
|
||||
///
|
||||
/// # Safety
|
||||
/// `info` and `uc` must be the pointers the kernel passed to an `SA_SIGINFO`
|
||||
/// handler (or null).
|
||||
pub(crate) unsafe fn capture(
|
||||
n: &mut Native,
|
||||
info: *const libc::siginfo_t,
|
||||
uc: *const libc::c_void,
|
||||
) {
|
||||
// SAFETY: per the contract (a non-null siginfo is valid for reads).
|
||||
if let Some(info) = unsafe { info.as_ref() } {
|
||||
n.signo = info.si_signo;
|
||||
n.code = info.si_code;
|
||||
n.addr = info.si_addr as u64;
|
||||
}
|
||||
// SAFETY: per the contract.
|
||||
let Some(uc) = (unsafe { uc.cast::<Ucontext>().as_ref() }) else {
|
||||
return;
|
||||
};
|
||||
if uc.uc_mcontext.is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: the mcontext holds the exception state followed by the thread
|
||||
// state; the kernel built it for this signal frame.
|
||||
let ss = unsafe { uc.uc_mcontext.add(EXCEPTION_STATE_SIZE) };
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
// SAFETY: as above; the layout is `x86_thread_state64_t`.
|
||||
let s = unsafe { &*ss.cast::<mach2::structs::x86_thread_state64_t>() };
|
||||
for (name, v) in [
|
||||
("rax", s.__rax),
|
||||
("rbx", s.__rbx),
|
||||
("rcx", s.__rcx),
|
||||
("rdx", s.__rdx),
|
||||
("rdi", s.__rdi),
|
||||
("rsi", s.__rsi),
|
||||
("rbp", s.__rbp),
|
||||
("rsp", s.__rsp),
|
||||
("r8", s.__r8),
|
||||
("r9", s.__r9),
|
||||
("r10", s.__r10),
|
||||
("r11", s.__r11),
|
||||
("r12", s.__r12),
|
||||
("r13", s.__r13),
|
||||
("r14", s.__r14),
|
||||
("r15", s.__r15),
|
||||
("rip", s.__rip),
|
||||
("rflags", s.__rflags),
|
||||
("cs", s.__cs),
|
||||
("fs", s.__fs),
|
||||
("gs", s.__gs),
|
||||
] {
|
||||
n.push_reg(name, v);
|
||||
}
|
||||
n.walk(s.__rip, s.__rbp, read_mem);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
// SAFETY: as above; the layout is `arm_thread_state64_t`.
|
||||
let s = unsafe { &*ss.cast::<mach2::structs::arm_thread_state64_t>() };
|
||||
for (name, v) in X_NAMES.iter().zip(s.__x) {
|
||||
n.push_reg(name, v);
|
||||
}
|
||||
n.push_reg("fp", s.__fp);
|
||||
n.push_reg("lr", s.__lr);
|
||||
n.push_reg("sp", s.__sp);
|
||||
n.push_reg("pc", s.__pc);
|
||||
n.push_reg("cpsr", u64::from(s.__cpsr));
|
||||
// arm64 may sign pointers (PAC): strip the high bits before use.
|
||||
let strip = |v: u64| v & 0x0000_007f_ffff_ffff;
|
||||
// The pc is exact; the faulting leaf function did not necessarily
|
||||
// push a frame record, so the link register is the caller.
|
||||
n.walk(s.__pc, strip(s.__fp), read_mem);
|
||||
if n.nframes == 1 && s.__lr != 0 {
|
||||
n.push_frame(strip(s.__lr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,16 @@
|
||||
#![cfg_attr(windows, allow(dead_code))]
|
||||
|
||||
pub mod context;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
mod darwin;
|
||||
pub mod dir;
|
||||
pub mod envelope;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod minidump;
|
||||
// Pure (platform independent) event-context formatting; only used by the
|
||||
// Darwin signal path but unit-tested everywhere.
|
||||
#[cfg_attr(not(target_vendor = "apple"), allow(dead_code))]
|
||||
mod native;
|
||||
pub mod sentry;
|
||||
#[cfg(unix)]
|
||||
mod signal;
|
||||
|
||||
@@ -92,7 +92,7 @@ pub(crate) unsafe fn crash_context(
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(uc.cast::<ucontext_t>(), &raw mut cc.context, 1);
|
||||
}
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
|
||||
{
|
||||
let fp = cc.context.uc_mcontext.fpregs;
|
||||
if !fp.is_null() {
|
||||
@@ -101,10 +101,61 @@ pub(crate) unsafe fn crash_context(
|
||||
unsafe { std::ptr::copy_nonoverlapping(fp, &raw mut cc.float_state, 1) };
|
||||
}
|
||||
}
|
||||
// AArch64 has no `fpregs` pointer: the FP/SIMD state is the
|
||||
// `fpsimd_context` record in the chain of context extension records
|
||||
// stored in `mcontext_t::__reserved`.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
// SAFETY: `__reserved` is plain initialized memory inside `cc`.
|
||||
let reserved: &[u8] = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(&raw const cc.context.uc_mcontext.__reserved).cast::<u8>(),
|
||||
size_of_val(&cc.context.uc_mcontext.__reserved),
|
||||
)
|
||||
};
|
||||
if let Some(off) = fpsimd_offset(reserved)
|
||||
&& off + size_of::<minidump_writer::crash_context::fpregset_t>() <= reserved.len()
|
||||
{
|
||||
// SAFETY: bounds checked above; both sides are plain old
|
||||
// data and the destination is our static (unaligned source).
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
reserved.as_ptr().add(off),
|
||||
(&raw mut cc.float_state).cast::<u8>(),
|
||||
size_of::<minidump_writer::crash_context::fpregset_t>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cc
|
||||
}
|
||||
|
||||
/// Magic of the AArch64 `fpsimd_context` record (`FPSIMD_MAGIC`).
|
||||
#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
|
||||
const FPSIMD_MAGIC: u32 = 0x4650_8001;
|
||||
|
||||
/// Walk the AArch64 context extension records (`struct _aarch64_ctx`: u32
|
||||
/// magic, u32 size, native endian) in `mcontext_t::__reserved` and return the
|
||||
/// byte offset of the `fpsimd_context` record, if present. The chain ends at
|
||||
/// a zero magic. Pure, so it is testable on every architecture.
|
||||
#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
|
||||
fn fpsimd_offset(reserved: &[u8]) -> Option<usize> {
|
||||
let mut off = 0usize;
|
||||
while off + 8 <= reserved.len() {
|
||||
let magic = u32::from_ne_bytes(reserved[off..off + 4].try_into().ok()?);
|
||||
let size = u32::from_ne_bytes(reserved[off + 4..off + 8].try_into().ok()?) as usize;
|
||||
if magic == FPSIMD_MAGIC {
|
||||
return Some(off);
|
||||
}
|
||||
if magic == 0 || size < 8 {
|
||||
return None;
|
||||
}
|
||||
off += size;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Fork the dumping child and wait for it. Returns true if the envelope with
|
||||
/// the minidump has been written to `cap.path`.
|
||||
pub(crate) fn capture(cap: &Capture<'_>, cc: &CrashContext) -> bool {
|
||||
@@ -324,3 +375,38 @@ fn write_envelope(cap: &Capture<'_>, dump: &[u8]) -> bool {
|
||||
}
|
||||
ok
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FPSIMD_MAGIC, fpsimd_offset};
|
||||
|
||||
fn record(magic: u32, size: u32, out: &mut Vec<u8>) {
|
||||
out.extend_from_slice(&magic.to_ne_bytes());
|
||||
out.extend_from_slice(&size.to_ne_bytes());
|
||||
out.resize(out.len() + size as usize - 8, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_fpsimd_record_in_extension_chain() {
|
||||
let mut buf = Vec::new();
|
||||
// An unrelated record (e.g. esr_context) first, then fpsimd.
|
||||
record(0x4553_5201, 16, &mut buf);
|
||||
let at = buf.len();
|
||||
record(FPSIMD_MAGIC, 528, &mut buf);
|
||||
record(0, 8, &mut buf);
|
||||
assert_eq!(fpsimd_offset(&buf), Some(at));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_corrupt_chain_yields_none() {
|
||||
let mut buf = Vec::new();
|
||||
record(0, 8, &mut buf);
|
||||
assert_eq!(fpsimd_offset(&buf), None);
|
||||
assert_eq!(fpsimd_offset(&[]), None);
|
||||
// A record with a bogus size must not loop forever.
|
||||
let mut bad = Vec::new();
|
||||
bad.extend_from_slice(&1u32.to_ne_bytes());
|
||||
bad.extend_from_slice(&0u32.to_ne_bytes());
|
||||
assert_eq!(fpsimd_offset(&bad), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
//! Native crash context for the event JSON: signal info, CPU registers and a
|
||||
//! stack trace of the faulting thread.
|
||||
//!
|
||||
//! This is what sentry-native's signal handler records on macOS (where
|
||||
//! Ghostty's Zig build links sentry with the breakpad backend, whose handler
|
||||
//! captures the faulting thread's machine state): the `siginfo`, the thread
|
||||
//! state (registers) and the stack. We have no minidump writer that can run
|
||||
//! from a signal handler on Darwin (a Mach exception port handler thread
|
||||
//! would be required), so the same information goes into the event itself as
|
||||
//! `exception.values[0].stacktrace` (`frames` + `registers`) and
|
||||
//! `mechanism.meta.signal`, following the Sentry event schema.
|
||||
//!
|
||||
//! Everything here is allocation free and architecture independent (the
|
||||
//! Darwin glue in `darwin.rs` only fills [`Native`] from the `ucontext`), so
|
||||
//! it is unit-tested on every platform.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Maximum number of stack frames recorded.
|
||||
pub(crate) const MAX_FRAMES: usize = 64;
|
||||
/// Maximum number of registers recorded (x86_64: 21, arm64: 34).
|
||||
pub(crate) const MAX_REGS: usize = 40;
|
||||
|
||||
/// Machine/signal context of the faulting thread.
|
||||
pub(crate) struct Native {
|
||||
pub signo: i32,
|
||||
pub code: i32,
|
||||
/// Fault address (`si_addr`).
|
||||
pub addr: u64,
|
||||
pub reg_names: [&'static str; MAX_REGS],
|
||||
pub reg_vals: [u64; MAX_REGS],
|
||||
pub nregs: usize,
|
||||
/// Return addresses, innermost first (`frames[0]` is the pc).
|
||||
pub frames: [u64; MAX_FRAMES],
|
||||
pub nframes: usize,
|
||||
}
|
||||
|
||||
impl Native {
|
||||
pub(crate) const fn new() -> Native {
|
||||
Native {
|
||||
signo: 0,
|
||||
code: 0,
|
||||
addr: 0,
|
||||
reg_names: [""; MAX_REGS],
|
||||
reg_vals: [0; MAX_REGS],
|
||||
nregs: 0,
|
||||
frames: [0; MAX_FRAMES],
|
||||
nframes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_reg(&mut self, name: &'static str, val: u64) {
|
||||
if self.nregs < MAX_REGS {
|
||||
self.reg_names[self.nregs] = name;
|
||||
self.reg_vals[self.nregs] = val;
|
||||
self.nregs += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_frame(&mut self, addr: u64) {
|
||||
if self.nframes < MAX_FRAMES {
|
||||
self.frames[self.nframes] = addr;
|
||||
self.nframes += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Record the pc and then walk the frame-pointer chain starting at `fp`.
|
||||
///
|
||||
/// Both x86_64 and arm64 Darwin keep a frame record `[prev_fp,
|
||||
/// return_address]` at `fp`. `read` copies 16 bytes of the crashed
|
||||
/// process' memory, returning false instead of faulting when the address
|
||||
/// is unmapped. The walk stops at a null/misaligned/non-increasing frame
|
||||
/// pointer, a failed read or a null return address.
|
||||
pub(crate) fn walk(
|
||||
&mut self,
|
||||
pc: u64,
|
||||
mut fp: u64,
|
||||
mut read: impl FnMut(u64) -> Option<[u8; 16]>,
|
||||
) {
|
||||
self.push_frame(pc);
|
||||
let mut prev = 0u64;
|
||||
while self.nframes < MAX_FRAMES && fp != 0 && fp.is_multiple_of(8) && fp > prev {
|
||||
let Some(rec) = read(fp) else { break };
|
||||
let next = u64::from_ne_bytes([
|
||||
rec[0], rec[1], rec[2], rec[3], rec[4], rec[5], rec[6], rec[7],
|
||||
]);
|
||||
let ret = u64::from_ne_bytes([
|
||||
rec[8], rec[9], rec[10], rec[11], rec[12], rec[13], rec[14], rec[15],
|
||||
]);
|
||||
if ret == 0 {
|
||||
break;
|
||||
}
|
||||
self.push_frame(ret);
|
||||
prev = fp;
|
||||
fp = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `si_code` names for the fault signals (POSIX / Darwin `<sys/signal.h>`).
|
||||
fn code_name(signo: i32, code: i32) -> &'static str {
|
||||
match (signo, code) {
|
||||
(11, 1) => "SEGV_MAPERR",
|
||||
(11, 2) => "SEGV_ACCERR",
|
||||
(10, 1) => "BUS_ADRALN",
|
||||
(10, 2) => "BUS_ADRERR",
|
||||
(10, 3) => "BUS_OBJERR",
|
||||
(4, 1) => "ILL_ILLOPC",
|
||||
(4, 2) => "ILL_ILLTRP",
|
||||
(4, 3) => "ILL_PRVOPC",
|
||||
(8, 1) => "FPE_INTDIV",
|
||||
(8, 2) => "FPE_INTOVF",
|
||||
(8, 3) => "FPE_FLTDIV",
|
||||
(8, 4) => "FPE_FLTOVF",
|
||||
(8, 5) => "FPE_FLTUND",
|
||||
(8, 6) => "FPE_FLTRES",
|
||||
(8, 7) => "FPE_FLTINV",
|
||||
(_, 0) => "SI_USER",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Write `"meta":{...}` (inside the mechanism object) and, after the
|
||||
/// mechanism, the `stacktrace` of the exception value. The caller has just
|
||||
/// written `"mechanism":{"type":..,"handled":false`; this appends the
|
||||
/// remaining mechanism fields and closes the mechanism object.
|
||||
pub(crate) fn write_mechanism_rest_and_stacktrace<W: fmt::Write>(
|
||||
w: &mut W,
|
||||
n: &Native,
|
||||
) -> fmt::Result {
|
||||
write!(
|
||||
w,
|
||||
",\"meta\":{{\"signal\":{{\"number\":{},\"code\":{}",
|
||||
n.signo, n.code
|
||||
)?;
|
||||
let name = code_name(n.signo, n.code);
|
||||
if !name.is_empty() {
|
||||
write!(w, ",\"code_name\":\"{name}\"")?;
|
||||
}
|
||||
write!(
|
||||
w,
|
||||
"}}}},\"data\":{{\"relevant_address\":\"0x{:x}\"}}}}",
|
||||
n.addr
|
||||
)?;
|
||||
|
||||
// Sentry wants frames oldest first.
|
||||
w.write_str(",\"stacktrace\":{\"frames\":[")?;
|
||||
for (i, addr) in n.frames[..n.nframes].iter().rev().enumerate() {
|
||||
if i > 0 {
|
||||
w.write_char(',')?;
|
||||
}
|
||||
write!(w, "{{\"instruction_addr\":\"0x{addr:x}\"}}")?;
|
||||
}
|
||||
w.write_str("],\"registers\":{")?;
|
||||
for i in 0..n.nregs {
|
||||
if i > 0 {
|
||||
w.write_char(',')?;
|
||||
}
|
||||
write!(w, "\"{}\":\"0x{:x}\"", n.reg_names[i], n.reg_vals[i])?;
|
||||
}
|
||||
w.write_str("}}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn sample() -> Native {
|
||||
let mut n = Native::new();
|
||||
n.signo = 11;
|
||||
n.code = 1;
|
||||
n.addr = 0xdead_beef;
|
||||
n.push_reg("pc", 0x1000);
|
||||
n.push_reg("sp", 0x7000);
|
||||
n.walk(0x1000, 0x7000, |fp| {
|
||||
// Two frames: [0x7100, 0x2000], [0x7200, 0x3000], then end.
|
||||
let (next, ret) = match fp {
|
||||
0x7000 => (0x7100u64, 0x2000u64),
|
||||
0x7100 => (0x7200, 0x3000),
|
||||
_ => return None,
|
||||
};
|
||||
let mut b = [0u8; 16];
|
||||
b[..8].copy_from_slice(&next.to_ne_bytes());
|
||||
b[8..].copy_from_slice(&ret.to_ne_bytes());
|
||||
Some(b)
|
||||
});
|
||||
n
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walks_frame_pointer_chain() {
|
||||
let n = sample();
|
||||
assert_eq!(&n.frames[..n.nframes], &[0x1000, 0x2000, 0x3000]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_stops_on_cycles_and_bad_pointers() {
|
||||
let mut n = Native::new();
|
||||
// fp chain pointing at itself: must terminate.
|
||||
n.walk(1, 0x7000, |_| {
|
||||
let mut b = [0u8; 16];
|
||||
b[..8].copy_from_slice(&0x7000u64.to_ne_bytes());
|
||||
b[8..].copy_from_slice(&0x42u64.to_ne_bytes());
|
||||
Some(b)
|
||||
});
|
||||
assert_eq!(&n.frames[..n.nframes], &[1, 0x42]);
|
||||
// Misaligned fp: no read at all.
|
||||
let mut m = Native::new();
|
||||
m.walk(1, 0x7001, |_| panic!("read"));
|
||||
assert_eq!(m.nframes, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_json_is_valid_with_registers_and_frames() {
|
||||
let n = sample();
|
||||
let mut s = String::from("{\"mechanism\":{\"type\":\"signalhandler\",\"handled\":false");
|
||||
write_mechanism_rest_and_stacktrace(&mut s, &n).expect("format");
|
||||
s.push('}');
|
||||
let v: serde_json::Value = serde_json::from_str(&s).expect("valid json");
|
||||
let mech = &v["mechanism"];
|
||||
assert_eq!(mech["meta"]["signal"]["number"], 11);
|
||||
assert_eq!(mech["meta"]["signal"]["code_name"], "SEGV_MAPERR");
|
||||
assert_eq!(mech["data"]["relevant_address"], "0xdeadbeef");
|
||||
let frames = v["stacktrace"]["frames"].as_array().expect("array");
|
||||
// Oldest first.
|
||||
assert_eq!(frames[0]["instruction_addr"], "0x3000");
|
||||
assert_eq!(frames[2]["instruction_addr"], "0x1000");
|
||||
assert_eq!(v["stacktrace"]["registers"]["pc"], "0x1000");
|
||||
assert_eq!(v["stacktrace"]["registers"]["sp"], "0x7000");
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,9 @@ pub(crate) struct Crash<'a> {
|
||||
pub value: &'a str,
|
||||
/// Sentry mechanism type: `panic` or `signalhandler`.
|
||||
pub mechanism: &'a str,
|
||||
/// Signal info, registers and stack of the faulting thread (Darwin
|
||||
/// signal path; see `native.rs`).
|
||||
pub native: Option<&'a crate::native::Native>,
|
||||
}
|
||||
|
||||
/// Write the event JSON (single line). Mirrors what Sentry produces after
|
||||
@@ -281,7 +284,13 @@ pub(crate) fn write_event<W: fmt::Write>(w: &mut W, st: &State, c: &Crash<'_>) -
|
||||
json_str(w, c.value)?;
|
||||
w.write_str(",\"mechanism\":{\"type\":")?;
|
||||
json_str(w, c.mechanism)?;
|
||||
w.write_str(",\"handled\":false}}]}}")
|
||||
w.write_str(",\"handled\":false")?;
|
||||
if let Some(n) = c.native {
|
||||
crate::native::write_mechanism_rest_and_stacktrace(w, n)?;
|
||||
w.write_str("}]}}")
|
||||
} else {
|
||||
w.write_str("}}]}}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the envelope header line and the event item header line (the event
|
||||
@@ -362,6 +371,7 @@ fn write_report(
|
||||
kind,
|
||||
value,
|
||||
mechanism,
|
||||
native: None,
|
||||
};
|
||||
|
||||
let mut event = String::new();
|
||||
|
||||
@@ -34,6 +34,8 @@ struct Scratch {
|
||||
event: [u8; EVENT_CAP],
|
||||
head: [u8; HEAD_CAP],
|
||||
path: [u8; PATH_CAP],
|
||||
#[cfg(target_vendor = "apple")]
|
||||
native: crate::native::Native,
|
||||
}
|
||||
|
||||
struct ScratchCell(UnsafeCell<Scratch>);
|
||||
@@ -44,6 +46,8 @@ static SCRATCH: ScratchCell = ScratchCell(UnsafeCell::new(Scratch {
|
||||
event: [0; EVENT_CAP],
|
||||
head: [0; HEAD_CAP],
|
||||
path: [0; PATH_CAP],
|
||||
#[cfg(target_vendor = "apple")]
|
||||
native: crate::native::Native::new(),
|
||||
}));
|
||||
static BUSY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
@@ -150,6 +154,16 @@ fn write_signal_report(name: &str, desc: &str, info: *mut libc::siginfo_t, uc: *
|
||||
return;
|
||||
};
|
||||
let (secs, micros) = sentry::now();
|
||||
// Darwin: record the faulting thread's registers and stack in the event
|
||||
// (no minidump there; see `native.rs`).
|
||||
#[cfg(target_vendor = "apple")]
|
||||
let native_ctx = {
|
||||
// SAFETY: `info`/`uc` are the pointers the kernel gave the handler.
|
||||
unsafe { crate::darwin::capture(&mut sc.native, info, uc) };
|
||||
Some(&sc.native)
|
||||
};
|
||||
#[cfg(not(target_vendor = "apple"))]
|
||||
let native_ctx = None;
|
||||
let crash = Crash {
|
||||
uuid,
|
||||
secs,
|
||||
@@ -157,6 +171,7 @@ fn write_signal_report(name: &str, desc: &str, info: *mut libc::siginfo_t, uc: *
|
||||
kind: name,
|
||||
value: desc,
|
||||
mechanism: "signalhandler",
|
||||
native: native_ctx,
|
||||
};
|
||||
|
||||
let mut ev = SliceWriter {
|
||||
|
||||
Reference in new issue
Block a user