Skip to content

Commit c9b125b

Browse files
committed
Improve backtrace formating while panicking.
- `RUST_BACKTRACE=full` prints all the informations (old behaviour) - `RUST_BACKTRACE=(0|no)` disables the backtrace. - `RUST_BACKTRACE=<everything else>` (including `1`) shows a simplified backtrace, without the function addresses and with cleaned filenames and symbols. Also removes some unneded frames at the beginning and the end. Fixes rust-lang#37783. PR is rust-lang#38165.
1 parent 10f6a5c commit c9b125b

File tree

18 files changed

+731
-449
lines changed

18 files changed

+731
-449
lines changed

src/doc/book/functions.md

+14-1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,19 @@ If you want more information, you can get a backtrace by setting the
230230
```text
231231
$ RUST_BACKTRACE=1 ./diverges
232232
thread 'main' panicked at 'This function never returns!', hello.rs:2
233+
Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
234+
stack backtrace:
235+
hello::diverges
236+
at ./hello.rs:2
237+
hello::main
238+
at ./hello.rs:6
239+
```
240+
241+
If you want the complete backtrace and filenames:
242+
243+
```text
244+
$ RUST_BACKTRACE=full ./diverges
245+
thread 'main' panicked at 'This function never returns!', hello.rs:2
233246
stack backtrace:
234247
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
235248
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
@@ -262,7 +275,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
262275
`RUST_BACKTRACE` also works with Cargo’s `run` command:
263276

264277
```text
265-
$ RUST_BACKTRACE=1 cargo run
278+
$ RUST_BACKTRACE=full cargo run
266279
Running `target/debug/diverges`
267280
thread 'main' panicked at 'This function never returns!', hello.rs:2
268281
stack backtrace:

src/libstd/panicking.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,11 @@ fn default_hook(info: &PanicInfo) {
320320
let log_backtrace = {
321321
let panics = update_panic_count(0);
322322

323-
panics >= 2 || backtrace::log_enabled()
323+
if panics >= 2 {
324+
Some(backtrace::PrintFormat::Full)
325+
} else {
326+
backtrace::log_enabled()
327+
}
324328
};
325329

326330
let file = info.location.file;
@@ -347,8 +351,8 @@ fn default_hook(info: &PanicInfo) {
347351

348352
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
349353

350-
if log_backtrace {
351-
let _ = backtrace::write(err);
354+
if let Some(format) = log_backtrace {
355+
let _ = backtrace::print(err, format);
352356
} else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) {
353357
let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace.");
354358
}

src/libstd/sys/redox/backtrace.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010

1111
use libc;
1212
use io;
13-
use sys_common::backtrace::output;
13+
use sys_common::backtrace::Frame;
14+
15+
pub use sys_common::gnu::libbacktrace::*;
16+
pub struct BacktraceContext;
1417

1518
#[inline(never)]
16-
pub fn write(w: &mut io::Write) -> io::Result<()> {
17-
output(w, 0, 0 as *mut libc::c_void, None)
19+
pub fn unwind_backtrace(frames: &mut [Frame])
20+
-> io::Result<(usize, BacktraceContext)>
21+
{
22+
Ok((0, BacktraceContext))
1823
}

src/libstd/sys/unix/backtrace/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@
8383
/// to symbols. This is a bit of a hokey implementation as-is, but it works for
8484
/// all unix platforms we support right now, so it at least gets the job done.
8585
86-
pub use self::tracing::write;
86+
pub use self::tracing::unwind_backtrace;
87+
pub use self::printing::{foreach_symbol_fileline, resolve_symname};
8788

8889
// tracing impls:
8990
mod tracing;
@@ -100,3 +101,5 @@ pub mod gnu {
100101
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
101102
}
102103
}
104+
105+
pub struct BacktraceContext;

src/libstd/sys/unix/backtrace/printing/dladdr.rs

+22-9
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
use io;
1212
use io::prelude::*;
1313
use libc;
14+
use sys::backtrace::BacktraceContext;
15+
use sys_common::backtrace::Frame;
1416

15-
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
16-
_symaddr: *mut libc::c_void) -> io::Result<()> {
17-
use sys_common::backtrace::{output};
17+
pub fn resolve_symname<F>(frame: Frame,
18+
callback: F,
19+
_: &BacktraceContext) -> io::Result<()>
20+
where F: FnOnce(Option<&str>) -> io::Result<()>
21+
{
1822
use intrinsics;
1923
use ffi::CStr;
2024

@@ -31,11 +35,20 @@ pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
3135
}
3236

3337
let mut info: Dl_info = unsafe { intrinsics::init() };
34-
if unsafe { dladdr(addr, &mut info) == 0 } {
35-
output(w, idx,addr, None)
38+
let symname = if unsafe { dladdr(frame.exact_position, &mut info) == 0 } {
39+
None
3640
} else {
37-
output(w, idx, addr, Some(unsafe {
38-
CStr::from_ptr(info.dli_sname).to_bytes()
39-
}))
40-
}
41+
unsafe {
42+
CStr::from_ptr(info.dli_sname).to_str().ok()
43+
}
44+
};
45+
callback(symname)
46+
}
47+
48+
pub fn foreach_symbol_fileline<F>(_symbol_addr: Frame,
49+
mut _f: F
50+
_: &BacktraceContext) -> io::Result<bool>
51+
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
52+
{
53+
Ok(())
4154
}

src/libstd/sys/unix/backtrace/printing/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
pub use self::imp::print;
11+
pub use self::imp::{foreach_symbol_fileline, resolve_symname};
1212

1313
#[cfg(any(target_os = "macos", target_os = "ios",
1414
target_os = "emscripten"))]
@@ -17,5 +17,6 @@ mod imp;
1717

