summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_driver_impl/src/signal_handler.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
commitc23a457e72abe608715ac76f076f47dc42af07a5 (patch)
tree2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /compiler/rustc_driver_impl/src/signal_handler.rs
parentReleasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz
rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_driver_impl/src/signal_handler.rs')
-rw-r--r--compiler/rustc_driver_impl/src/signal_handler.rs142
1 files changed, 142 insertions, 0 deletions
diff --git a/compiler/rustc_driver_impl/src/signal_handler.rs b/compiler/rustc_driver_impl/src/signal_handler.rs
new file mode 100644
index 000000000..deca10822
--- /dev/null
+++ b/compiler/rustc_driver_impl/src/signal_handler.rs
@@ -0,0 +1,142 @@
+//! Signal handler for rustc
+//! Primarily used to extract a backtrace from stack overflow
+
+use std::alloc::{alloc, Layout};
+use std::{fmt, mem, ptr};
+
+extern "C" {
+ fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int);
+}
+
+fn backtrace_stderr(buffer: &[*mut libc::c_void]) {
+ let size = buffer.len().try_into().unwrap_or_default();
+ unsafe { backtrace_symbols_fd(buffer.as_ptr(), size, libc::STDERR_FILENO) };
+}
+
+/// Unbuffered, unsynchronized writer to stderr.
+///
+/// Only acceptable because everything will end soon anyways.
+struct RawStderr(());
+
+impl fmt::Write for RawStderr {
+ fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
+ let ret = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) };
+ if ret == -1 { Err(fmt::Error) } else { Ok(()) }
+ }
+}
+
+/// We don't really care how many bytes we actually get out. SIGSEGV comes for our head.
+/// Splash stderr with letters of our own blood to warn our friends about the monster.
+macro raw_errln($tokens:tt) {
+ let _ = ::core::fmt::Write::write_fmt(&mut RawStderr(()), format_args!($tokens));
+ let _ = ::core::fmt::Write::write_char(&mut RawStderr(()), '\n');
+}
+
+/// Signal handler installed for SIGSEGV
+extern "C" fn print_stack_trace(_: libc::c_int) {
+ const MAX_FRAMES: usize = 256;
+ // Reserve data segment so we don't have to malloc in a signal handler, which might fail
+ // in incredibly undesirable and unexpected ways due to e.g. the allocator deadlocking
+ static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [ptr::null_mut(); MAX_FRAMES];
+ let stack = unsafe {
+ // Collect return addresses
+ let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32);
+ if depth == 0 {
+ return;
+ }
+ &STACK_TRACE.as_slice()[0..(depth as _)]
+ };
+
+ // Just a stack trace is cryptic. Explain what we're doing.
+ raw_errln!("error: rustc interrupted by SIGSEGV, printing backtrace\n");
+ let mut written = 1;
+ let mut consumed = 0;
+ // Begin elaborating return addrs into symbols and writing them directly to stderr
+ // Most backtraces are stack overflow, most stack overflows are from recursion
+ // Check for cycles before writing 250 lines of the same ~5 symbols
+ let cycled = |(runner, walker)| runner == walker;
+ let mut cyclic = false;
+ if let Some(period) = stack.iter().skip(1).step_by(2).zip(stack).position(cycled) {
+ let period = period.saturating_add(1); // avoid "what if wrapped?" branches
+ let Some(offset) = stack.iter().skip(period).zip(stack).position(cycled) else {
+ // impossible.
+ return;
+ };
+
+ // Count matching trace slices, else we could miscount "biphasic cycles"
+ // with the same period + loop entry but a different inner loop
+ let next_cycle = stack[offset..].chunks_exact(period).skip(1);
+ let cycles = 1 + next_cycle
+ .zip(stack[offset..].chunks_exact(period))
+ .filter(|(next, prev)| next == prev)
+ .count();
+ backtrace_stderr(&stack[..offset]);
+ written += offset;
+ consumed += offset;
+ if cycles > 1 {
+ raw_errln!("\n### cycle encountered after {offset} frames with period {period}");
+ backtrace_stderr(&stack[consumed..consumed + period]);
+ raw_errln!("### recursed {cycles} times\n");
+ written += period + 4;
+ consumed += period * cycles;
+ cyclic = true;
+ };
+ }
+ let rem = &stack[consumed..];
+ backtrace_stderr(rem);
+ raw_errln!("");
+ written += rem.len() + 1;
+
+ let random_depth = || 8 * 16; // chosen by random diceroll (2d20)
+ if cyclic || stack.len() > random_depth() {
+ // technically speculation, but assert it with confidence anyway.
+ // rustc only arrived in this signal handler because bad things happened
+ // and this message is for explaining it's not the programmer's fault
+ raw_errln!("note: rustc unexpectedly overflowed its stack! this is a bug");
+ written += 1;
+ }
+ if stack.len() == MAX_FRAMES {
+ raw_errln!("note: maximum backtrace depth reached, frames may have been lost");
+ written += 1;
+ }
+ raw_errln!("note: we would appreciate a report at https://github.com/rust-lang/rust");
+ written += 1;
+ if written > 24 {
+ // We probably just scrolled the earlier "we got SIGSEGV" message off the terminal
+ raw_errln!("note: backtrace dumped due to SIGSEGV! resuming signal");
+ };
+}
+
+/// When SIGSEGV is delivered to the process, print a stack trace and then exit.
+pub(super) fn install() {
+ unsafe {
+ let alt_stack_size: usize = min_sigstack_size() + 64 * 1024;
+ let mut alt_stack: libc::stack_t = mem::zeroed();
+ alt_stack.ss_sp = alloc(Layout::from_size_align(alt_stack_size, 1).unwrap()).cast();
+ alt_stack.ss_size = alt_stack_size;
+ libc::sigaltstack(&alt_stack, ptr::null_mut());
+
+ let mut sa: libc::sigaction = mem::zeroed();
+ sa.sa_sigaction = print_stack_trace as libc::sighandler_t;
+ sa.sa_flags = libc::SA_NODEFER | libc::SA_RESETHAND | libc::SA_ONSTACK;
+ libc::sigemptyset(&mut sa.sa_mask);
+ libc::sigaction(libc::SIGSEGV, &sa, ptr::null_mut());
+ }
+}
+
+/// Modern kernels on modern hardware can have dynamic signal stack sizes.
+#[cfg(any(target_os = "linux", target_os = "android"))]
+fn min_sigstack_size() -> usize {
+ const AT_MINSIGSTKSZ: core::ffi::c_ulong = 51;
+ let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) };
+ // If getauxval couldn't find the entry, it returns 0,
+ // so take the higher of the "constant" and auxval.
+ // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ
+ libc::MINSIGSTKSZ.max(dynamic_sigstksz as _)
+}
+
+/// Not all OS support hardware where this is needed.
+#[cfg(not(any(target_os = "linux", target_os = "android")))]
+fn min_sigstack_size() -> usize {
+ libc::MINSIGSTKSZ
+}