Skip to content

Commit

Permalink
Rollup merge of rust-lang#38165 - Yamakaky:better-backtrace, r=alexcr…
Browse files Browse the repository at this point in the history
…ichton

Improve backtrace formating while panicking.

Fixes rust-lang#37783.

Done:

- Fix alignment of file paths for better readability
- `RUST_BACKTRACE=full` prints all the informations (current behaviour)
- `RUST_BACKTRACE=(short|yes)` is the default and does:
  - Skip irrelevant frames at the beginning and the end
  - Remove function address
  - Remove the current directory from the absolute paths
  - Remove `::hfabe6541873` at the end of the symbols
- `RUST_BACKTRACE=(0|no)` disables the backtrace.
- `RUST_BACKTRACE=<everything else>` is equivalent to `short` for
  backward compatibility.
- doc
- More uniform printing across platforms.

Removed, TODO in a new PR:

- Remove path prefix for libraries and libstd

Example of short backtrace:
```rust
fn fail() {
    panic!();
}

fn main() {
    let closure = || fail();
    closure();
}
```
Short:
```
thread 'main' panicked at 'explicit panic', t.rs:2
Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
   0: t::fail
            at ./t.rs:2
   1: t::main::{{closure}}
            at ./t.rs:6
   2: t::main
            at ./t.rs:7
```
Full:
```
thread 'main' panicked at 'This function never returns!', t.rs:2
stack backtrace:
   0:     0x558ddf666478 - std::sys::imp::backtrace::tracing::imp::unwind_backtrace::hec84c9dd8389cc5d
                               at /home/yamakaky/dev/rust/rust/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1:     0x558ddf65d90e - std::sys_common::backtrace::_print::hfa25f8b31f4b4353
                               at /home/yamakaky/dev/rust/rust/src/libstd/sys_common/backtrace.rs:71
   2:     0x558ddf65cb5e - std::sys_common::backtrace::print::h9b711e11ac3ba805
                               at /home/yamakaky/dev/rust/rust/src/libstd/sys_common/backtrace.rs:60
   3:     0x558ddf66796e - std::panicking::default_hook::{{closure}}::h736d216e74748044
                               at /home/yamakaky/dev/rust/rust/src/libstd/panicking.rs:355
   4:     0x558ddf66743c - std::panicking::default_hook::h16baff397e46ea10
                               at /home/yamakaky/dev/rust/rust/src/libstd/panicking.rs:371
   5:     0x558ddf6682bc - std::panicking::rust_panic_with_hook::h6d5a9bb4eca42c80
                               at /home/yamakaky/dev/rust/rust/src/libstd/panicking.rs:559
   6:     0x558ddf64ea93 - std::panicking::begin_panic::h17dc549df2f10b99
                               at /home/yamakaky/dev/rust/rust/src/libstd/panicking.rs:521
   7:     0x558ddf64ec42 - t::diverges::he6bc43fc925905f5
                               at /tmp/p/t.rs:2
   8:     0x558ddf64ec5a - t::main::h0ffc20356b8a69c0
                               at /tmp/p/t.rs:6
   9:     0x558ddf6687f5 - core::ops::FnOnce::call_once::hce41f19c0db56f93
  10:     0x558ddf667cde - std::panicking::try::do_call::hd4c8c97efb4291df
                               at /home/yamakaky/dev/rust/rust/src/libstd/panicking.rs:464
  11:     0x558ddf698d77 - __rust_try
  12:     0x558ddf698c57 - __rust_maybe_catch_panic
                               at /home/yamakaky/dev/rust/rust/src/libpanic_unwind/lib.rs:98
  13:     0x558ddf667adb - std::panicking::try::h2c56ed2a59ec1d12
                               at /home/yamakaky/dev/rust/rust/src/libstd/panicking.rs:440
  14:     0x558ddf66cc9a - std::panic::catch_unwind::h390834e0251cc9af
                               at /home/yamakaky/dev/rust/rust/src/libstd/panic.rs:361
  15:     0x558ddf6809ee - std::rt::lang_start::hb73087428e233982
                               at /home/yamakaky/dev/rust/rust/src/libstd/rt.rs:57
  16:     0x558ddf64ec92 - main
  17:     0x7fecb869e290 - __libc_start_main
  18:     0x558ddf64e8b9 - _start
  19:                0x0 - <unknown>
```
  • Loading branch information
frewsxcv committed Feb 8, 2017
2 parents 4379e2f + fdd8c34 commit 168fd73
Show file tree
Hide file tree
Showing 18 changed files with 715 additions and 447 deletions.
15 changes: 14 additions & 1 deletion src/doc/book/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ If you want more information, you can get a backtrace by setting the
```text
$ RUST_BACKTRACE=1 ./diverges
thread 'main' panicked at 'This function never returns!', hello.rs:2
Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
hello::diverges
at ./hello.rs:2
hello::main
at ./hello.rs:6
```

