mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-23 02:27:39 +03:00
286cdc81a8
On `x86_64-pc-windows-msvc`, we often get backtraces which look like
this:
```
10: 0x7ff77e0e9be5 - std::panicking::rust_panic_with_hook
11: 0x7ff77e0e11b4 - std::sys_common::backtrace::__rust_begin_short_backtrace::h5769736bdb11136c
12: 0x7ff77e0e116f - std::sys_common::backtrace::__rust_end_short_backtrace::h61c7ecb1b55338ae
13: 0x7ff77e0f89dd - std::panicking::begin_panic::h8e60ef9f82a41805
14: 0x7ff77e0e108c - d
15: 0x7ff77e0e1069 - c
16: 0x7ff77e0e1059 - b
17: 0x7ff77e0e1049 - a
18: 0x7ff77e0e1039 - core::ptr::drop_in_place<std::rt::lang_start<()>::{{closure}}>::h1bfcd14d5e15ba81
19: 0x7ff77e0e1186 - std::sys_common::backtrace::__rust_begin_short_backtrace::h5769736bdb11136c
20: 0x7ff77e0e100c - std::rt::lang_start::{{closure}}::ha054184bbf9921e3
```
Notice that `__rust_begin_short_backtrace` appears on frame 11 before
`__rust_end_short_backtrace` on frame 12. This is because in typical
release binaries without debug symbols, dbghelp.dll, which we use to walk
and symbolize the stack, does not know where CGU internal functions
start or end and so the closure invoked by `__rust_end_short_backtrace`
is incorrectly described as `__rust_begin_short_backtrace` because it
happens to be near that symbol.
While that can obviously change, this has been happening quite
consistently since #75048. Since this is a very small change to the std
and the change makes sense by itself, I think this is worth doing.
This doesn't completely resolve the situation for release binaries on
Windows, since without debug symbols, the stack printed can still show
incorrect symbol names (this is why the test uses `#[no_mangle]`) but it
does slightly improve the situation in that you see the same backtrace
you would see with `RUST_BACKTRACE=full` or in a debugger (without the
uninteresting bits at the top and bottom).
232 lines
7.4 KiB
Rust
232 lines
7.4 KiB
Rust
use crate::backtrace_rs::{self, BacktraceFmt, BytesOrWideString, PrintFmt};
|
|
use crate::borrow::Cow;
|
|
/// Common code for printing the backtrace in the same way across the different
|
|
/// supported platforms.
|
|
use crate::env;
|
|
use crate::fmt;
|
|
use crate::io;
|
|
use crate::io::prelude::*;
|
|
use crate::path::{self, Path, PathBuf};
|
|
use crate::sync::atomic::{self, Ordering};
|
|
use crate::sys_common::mutex::StaticMutex;
|
|
|
|
/// Max number of frames to print.
|
|
const MAX_NB_FRAMES: usize = 100;
|
|
|
|
// SAFETY: Don't attempt to lock this reentrantly.
|
|
pub unsafe fn lock() -> impl Drop {
|
|
static LOCK: StaticMutex = StaticMutex::new();
|
|
LOCK.lock()
|
|
}
|
|
|
|
/// Prints the current backtrace.
|
|
pub fn print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
|
|
// There are issues currently linking libbacktrace into tests, and in
|
|
// general during libstd's own unit tests we're not testing this path. In
|
|
// test mode immediately return here to optimize away any references to the
|
|
// libbacktrace symbols
|
|
if cfg!(test) {
|
|
return Ok(());
|
|
}
|
|
|
|
// Use a lock to prevent mixed output in multithreading context.
|
|
// Some platforms also requires it, like `SymFromAddr` on Windows.
|
|
unsafe {
|
|
let _lock = lock();
|
|
_print(w, format)
|
|
}
|
|
}
|
|
|
|
unsafe fn _print(w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
|
|
struct DisplayBacktrace {
|
|
format: PrintFmt,
|
|
}
|
|
impl fmt::Display for DisplayBacktrace {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
unsafe { _print_fmt(fmt, self.format) }
|
|
}
|
|
}
|
|
write!(w, "{}", DisplayBacktrace { format })
|
|
}
|
|
|
|
unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::Result {
|
|
// Always 'fail' to get the cwd when running under Miri -
|
|
// this allows Miri to display backtraces in isolation mode
|
|
let cwd = if !cfg!(miri) { env::current_dir().ok() } else { None };
|
|
|
|
let mut print_path = move |fmt: &mut fmt::Formatter<'_>, bows: BytesOrWideString<'_>| {
|
|
output_filename(fmt, bows, print_fmt, cwd.as_ref())
|
|
};
|
|
writeln!(fmt, "stack backtrace:")?;
|
|
let mut bt_fmt = BacktraceFmt::new(fmt, print_fmt, &mut print_path);
|
|
bt_fmt.add_context()?;
|
|
let mut idx = 0;
|
|
let mut res = Ok(());
|
|
// Start immediately if we're not using a short backtrace.
|
|
let mut start = print_fmt != PrintFmt::Short;
|
|
backtrace_rs::trace_unsynchronized(|frame| {
|
|
if print_fmt == PrintFmt::Short && idx > MAX_NB_FRAMES {
|
|
return false;
|
|
}
|
|
|
|
let mut hit = false;
|
|
let mut stop = false;
|
|
backtrace_rs::resolve_frame_unsynchronized(frame, |symbol| {
|
|
hit = true;
|
|
if print_fmt == PrintFmt::Short {
|
|
if let Some(sym) = symbol.name().and_then(|s| s.as_str()) {
|
|
if start && sym.contains("__rust_begin_short_backtrace") {
|
|
stop = true;
|
|
return;
|
|
}
|
|
if sym.contains("__rust_end_short_backtrace") {
|
|
start = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if start {
|
|
res = bt_fmt.frame().symbol(frame, symbol);
|
|
}
|
|
});
|
|
if stop {
|
|
return false;
|
|
}
|
|
if !hit {
|
|
if start {
|
|
res = bt_fmt.frame().print_raw(frame.ip(), None, None, None);
|
|
}
|
|
}
|
|
|
|
idx += 1;
|
|
res.is_ok()
|
|
});
|
|
res?;
|
|
bt_fmt.finish()?;
|
|
if print_fmt == PrintFmt::Short {
|
|
writeln!(
|
|
fmt,
|
|
"note: Some details are omitted, \
|
|
run with `RUST_BACKTRACE=full` for a verbose backtrace."
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
|
|
/// this is only inline(never) when backtraces in libstd are enabled, otherwise
|
|
/// it's fine to optimize away.
|
|
#[cfg_attr(feature = "backtrace", inline(never))]
|
|
pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
|
|
where
|
|
F: FnOnce() -> T,
|
|
{
|
|
let result = f();
|
|
|
|
// prevent this frame from being tail-call optimised away
|
|
crate::hint::black_box(());
|
|
|
|
result
|
|
}
|
|
|
|
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
|
|
/// this is only inline(never) when backtraces in libstd are enabled, otherwise
|
|
/// it's fine to optimize away.
|
|
#[cfg_attr(feature = "backtrace", inline(never))]
|
|
pub fn __rust_end_short_backtrace<F, T>(f: F) -> T
|
|
where
|
|
F: FnOnce() -> T,
|
|
{
|
|
let result = f();
|
|
|
|
// prevent this frame from being tail-call optimised away
|
|
crate::hint::black_box(());
|
|
|
|
result
|
|
}
|
|
|
|
pub enum RustBacktrace {
|
|
Print(PrintFmt),
|
|
Disabled,
|
|
RuntimeDisabled,
|
|
}
|
|
|
|
// For now logging is turned off by default, and this function checks to see
|
|
// whether the magical environment variable is present to see if it's turned on.
|
|
pub fn rust_backtrace_env() -> RustBacktrace {
|
|
// If the `backtrace` feature of this crate isn't enabled quickly return
|
|
// `None` so this can be constant propagated all over the place to turn
|
|
// optimize away callers.
|
|
if !cfg!(feature = "backtrace") {
|
|
return RustBacktrace::Disabled;
|
|
}
|
|
|
|
// Setting environment variables for Fuchsia components isn't a standard
|
|
// or easily supported workflow. For now, always display backtraces.
|
|
if cfg!(target_os = "fuchsia") {
|
|
return RustBacktrace::Print(PrintFmt::Full);
|
|
}
|
|
|
|
static ENABLED: atomic::AtomicIsize = atomic::AtomicIsize::new(0);
|
|
match ENABLED.load(Ordering::SeqCst) {
|
|
0 => {}
|
|
1 => return RustBacktrace::RuntimeDisabled,
|
|
2 => return RustBacktrace::Print(PrintFmt::Short),
|
|
_ => return RustBacktrace::Print(PrintFmt::Full),
|
|
}
|
|
|
|
let (format, cache) = env::var_os("RUST_BACKTRACE")
|
|
.map(|x| {
|
|
if &x == "0" {
|
|
(RustBacktrace::RuntimeDisabled, 1)
|
|
} else if &x == "full" {
|
|
(RustBacktrace::Print(PrintFmt::Full), 3)
|
|
} else {
|
|
(RustBacktrace::Print(PrintFmt::Short), 2)
|
|
}
|
|
})
|
|
.unwrap_or((RustBacktrace::RuntimeDisabled, 1));
|
|
ENABLED.store(cache, Ordering::SeqCst);
|
|
format
|
|
}
|
|
|
|
/// Prints the filename of the backtrace frame.
|
|
///
|
|
/// See also `output`.
|
|
pub fn output_filename(
|
|
fmt: &mut fmt::Formatter<'_>,
|
|
bows: BytesOrWideString<'_>,
|
|
print_fmt: PrintFmt,
|
|
cwd: Option<&PathBuf>,
|
|
) -> fmt::Result {
|
|
let file: Cow<'_, Path> = match bows {
|
|
#[cfg(unix)]
|
|
BytesOrWideString::Bytes(bytes) => {
|
|
use crate::os::unix::prelude::*;
|
|
Path::new(crate::ffi::OsStr::from_bytes(bytes)).into()
|
|
}
|
|
#[cfg(not(unix))]
|
|
BytesOrWideString::Bytes(bytes) => {
|
|
Path::new(crate::str::from_utf8(bytes).unwrap_or("<unknown>")).into()
|
|
}
|
|
#[cfg(windows)]
|
|
BytesOrWideString::Wide(wide) => {
|
|
use crate::os::windows::prelude::*;
|
|
Cow::Owned(crate::ffi::OsString::from_wide(wide).into())
|
|
}
|
|
#[cfg(not(windows))]
|
|
BytesOrWideString::Wide(_wide) => Path::new("<unknown>").into(),
|
|
};
|
|
if print_fmt == PrintFmt::Short && file.is_absolute() {
|
|
if let Some(cwd) = cwd {
|
|
if let Ok(stripped) = file.strip_prefix(&cwd) {
|
|
if let Some(s) = stripped.to_str() {
|
|
return write!(fmt, ".{}{}", path::MAIN_SEPARATOR, s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fmt::Display::fmt(&file.display(), fmt)
|
|
}
|