diff options
Diffstat (limited to 'third_party/rust/crash-context/src')
-rw-r--r-- | third_party/rust/crash-context/src/lib.rs | 38 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/linux.rs | 273 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/linux/getcontext.rs | 24 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/linux/getcontext/aarch64.rs | 86 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/linux/getcontext/arm.rs | 53 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/linux/getcontext/x86.rs | 53 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/linux/getcontext/x86_64.rs | 105 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/mac.rs | 32 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/mac/guard.rs | 92 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/mac/ipc.rs | 553 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/mac/resource.rs | 516 | ||||
-rw-r--r-- | third_party/rust/crash-context/src/windows.rs | 262 |
12 files changed, 2087 insertions, 0 deletions
diff --git a/third_party/rust/crash-context/src/lib.rs b/third_party/rust/crash-context/src/lib.rs new file mode 100644 index 0000000000..4056de8e73 --- /dev/null +++ b/third_party/rust/crash-context/src/lib.rs @@ -0,0 +1,38 @@ +//! This crate exposes a platform specific [`CrashContext`] which contains the +//! details for a crash (signal, exception, etc). This crate is fairly minimal +//! since the intended use case is to more easily share these crash details +//! between different crates without requiring lots of dependencies, and is +//! currently only really made for the purposes of the crates in this repo, and +//! [minidump-writer](https://github.com/rust-minidump/minidump-writer). +//! +//! ## Linux/Android +//! +//! This crate also contains a portable implementation of [`getcontext`]( +//! https://man7.org/linux/man-pages/man3/getcontext.3.html), as not all libc +//! implementations (notably `musl`) implement it as it has been deprecated from +//! POSIX. +//! +//! ## Macos +//! +//! One major difference on Macos is that the details in the [`CrashContext`] +//! cannot be transferred to another process via normal methods (eg. sockets) +//! and must be sent via the criminally undocumented mach ports. This crate +//! provides a `Client` and `Server` that can be used to send and receive a +//! [`CrashContext`] across processes so that you don't have to suffer like I +//! did. + +// crate-specific exceptions: +#![allow(unsafe_code, nonstandard_style)] + +cfg_if::cfg_if! { + if #[cfg(any(target_os = "linux", target_os = "android"))] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "windows")] { + mod windows; + pub use windows::*; + } else if #[cfg(target_os = "macos")] { + mod mac; + pub use mac::*; + } +} diff --git a/third_party/rust/crash-context/src/linux.rs b/third_party/rust/crash-context/src/linux.rs new file mode 100644 index 0000000000..f9952c12d9 --- /dev/null +++ b/third_party/rust/crash-context/src/linux.rs @@ -0,0 +1,273 @@ +mod getcontext; + +pub use getcontext::crash_context_getcontext; + +/// The full context for a Linux/Android crash +#[repr(C)] +#[derive(Clone)] +pub struct CrashContext { + /// Crashing thread context. + /// + /// Note that we use [`crate::ucontext_t`] instead of [`libc::ucontext_t`] + /// as libc's differs between glibc and musl <https://github.com/rust-lang/libc/pull/1646> + /// even though the ucontext_t received from a signal will be the same + /// regardless of the libc implementation used as it is only arch specific + /// and not libc specific + /// + /// Note that we hide `ucontext_t::uc_link` as it is a pointer and thus can't + /// be accessed in a process other than the one the `CrashContext` was created + /// in. This is a just a self-reference so is not useful in practice. + /// + /// Note that the same applies to [`mcontext_t::fpregs`], but since that points + /// to floating point registers and _is_ interesting to read in another process, + /// those registers available as [`Self::float_state`], except on the `arm` + /// architecture since they aren't part of `mcontext_t` at all. + pub context: ucontext_t, + /// State of floating point registers. + /// + /// This isn't part of the user ABI for Linux arm + #[cfg(not(target_arch = "arm"))] + pub float_state: fpregset_t, + /// The signal info for the crash + pub siginfo: libc::signalfd_siginfo, + /// The id of the crashing process + pub pid: libc::pid_t, + /// The id of the crashing thread + pub tid: libc::pid_t, +} + +unsafe impl Send for CrashContext {} + +impl CrashContext { + pub fn as_bytes(&self) -> &[u8] { + unsafe { + let size = std::mem::size_of_val(self); + let ptr = (self as *const Self).cast(); + std::slice::from_raw_parts(ptr, size) + } + } + + pub fn from_bytes(bytes: &[u8]) -> Option<Self> { + if bytes.len() != std::mem::size_of::<Self>() { + return None; + } + + unsafe { Some((*bytes.as_ptr().cast::<Self>()).clone()) } + } +} + +#[repr(C)] +#[derive(Clone)] +#[doc(hidden)] +pub struct sigset_t { + #[cfg(target_pointer_width = "32")] + __val: [u32; 32], + #[cfg(target_pointer_width = "64")] + __val: [u64; 16], +} + +#[repr(C)] +#[derive(Clone)] +#[doc(hidden)] +pub struct stack_t { + pub ss_sp: *mut std::ffi::c_void, + pub ss_flags: i32, + pub ss_size: usize, +} + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct ucontext_t { + pub uc_flags: u64, + uc_link: *mut ucontext_t, + pub uc_stack: stack_t, + pub uc_mcontext: mcontext_t, + pub uc_sigmask: sigset_t, + __private: [u8; 512], + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct mcontext_t { + pub gregs: [i64; 23], + pub fpregs: *mut fpregset_t, + __reserved: [u64; 8], + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct fpregset_t { + pub cwd: u16, + pub swd: u16, + pub ftw: u16, + pub fop: u16, + pub rip: u64, + pub rdp: u64, + pub mxcsr: u32, + pub mxcr_mask: u32, + pub st_space: [u32; 32], + pub xmm_space: [u32; 64], + __padding: [u64; 12], + } + } else if #[cfg(target_arch = "x86")] { + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct ucontext_t { + pub uc_flags: u32, + uc_link: *mut ucontext_t, + pub uc_stack: stack_t, + pub uc_mcontext: mcontext_t, + pub uc_sigmask: sigset_t, + pub __fpregs_mem: [u32; 28], + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct mcontext_t { + pub gregs: [i32; 23], + pub fpregs: *mut fpregset_t, + pub oldmask: u32, + pub cr2: u32, + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct fpreg_t { + pub significand: [u16; 4], + pub exponent: u16, + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct fpregset_t { + pub cw: u32, + pub sw: u32, + pub tag: u32, + pub ipoff: u32, + pub cssel: u32, + pub dataoff: u32, + pub datasel: u32, + pub _st: [fpreg_t; 8], + pub status: u32, + } + } else if #[cfg(target_arch = "aarch64")] { + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct ucontext_t { + pub uc_flags: u64, + uc_link: *mut ucontext_t, + pub uc_stack: stack_t, + pub uc_sigmask: sigset_t, + pub uc_mcontext: mcontext_t, + } + + // Note you might see this defined in C with `unsigned long` or + // `unsigned long long` and think, WTF, those aren't the same! Except + // `long` means either 32-bit _or_ 64-bit depending on the data model, + // and the default data model for C/C++ is LP64 which means long is + // 64-bit. I had forgotten what a trash type long was. + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct mcontext_t { + // Note in the kernel this is just part of regs which is length 32 + pub fault_address: u64, + pub regs: [u64; 31], + pub sp: u64, + pub pc: u64, + pub pstate: u64, + // Note that u128 is ABI safe on aarch64, this is actually a + // `long double` in C which Rust doesn't have native support + pub __reserved: [u128; 256], + } + + /// Magic value written by the kernel and our custom getcontext + #[doc(hidden)] + pub const FPSIMD_MAGIC: u32 = 0x46508001; + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct _aarch64_ctx { + pub magic: u32, + pub size: u32, + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct fpsimd_context { + pub head: _aarch64_ctx, + pub fpsr: u32, + pub fpcr: u32, + pub vregs: [u128; 32], + } + + #[doc(hidden)] + pub type fpregset_t = fpsimd_context; + } else if #[cfg(target_arch = "arm")] { + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct ucontext_t { + pub uc_flags: u32, + uc_link: *mut ucontext_t, + pub uc_stack: stack_t, + // Note that the mcontext_t and sigset_t are swapped compared to + // all of the other arches currently supported :p + pub uc_mcontext: mcontext_t, + pub uc_sigmask: sigset_t, + pub uc_regspace: [u64; 64], + } + + #[repr(C)] + #[derive(Clone)] + #[doc(hidden)] + pub struct mcontext_t { + pub trap_no: u32, + pub error_code: u32, + pub oldmask: u32, + pub arm_r0: u32, + pub arm_r1: u32, + pub arm_r2: u32, + pub arm_r3: u32, + pub arm_r4: u32, + pub arm_r5: u32, + pub arm_r6: u32, + pub arm_r7: u32, + pub arm_r8: u32, + pub arm_r9: u32, + pub arm_r10: u32, + pub arm_fp: u32, + pub arm_ip: u32, + pub arm_sp: u32, + pub arm_lr: u32, + pub arm_pc: u32, + pub arm_cpsr: u32, + pub fault_address: u32, + } + } +} + +#[cfg(test)] +mod test { + // Musl doesn't contain fpregs in libc because reasons https://github.com/rust-lang/libc/pull/1646 + #[cfg(not(target_env = "musl"))] + #[test] + fn matches_libc() { + assert_eq!( + std::mem::size_of::<libc::ucontext_t>(), + std::mem::size_of::<super::ucontext_t>() + ); + } +} diff --git a/third_party/rust/crash-context/src/linux/getcontext.rs b/third_party/rust/crash-context/src/linux/getcontext.rs new file mode 100644 index 0000000000..93bb5fdaff --- /dev/null +++ b/third_party/rust/crash-context/src/linux/getcontext.rs @@ -0,0 +1,24 @@ +//! Implementation of [`getcontext`](https://man7.org/linux/man-pages/man3/getcontext.3.html) + +extern "C" { + /// A portable implementation of [`getcontext`](https://man7.org/linux/man-pages/man3/getcontext.3.html) + /// since it is not supported by all libc implementations, namely `musl`, as + /// it has been deprecated from POSIX for over a decade + /// + /// The implementation is ported from Breakpad, which is itself ported from + /// libunwind + #[cfg_attr(target_arch = "aarch64", allow(improper_ctypes))] + pub fn crash_context_getcontext(ctx: *mut super::ucontext_t) -> i32; +} + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + mod x86_64; + } else if #[cfg(target_arch = "x86")] { + mod x86; + } else if #[cfg(target_arch = "aarch64")] { + mod aarch64; + } else if #[cfg(target_arch = "arm")] { + mod arm; + } +} diff --git a/third_party/rust/crash-context/src/linux/getcontext/aarch64.rs b/third_party/rust/crash-context/src/linux/getcontext/aarch64.rs new file mode 100644 index 0000000000..3cc7e66c97 --- /dev/null +++ b/third_party/rust/crash-context/src/linux/getcontext/aarch64.rs @@ -0,0 +1,86 @@ +// GREGS_OFFSET = 184 +// REGISTER_SIZE = 8 +// SIMD_REGISTER_SIZE = 16 + +std::arch::global_asm! { + ".text", + ".global crash_context_getcontext", + ".hidden crash_context_getcontext", + ".type crash_context_getcontext, #function", + ".align 4", + ".cfi_startproc", +"crash_context_getcontext:", + + // The saved context will return to the getcontext() call point + // with a return value of 0 + "str xzr, [x0, 184]", // GREGS_OFFSET + + "stp x18, x19, [x0, 328]", // GREGS_OFFSET + 18 * REGISTER_SIZE + "stp x20, x21, [x0, 344]", // GREGS_OFFSET + 20 * REGISTER_SIZE + "stp x22, x23, [x0, 360]", // GREGS_OFFSET + 22 * REGISTER_SIZE + "stp x24, x25, [x0, 376]", // GREGS_OFFSET + 24 * REGISTER_SIZE + "stp x26, x27, [x0, 392]", // GREGS_OFFSET + 26 * REGISTER_SIZE + "stp x28, x29, [x0, 408]", // GREGS_OFFSET + 28 * REGISTER_SIZE + "str x30, [x0, 424]", // GREGS_OFFSET + 30 * REGISTER_SIZE + + // Place LR into the saved PC, this will ensure that when switching to this + // saved context with setcontext() control will pass back to the caller of + // getcontext(), we have already arranged to return the appropriate return + // value in x0 above. + "str x30, [x0, 440]", + + // Save the current SP + "mov x2, sp", + "str x2, [x0, 432]", + + // Initialize the pstate. + "str xzr, [x0, 448]", + + // Figure out where to place the first context extension block. + "add x2, x0, #464", + + // Write the context extension fpsimd header. + // FPSIMD_MAGIC = 0x46508001 + "mov w3, #(0x46508001 & 0xffff)", + "movk w3, #(0x46508001 >> 16), lsl #16", + "str w3, [x2, #0]", // FPSIMD_CONTEXT_MAGIC_OFFSET + "mov w3, #528", // FPSIMD_CONTEXT_SIZE + "str w3, [x2, #4]", // FPSIMD_CONTEXT_SIZE_OFFSET + + // Fill in the FP SIMD context. + "add x3, x2, #144", // VREGS_OFFSET + 8 * SIMD_REGISTER_SIZE + "stp d8, d9, [x3], #32", + "stp d10, d11, [x3], #32", + "stp d12, d13, [x3], #32", + "stp d14, d15, [x3], #32", + + "add x3, x2, 8", // FPSR_OFFSET + + "mrs x4, fpsr", + "str w4, [x3]", + + "mrs x4, fpcr", + "str w4, [x3, 4]", // FPCR_OFFSET - FPSR_OFFSET + + // Write the termination context extension header. + "add x2, x2, #528", // FPSIMD_CONTEXT_SIZE + + "str xzr, [x2, #0]", // FPSIMD_CONTEXT_MAGIC_OFFSET + "str xzr, [x2, #4]", // FPSIMD_CONTEXT_SIZE_OFFSET + + // Grab the signal mask + // rt_sigprocmask (SIG_BLOCK, NULL, &ucp->uc_sigmask, _NSIG8) + "add x2, x0, #40", // UCONTEXT_SIGMASK_OFFSET + "mov x0, #0", // SIG_BLOCK + "mov x1, #0", // NULL + "mov x3, #(64 / 8)", // _NSIG / 8 + "mov x8, #135", // __NR_rt_sigprocmask + "svc 0", + + // Return x0 for success + "mov x0, 0", + "ret", + + ".cfi_endproc", + ".size crash_context_getcontext, . - crash_context_getcontext", +} diff --git a/third_party/rust/crash-context/src/linux/getcontext/arm.rs b/third_party/rust/crash-context/src/linux/getcontext/arm.rs new file mode 100644 index 0000000000..0734634a3e --- /dev/null +++ b/third_party/rust/crash-context/src/linux/getcontext/arm.rs @@ -0,0 +1,53 @@ +std::arch::global_asm! { + ".text", + ".global crash_context_getcontext", + ".hidden crash_context_getcontext", + ".type crash_context_getcontext, #function", + ".align 0", + ".fnstart", +"crash_context_getcontext:", + + // First, save r4-r11 + "add r1, r0, #(32 + 4 * 4)", + "stm r1, {{r4-r11}}", + + // r12 is a scratch register, don't save it + + // Save sp and lr explicitly. + // - sp can't be stored with stmia in Thumb-2 + // - STM instructions that store sp and pc are deprecated in ARM + "str sp, [r0, #(32 + 13 * 4)]", + "str lr, [r0, #(32 + 14 * 4)]", + + // Save the caller's address in 'pc' + "str lr, [r0, #(32 + 15 * 4)]", + + // Save ucontext_t* pointer across next call + "mov r4, r0", + + // Call sigprocmask(SIG_BLOCK, NULL, &(ucontext->uc_sigmask)) + "mov r0, #0", // SIG_BLOCK + "mov r1, #0", // NULL + "add r2, r4, #104", // UCONTEXT_SIGMASK_OFFSET + "bl sigprocmask(PLT)", + + /* Intentionally do not save the FPU state here. This is because on + * Linux/ARM, one should instead use ptrace(PTRACE_GETFPREGS) or + * ptrace(PTRACE_GETVFPREGS) to get it. + * + * Note that a real implementation of getcontext() would need to save + * this here to allow setcontext()/swapcontext() to work correctly. + */ + + // Restore the values of r4 and lr + "mov r0, r4", + "ldr lr, [r0, #(32 + 14 * 4)]", + "ldr r4, [r0, #(32 + 4 * 4)]", + + // Return 0 + "mov r0, #0", + "bx lr", + + ".fnend", + ".size crash_context_getcontext, . - crash_context_getcontext", +} diff --git a/third_party/rust/crash-context/src/linux/getcontext/x86.rs b/third_party/rust/crash-context/src/linux/getcontext/x86.rs new file mode 100644 index 0000000000..b9d4b5ab72 --- /dev/null +++ b/third_party/rust/crash-context/src/linux/getcontext/x86.rs @@ -0,0 +1,53 @@ +#[cfg(target_os = "android")] +compile_error!("please file an issue if you care about this target"); + +std::arch::global_asm! { + ".text", + ".global crash_context_getcontext", + ".hidden crash_context_getcontext", + ".align 4", + ".type crash_context_getcontext, @function", +"crash_context_getcontext:", + "movl 4(%esp), %eax", // eax = uc + + // Save register values + "movl %ecx, 0x3c(%eax)", + "movl %edx, 0x38(%eax)", + "movl %ebx, 0x34(%eax)", + "movl %edi, 0x24(%eax)", + "movl %esi, 0x28(%eax)", + "movl %ebp, 0x2c(%eax)", + + "movl (%esp), %edx", /* return address */ + "lea 4(%esp), %ecx", /* exclude return address from stack */ + "mov %edx, 0x4c(%eax)", + "mov %ecx, 0x30(%eax)", + + "xorl %ecx, %ecx", + "movw %fs, %cx", + "mov %ecx, 0x18(%eax)", + + "movl $0, 0x40(%eax)", + + // Save floating point state to fpregstate, then update + // the fpregs pointer to point to it + "leal 0xec(%eax), %ecx", + "fnstenv (%ecx)", + "fldenv (%ecx)", + "mov %ecx, 0x60(%eax)", + + // Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) + "leal 0x6c(%eax), %edx", + "xorl %ecx, %ecx", + "push %edx", /* &uc->uc_sigmask */ + "push %ecx", /* NULL */ + "push %ecx", /* SIGBLOCK == 0 on i386 */ + "call sigprocmask@PLT", + "addl $12, %esp", + + "movl $0, %eax", + "ret", + + ".size crash_context_getcontext, . - crash_context_getcontext", + options(att_syntax) +} diff --git a/third_party/rust/crash-context/src/linux/getcontext/x86_64.rs b/third_party/rust/crash-context/src/linux/getcontext/x86_64.rs new file mode 100644 index 0000000000..0eba4b88ce --- /dev/null +++ b/third_party/rust/crash-context/src/linux/getcontext/x86_64.rs @@ -0,0 +1,105 @@ +/* The x64 implementation of breakpad_getcontext was derived in part +from the implementation of libunwind which requires the following +notice. */ +/* libunwind - a platform-independent unwind library + Copyright (C) 2008 Google, Inc + Contributed by Paul Pluzhnikov <ppluzhnikov@google.com> + Copyright (C) 2010 Konstantin Belousov <kib@freebsd.org> + +This file is part of libunwind. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +// Oof, unfortunately android libc and normal linux libc don't have the same +// fpregs offset in ucontext_t, but asm! in Rust is...not convenient for only +// adding certain lines/blocks of asm based using cfg https://github.com/rust-lang/rust/issues/15701 +// and they're not really inputs, just literals, so...yah + +#[cfg(target_os = "linux")] + +// Unfortunately, the asm! macro has a few really annoying limitations at the +// moment +// +// 1. const operands are unstable +// 2. cfg attributes can't be used inside the asm macro at all +// +// and the worst part is we need it for literally only 1 thing, using a different +// offset to the fpstate in ucontext depending on whether we are targeting android +// or not :( +macro_rules! asm_func { + ($offset:expr) => { + std::arch::global_asm! { + ".text", + ".global crash_context_getcontext", + ".hidden crash_context_getcontext", + ".align 4", + ".type crash_context_getcontext, @function", + "crash_context_getcontext:", + ".cfi_startproc", + // Callee saved: RBX, RBP, R12-R15 + "movq %r12, 0x48(%rdi)", + "movq %r13, 0x50(%rdi)", + "movq %r14, 0x58(%rdi)", + "movq %r15, 0x60(%rdi)", + "movq %rbp, 0x78(%rdi)", + "movq %rbx, 0x80(%rdi)", + + // Save argument registers + "movq %r8, 0x28(%rdi)", + "movq %r9, 0x30(%rdi)", + "movq %rdi, 0x68(%rdi)", + "movq %rsi, 0x70(%rdi)", + "movq %rdx, 0x88(%rdi)", + "movq %rax, 0x90(%rdi)", + "movq %rcx, 0x98(%rdi)", + + // Save fp state + stringify!(leaq $offset(%rdi),%r8), + "movq %r8, 0xe0(%rdi)", + "fnstenv (%r8)", + "stmxcsr 0x18(%r8)", + + // Exclude this call + "leaq 8(%rsp), %rax", + "movq %rax, 0xa0(%rdi)", + + "movq 0(%rsp), %rax", + "movq %rax, 0xa8(%rdi)", + + // Save signal mask: sigprocmask(SIGBLOCK, NULL, &uc->uc_sigmask) + "leaq 0x128(%rdi), %rdx", // arg3 + "xorq %rsi, %rsi", // arg2 NULL + "xorq %rdi, %rdi", // arg1 SIGBLOCK == 0 + "call sigprocmask@PLT", + + // Always return 0 for success, even if sigprocmask failed. + "xorl %eax, %eax", + "ret", + ".cfi_endproc", + ".size crash_context_getcontext, . - crash_context_getcontext", + options(att_syntax) + } + }; +} + +#[cfg(target_os = "linux")] +asm_func!(0x1a8); +#[cfg(target_os = "android")] +asm_func!(0x130); diff --git a/third_party/rust/crash-context/src/mac.rs b/third_party/rust/crash-context/src/mac.rs new file mode 100644 index 0000000000..fcbe97df69 --- /dev/null +++ b/third_party/rust/crash-context/src/mac.rs @@ -0,0 +1,32 @@ +pub mod guard; +pub mod ipc; +pub mod resource; + +use mach2::mach_types as mt; + +/// Information on the exception that caused the crash +#[derive(Copy, Clone, Debug)] +pub struct ExceptionInfo { + /// The exception kind + pub kind: u32, + /// The exception code + pub code: u64, + /// Optional subcode with different meanings depending on the exception type + /// * `EXC_BAD_ACCESS` - The address that caused the exception + /// * `EXC_GUARD` - The unique guard identifier that was guarding a resource + /// * `EXC_RESOURCE` - Additional details depending on the resource type + pub subcode: Option<u64>, +} + +/// Full Macos crash context +#[derive(Debug)] +pub struct CrashContext { + /// The process which crashed + pub task: mt::task_t, + /// The thread in the process that crashed + pub thread: mt::thread_t, + /// The thread that handled the exception. This may be useful to ignore. + pub handler_thread: mt::thread_t, + /// Optional exception information + pub exception: Option<ExceptionInfo>, +} diff --git a/third_party/rust/crash-context/src/mac/guard.rs b/third_party/rust/crash-context/src/mac/guard.rs new file mode 100644 index 0000000000..7caee5ffe3 --- /dev/null +++ b/third_party/rust/crash-context/src/mac/guard.rs @@ -0,0 +1,92 @@ +//! Contains types and helpers for dealing with `EXC_GUARD` exceptions. +//! +//! `EXC_GUARD` exceptions embed details about the guarded resource in the `code` +//! and `subcode` fields of the exception +//! +//! See <https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/kern/exc_guard.h> +//! for the top level types that this module wraps. + +use mach2::exception_types::EXC_GUARD; + +/// The set of possible guard kinds +#[derive(Copy, Clone, Debug)] +#[repr(u8)] +pub enum GuardKind { + /// Null variant + None = 0, + /// A `mach_port_t` + MachPort = 1, + /// File descriptor + Fd = 2, + /// Userland assertion + User = 3, + /// Vnode + Vnode = 4, + /// Virtual memory operation + VirtualMemory = 5, + /// Rejected system call trap + RejectedSyscall = 6, +} + +#[inline] +pub fn extract_guard_kind(code: u64) -> u8 { + ((code >> 61) & 0x7) as u8 +} + +#[inline] +pub fn extract_guard_flavor(code: u64) -> u32 { + ((code >> 32) & 0x1fffffff) as u32 +} + +#[inline] +pub fn extract_guard_target(code: u64) -> u32 { + code as u32 +} + +/// The extracted details of an `EXC_GUARD` exception +pub struct GuardException { + /// One of [`GuardKind`] + pub kind: u8, + /// The specific guard flavor that was violated, specific to each `kind` + pub flavor: u32, + /// The resource that was guarded + pub target: u32, + /// Target specific guard information + pub identifier: u64, +} + +/// Extracts the guard details from an exceptions code and subcode +/// +/// code: +/// +-------------------+----------------+--------------+ +/// |[63:61] guard type | [60:32] flavor | [31:0] target| +/// +-------------------+----------------+--------------+ +/// +/// subcode: +/// +---------------------------------------------------+ +/// |[63:0] guard identifier | +/// +---------------------------------------------------+ +#[inline] +pub fn extract_guard_exception(code: u64, subcode: u64) -> GuardException { + GuardException { + kind: extract_guard_kind(code), + flavor: extract_guard_flavor(code), + target: extract_guard_target(code), + identifier: subcode, + } +} + +impl super::ExceptionInfo { + /// If this is an `EXC_GUARD` exception, retrieves the exception metadata + /// from the code, otherwise returns `None` + pub fn guard_exception(&self) -> Option<GuardException> { + if self.kind != EXC_GUARD { + return None; + } + + Some(extract_guard_exception( + self.code, + self.subcode.unwrap_or_default(), + )) + } +} diff --git a/third_party/rust/crash-context/src/mac/ipc.rs b/third_party/rust/crash-context/src/mac/ipc.rs new file mode 100644 index 0000000000..cb5fb2fcf1 --- /dev/null +++ b/third_party/rust/crash-context/src/mac/ipc.rs @@ -0,0 +1,553 @@ +//! Unfortunately, sending a [`CrashContext`] to another process on Macos +//! needs to be done via mach ports, as, for example, `mach_task_self` is a +//! special handle that needs to be translated into the "actual" task when used +//! by another process, this _might_ be possible completely in userspace, but +//! examining the source code for this leads me to believe that there are enough +//! footguns, particularly around security, that this might take a while, so for +//! now, if you need to use a [`CrashContext`] across processes, you need +//! to use the IPC mechanisms here to get meaningful/accurate data +//! +//! Note that in all cases of an optional timeout, a `None` will return +//! immediately regardless of whether the messaged has been enqueued or +//! dequeued from the kernel queue, so it is _highly_ recommended to use +//! reasonable timeouts for sending and receiving messages between processes. + +use crate::CrashContext; +use mach2::{ + bootstrap, kern_return::KERN_SUCCESS, mach_port, message as msg, port, task, + traps::mach_task_self, +}; +pub use mach2::{kern_return::kern_return_t, message::mach_msg_return_t}; +use std::{ffi::CStr, time::Duration}; + +extern "C" { + /// From <usr/include/mach/mach_traps.h>, there is no binding for this in mach2 + pub fn pid_for_task(task: port::mach_port_name_t, pid: *mut i32) -> kern_return_t; +} + +/// <https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/mach/message.h#L379-L391> +#[repr(C, packed(4))] +struct MachMsgPortDescriptor { + name: u32, + __pad1: u32, + __pad2: u16, + disposition: u8, + __type: u8, +} + +impl MachMsgPortDescriptor { + fn new(name: u32, disposition: u32) -> Self { + Self { + name, + disposition: disposition as u8, + __pad1: 0, + __pad2: 0, + __type: msg::MACH_MSG_PORT_DESCRIPTOR as u8, + } + } +} + +#[repr(C, packed(4))] +struct MachMsgBody { + pub descriptor_count: u32, +} + +#[repr(C, packed(4))] +pub struct MachMsgTrailer { + pub kind: u32, + pub size: u32, +} + +/// <https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/mach/message.h#L545-L552> +#[repr(C, packed(4))] +struct MachMsgHeader { + pub bits: u32, + pub size: u32, + pub remote_port: u32, + pub local_port: u32, + pub voucher_port: u32, + pub id: u32, +} + +/// The actual crash context message sent and received. This message is a single +/// struct since it needs to be contiguous block of memory. I suppose it's like +/// this because people are expected to use MIG to generate the interface code, +/// but it's ugly as hell regardless. +#[repr(C, packed(4))] +struct CrashContextMessage { + head: MachMsgHeader, + /// When providing port descriptors, this must be present to say how many + /// of them follow the header and body + body: MachMsgBody, + // These are the really the critical piece of the payload, during + // sending (or receiving?) these are turned into descriptors that + // can actually be used by another process + /// The task that crashed (ie `mach_task_self`) + task: MachMsgPortDescriptor, + /// The thread that crashed + crash_thread: MachMsgPortDescriptor, + /// The handler thread, probably, but not necessarily `mach_thread_self` + handler_thread: MachMsgPortDescriptor, + // Port opened by the client to receive an ack from the server + ack_port: MachMsgPortDescriptor, + /// Combination of the FLAG_* constants + flags: u32, + /// The exception type + exception_kind: u32, + /// The exception code + exception_code: u64, + /// The optional exception subcode + exception_subcode: u64, + /// We don't actually send this, but it's tacked on by the kernel :( + trailer: MachMsgTrailer, +} + +const FLAG_HAS_EXCEPTION: u32 = 0x1; +const FLAG_HAS_SUBCODE: u32 = 0x2; + +/// Message sent from the [`Receiver`] upon receiving and handling a [`CrashContextMessage`] +#[repr(C, packed(4))] +struct AcknowledgementMessage { + head: MachMsgHeader, + result: u32, +} + +/// An error that can occur while interacting with mach ports +#[derive(Copy, Clone, Debug)] +pub enum Error { + /// A kernel error will generally indicate an error occurred while creating + /// or modifying a mach port + Kernel(kern_return_t), + /// A message error indicates an error occurred while sending or receiving + /// a message on a mach port + Message(mach_msg_return_t), +} + +impl std::error::Error for Error {} + +use std::fmt; + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: use a good string for the error codes + write!(f, "{:?}", self) + } +} + +macro_rules! kern { + ($call:expr) => {{ + let res = $call; + + if res != KERN_SUCCESS { + return Err(Error::Kernel(res)); + } + }}; +} + +macro_rules! msg { + ($call:expr) => {{ + let res = $call; + + if res != msg::MACH_MSG_SUCCESS { + return Err(Error::Message(res)); + } + }}; +} + +/// Sends a [`CrashContext`] from a crashing process to another process running +/// a [`Server`] with the same name +pub struct Client { + port: port::mach_port_t, +} + +impl Client { + /// Attempts to create a new client that can send messages to a [`Server`] + /// that was created with the specified name. + /// + /// # Errors + /// + /// The specified port is not available for some reason, if you expect the + /// port to be created you can retry this function until it connects. + pub fn create(name: &CStr) -> Result<Self, Error> { + // SAFETY: syscalls. The user has no invariants to uphold, hence the + // unsafe not being on the function as a whole + unsafe { + let mut task_bootstrap_port = 0; + kern!(task::task_get_special_port( + mach_task_self(), + task::TASK_BOOTSTRAP_PORT, + &mut task_bootstrap_port + )); + + let mut port = 0; + kern!(bootstrap::bootstrap_look_up( + task_bootstrap_port, + name.as_ptr(), + &mut port + )); + + Ok(Self { port }) + } + } + + /// Sends the specified [`CrashContext`] to a [`Server`]. + /// + /// If the ack from the [`Server`] times out `Ok(None)` is returned, otherwise + /// it is the value specified in the [`Server`] process to [`Acknowledger::send_ack`] + /// + /// # Errors + /// + /// The send of the [`CrashContext`] or the receive of the ack fails. + pub fn send_crash_context( + &self, + ctx: &CrashContext, + send_timeout: Option<Duration>, + receive_timeout: Option<Duration>, + ) -> Result<Option<u32>, Error> { + // SAFETY: syscalls. Again, the user has no invariants to uphold, so + // the function itself is not marked unsafe + unsafe { + // Create a new port to receive a response from the reciving end of + // this port so we that we know when it has actually processed the + // CrashContext, which is (presumably) interesting for the caller. If + // that is not interesting they can set the receive_timeout to 0 to + // just return immediately + let mut ack_port = AckReceiver::new()?; + + let (flags, exception_kind, exception_code, exception_subcode) = + if let Some(exc) = ctx.exception { + ( + FLAG_HAS_EXCEPTION + | if exc.subcode.is_some() { + FLAG_HAS_SUBCODE + } else { + 0 + }, + exc.kind, + exc.code, + exc.subcode.unwrap_or_default(), + ) + } else { + (0, 0, 0, 0) + }; + + let mut msg = CrashContextMessage { + head: MachMsgHeader { + bits: msg::MACH_MSG_TYPE_COPY_SEND | msg::MACH_MSGH_BITS_COMPLEX, + // We don't send the trailer, that's added by the kernel + size: std::mem::size_of::<CrashContextMessage>() as u32 - 8, + remote_port: self.port, + local_port: port::MACH_PORT_NULL, + voucher_port: port::MACH_PORT_NULL, + id: 0, + }, + body: MachMsgBody { + descriptor_count: 4, + }, + task: MachMsgPortDescriptor::new(ctx.task, msg::MACH_MSG_TYPE_COPY_SEND), + crash_thread: MachMsgPortDescriptor::new(ctx.thread, msg::MACH_MSG_TYPE_COPY_SEND), + handler_thread: MachMsgPortDescriptor::new( + ctx.handler_thread, + msg::MACH_MSG_TYPE_COPY_SEND, + ), + ack_port: MachMsgPortDescriptor::new(ack_port.port, msg::MACH_MSG_TYPE_COPY_SEND), + flags, + exception_kind, + exception_code, + exception_subcode, + // We don't actually send this but I didn't feel like making + // two types + trailer: MachMsgTrailer { kind: 0, size: 8 }, + }; + + // Try to actually send the message to the Server + msg!(msg::mach_msg( + ((&mut msg.head) as *mut MachMsgHeader).cast(), + msg::MACH_SEND_MSG | msg::MACH_SEND_TIMEOUT, + msg.head.size, + 0, + port::MACH_PORT_NULL, + send_timeout + .map(|st| st.as_millis() as u32) + .unwrap_or_default(), + port::MACH_PORT_NULL + )); + + // Wait for a response from the Server + match ack_port.recv_ack(receive_timeout) { + Ok(result) => Ok(Some(result)), + Err(Error::Message(msg::MACH_RCV_TIMED_OUT)) => Ok(None), + Err(e) => Err(e), + } + } + } +} + +/// Returned from [`Server::try_recv_crash_context`] when a [`Client`] has sent +/// a crash context +pub struct ReceivedCrashContext { + /// The crash context sent by a [`Client`] + pub crash_context: CrashContext, + /// Allows the sending of an ack back to the [`Client`] to acknowledge that + /// your code has received and processed the [`CrashContext`] + pub acker: Acknowledger, + /// The process id of the process the [`Client`] lives in. This is retrieved + /// via `pid_for_task`. + pub pid: u32, +} + +/// Receives a [`CrashContext`] from another process +pub struct Server { + port: port::mach_port_t, +} + +impl Server { + /// Creates a new [`Server`] "bound" to the specified service name. + /// + /// # Errors + /// + /// We fail to acquire the bootstrap port, or fail to register the service. + pub fn create(name: &CStr) -> Result<Self, Error> { + // SAFETY: syscalls. Again, the caller has no invariants to uphold, so + // the entire function is not marked as unsafe + unsafe { + let mut task_bootstrap_port = 0; + kern!(task::task_get_special_port( + mach_task_self(), + task::TASK_BOOTSTRAP_PORT, + &mut task_bootstrap_port + )); + + let mut port = 0; + // Note that Breakpad uses bootstrap_register instead of this function as + // MacOS 10.5 apparently deprecated bootstrap_register and then provided + // bootstrap_check_in, but broken. However, 10.5 had its most recent update + // over 13 years ago, and is not supported by Apple, so why should we? + kern!(bootstrap::bootstrap_check_in( + task_bootstrap_port, + name.as_ptr(), + &mut port, + )); + + Ok(Self { port }) + } + } + + /// Attempts to retrieve a [`CrashContext`] sent from a crashing process. + /// + /// Note that in event of a timeout, this method will return `Ok(None)` to + /// indicate that a crash context was unavailable rather than an error. + /// + /// # Errors + /// + /// We fail to receive the [`CrashContext`] message for a reason other than + /// one not being in the queue, or we fail to translate the task identifier + /// into a pid + pub fn try_recv_crash_context( + &mut self, + timeout: Option<Duration>, + ) -> Result<Option<ReceivedCrashContext>, Error> { + // SAFETY: syscalls. The caller has no invariants to uphold, so the + // entire function is not marked unsafe. + unsafe { + let mut crash_ctx_msg: CrashContextMessage = std::mem::zeroed(); + crash_ctx_msg.head.local_port = self.port; + + let ret = msg::mach_msg( + ((&mut crash_ctx_msg.head) as *mut MachMsgHeader).cast(), + msg::MACH_RCV_MSG | msg::MACH_RCV_TIMEOUT, + 0, + std::mem::size_of::<CrashContextMessage>() as u32, + self.port, + timeout.map(|t| t.as_millis() as u32).unwrap_or_default(), + port::MACH_PORT_NULL, + ); + + if ret == msg::MACH_RCV_TIMED_OUT { + return Ok(None); + } else if ret != msg::MACH_MSG_SUCCESS { + return Err(Error::Message(ret)); + } + + // Reconstruct a crash context from the message we received + let exception = if crash_ctx_msg.flags & FLAG_HAS_EXCEPTION != 0 { + Some(crate::ExceptionInfo { + kind: crash_ctx_msg.exception_kind, + code: crash_ctx_msg.exception_code, + subcode: (crash_ctx_msg.flags & FLAG_HAS_SUBCODE != 0) + .then_some(crash_ctx_msg.exception_subcode), + }) + } else { + None + }; + + let crash_context = CrashContext { + task: crash_ctx_msg.task.name, + thread: crash_ctx_msg.crash_thread.name, + handler_thread: crash_ctx_msg.handler_thread.name, + exception, + }; + + // Translate the task to a pid so the user doesn't have to do it + // since there is not a binding available in libc/mach/mach2 for it + let mut pid = 0; + kern!(pid_for_task(crash_ctx_msg.task.name, &mut pid)); + let ack_port = crash_ctx_msg.ack_port.name; + + // Provide a way for the user to tell the client when they are done + // processing the crash context, unless the specified port was not + // set or somehow died immediately + let acker = Acknowledger { + port: (ack_port != port::MACH_PORT_DEAD && ack_port != port::MACH_PORT_NULL) + .then_some(ack_port), + }; + + Ok(Some(ReceivedCrashContext { + crash_context, + acker, + pid: pid as u32, + })) + } + } +} + +impl Drop for Server { + fn drop(&mut self) { + // SAFETY: syscall + unsafe { + mach_port::mach_port_deallocate(mach_task_self(), self.port); + } + } +} + +/// Used by a process running the [`Server`] to send a response back to the +/// [`Client`] that sent a [`CrashContext`] after it has finished +/// processing. +pub struct Acknowledger { + port: Option<port::mach_port_t>, +} + +impl Acknowledger { + /// Sends an ack back to the client that sent a [`CrashContext`] + /// + /// # Errors + /// + /// We fail to send the ack to the port created in the [`Client`] process + pub fn send_ack(&mut self, ack: u32, timeout: Option<Duration>) -> Result<(), Error> { + if let Some(port) = self.port { + // SAFETY: syscalls. The caller has no invariants to uphold, so the + // entire function is not marked unsafe. + unsafe { + let mut msg = AcknowledgementMessage { + head: MachMsgHeader { + bits: msg::MACH_MSG_TYPE_COPY_SEND, + size: std::mem::size_of::<AcknowledgementMessage>() as u32, + remote_port: port, + local_port: port::MACH_PORT_NULL, + voucher_port: port::MACH_PORT_NULL, + id: 0, + }, + result: ack, + }; + + // Try to actually send the message + msg!(msg::mach_msg( + ((&mut msg.head) as *mut MachMsgHeader).cast(), + msg::MACH_SEND_MSG | msg::MACH_SEND_TIMEOUT, + msg.head.size, + 0, + port::MACH_PORT_NULL, + timeout.map(|t| t.as_millis() as u32).unwrap_or_default(), + port::MACH_PORT_NULL + )); + + Ok(()) + } + } else { + Ok(()) + } + } +} + +/// Used by [`Sender::send_crash_context`] to create a port to receive the +/// external process's response to sending a [`CrashContext`] +struct AckReceiver { + port: port::mach_port_t, +} + +impl AckReceiver { + /// Allocates a new port to receive an ack from a [`Server`] + /// + /// # Errors + /// + /// We fail to allocate a port, or fail to add a send right to it. + /// + /// # Safety + /// + /// Performs syscalls. Only used internally hence the entire function being + /// marked unsafe. + unsafe fn new() -> Result<Self, Error> { + let mut port = 0; + kern!(mach_port::mach_port_allocate( + mach_task_self(), + port::MACH_PORT_RIGHT_RECEIVE, + &mut port + )); + + kern!(mach_port::mach_port_insert_right( + mach_task_self(), + port, + port, + msg::MACH_MSG_TYPE_MAKE_SEND + )); + + Ok(Self { port }) + } + + /// Waits for the specified duration to receive a result from the [`Server`] + /// that was sent a [`CrashContext`] + /// + /// # Errors + /// + /// We fail to receive an ack for some reason + /// + /// # Safety + /// + /// Performs syscalls. Only used internally hence the entire function being + /// marked unsafe. + unsafe fn recv_ack(&mut self, timeout: Option<Duration>) -> Result<u32, Error> { + let mut ack = AcknowledgementMessage { + head: MachMsgHeader { + bits: 0, + size: std::mem::size_of::<AcknowledgementMessage>() as u32, + remote_port: port::MACH_PORT_NULL, + local_port: self.port, + voucher_port: port::MACH_PORT_NULL, + id: 0, + }, + result: 0, + }; + + // Wait for a response from the Server + msg!(msg::mach_msg( + ((&mut ack.head) as *mut MachMsgHeader).cast(), + msg::MACH_RCV_MSG | msg::MACH_RCV_TIMEOUT, + 0, + ack.head.size, + self.port, + timeout.map(|t| t.as_millis() as u32).unwrap_or_default(), + port::MACH_PORT_NULL + )); + + Ok(ack.result) + } +} + +impl Drop for AckReceiver { + fn drop(&mut self) { + // SAFETY: syscall + unsafe { + mach_port::mach_port_deallocate(mach_task_self(), self.port); + } + } +} diff --git a/third_party/rust/crash-context/src/mac/resource.rs b/third_party/rust/crash-context/src/mac/resource.rs new file mode 100644 index 0000000000..c93acfe97a --- /dev/null +++ b/third_party/rust/crash-context/src/mac/resource.rs @@ -0,0 +1,516 @@ +//! Contains types and helpers for dealing with `EXC_RESOURCE` exceptions. +//! +//! `EXC_RESOURCE` exceptions embed details about the resource and the limits +//! it exceeded within the `code` and, in some cases `subcode`, fields of the exception +//! +//! See <https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/exc_resource.h> +//! for the various constants and decoding of exception information wrapped in +//! this module. + +use mach2::exception_types::EXC_RESOURCE; +use std::time::Duration; + +/// The details for an `EXC_RESOURCE` exception as retrieved from the exception's +/// code and subcode +pub enum ResourceException { + /// This is sent by the kernel when the CPU usage monitor is tripped. Possibly fatal. + Cpu(CpuResourceException), + /// This is sent by the kernel when the platform idle wakeups monitor is tripped. Possibly fatal. + Wakeups(WakeupsResourceException), + /// This is sent by the kernel when a task crosses its high watermark memory limit. Never fatal at least on current MacOS versions. + Memory(MemoryResourceException), + /// This is sent by the kernel when a task crosses its I/O limits. Never fatal. + Io(IoResourceException), + /// This is sent by the kernel when a task crosses its thread limit. Always fatal. + Threads(ThreadsResourceException), + /// This is sent by the kernel when the process is leaking ipc ports and has + /// filled its port space. Always fatal. + Ports(PortsResourceException), + /// An unknown resource kind due to an addition to the set of possible + /// resource exception kinds in exc_resource.h + Unknown { kind: u8, flavor: u8 }, +} + +/// Each different resource exception type has 1 or more flavors that it can be, +/// and while these most likely don't change often, we try to be forward +/// compatible by not failing if a particular flavor is unknown +#[derive(Copy, Clone, Debug)] +pub enum Flavor<T: Copy + Clone + std::fmt::Debug> { + Known(T), + Unknown(u8), +} + +impl<T: TryFrom<u8> + Copy + Clone + std::fmt::Debug> From<u64> for Flavor<T> { + #[inline] + fn from(code: u64) -> Self { + let flavor = resource_exc_flavor(code); + if let Ok(known) = T::try_from(flavor) { + Self::Known(known) + } else { + Self::Unknown(flavor) + } + } +} + +impl<T: PartialEq + Copy + Clone + std::fmt::Debug> PartialEq<T> for Flavor<T> { + fn eq(&self, o: &T) -> bool { + match self { + Self::Known(flavor) => flavor == o, + Self::Unknown(_) => false, + } + } +} + +/// Retrieves the resource exception kind from an exception code +#[inline] +pub fn resource_exc_kind(code: u64) -> u8 { + ((code >> 61) & 0x7) as u8 +} + +/// Retrieves the resource exception flavor from an exception code +#[inline] +pub fn resource_exc_flavor(code: u64) -> u8 { + ((code >> 58) & 0x7) as u8 +} + +impl super::ExceptionInfo { + /// If this is an `EXC_RESOURCE` exception, retrieves the exception metadata + /// from the code, otherwise returns `None` + pub fn resource_exception(&self) -> Option<ResourceException> { + if self.kind != EXC_RESOURCE { + return None; + } + + let kind = resource_exc_kind(self.code); + + let res_exc = if kind == ResourceKind::Cpu as u8 { + ResourceException::Cpu(CpuResourceException::from_exc_info(self.code, self.subcode)) + } else if kind == ResourceKind::Wakeups as u8 { + ResourceException::Wakeups(WakeupsResourceException::from_exc_info( + self.code, + self.subcode, + )) + } else if kind == ResourceKind::Memory as u8 { + ResourceException::Memory(MemoryResourceException::from_exc_info(self.code)) + } else if kind == ResourceKind::Io as u8 { + ResourceException::Io(IoResourceException::from_exc_info(self.code, self.subcode)) + } else if kind == ResourceKind::Threads as u8 { + ResourceException::Threads(ThreadsResourceException::from_exc_info(self.code)) + } else if kind == ResourceKind::Ports as u8 { + ResourceException::Ports(PortsResourceException::from_exc_info(self.code)) + } else { + ResourceException::Unknown { + kind, + flavor: resource_exc_flavor(self.code), + } + }; + + Some(res_exc) + } +} + +/// The types of resources that an `EXC_RESOURCE` exception can pertain to +#[repr(u8)] +pub enum ResourceKind { + Cpu = 1, + Wakeups = 2, + Memory = 3, + Io = 4, + Threads = 5, + Ports = 6, +} + +/// The flavors for a [`CpuResourceException`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum CpuFlavor { + /// The process has surpassed its CPU limit + Monitor = 1, + /// The process has surpassed its CPU limit, and the process has been configured + /// to make this exception fatal + MonitorFatal = 2, +} + +impl TryFrom<u8> for CpuFlavor { + type Error = (); + + fn try_from(flavor: u8) -> Result<Self, Self::Error> { + match flavor { + 1 => Ok(Self::Monitor), + 2 => Ok(Self::MonitorFatal), + _ => Err(()), + } + } +} + +/// These exceptions _may_ be fatal. They are not fatal by default at task +/// creation but can be made fatal by calling `proc_rlimit_control` with +/// `RLIMIT_CPU_USAGE_MONITOR` as the second argument and `CPUMON_MAKE_FATAL` +/// set in the flags. The flavor extracted from the exception code determines if +/// the exception is fatal. +/// +/// [Kernel code](https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/kern/thread.c#L2475-L2616) +#[derive(Copy, Clone, Debug)] +pub struct CpuResourceException { + pub flavor: Flavor<CpuFlavor>, + /// If the exception is fatal. Currently only true if the flavor is [`CpuFlavor::MonitorFatal`] + pub is_fatal: bool, + /// The time period in which the CPU limit was surpassed + pub observation_interval: Duration, + /// The CPU % limit + pub limit: u8, + /// The CPU % consumed by the task + pub consumed: u8, +} + +impl CpuResourceException { + /* + * code: + * +-----------------------------------------------+ + * |[63:61] RESOURCE |[60:58] FLAVOR_CPU_ |[57:32] | + * |_TYPE_CPU |MONITOR[_FATAL] |Unused | + * +-----------------------------------------------+ + * |[31:7] Interval (sec) | [6:0] CPU limit (%)| + * +-----------------------------------------------+ + * + * subcode: + * +-----------------------------------------------+ + * | | [6:0] % of CPU | + * | | actually consumed | + * +-----------------------------------------------+ + * + */ + #[inline] + pub fn from_exc_info(code: u64, subcode: Option<u64>) -> Self { + debug_assert_eq!(resource_exc_kind(code), ResourceKind::Cpu as u8); + + let flavor = Flavor::from(code); + let interval_seconds = (code >> 7) & 0x1ffffff; + let limit = (code & 0x7f) as u8; + let consumed = subcode.map_or(0, |sc| sc & 0x7f) as u8; + + // The default is that cpu resource exceptions are not fatal, so + // we only check the flavor against the (currently) one known value + // that indicates the exception is fatal + Self { + flavor, + is_fatal: flavor == CpuFlavor::MonitorFatal, + observation_interval: Duration::from_secs(interval_seconds), + limit, + consumed, + } + } +} + +/// The flavors for a [`WakeupsResourceException`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum WakeupsFlavor { + Monitor = 1, +} + +impl TryFrom<u8> for WakeupsFlavor { + type Error = (); + + fn try_from(flavor: u8) -> Result<Self, Self::Error> { + match flavor { + 1 => Ok(Self::Monitor), + _ => Err(()), + } + } +} + +/// These exceptions may be fatal. They are not fatal by default at task +/// creation, but can be made fatal by calling `proc_rlimit_control` with +/// `RLIMIT_WAKEUPS_MONITOR` as the second argument and `WAKEMON_MAKE_FATAL` +/// set in the flags. Calling [`proc_get_wakemon_params`](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/libsyscall/wrappers/libproc/libproc.c#L592-L608) +/// determines whether these exceptions are fatal. +/// +/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/task.c#L7501-L7580) +pub struct WakeupsResourceException { + pub flavor: Flavor<WakeupsFlavor>, + /// The time period in which the number of wakeups was surpassed + pub observation_interval: Duration, + /// The number of wakeups permitted per second + pub permitted: u32, + /// The number of wakeups observed per second + pub observed: u32, +} + +impl WakeupsResourceException { + /* + * code: + * +-----------------------------------------------+ + * |[63:61] RESOURCE |[60:58] FLAVOR_ |[57:32] | + * |_TYPE_WAKEUPS |WAKEUPS_MONITOR |Unused | + * +-----------------------------------------------+ + * | [31:20] Observation | [19:0] # of wakeups | + * | interval (sec) | permitted (per sec) | + * +-----------------------------------------------+ + * + * subcode: + * +-----------------------------------------------+ + * | | [19:0] # of wakeups | + * | | observed (per sec) | + * +-----------------------------------------------+ + * + */ + #[inline] + pub fn from_exc_info(code: u64, subcode: Option<u64>) -> Self { + debug_assert_eq!(resource_exc_kind(code), ResourceKind::Wakeups as u8); + + let flavor = Flavor::from(code); + // Note that Apple has a bug in exc_resource.h where the masks in the + // decode macros for the interval and the permitted wakeups have been swapped + let interval_seconds = (code >> 20) & 0xfff; + let permitted = (code & 0xfffff) as u32; + let observed = subcode.map_or(0, |sc| sc & 0xfffff) as u32; + + Self { + flavor, + observation_interval: Duration::from_secs(interval_seconds), + permitted, + observed, + } + } +} + +/// The flavors for a [`MemoryResourceException`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum MemoryFlavor { + HighWatermark = 1, +} + +impl TryFrom<u8> for MemoryFlavor { + type Error = (); + + fn try_from(flavor: u8) -> Result<Self, Self::Error> { + match flavor { + 1 => Ok(Self::HighWatermark), + _ => Err(()), + } + } +} + +/// These exceptions, as of this writing, are never fatal. +/// +/// While memory exceptions _can_ be fatal, this appears to only be possible if +/// the kernel is built with `CONFIG_JETSAM` or in `DEVELOPMENT` or `DEBUG` modes, +/// so as of now, they should never be considered fatal, at least on `MacOS` +/// +/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/task.c#L6767-L6874) +pub struct MemoryResourceException { + pub flavor: Flavor<MemoryFlavor>, + /// The limit in MiB of the high watermark + pub limit_mib: u16, +} + +impl MemoryResourceException { + /* + * code: + * +------------------------------------------------+ + * |[63:61] RESOURCE |[60:58] FLAVOR_HIGH_ |[57:32] | + * |_TYPE_MEMORY |WATERMARK |Unused | + * +------------------------------------------------+ + * | | [12:0] HWM limit (MB)| + * +------------------------------------------------+ + * + * subcode: + * +------------------------------------------------+ + * | unused | + * +------------------------------------------------+ + * + */ + #[inline] + pub fn from_exc_info(code: u64) -> Self { + debug_assert_eq!(resource_exc_kind(code), ResourceKind::Memory as u8); + + let flavor = Flavor::from(code); + let limit_mib = (code & 0x1fff) as u16; + + Self { flavor, limit_mib } + } +} + +/// The flavors for an [`IoResourceException`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum IoFlavor { + PhysicalWrites = 1, + LogicalWrites = 2, +} + +impl TryFrom<u8> for IoFlavor { + type Error = (); + + fn try_from(flavor: u8) -> Result<Self, Self::Error> { + match flavor { + 1 => Ok(Self::PhysicalWrites), + 2 => Ok(Self::LogicalWrites), + _ => Err(()), + } + } +} + +/// These exceptions are never fatal. +/// +/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/task.c#L7739-L7792) +pub struct IoResourceException { + pub flavor: Flavor<MemoryFlavor>, + /// The time period in which the I/O limit was surpassed + pub observation_interval: Duration, + /// The I/O limit in MiB of the high watermark + pub limit_mib: u16, + /// The observed I/O in MiB + pub observed_mib: u16, +} + +impl IoResourceException { + /* + * code: + * +-----------------------------------------------+ + * |[63:61] RESOURCE |[60:58] FLAVOR_IO_ |[57:32] | + * |_TYPE_IO |PHYSICAL/LOGICAL |Unused | + * +-----------------------------------------------+ + * |[31:15] Interval (sec) | [14:0] Limit (MB) | + * +-----------------------------------------------+ + * + * subcode: + * +-----------------------------------------------+ + * | | [14:0] I/O Count | + * | | (in MB) | + * +-----------------------------------------------+ + * + */ + #[inline] + pub fn from_exc_info(code: u64, subcode: Option<u64>) -> Self { + debug_assert_eq!(resource_exc_kind(code), ResourceKind::Io as u8); + + let flavor = Flavor::from(code); + let interval_seconds = (code >> 15) & 0x1ffff; + let limit_mib = (code & 0x7fff) as u16; + let observed_mib = subcode.map_or(0, |sc| sc & 0x7fff) as u16; + + Self { + flavor, + observation_interval: Duration::from_secs(interval_seconds), + limit_mib, + observed_mib, + } + } +} + +/// The flavors for a [`ThreadsResourceException`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum ThreadsFlavor { + HighWatermark = 1, +} + +impl TryFrom<u8> for ThreadsFlavor { + type Error = (); + + fn try_from(flavor: u8) -> Result<Self, Self::Error> { + match flavor { + 1 => Ok(Self::HighWatermark), + _ => Err(()), + } + } +} + +/// This exception is provided for completeness sake, but is only possible if +/// the kernel is built in `DEVELOPMENT` or `DEBUG` modes. +/// +/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/osfmk/kern/thread.c#L2575-L2620) +pub struct ThreadsResourceException { + pub flavor: Flavor<ThreadsFlavor>, + /// The thread limit + pub limit: u16, +} + +impl ThreadsResourceException { + /* + * code: + * +--------------------------------------------------+ + * |[63:61] RESOURCE |[60:58] FLAVOR_ |[57:32] | + * |_TYPE_THREADS |THREADS_HIGH_WATERMARK |Unused | + * +--------------------------------------------------+ + * |[31:15] Unused | [14:0] Limit | + * +--------------------------------------------------+ + * + * subcode: + * +-----------------------------------------------+ + * | | Unused | + * | | | + * +-----------------------------------------------+ + * + */ + #[inline] + pub fn from_exc_info(code: u64) -> Self { + debug_assert_eq!(resource_exc_kind(code), ResourceKind::Threads as u8); + + let flavor = Flavor::from(code); + let limit = (code & 0x7fff) as u16; + + Self { flavor, limit } + } +} + +/// The flavors for a [`PortsResourceException`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum PortsFlavor { + SpaceFull = 1, +} + +impl TryFrom<u8> for PortsFlavor { + type Error = (); + + fn try_from(flavor: u8) -> Result<Self, Self::Error> { + match flavor { + 1 => Ok(Self::SpaceFull), + _ => Err(()), + } + } +} + +/// This exception is always fatal, and in fact I'm unsure if this exception +/// is even observable, as the kernel will kill the offending process if +/// the port space is full +/// +/// [Kernel source](https://github.com/apple-oss-distributions/xnu/blob/e7776783b89a353188416a9a346c6cdb4928faad/osfmk/kern/task.c#L7907-L7969) +pub struct PortsResourceException { + pub flavor: Flavor<ThreadsFlavor>, + /// The number of allocated ports + pub allocated: u32, +} + +impl PortsResourceException { + /* + * code: + * +-----------------------------------------------+ + * |[63:61] RESOURCE |[60:58] FLAVOR_ |[57:32] | + * |_TYPE_PORTS |PORT_SPACE_FULL |Unused | + * +-----------------------------------------------+ + * | [31:24] Unused | [23:0] # of ports | + * | | allocated | + * +-----------------------------------------------+ + * + * subcode: + * +-----------------------------------------------+ + * | | Unused | + * | | | + * +-----------------------------------------------+ + * + */ + #[inline] + pub fn from_exc_info(code: u64) -> Self { + debug_assert_eq!(resource_exc_kind(code), ResourceKind::Ports as u8); + + let flavor = Flavor::from(code); + let allocated = (code & 0xffffff) as u32; + + Self { flavor, allocated } + } +} diff --git a/third_party/rust/crash-context/src/windows.rs b/third_party/rust/crash-context/src/windows.rs new file mode 100644 index 0000000000..231a608c08 --- /dev/null +++ b/third_party/rust/crash-context/src/windows.rs @@ -0,0 +1,262 @@ +/// Full Windows crash context +pub struct CrashContext { + /// The information on the exception. + /// + /// Note that this is a pointer into the actual memory of the crashed process, + /// and is a pointer to an [EXCEPTION_POINTERS](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-exception_pointers) + pub exception_pointers: *const EXCEPTION_POINTERS, + /// The top level exception code from the exception_pointers. This is provided + /// so that external processes don't need to use `ReadProcessMemory` to inspect + /// the exception code + pub exception_code: i32, + /// The pid of the process that crashed + pub process_id: u32, + /// The thread id on which the exception occurred + pub thread_id: u32, +} + +#[link(name = "kernel32")] +extern "system" { + #[link_name = "RtlCaptureContext"] + pub fn capture_context(ctx_rec: *mut CONTEXT); +} + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + #[repr(C, align(16))] + pub struct M128A { + pub Low: u64, + pub High: i64, + } + + #[repr(C)] + pub struct CONTEXT_0_0 { + pub Header: [M128A; 2], + pub Legacy: [M128A; 8], + pub Xmm0: M128A, + pub Xmm1: M128A, + pub Xmm2: M128A, + pub Xmm3: M128A, + pub Xmm4: M128A, + pub Xmm5: M128A, + pub Xmm6: M128A, + pub Xmm7: M128A, + pub Xmm8: M128A, + pub Xmm9: M128A, + pub Xmm10: M128A, + pub Xmm11: M128A, + pub Xmm12: M128A, + pub Xmm13: M128A, + pub Xmm14: M128A, + pub Xmm15: M128A, + } + + #[repr(C, align(16))] + pub struct XSAVE_FORMAT { + pub ControlWord: u16, + pub StatusWord: u16, + pub TagWord: u8, + pub Reserved1: u8, + pub ErrorOpcode: u16, + pub ErrorOffset: u32, + pub ErrorSelector: u16, + pub Reserved2: u16, + pub DataOffset: u32, + pub DataSelector: u16, + pub Reserved3: u16, + pub MxCsr: u32, + pub MxCsr_Mask: u32, + pub FloatRegisters: [M128A; 8], + pub XmmRegisters: [M128A; 16], + pub Reserved4: [u8; 96], + } + + #[repr(C)] + pub union CONTEXT_0 { + pub FltSave: std::mem::ManuallyDrop<XSAVE_FORMAT>, + pub Anonymous: std::mem::ManuallyDrop<CONTEXT_0_0>, + } + + #[repr(C, align(16))] + pub struct CONTEXT { + pub P1Home: u64, + pub P2Home: u64, + pub P3Home: u64, + pub P4Home: u64, + pub P5Home: u64, + pub P6Home: u64, + pub ContextFlags: u32, + pub MxCsr: u32, + pub SegCs: u16, + pub SegDs: u16, + pub SegEs: u16, + pub SegFs: u16, + pub SegGs: u16, + pub SegSs: u16, + pub EFlags: u32, + pub Dr0: u64, + pub Dr1: u64, + pub Dr2: u64, + pub Dr3: u64, + pub Dr6: u64, + pub Dr7: u64, + pub Rax: u64, + pub Rcx: u64, + pub Rdx: u64, + pub Rbx: u64, + pub Rsp: u64, + pub Rbp: u64, + pub Rsi: u64, + pub Rdi: u64, + pub R8: u64, + pub R9: u64, + pub R10: u64, + pub R11: u64, + pub R12: u64, + pub R13: u64, + pub R14: u64, + pub R15: u64, + pub Rip: u64, + pub Anonymous: CONTEXT_0, + pub VectorRegister: [M128A; 26], + pub VectorControl: u64, + pub DebugControl: u64, + pub LastBranchToRip: u64, + pub LastBranchFromRip: u64, + pub LastExceptionToRip: u64, + pub LastExceptionFromRip: u64, + } + } else if #[cfg(target_arch = "x86")] { + #[repr(C)] + pub struct FLOATING_SAVE_AREA { + pub ControlWord: u32, + pub StatusWord: u32, + pub TagWord: u32, + pub ErrorOffset: u32, + pub ErrorSelector: u32, + pub DataOffset: u32, + pub DataSelector: u32, + pub RegisterArea: [u8; 80], + pub Spare0: u32, + } + + #[repr(C, packed(4))] + pub struct CONTEXT { + pub ContextFlags: u32, + pub Dr0: u32, + pub Dr1: u32, + pub Dr2: u32, + pub Dr3: u32, + pub Dr6: u32, + pub Dr7: u32, + pub FloatSave: FLOATING_SAVE_AREA, + pub SegGs: u32, + pub SegFs: u32, + pub SegEs: u32, + pub SegDs: u32, + pub Edi: u32, + pub Esi: u32, + pub Ebx: u32, + pub Edx: u32, + pub Ecx: u32, + pub Eax: u32, + pub Ebp: u32, + pub Eip: u32, + pub SegCs: u32, + pub EFlags: u32, + pub Esp: u32, + pub SegSs: u32, + pub ExtendedRegisters: [u8; 512], + } + } else if #[cfg(target_arch = "aarch64")] { + #[repr(C)] + pub struct ARM64_NT_NEON128_0 { + pub Low: u64, + pub High: i64, + } + #[repr(C)] + pub union ARM64_NT_NEON128 { + pub Anonymous: std::mem::ManuallyDrop<ARM64_NT_NEON128_0>, + pub D: [f64; 2], + pub S: [f32; 4], + pub H: [u16; 8], + pub B: [u8; 16], + } + + #[repr(C)] + pub struct CONTEXT_0_0 { + pub X0: u64, + pub X1: u64, + pub X2: u64, + pub X3: u64, + pub X4: u64, + pub X5: u64, + pub X6: u64, + pub X7: u64, + pub X8: u64, + pub X9: u64, + pub X10: u64, + pub X11: u64, + pub X12: u64, + pub X13: u64, + pub X14: u64, + pub X15: u64, + pub X16: u64, + pub X17: u64, + pub X18: u64, + pub X19: u64, + pub X20: u64, + pub X21: u64, + pub X22: u64, + pub X23: u64, + pub X24: u64, + pub X25: u64, + pub X26: u64, + pub X27: u64, + pub X28: u64, + pub Fp: u64, + pub Lr: u64, + } + + #[repr(C)] + pub union CONTEXT_0 { + pub Anonymous: std::mem::ManuallyDrop<CONTEXT_0_0>, + pub X: [u64; 31], + } + + #[repr(C, align(16))] + pub struct CONTEXT { + pub ContextFlags: u32, + pub Cpsr: u32, + pub Anonymous: CONTEXT_0, + pub Sp: u64, + pub Pc: u64, + pub V: [ARM64_NT_NEON128; 32], + pub Fpcr: u32, + pub Fpsr: u32, + pub Bcr: [u32; 8], + pub Bvr: [u64; 8], + pub Wcr: [u32; 2], + pub Wvr: [u64; 2], + } + } +} + +pub type NTSTATUS = i32; +pub type BOOL = i32; + +#[repr(C)] +pub struct EXCEPTION_RECORD { + pub ExceptionCode: NTSTATUS, + pub ExceptionFlags: u32, + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ExceptionAddress: *mut std::ffi::c_void, + pub NumberParameters: u32, + pub ExceptionInformation: [usize; 15], +} + +#[repr(C)] +pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ContextRecord: *mut CONTEXT, +} |