If you want the complete backtrace and filenames:

```text
$ RUST_BACKTRACE=full ./diverges
thread 'main' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
Expand Down Expand Up @@ -262,7 +275,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
`RUST_BACKTRACE` also works with Cargo’s `run` command:

```text
$ RUST_BACKTRACE=1 cargo run
$ RUST_BACKTRACE=full cargo run
Running `target/debug/diverges`
thread 'main' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
Expand Down
10 changes: 7 additions & 3 deletions src/libstd/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,11 @@ fn default_hook(info: &PanicInfo) {
let log_backtrace = {
let panics = update_panic_count(0);

panics >= 2 || backtrace::log_enabled()
if panics >= 2 {
Some(backtrace::PrintFormat::Full)
} else {
backtrace::log_enabled()
}
};

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

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

if log_backtrace {
let _ = backtrace::write(err);
if let Some(format) = log_backtrace {
let _ = backtrace::print(err, format);
} else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) {
let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace.");
}
Expand Down
11 changes: 8 additions & 3 deletions src/libstd/sys/redox/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@

use libc;
use io;
use sys_common::backtrace::output;
use sys_common::backtrace::Frame;

pub use sys_common::gnu::libbacktrace::*;
pub struct BacktraceContext;

#[inline(never)]
pub fn write(w: &mut io::Write) -> io::Result<()> {
output(w, 0, 0 as *mut libc::c_void, None)
pub fn unwind_backtrace(frames: &mut [Frame])
-> io::Result<(usize, BacktraceContext)>
{
Ok((0, BacktraceContext))
}
5 changes: 4 additions & 1 deletion src/libstd/sys/unix/backtrace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
/// to symbols. This is a bit of a hokey implementation as-is, but it works for
/// all unix platforms we support right now, so it at least gets the job done.

pub use self::tracing::write;
pub use self::tracing::unwind_backtrace;
pub use self::printing::{foreach_symbol_fileline, resolve_symname};

// tracing impls:
mod tracing;
Expand All @@ -100,3 +101,5 @@ pub mod gnu {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
}
}

pub struct BacktraceContext;
28 changes: 19 additions & 9 deletions src/libstd/sys/unix/backtrace/printing/dladdr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
use io;
use io::prelude::*;
use libc;
use sys_common::backtrace::Frame;

pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
_symaddr: *mut libc::c_void) -> io::Result<()> {
use sys_common::backtrace::{output};
pub fn resolve_symname<F>(frame: Frame, callback: F) -> io::Result<()>
where F: FnOnce(Option<&str>) -> io::Result<()>
{
use intrinsics;
use ffi::CStr;

Expand All @@ -31,11 +32,20 @@ pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
}

let mut info: Dl_info = unsafe { intrinsics::init() };
if unsafe { dladdr(addr, &mut info) == 0 } {
output(w, idx,addr, None)
let symname = if unsafe { dladdr(frame.exact_position, &mut info) == 0 } {
None
} else {
output(w, idx, addr, Some(unsafe {
CStr::from_ptr(info.dli_sname).to_bytes()
}))
}
unsafe {
CStr::from_ptr(info.dli_sname).to_str().ok()
}
};
callback(symname)
}

pub fn foreach_symbol_fileline<F>(_symbol_addr: Frame,
mut _f: F
_: &BacktraceContext) -> io::Result<bool>
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
{
Ok(())
}
7 changes: 4 additions & 3 deletions src/libstd/sys/unix/backtrace/printing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

pub use self::imp::print;
pub use self::imp::{foreach_symbol_fileline, resolve_symname};

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

#[cfg(not(any(target_os = "macos", target_os = "ios",
target_os = "emscripten")))]
#[path = "gnu.rs"]
mod imp;
mod imp {
pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname};
}
49 changes: 20 additions & 29 deletions src/libstd/sys/unix/backtrace/tracing/backtrace_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,26 @@ use io::prelude::*;
use io;
use libc;
use mem;
use sys::mutex::Mutex;
use sys::backtrace::BacktraceContext;
use sys_common::backtrace::Frame;

use super::super::printing::print;

