mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 04:57:19 +00:00 
			
		
		
		
	 3dee9775a8
			
		
	
	
		3dee9775a8
		
	
	
	
	
		
			
			First, we reuse the `MAX_FRAMES` constant. Co-authored-by: erikdesjardins <erikdesjardins@users.noreply.github.com> Then we choose an arbitrary recursion depth for the other case. In this case, I used 2d20. Honest.
		
			
				
	
	
		
			143 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! Signal handler for rustc
 | |
| //! Primarily used to extract a backtrace from stack overflow
 | |
| 
 | |
| use std::alloc::{alloc, Layout};
 | |
| use std::{fmt, mem, ptr};
 | |
| 
 | |
| extern "C" {
 | |
|     fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int);
 | |
| }
 | |
| 
 | |
| fn backtrace_stderr(buffer: &[*mut libc::c_void]) {
 | |
|     let size = buffer.len().try_into().unwrap_or_default();
 | |
|     unsafe { backtrace_symbols_fd(buffer.as_ptr(), size, libc::STDERR_FILENO) };
 | |
| }
 | |
| 
 | |
| /// Unbuffered, unsynchronized writer to stderr.
 | |
| ///
 | |
| /// Only acceptable because everything will end soon anyways.
 | |
| struct RawStderr(());
 | |
| 
 | |
| impl fmt::Write for RawStderr {
 | |
|     fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
 | |
|         let ret = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) };
 | |
|         if ret == -1 { Err(fmt::Error) } else { Ok(()) }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// We don't really care how many bytes we actually get out. SIGSEGV comes for our head.
 | |
| /// Splash stderr with letters of our own blood to warn our friends about the monster.
 | |
| macro raw_errln($tokens:tt) {
 | |
|     let _ = ::core::fmt::Write::write_fmt(&mut RawStderr(()), format_args!($tokens));
 | |
|     let _ = ::core::fmt::Write::write_char(&mut RawStderr(()), '\n');
 | |
| }
 | |
| 
 | |
| /// Signal handler installed for SIGSEGV
 | |
