solar_cli/
sigsegv_handler.rs1use std::{
6 alloc::{Layout, alloc},
7 fmt, mem, ptr,
8};
9
10unsafe extern "C" {
11 fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int);
12}
13
14fn backtrace_stderr(buffer: &[*mut libc::c_void]) {
15 let size = buffer.len().try_into().unwrap_or_default();
16 unsafe { backtrace_symbols_fd(buffer.as_ptr(), size, libc::STDERR_FILENO) };
17}
18
19struct RawStderr(());
23
24impl fmt::Write for RawStderr {
25 fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
26 let ret = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) };
27 if ret == -1 { Err(fmt::Error) } else { Ok(()) }
28 }
29}
30
31macro_rules! raw_errln {
34 ($($tokens:tt)*) => {
35 let _ = ::core::fmt::Write::write_fmt(&mut RawStderr(()), format_args!($($tokens)*));
36 let _ = ::core::fmt::Write::write_char(&mut RawStderr(()), '\n');
37 };
38}
39
40extern "C" fn print_stack_trace(_: libc::c_int) {
42 const MAX_FRAMES: usize = 256;
43 let mut stack_trace = [ptr::null_mut(); MAX_FRAMES];
44 let stack = unsafe {
45 let depth = libc::backtrace(stack_trace.as_mut_ptr(), MAX_FRAMES as i32);
47 if depth == 0 {
48 return;
49 }
50 &stack_trace.as_slice()[0..(depth as _)]
51 };
52
53 raw_errln!("error: solar interrupted by SIGSEGV, printing backtrace\n");
55 let mut written = 1;
56 let mut consumed = 0;
57 let cycled = |(runner, walker)| runner == walker;
61 let mut cyclic = false;
62 if let Some(period) = stack.iter().skip(1).step_by(2).zip(stack).position(cycled) {
63 let period = period.saturating_add(1); let Some(offset) = stack.iter().skip(period).zip(stack).position(cycled) else {
65 return;
67 };
68
69 let next_cycle = stack[offset..].chunks_exact(period).skip(1);
72 let cycles = 1 + next_cycle
73 .zip(stack[offset..].chunks_exact(period))
74 .filter(|(next, prev)| next == prev)
75 .count();
76 backtrace_stderr(&stack[..offset]);
77 written += offset;
78 consumed += offset;
79 if cycles > 1 {
80 raw_errln!("\n### cycle encountered after {offset} frames with period {period}");
81 backtrace_stderr(&stack[consumed..consumed + period]);
82 raw_errln!("### recursed {cycles} times\n");
83 written += period + 4;
84 consumed += period * cycles;
85 cyclic = true;
86 };
87 }
88 let rem = &stack[consumed..];
89 backtrace_stderr(rem);
90 raw_errln!("");
91 written += rem.len() + 1;
92
93 let random_depth = || 8 * 16; if cyclic || stack.len() > random_depth() {
95 raw_errln!("note: solar unexpectedly overflowed its stack! this is a bug");
99 written += 1;
100 }
101 if stack.len() == MAX_FRAMES {
102 raw_errln!("note: maximum backtrace depth reached, frames may have been lost");
103 written += 1;
104 }
105 raw_errln!("note: we would appreciate a report at https://github.com/paradigmxyz/solar");
106 written += 1;
107 if written > 24 {
108 raw_errln!("note: backtrace dumped due to SIGSEGV! resuming signal");
110 }
111}
112
113pub fn install() {
117 unsafe {
118 let alt_stack_size: usize = min_sigstack_size() + 64 * 1024;
119 let mut alt_stack: libc::stack_t = mem::zeroed();
120 alt_stack.ss_sp = alloc(Layout::from_size_align(alt_stack_size, 1).unwrap()).cast();
121 alt_stack.ss_size = alt_stack_size;
122 libc::sigaltstack(&alt_stack, ptr::null_mut());
123
124 let mut sa: libc::sigaction = mem::zeroed();
125 sa.sa_sigaction = print_stack_trace as libc::sighandler_t;
126 sa.sa_flags = libc::SA_NODEFER | libc::SA_RESETHAND | libc::SA_ONSTACK;
127 libc::sigemptyset(&mut sa.sa_mask);
128 libc::sigaction(libc::SIGSEGV, &sa, ptr::null_mut());
129 }
130}
131
132#[cfg(any(target_os = "linux", target_os = "android"))]
134fn min_sigstack_size() -> usize {
135 const AT_MINSIGSTKSZ: core::ffi::c_ulong = 51;
136 let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) };
137 libc::MINSIGSTKSZ.max(dynamic_sigstksz as _)
141}
142
143#[cfg(not(any(target_os = "linux", target_os = "android")))]
145fn min_sigstack_size() -> usize {
146 libc::MINSIGSTKSZ
147}