Skip to content

Commit dafea5f

Browse files
authored
Rollup merge of #113565 - workingjubilee:better-signal-handler-message, r=pnkfelix
Make SIGSEGV handler emit nicer backtraces This annotates the code heavily with comments to explain what is going on, for the benefit of other compiler contributors. The backtrace also emits appropriate comments to clarify, to a programmer who may not know why a bunch of file paths and hexadecimal blather was just dumped into stderr, what is going on. Finally, it detects cycles and uses their regularity to avoid repeating a bunch of text. The previous backtraces we were emitting was extremely unfriendly, potentially confusing, and often alarming, and this makes things almost "nice". We can't necessarily make them much nicer than this, because a signal handler must use "signal-safe" functions. This precludes conveniences like dynamic allocations. Fortunately, Rust's stdlib has allocation-free formatting, but it may hinder integrating this error with our localization middleware, as I wasn't able to clearly ascertain, at a glance, whether there was a zero-alloc path through it. r? `@Nilstrieb`
2 parents d64c845 + 3dee977 commit dafea5f

File tree

2 files changed

+151
-66
lines changed

2 files changed

+151
-66
lines changed

compiler/rustc_driver_impl/src/lib.rs

+9-66
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ pub mod pretty;
8585
#[macro_use]
8686
mod print;
8787
mod session_diagnostics;
88+
#[cfg(all(unix, any(target_env = "gnu", target_os = "macos")))]
89+
mod signal_handler;
90+
91+
#[cfg(not(all(unix, any(target_env = "gnu", target_os = "macos"))))]
92+
mod signal_handler {
93+
/// On platforms which don't support our signal handler's requirements,
94+
/// simply use the default signal handler provided by std.
95+
pub(super) fn install() {}
96+
}
8897

8998
use crate::session_diagnostics::{
9099
RLinkEmptyVersionNumber, RLinkEncodingVersionMismatch, RLinkRustcVersionMismatch,
@@ -1440,72 +1449,6 @@ pub fn init_env_logger(handler: &EarlyErrorHandler, env: &str) {
14401449
}
14411450
}
14421451

1443-
#[cfg(all(unix, any(target_env = "gnu", target_os = "macos")))]
1444-
mod signal_handler {
1445-
extern "C" {
1446-
fn backtrace_symbols_fd(
1447-
buffer: *const *mut libc::c_void,
1448-
size: libc::c_int,
1449-
fd: libc::c_int,
1450-
);
1451-
}
1452-
1453-
extern "C" fn print_stack_trace(_: libc::c_int) {
1454-
const MAX_FRAMES: usize = 256;
1455-
static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] =
1456-
[std::ptr::null_mut(); MAX_FRAMES];
1457-
unsafe {
1458-
let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32);
1459-
if depth == 0 {
1460-
return;
1461-
}
1462-
backtrace_symbols_fd(STACK_TRACE.as_ptr(), depth, 2);
1463-
}
1464-
}
1465-
1466-
/// When an error signal (such as SIGABRT or SIGSEGV) is delivered to the
1467-
/// process, print a stack trace and then exit.
1468-
pub(super) fn install() {
1469-
use std::alloc::{alloc, Layout};
1470-
1471-
unsafe {
1472-
let alt_stack_size: usize = min_sigstack_size() + 64 * 1024;
1473-
let mut alt_stack: libc::stack_t = std::mem::zeroed();
1474-
alt_stack.ss_sp = alloc(Layout::from_size_align(alt_stack_size, 1).unwrap()).cast();
1475-
alt_stack.ss_size = alt_stack_size;
1476-
libc::sigaltstack(&alt_stack, std::ptr::null_mut());
1477-
1478-
let mut sa: libc::sigaction = std::mem::zeroed();
1479-
sa.sa_sigaction = print_stack_trace as libc::sighandler_t;
1480-
sa.sa_flags = libc::SA_NODEFER | libc::SA_RESETHAND | libc::SA_ONSTACK;
1481-
libc::sigemptyset(&mut sa.sa_mask);
1482-
libc::sigaction(libc::SIGSEGV, &sa, std::ptr::null_mut());
1483-
}
1484-
}
1485-
1486-
/// Modern kernels on modern hardware can have dynamic signal stack sizes.
1487-
#[cfg(any(target_os = "linux", target_os = "android"))]
1488-
fn min_sigstack_size() -> usize {
1489-
const AT_MINSIGSTKSZ: core::ffi::c_ulong = 51;
1490-
let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) };
1491-
// If getauxval couldn't find the entry, it returns 0,
1492-
// so take the higher of the "constant" and auxval.
1493-
// This transparently supports older kernels which don't provide AT_MINSIGSTKSZ
1494-
libc::MINSIGSTKSZ.max(dynamic_sigstksz as _)
1495-
}
1496-
1497-
/// Not all OS support hardware where this is needed.
1498-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
1499-
fn min_sigstack_size() -> usize {
1500-
libc::MINSIGSTKSZ
1501-
}
1502-
}
1503-
1504-
#[cfg(not(all(unix, any(target_env = "gnu", target_os = "macos"))))]
1505-
mod signal_handler {
1506-
pub(super) fn install() {}
1507-
}
1508-
15091452
pub fn main() -> ! {
15101453
let start_time = Instant::now();
15111454
let start_rss = get_resident_set_size();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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

Comments
 (0)