| extern "C" fn print_stack_trace(_: libc::c_int) {
 | |
|     const MAX_FRAMES: usize = 256;
 | |
|     // Reserve data segment so we don't have to malloc in a signal handler, which might fail
 | |
|     // in incredibly undesirable and unexpected ways due to e.g. the allocator deadlocking
 | |
|     static mut STACK_TRACE: [*mut libc::c_void; MAX_FRAMES] = [ptr::null_mut(); MAX_FRAMES];
 | |
|     let stack = unsafe {
 | |
|         // Collect return addresses
 | |
|         let depth = libc::backtrace(STACK_TRACE.as_mut_ptr(), MAX_FRAMES as i32);
 | |
|         if depth == 0 {
 | |
|             return;
 | |
|         }
 | |
|         &STACK_TRACE.as_slice()[0..(depth as _)]
 | |
|     };
 | |
| 
 | |
|     // Just a stack trace is cryptic. Explain what we're doing.
 | |
|     raw_errln!("error: rustc interrupted by SIGSEGV, printing backtrace\n");
 | |
|     let mut written = 1;
 | |
|     let mut consumed = 0;
 | |
|     // Begin elaborating return addrs into symbols and writing them directly to stderr
 | |
|     // Most backtraces are stack overflow, most stack overflows are from recursion
 | |
|     // Check for cycles before writing 250 lines of the same ~5 symbols
 | |
|     let cycled = |(runner, walker)| runner == walker;
 | |
|     let mut cyclic = false;
 | |
|     if let Some(period) = stack.iter().skip(1).step_by(2).zip(stack).position(cycled) {
 | |
|         let period = period.saturating_add(1); // avoid "what if wrapped?" branches
 | |
|         let Some(offset) = stack.iter().skip(period).zip(stack).position(cycled) else {
 | |
|             // impossible.
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         // Count matching trace slices, else we could miscount "biphasic cycles"
 | |
|         // with the same period + loop entry but a different inner loop
 | |
|         let next_cycle = stack[offset..].chunks_exact(period).skip(1);
 | |
|         let cycles = 1 + next_cycle
 | |
|             .zip(stack[offset..].chunks_exact(period))
 | |
|             .filter(|(next, prev)| next == prev)
 | |
|             .count();
 | |
|         backtrace_stderr(&stack[..offset]);
 | |
|         written += offset;
 | |
|         consumed += offset;
 | |
|         if cycles > 1 {
 | |
|             raw_errln!("\n### cycle encountered after {offset} frames with period {period}");
 | |
|             backtrace_stderr(&stack[consumed..consumed + period]);
 | |
|             raw_errln!("### recursed {cycles} times\n");
 | |
|             written += period + 4;
 | |
|             consumed += period * cycles;
 | |
|             cyclic = true;
 | |
|         };
 | |
|     }
 | |
|     let rem = &stack[consumed..];
 | |
|     backtrace_stderr(rem);
 | |
|     raw_errln!("");
 | |
|     written += rem.len() + 1;
 | |
| 
 | |
|     let random_depth = || 8 * 16; // chosen by random diceroll (2d20)
 | |
|     if cyclic || stack.len() > random_depth() {
 | |
|         // technically speculation, but assert it with confidence anyway.
 | |
|         // rustc only arrived in this signal handler because bad things happened
 | |
|         // and this message is for explaining it's not the programmer's fault
 | |
|         raw_errln!("note: rustc unexpectedly overflowed its stack! this is a bug");
 | |
|         written += 1;
 | |
|     }
 | |
|     if stack.len() == MAX_FRAMES {
 | |
|         raw_errln!("note: maximum backtrace depth reached, frames may have been lost");
 | |
|         written += 1;
 | |
|     }
 | |
|     raw_errln!("note: we would appreciate a report at https://github.com/rust-lang/rust");
 | |
|     written += 1;
 | |
|     if written > 24 {
 | |
|         // We probably just scrolled the earlier "we got SIGSEGV" message off the terminal
 | |
|         raw_errln!("note: backtrace dumped due to SIGSEGV! resuming signal");
 | |
|     };
 | |
| }
 | |
| 
 | |
| /// When SIGSEGV is delivered to the process, print a stack trace and then exit.
 | |
| pub(super) fn install() {
 | |
|     unsafe {
 | |
|         let alt_stack_size: usize = min_sigstack_size() + 64 * 1024;
 | |
|         let mut alt_stack: libc::stack_t = mem::zeroed();
 | |
|         alt_stack.ss_sp = alloc(Layout::from_size_align(alt_stack_size, 1).unwrap()).cast();
 | |
|         alt_stack.ss_size = alt_stack_size;
 | |
|         libc::sigaltstack(&alt_stack, ptr::null_mut());
 | |
| 
 | |
|         let mut sa: libc::sigaction = mem::zeroed();
 | |
|         sa.sa_sigaction = print_stack_trace as libc::sighandler_t;
 | |
|         sa.sa_flags = libc::SA_NODEFER | libc::SA_RESETHAND | libc::SA_ONSTACK;
 | |
|         libc::sigemptyset(&mut sa.sa_mask);
 | |
|         libc::sigaction(libc::SIGSEGV, &sa, ptr::null_mut());
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Modern kernels on modern hardware can have dynamic signal stack sizes.
 | |
| #[cfg(any(target_os = "linux", target_os = "android"))]
 | |
| fn min_sigstack_size() -> usize {
 | |
|     const AT_MINSIGSTKSZ: core::ffi::c_ulong = 51;
 | |
|     let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) };
 | |
|     // If getauxval couldn't find the entry, it returns 0,
 | |
|     // so take the higher of the "constant" and auxval.
 | |
|     // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ
 | |
|     libc::MINSIGSTKSZ.max(dynamic_sigstksz as _)
 | |
| }
 | |
| 
 | |
| /// Not all OS support hardware where this is needed.
 | |
| #[cfg(not(any(target_os = "linux", target_os = "android")))]
 | |
| fn min_sigstack_size() -> usize {
 | |
|     libc::MINSIGSTKSZ
 | |
| }
 |