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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user