#[inline(never)]
pub fn write(w: &mut Write) -> io::Result<()> {
extern {
fn backtrace(buf: *mut *mut libc::c_void,
sz: libc::c_int) -> libc::c_int;
}

// while it doesn't requires lock for work as everything is
// local, it still displays much nicer backtraces when a
// couple of threads panic simultaneously
static LOCK: Mutex = Mutex::new();
unsafe {
LOCK.lock();

writeln!(w, "stack backtrace:")?;
// 100 lines should be enough
const SIZE: usize = 100;
let mut buf: [*mut libc::c_void; SIZE] = mem::zeroed();
let cnt = backtrace(buf.as_mut_ptr(), SIZE as libc::c_int) as usize;

// skipping the first one as it is write itself
for i in 1..cnt {
print(w, i as isize, buf[i], buf[i])?
}
LOCK.unlock();
#[inline(never)] // if we know this is a function call, we can skip it when
// tracing
pub fn unwind_backtrace(frames: &mut [Frame])
-> io::Result<(usize, BacktraceContext)>
{
const FRAME_LEN = 100;
assert!(FRAME_LEN >= frames);
let mut raw_frames = [0; FRAME_LEN];
let nb_frames = backtrace(frames.as_mut_ptr(),
frames.len() as libc::c_int);
for (from, to) in raw_frames[..nb_frames].iter().zip(frames.iter_mut()) {
*to = Frame {
exact_position: *from,
symbol_addr: *from,
};
}
Ok(())
(nb_frames as usize, BacktraceContext)
}

extern fn backtrace(buf: *mut *mut libc::c_void, sz: libc::c_int) -> libc::c_int;
159 changes: 77 additions & 82 deletions src/libstd/sys/unix/backtrace/tracing/gcc_s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,102 +8,97 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use error::Error;
use io;
use io::prelude::*;
use libc;
use mem;
use sys_common::mutex::Mutex;
use sys::backtrace::BacktraceContext;
use sys_common::backtrace::Frame;

use super::super::printing::print;
use unwind as uw;

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

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

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

let mut cx = Context { writer: w, last_error: None, idx: 0 };
let ret = match {
uw::_Unwind_Backtrace(trace_fn,
&mut cx as *mut Context as *mut libc::c_void)
} {
uw::_URC_NO_REASON => {
match cx.last_error {
Some(err) => Err(err),
None => Ok(())
}
}
_ => Ok(()),
};
LOCK.unlock();
return ret
impl ::fmt::Display for UnwindError {
fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result {
write!(f, "{}: {:?}", self.description(), self.0)
}
}

extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
let cx: &mut Context = unsafe { mem::transmute(arg) };
let mut ip_before_insn = 0;
let mut ip = unsafe {
uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
};
if !ip.is_null() && ip_before_insn == 0 {
// this is a non-signaling frame, so `ip` refers to the address
// after the calling instruction. account for that.
ip = (ip as usize - 1) as *mut _;
#[inline(never)] // if we know this is a function call, we can skip it when
// tracing
pub fn unwind_backtrace(frames: &mut [Frame])
-> io::Result<(usize, BacktraceContext)>
{
let mut cx = Context {
idx: 0,
frames: frames,
};
let result_unwind = unsafe {
uw::_Unwind_Backtrace(trace_fn,
&mut cx as *mut Context
as *mut libc::c_void)
};
// See libunwind:src/unwind/Backtrace.c for the return values.
// No, there is no doc.
match result_unwind {
uw::_URC_END_OF_STACK | uw::_URC_FATAL_PHASE1_ERROR => {
Ok((cx.idx, BacktraceContext))
}

// dladdr() on osx gets whiny when we use FindEnclosingFunction, and
// it appears to work fine without it, so we only use
// FindEnclosingFunction on non-osx platforms. In doing so, we get a
// slightly more accurate stack trace in the process.
//
// This is often because panic involves the last instruction of a
// function being "call std::rt::begin_unwind", with no ret
// instructions after it. This means that the return instruction
// pointer points *outside* of the calling function, and by
// unwinding it we go back to the original function.
let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
ip
} else {
unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
};

// Don't print out the first few frames (they're not user frames)
cx.idx += 1;
if cx.idx <= 0 { return uw::_URC_NO_REASON }
// Don't print ginormous backtraces
if cx.idx > 100 {
match write!(cx.writer, " ... <frames omitted>\n") {
Ok(()) => {}
Err(e) => { cx.last_error = Some(e); }
}
return uw::_URC_FAILURE
_ => {
Err(io::Error::new(io::ErrorKind::Other,
UnwindError(result_unwind)))
}
}
}

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

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

// keep going
uw::_URC_NO_REASON
if cx.idx < cx.frames.len() {
cx.frames[cx.idx] = Frame {
symbol_addr: symaddr,
exact_position: ip,
};
cx.idx += 1;
}

uw::_URC_NO_REASON
}
File renamed without changes.
Loading

0 comments on commit 168fd73

Please sign in to comment.