1818
#[cfg(not(any(target_os = "macos", target_os = "ios",
1919
target_os = "emscripten")))]
20-
#[path = "gnu.rs"]
21-
mod imp;
20+
mod imp {
21+
pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname};
22+
}

src/libstd/sys/unix/backtrace/tracing/backtrace_fn.rs

+20-29
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,26 @@ use io::prelude::*;
2222
use io;
2323
use libc;
2424
use mem;
25-
use sys::mutex::Mutex;
25+
use sys::backtrace::BacktraceContext;
26+
use sys_common::backtrace::Frame;
2627

27-
use super::super::printing::print;
28-
29-
#[inline(never)]
30-
pub fn write(w: &mut Write) -> io::Result<()> {
31-
extern {
32-
fn backtrace(buf: *mut *mut libc::c_void,
33-
sz: libc::c_int) -> libc::c_int;
34-
}
35-
36-
// while it doesn't requires lock for work as everything is
37-
// local, it still displays much nicer backtraces when a
38-
// couple of threads panic simultaneously
39-
static LOCK: Mutex = Mutex::new();
40-
unsafe {
41-
LOCK.lock();
42-
43-
writeln!(w, "stack backtrace:")?;
44-
// 100 lines should be enough
45-
const SIZE: usize = 100;
46-
let mut buf: [*mut libc::c_void; SIZE] = mem::zeroed();
47-
let cnt = backtrace(buf.as_mut_ptr(), SIZE as libc::c_int) as usize;
48-
49-
// skipping the first one as it is write itself
50-
for i in 1..cnt {
51-
print(w, i as isize, buf[i], buf[i])?
52-
}
53-
LOCK.unlock();
28+
#[inline(never)] // if we know this is a function call, we can skip it when
29+
// tracing
30+
pub fn unwind_backtrace(frames: &mut [Frame])
31+
-> io::Result<(usize, BacktraceContext)>
32+
{
33+
const FRAME_LEN = 100;
34+
assert!(FRAME_LEN >= frames);
35+
let mut raw_frames = [0; FRAME_LEN];
36+
let nb_frames = backtrace(frames.as_mut_ptr(),
37+
frames.len() as libc::c_int);
38+
for (from, to) in raw_frames[..nb_frames].iter().zip(frames.iter_mut()) {
39+
*to = Frame {
40+
exact_position: *from,
41+
symbol_addr: *from,
42+
};
5443
}
55-
Ok(())
44+
(nb_frames as usize, BacktraceContext)
5645
}
46+
47+
extern fn backtrace(buf: *mut *mut libc::c_void, sz: libc::c_int) -> libc::c_int;

src/libstd/sys/unix/backtrace/tracing/gcc_s.rs

+77-82
Original file line numberDiff line numberDiff line change
@@ -8,102 +8,97 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use error::Error;
1112
use io;
12-
use io::prelude::*;
1313
use libc;
14-
use mem;
15-
use sys_common::mutex::Mutex;
14+
use sys::backtrace::BacktraceContext;
15+
use sys_common::backtrace::Frame;
1616

17-
use super::super::printing::print;
1817
use unwind as uw;
1918

20-
#[inline(never)] // if we know this is a function call, we can skip it when
21-
// tracing
22-
pub fn write(w: &mut Write) -> io::Result<()> {
23-
struct Context<'a> {
24-
idx: isize,
25-
writer: &'a mut (Write+'a),
26-
last_error: Option<io::Error>,
27-
}
19+
struct Context<'a> {
20+
idx: usize,
21+
frames: &'a mut [Frame],
22+
}
2823

29-
// When using libbacktrace, we use some necessary global state, so we
30-
// need to prevent more than one thread from entering this block. This
31-
// is semi-reasonable in terms of printing anyway, and we know that all
32-
// I/O done here is blocking I/O, not green I/O, so we don't have to
33-
// worry about this being a native vs green mutex.
34-
static LOCK: Mutex = Mutex::new();
35-
unsafe {
36-
LOCK.lock();
24+
#[derive(Debug)]
25+
struct UnwindError(uw::_Unwind_Reason_Code);
3726

38-
writeln!(w, "stack backtrace:")?;
27+
impl Error for UnwindError {
28+
fn description(&self) -> &'static str {
29+
"unexpected return value while unwinding"
30+
}
31+
}
3932

