Skip to content

Commit 09b79d7

Browse files
committed
std: print a backtrace on stackoverflow
Since `backtrace` requires locking and memory allocation, it cannot be used from inside a signal handler. Instead, this uses `libunwind` and `dladdr`, even though both of them are not guaranteed to be async-signal-safe, strictly speaking. However, at least LLVM's libunwind (used by macOS) has a [test] for unwinding in signal handlers, and `dladdr` is used by `backtrace_symbols_fd` in glibc, which it [documents] as async-signal-safe. In practice, this hack works well enough on GNU/Linux and macOS (and perhaps some other platforms in the future). Realistically, the worst thing that can happen is that the stack overflow occurred inside the dynamic loaded while it holds some sort of lock, which could result in a deadlock if that happens in just the right moment. That's unlikely enough and not the *worst* thing to happen considering that a stack overflow is already an unrecoverable error and most likely indicates a bug. Fixes #51405 [test]: https://github.com/llvm/llvm-project/blob/a6385a3fc8a88f092d07672210a1e773481c2919/libunwind/test/signal_unwind.pass.cpp [documents]: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html#index-backtrace_005fsymbols_005ffd
1 parent ee4a56e commit 09b79d7

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

Diff for: library/std/src/sys/pal/unix/stack_overflow.rs

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
pub use self::imp::{cleanup, init};
44
use self::imp::{drop_handler, make_handler};
55

6+
#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos",))]
7+
mod backtrace;
8+
69
pub struct Handler {
710
data: *mut libc::c_void,
811
}
@@ -104,6 +107,13 @@ mod imp {
104107
"\nthread '{}' has overflowed its stack\n",
105108
thread::current().name().unwrap_or("<unknown>")
106109
);
110+
111+
#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos",))]
112+
{
113+
rtprintpanic!("backtrace:\n\n");
114+
super::backtrace::print();
115+
}
116+
107117
rtabort!("stack overflow");
108118
} else {
109119
// Unregister ourselves by reverting back to the default behavior.
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use crate::ffi::{CStr, c_void};
2+
use crate::mem::MaybeUninit;
3+
use crate::ptr;
4+
5+
/// Prints the current backtrace, even in a signal handlers.
6+
///
7+
/// Since `backtrace` requires locking and memory allocation, it cannot be used
8+
/// from inside a signal handler. Instead, this uses `libunwind` and `dladdr`,
9+
/// even though both of them are not guaranteed to be async-signal-safe, strictly
10+
/// speaking. However, at least LLVM's libunwind (used by macOS) has a [test] for
11+
/// unwinding in signal handlers, and `dladdr` is used by `backtrace_symbols_fd`
12+
/// in glibc, which it [documents] as async-signal-safe.
13+
///
14+
/// In practice, this hack works well enough on GNU/Linux and macOS (and perhaps
15+
/// some other platforms). Realistically, the worst thing that can happen is that
16+
/// the stack overflow occurred inside the dynamic loaded while it holds some sort
17+
/// of lock, which could result in a deadlock if that happens in just the right
18+
/// moment. That's unlikely enough and not the *worst* thing to happen considering
19+
/// that a stack overflow is already an unrecoverable error and most likely
20+
/// indicates a bug.
21+
///
22+
/// [test]: https://github.com/llvm/llvm-project/blob/a6385a3fc8a88f092d07672210a1e773481c2919/libunwind/test/signal_unwind.pass.cpp
23+
/// [documents]: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html#index-backtrace_005fsymbols_005ffd
24+
pub fn print() {
25+
extern "C" fn frame(
26+
ctx: *mut unwind::_Unwind_Context,
27+
arg: *mut c_void,
28+
) -> unwind::_Unwind_Reason_Code {
29+
let count = unsafe { &mut *(arg as *mut usize) };
30+
let depth = *count;
31+
*count += 1;
32+
if depth > 128 {
33+
return unwind::_URC_NO_REASON;
34+
}
35+
36+
let ip = unsafe { unwind::_Unwind_GetIP(ctx) };
37+
let mut info = MaybeUninit::uninit();
38+
let r = unsafe { libc::dladdr(ip.cast(), info.as_mut_ptr()) };
39+
if r != 0 {
40+
let info = unsafe { info.assume_init() };
41+
let name = unsafe { CStr::from_ptr(info.dli_sname) };
42+
if let Ok(name) = name.to_str() {
43+
rtprintpanic!("{depth}: {}\n", rustc_demangle::demangle(name));
44+
return unwind::_URC_NO_REASON;
45+
}
46+
}
47+
48+
rtprintpanic!("{depth}: {ip:p}\n");
49+
unwind::_URC_NO_REASON
50+
}
51+
52+
let mut count = 0usize;
53+
unsafe { unwind::_Unwind_Backtrace(frame, ptr::from_mut(&mut count).cast()) };
54+
if count > 128 {
55+
rtprintpanic!(
56+
"[... omitted {} frame{} ...]\n",
57+
count - 128,
58+
if count - 128 > 1 { "s" } else { "" }
59+
);
60+
}
61+
}

0 commit comments

Comments
 (0)