Skip to content

Commit 6ecfca4

Browse files
committed
Fix short backtraces from stripped executables
Locate the beginning and ending frames for short backtraces by address in addition to symbol name, so that they work even when symbols have been stripped from the executable. We need to retain matching by symbol name for the time being, because rustc (and some associated ui tests) rely upon it to have ICE backtraces abbreviated. They can be updated once this commit lands in beta/stage0.
1 parent d222ddc commit 6ecfca4

4 files changed

Lines changed: 87 additions & 28 deletions

File tree

library/std/src/backtrace.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ use crate::panic::UnwindSafe;
9494
use crate::sync::LazyLock;
9595
use crate::sync::atomic::Ordering::Relaxed;
9696
use crate::sync::atomic::{Atomic, AtomicU8};
97+
#[unstable(
98+
feature = "short_backtrace_termini",
99+
reason = "for rustc to have ICE backtraces abbreviated",
100+
issue = "none"
101+
)]
102+
#[doc(hidden)]
103+
pub use crate::sys::backtrace::{
104+
__rust_begin_short_backtrace as begin_short_backtrace,
105+
__rust_end_short_backtrace as end_short_backtrace,
106+
};
97107
use crate::sys::backtrace::{lock, output_filename, set_image_base};
98108
use crate::{env, fmt};
99109

library/std/src/sys/backtrace.rs

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use crate::backtrace_rs::{self, BacktraceFmt, BytesOrWideString, PrintFmt};
55
use crate::borrow::Cow;
66
use crate::io::prelude::*;
7+
use crate::mem::{ManuallyDrop, MaybeUninit};
78
use crate::path::{self, Path, PathBuf};
89
use crate::sync::{Mutex, MutexGuard, PoisonError};
910
use crate::{env, fmt, io};
@@ -81,12 +82,26 @@ unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::
8182
let frame_ip = frame.ip();
8283
res = writeln!(bt_fmt.formatter(), "{idx:4}: {frame_ip:HEX_WIDTH$?}");
8384
} else {
85+
// `call_with_end_short_backtrace_marker` means we are done hiding symbols
86+
// for now. Print until we see `call_with_begin_short_backtrace_marker`.
87+
if print_fmt == PrintFmt::Short {
88+
let sym = frame.symbol_address();
89+
if sym == call_with_end_short_backtrace_marker as _ {
90+
print = true;
91+
return true;
92+
} else if print && sym == call_with_begin_short_backtrace_marker as _ {
93+
print = false;
94+
return true;
95+
}
96+
}
97+
8498
let mut hit = false;
8599
backtrace_rs::resolve_frame_unsynchronized(frame, |symbol| {
86100
hit = true;
87101

88-
// `__rust_end_short_backtrace` means we are done hiding symbols
89-
// for now. Print until we see `__rust_begin_short_backtrace`.
102+
// Hide `__rust_[begin|end]_short_backtrace` frames from short backtraces.
103+
// Unfortunately these generic functions have to be matched by name, as we do
104+
// not know their generic parameters.
90105
if print_fmt == PrintFmt::Short {
91106
if let Some(sym) = symbol.name().and_then(|s| s.as_str()) {
92107
if sym.contains("__rust_end_short_backtrace") {
@@ -155,36 +170,43 @@ unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::
155170
Ok(())
156171
}
157172

158-
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
159-
/// this is only inline(never) when backtraces in std are enabled, otherwise
160-
/// it's fine to optimize away.
161-
#[cfg_attr(feature = "backtrace", inline(never))]
162-
pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
163-
where
164-
F: FnOnce() -> T,
165-
{
166-
let result = f();
173+
macro_rules! short_backtrace_termini {
174+
($($adapter:ident => $marker:ident($unique:literal)),* $(,)?) => {$(
175+
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
176+
/// this is only inline(never) when backtraces in std are enabled, otherwise
177+
/// it's fine to optimize away.
178+
#[cfg_attr(feature = "backtrace", inline(never))]
179+
fn $marker(f: &mut dyn FnMut()) {
180+
f();
167181

168-
// prevent this frame from being tail-call optimised away
169-
crate::hint::black_box(());
182+
// (Try to) prevent both Identical Code Folding (which might merge the different
183+
// versions of this function, giving them the same address) and Tail Call Optimisation
184+
// (which could remove their frames from the call stack).
185+
crate::hint::black_box($unique);
186+
}
170187

171-
result
188+
/// A function that adapts the non-generic `$marker` to be more ergonomically
189+
/// called with a returning-value callback rather than one that writes to its
190+
/// captured environment.
191+
#[doc(hidden)]
192+
#[unstable(feature = "short_backtrace_termini", reason = "for rustc to have ICE backtraces abbreviated", issue = "none")]
193+
#[inline(always)]
194+
pub fn $adapter<F, T>(f: F) -> T
195+
where
196+
F: FnOnce() -> T,
197+
{
198+
let mut result = MaybeUninit::<T>::uninit();
199+
let mut f = ManuallyDrop::new(f);
200+
let mut f = || { result.write(unsafe { ManuallyDrop::take(&mut f) }()); };
201+
$marker(&mut f);
202+
unsafe { result.assume_init() }
203+
}
204+
)*};
172205
}
173206

174-
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
175-
/// this is only inline(never) when backtraces in std are enabled, otherwise
176-
/// it's fine to optimize away.
177-
#[cfg_attr(feature = "backtrace", inline(never))]
178-
pub fn __rust_end_short_backtrace<F, T>(f: F) -> T
179-
where
180-
F: FnOnce() -> T,
181-
{
182-
let result = f();
183-
184-
// prevent this frame from being tail-call optimised away
185-
crate::hint::black_box(());
186-
187-
result
207+
short_backtrace_termini! {
208+
__rust_begin_short_backtrace => call_with_begin_short_backtrace_marker(0),
209+
__rust_end_short_backtrace => call_with_end_short_backtrace_marker(1),
188210
}
189211

190212
/// Prints the filename of the backtrace frame.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//! Short backtraces should still be emitted from stripped binaries.
2+
//! Regression test for https://github.com/rust-lang/rust/issues/147846
3+
//
4+
//@ compile-flags: -Cstrip=symbols
5+
//@ exec-env: RUST_BACKTRACE=1
6+
//@ run-fail
7+
//@ check-run-results
8+
//
9+
// Name mangling scheme differences
10+
//@ normalize-stderr: "begin_panic::<&str>" -> "begin_panic"
11+
//
12+
// macOS with `rust.debuginfo-level = "line-tables-only"` (#133997)
13+
//@ normalize-stderr: " begin_panic<&str>" -> " std::panicking::begin_panic"
14+
//
15+
// debuginfo
16+
//@ normalize-stderr: "\n +at [^\n]+" -> ""
17+
18+
fn main() { panic!(); }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
thread 'main' ($TID) panicked at $DIR/short-backtrace-in-stripped.rs:18:13:
3+
explicit panic
4+
stack backtrace:
5+
0: std::panicking::begin_panic
6+
1: <unknown>
7+
2: <unknown>
8+
3: <unknown>
9+
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

0 commit comments

Comments
 (0)