1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! By default, Rust aborts on stackoverflow without printing a backtrace:
//!
//! ```console
//! λ bat src/main.rs
//! fn main() {
//!     f(92)
//! }
//!
//! fn f(x: u64) {
//!     f(x)
//! }
//! λ cargo run
//!     Finished dev [unoptimized + debuginfo] target(s) in 0.00s
//!      Running `target/debug/so`
//!
//! thread 'main' has overflowed its stack
//! fatal runtime error: stack overflow
//! fish: Job 1, 'cargo run' terminated by signal SIGABRT (Abort)
//! ```
//!
//! This crate fixes this:
//!
//! ```console
//! λ bat src/main.rs
//! fn main() {
//!     unsafe { backtrace_on_stack_overflow::enable() };
//!     f(92)
//! }
//!
//! fn f(x: u64) {
//!     f(x)
//! }
//! λ cargo run
//!     Finished dev [unoptimized + debuginfo] target(s) in 0.01s
//!      Running `target/debug/so`
//! Stack Overflow:
//!    0: backtrace_on_stack_overflow::handle_sigsegv
//!              at /home/matklad/p/backtrace-on-stack-overflow/src/lib.rs:33:40
//!    1: <unknown>
//!    2: so::f
//!              at src/main.rs:6
//!    3: so::f
//!              at src/main.rs:7:5
//!    4: so::f
//!              at src/main.rs:7:5
//!    5: so::f
//!              at src/main.rs:7:5
//!    6: so::f
//!              at src/main.rs:7:5
//!    7: so::f
//!              at src/main.rs:7:5
//!    8: so::f
//!              at src/main.rs:7:5
//!    9: so::f
//!              at src/main.rs:7:5
//!   10: so::f
//!              at src/main.rs:7:5
//! ```
//!
//! This crate works for debugging, but is unsuited for being enabled in production.
use nix::sys::signal;

/// Best effort printing of backtrace on stack overflow.
///
/// Works on my machine, may summon laundry-eating nasal daemons.
///
/// PRs to make this more robust are welcome
pub unsafe fn enable() {
    static ONCE: std::sync::Once = std::sync::Once::new();

    ONCE.call_once(|| {
        // Use u128 for alignment.
        let buf = Vec::leak(vec![0u128; 4096]);
        let stack = libc::stack_t {
            ss_sp: buf.as_ptr() as *mut libc::c_void,
            ss_flags: 0,
            ss_size: buf.len() * std::mem::size_of::<u128>(),
        };
        let mut old = libc::stack_t { ss_sp: std::ptr::null_mut(), ss_flags: 0, ss_size: 0 };
        let ret = libc::sigaltstack(&stack, &mut old);
        assert_eq!(ret, 0, "sigaltstack failed");

        let sig_action = signal::SigAction::new(
            signal::SigHandler::Handler(handle_sigsegv),
            signal::SaFlags::SA_NODEFER | signal::SaFlags::SA_ONSTACK,
            signal::SigSet::empty(),
        );
        signal::sigaction(signal::SIGSEGV, &sig_action).unwrap();
        signal::sigaction(signal::SIGABRT, &sig_action).unwrap();
    })
}

extern "C" fn handle_sigsegv(_: i32) {
    eprintln!("Stack Overflow:\n{:?}", backtrace::Backtrace::new());
    std::process::abort();
}