|
| 1 | +//! Signal handler for rustc |
| 2 | +//! Primarily used to extract a backtrace from stack overflow |
| 3 | +
|
| 4 | +use std::alloc::{alloc, Layout}; |
| 5 | +use std::{fmt, mem, ptr}; |
| 6 | + |
| 7 | +extern "C" { |
| 8 | + fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int); |
| 9 | +} |
| 10 | + |
| 11 | +fn backtrace_stderr(buffer: &[*mut libc::c_void]) { |
| 12 | + let size = buffer.len().try_into().unwrap_or_default(); |
| 13 | + unsafe { backtrace_symbols_fd(buffer.as_ptr(), size, libc::STDERR_FILENO) }; |
| 14 | +} |
| 15 | + |
| 16 | +/// Unbuffered, unsynchronized writer to stderr. |
| 17 | +/// |
| 18 | +/// Only acceptable because everything will end soon anyways. |
| 19 | +struct RawStderr(()); |
| 20 | + |
| 21 | +impl fmt::Write for RawStderr { |
| 22 | + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { |
| 23 | + let ret = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) }; |
| 24 | + if ret == -1 { Err(fmt::Error) } else { Ok(()) } |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +/// We don't really care how many bytes we actually get out. SIGSEGV comes for our head. |
| 29 | +/// Splash stderr with letters of our own blood to warn our friends about the monster. |
| 30 | +macro raw_errln($tokens:tt) { |
| 31 | + let _ = ::core::fmt::Write::write_fmt(&mut RawStderr(()), format_args!($tokens)); |
| 32 | + let _ = ::core::fmt::Write::write_char(&mut RawStderr(()), '\n'); |
| 33 | +} |
| 34 | + |
| 35 | +/// Signal handler installed for SIGSEGV |
| 36 | +extern "C" fn print_stack_trace(_: libc::c_int) { |
| 37 | + const MAX_FRAMES: usize = 256; |
| 38 | + // Reserve data segment so we don't have to malloc in a signal handler, which might fail |
| 39 | + // in incredibly undesirable and unexpected ways due to e.g. the allocator deadlocking |
| 40 | + static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [ptr::null_mut(); MAX_FRAMES]; |
| 41 | + let stack = unsafe { |
| 42 | + // Collect return addresses |
| 43 | + let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32); |
| 44 | + if depth == 0 { |
| 45 | + return; |
| 46 | + } |
| 47 | + &STACK_TRACE.as_slice()[0..(depth as _)] |
| 48 | + }; |
| 49 | + |
| 50 | + // Just a stack trace is cryptic. Explain what we're doing. |
| 51 | + raw_errln!("error: rustc interrupted by SIGSEGV, printing backtrace\n"); |
| 52 | + let mut written = 1; |
| 53 | + let mut consumed = 0; |
| 54 | + // Begin elaborating return addrs into symbols and writing them directly to stderr |
| 55 | + // Most backtraces are stack overflow, most stack overflows are from recursion |
| 56 | + // Check for cycles before writing 250 lines of the same ~5 symbols |
| 57 | + let cycled = |(runner, walker)| runner == walker; |
| 58 | + let mut cyclic = false; |
| 59 | + if let Some(period) = stack.iter().skip(1).step_by(2).zip(stack).position(cycled) { |
| 60 | + let period = period.saturating_add(1); // avoid "what if wrapped?" branches |
| 61 | + let Some(offset) = stack.iter().skip(period).zip(stack).position(cycled) else { |
| 62 | + // impossible. |
| 63 | + return; |
| 64 | + }; |
| 65 | + |
| 66 | + // Count matching trace slices, else we could miscount "biphasic cycles" |
| 67 | + // with the same period + loop entry but a different inner loop |
| 68 | + let next_cycle = stack[offset..].chunks_exact(period).skip(1); |
| 69 | + let cycles = 1 + next_cycle |
| 70 | + .zip(stack[offset..].chunks_exact(period)) |
| 71 | + .filter(|(next, prev)| next == prev) |
| 72 | + .count(); |
| 73 | + backtrace_stderr(&stack[..offset]); |
| 74 | + written += offset; |
| 75 | + consumed += offset; |
| 76 | + if cycles > 1 { |
| 77 | + raw_errln!("\n### cycle encountered after {offset} frames with period {period}"); |
| 78 | + backtrace_stderr(&stack[consumed..consumed + period]); |
| 79 | + raw_errln!("### recursed {cycles} times\n"); |
| 80 | + written += period + 4; |
| 81 | + consumed += period * cycles; |
| 82 | + cyclic = true; |
| 83 | + }; |
| 84 | + } |
| 85 | + let rem = &stack[consumed..]; |
| 86 | + backtrace_stderr(rem); |
| 87 | + raw_errln!(""); |
| 88 | + written += rem.len() + 1; |
| 89 | + |
| 90 | + let random_depth = || 8 * 16; // chosen by random diceroll (2d20) |
| 91 | + if cyclic || stack.len() > random_depth() { |
| 92 | + // technically speculation, but assert it with confidence anyway. |
| 93 | + // rustc only arrived in this signal handler because bad things happened |
| 94 | + // and this message is for explaining it's not the programmer's fault |
| 95 | + raw_errln!("note: rustc unexpectedly overflowed its stack! this is a bug"); |
| 96 | + written += 1; |
| 97 | + } |
| 98 | + if stack.len() == MAX_FRAMES { |
| 99 | + raw_errln!("note: maximum backtrace depth reached, frames may have been lost"); |
| 100 | + written += 1; |
| 101 | + } |
| 102 | + raw_errln!("note: we would appreciate a report at https://github.com/rust-lang/rust"); |
| 103 | + written += 1; |
| 104 | + if written > 24 { |
| 105 | + // We probably just scrolled the earlier "we got SIGSEGV" message off the terminal |
| 106 | + raw_errln!("note: backtrace dumped due to SIGSEGV! resuming signal"); |
| 107 | + }; |
| 108 | +} |
| 109 | + |
| 110 | +/// When SIGSEGV is delivered to the process, print a stack trace and then exit. |
| 111 | +pub(super) fn install() { |
| 112 | + unsafe { |
| 113 | + let alt_stack_size: usize = min_sigstack_size() + 64 * 1024; |
| 114 | + let mut alt_stack: libc::stack_t = mem::zeroed(); |
| 115 | + alt_stack.ss_sp = alloc(Layout::from_size_align(alt_stack_size, 1).unwrap()).cast(); |
| 116 | + alt_stack.ss_size = alt_stack_size; |
| 117 | + libc::sigaltstack(&alt_stack, ptr::null_mut()); |
| 118 | + |
| 119 | + let mut sa: libc::sigaction = mem::zeroed(); |
| 120 | + sa.sa_sigaction = print_stack_trace as libc::sighandler_t; |
| 121 | + sa.sa_flags = libc::SA_NODEFER | libc::SA_RESETHAND | libc::SA_ONSTACK; |
| 122 | + libc::sigemptyset(&mut sa.sa_mask); |
| 123 | + libc::sigaction(libc::SIGSEGV, &sa, ptr::null_mut()); |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +/// Modern kernels on modern hardware can have dynamic signal stack sizes. |
| 128 | +#[cfg(any(target_os = "linux", target_os = "android"))] |
| 129 | +fn min_sigstack_size() -> usize { |
| 130 | + const AT_MINSIGSTKSZ: core::ffi::c_ulong = 51; |
| 131 | + let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) }; |
| 132 | + // If getauxval couldn't find the entry, it returns 0, |
| 133 | + // so take the higher of the "constant" and auxval. |
| 134 | + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ |
| 135 | + libc::MINSIGSTKSZ.max(dynamic_sigstksz as _) |
| 136 | +} |
| 137 | + |
| 138 | +/// Not all OS support hardware where this is needed. |
| 139 | +#[cfg(not(any(target_os = "linux", target_os = "android")))] |
| 140 | +fn min_sigstack_size() -> usize { |
| 141 | + libc::MINSIGSTKSZ |
| 142 | +} |
0 commit comments