40-
let mut cx = Context { writer: w, last_error: None, idx: 0 };
41-
let ret = match {
42-
uw::_Unwind_Backtrace(trace_fn,
43-
&mut cx as *mut Context as *mut libc::c_void)
44-
} {
45-
uw::_URC_NO_REASON => {
46-
match cx.last_error {
47-
Some(err) => Err(err),
48-
None => Ok(())
49-
}
50-
}
51-
_ => Ok(()),
52-
};
53-
LOCK.unlock();
54-
return ret
33+
impl ::fmt::Display for UnwindError {
34+
fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result {
35+
write!(f, "{}: {:?}", self.description(), self.0)
5536
}
37+
}
5638

57-
extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
58-
arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
59-
let cx: &mut Context = unsafe { mem::transmute(arg) };
60-
let mut ip_before_insn = 0;
61-
let mut ip = unsafe {
62-
uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
63-
};
64-
if !ip.is_null() && ip_before_insn == 0 {
65-
// this is a non-signaling frame, so `ip` refers to the address
66-
// after the calling instruction. account for that.
67-
ip = (ip as usize - 1) as *mut _;
39+
#[inline(never)] // if we know this is a function call, we can skip it when
40+
// tracing
41+
pub fn unwind_backtrace(frames: &mut [Frame])
42+
-> io::Result<(usize, BacktraceContext)>
43+
{
44+
let mut cx = Context {
45+
idx: 0,
46+
frames: frames,
47+
};
48+
let result_unwind = unsafe {
49+
uw::_Unwind_Backtrace(trace_fn,
50+
&mut cx as *mut Context
51+
as *mut libc::c_void)
52+
};
53+
// See libunwind:src/unwind/Backtrace.c for the return values.
54+
// No, there is no doc.
55+
match result_unwind {
56+
uw::_URC_END_OF_STACK | uw::_URC_FATAL_PHASE1_ERROR => {
57+
Ok((cx.idx, BacktraceContext))
6858
}
69-
70-
// dladdr() on osx gets whiny when we use FindEnclosingFunction, and
71-
// it appears to work fine without it, so we only use
72-
// FindEnclosingFunction on non-osx platforms. In doing so, we get a
73-
// slightly more accurate stack trace in the process.
74-
//
75-
// This is often because panic involves the last instruction of a
76-
// function being "call std::rt::begin_unwind", with no ret
77-
// instructions after it. This means that the return instruction
78-
// pointer points *outside* of the calling function, and by
79-
// unwinding it we go back to the original function.
80-
let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
81-
ip
82-
} else {
83-
unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
84-
};
85-
86-
// Don't print out the first few frames (they're not user frames)
87-
cx.idx += 1;
88-
if cx.idx <= 0 { return uw::_URC_NO_REASON }
89-
// Don't print ginormous backtraces
90-
if cx.idx > 100 {
91-
match write!(cx.writer, " ... <frames omitted>\n") {
92-
Ok(()) => {}
93-
Err(e) => { cx.last_error = Some(e); }
94-
}
95-
return uw::_URC_FAILURE
59+
_ => {
60+
Err(io::Error::new(io::ErrorKind::Other,
61+
UnwindError(result_unwind)))
9662
}
63+
}
64+
}
9765

98-
// Once we hit an error, stop trying to print more frames
99-
if cx.last_error.is_some() { return uw::_URC_FAILURE }
66+
extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
67+
arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
68+
let cx = unsafe { &mut *(arg as *mut Context) };
69+
let mut ip_before_insn = 0;
70+
let mut ip = unsafe {
71+
uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
72+
};
73+
if !ip.is_null() && ip_before_insn == 0 {
74+
// this is a non-signaling frame, so `ip` refers to the address
75+
// after the calling instruction. account for that.
76+
ip = (ip as usize - 1) as *mut _;
77+
}
10078

101-
match print(cx.writer, cx.idx, ip, symaddr) {
102-
Ok(()) => {}
103-
Err(e) => { cx.last_error = Some(e); }
104-
}
79+
// dladdr() on osx gets whiny when we use FindEnclosingFunction, and
80+
// it appears to work fine without it, so we only use
81+
// FindEnclosingFunction on non-osx platforms. In doing so, we get a
82+
// slightly more accurate stack trace in the process.
83+
//
84+
// This is often because panic involves the last instruction of a
85+
// function being "call std::rt::begin_unwind", with no ret
86+
// instructions after it. This means that the return instruction
87+
// pointer points *outside* of the calling function, and by
88+
// unwinding it we go back to the original function.
89+
let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
90+
ip
91+
} else {
92+
unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
93+
};
10594

106-
// keep going
107-
uw::_URC_NO_REASON
95+
if cx.idx < cx.frames.len() {
96+
cx.frames[cx.idx] = Frame {
97+
symbol_addr: symaddr,
98+
exact_position: ip,
99+
};
100+
cx.idx += 1;
108101
}
102+
103+
uw::_URC_NO_REASON
109104
}

0 commit comments

Comments
 (0)