|
2 | 2 | //! Primarily used to extract a backtrace from stack overflow
|
3 | 3 |
|
4 | 4 | use std::alloc::{alloc, Layout};
|
5 |
| -use std::{mem, ptr}; |
| 5 | +use std::{fmt, mem, ptr}; |
6 | 6 |
|
7 | 7 | extern "C" {
|
8 | 8 | fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int);
|
9 | 9 | }
|
10 | 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 | + |
11 | 35 | /// Signal handler installed for SIGSEGV
|
12 | 36 | extern "C" fn print_stack_trace(_: libc::c_int) {
|
13 | 37 | const MAX_FRAMES: usize = 256;
|
14 | 38 | // Reserve data segment so we don't have to malloc in a signal handler, which might fail
|
15 | 39 | // in incredibly undesirable and unexpected ways due to e.g. the allocator deadlocking
|
16 | 40 | static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [ptr::null_mut(); MAX_FRAMES];
|
17 |
| - unsafe { |
| 41 | + let stack = unsafe { |
18 | 42 | // Collect return addresses
|
19 | 43 | let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32);
|
20 | 44 | if depth == 0 {
|
21 | 45 | return;
|
22 | 46 | }
|
23 |
| - // Just a stack trace is cryptic. Explain what we're doing. |
24 |
| - write_raw_err("error: rustc interrupted by SIGSEGV, printing stack trace:\n\n"); |
25 |
| - // Elaborate return addrs into symbols and write them directly to stderr |
26 |
| - backtrace_symbols_fd(STACK_TRACE.as_ptr(), depth, libc::STDERR_FILENO); |
27 |
| - if depth > 22 { |
28 |
| - // We probably just scrolled that "we got SIGSEGV" message off the terminal |
29 |
| - write_raw_err("\nerror: stack trace dumped due to SIGSEGV, possible stack overflow"); |
| 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; |
30 | 83 | };
|
31 |
| - write_raw_err("\nerror: please report a bug to https://github.com/rust-lang/rust\n"); |
32 | 84 | }
|
33 |
| -} |
| 85 | + let rem = &stack[consumed..]; |
| 86 | + backtrace_stderr(rem); |
| 87 | + raw_errln!(""); |
| 88 | + written += rem.len() + 1; |
34 | 89 |
|
35 |
| -/// Write without locking stderr. |
36 |
| -/// |
37 |
| -/// Only acceptable because everything will end soon anyways. |
38 |
| -fn write_raw_err(input: &str) { |
39 |
| - // We do not care how many bytes we actually get out. SIGSEGV comes for our head. |
40 |
| - // Splash stderr with letters of our own blood to warn our friends about the monster. |
41 |
| - let _ = unsafe { libc::write(libc::STDERR_FILENO, input.as_ptr().cast(), input.len()) }; |
| 90 | + if cyclic || stack.len() == 256 { |
| 91 | + // technically speculation, but assert it with confidence anyway. |
| 92 | + // rustc only arrived in this signal handler because bad things happened |
| 93 | + // and this message is for explaining it's not the programmer's fault |
| 94 | + raw_errln!("note: rustc unexpectedly overflowed its stack! this is a bug"); |
| 95 | + written += 1; |
| 96 | + } |
| 97 | + if stack.len() == 256 { |
| 98 | + raw_errln!("note: maximum backtrace depth reached, frames may have been lost"); |
| 99 | + written += 1; |
| 100 | + } |
| 101 | + raw_errln!("note: we would appreciate a report at https://github.com/rust-lang/rust"); |
| 102 | + written += 1; |
| 103 | + if written > 24 { |
| 104 | + // We probably just scrolled the earlier "we got SIGSEGV" message off the terminal |
| 105 | + raw_errln!("note: backtrace dumped due to SIGSEGV! resuming signal"); |
| 106 | + }; |
42 | 107 | }
|
43 | 108 |
|
44 | 109 | /// When SIGSEGV is delivered to the process, print a stack trace and then exit.
|
|
0 commit comments