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 #37783. PR is #38165.
This commit is contained in:
parent
e0044bd389
commit
d50e4cc064
19 changed files with 804 additions and 526 deletions
|
@ -230,6 +230,19 @@ If you want more information, you can get a backtrace by setting the
|
||||||
```text
|
```text
|
||||||
$ RUST_BACKTRACE=1 ./diverges
|
$ RUST_BACKTRACE=1 ./diverges
|
||||||
thread 'main' panicked at 'This function never returns!', hello.rs:2
|
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:
|
stack backtrace:
|
||||||
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
|
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
|
||||||
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
|
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
|
||||||
|
@ -262,7 +275,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||||
`RUST_BACKTRACE` also works with Cargo’s `run` command:
|
`RUST_BACKTRACE` also works with Cargo’s `run` command:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
$ RUST_BACKTRACE=1 cargo run
|
$ RUST_BACKTRACE=full cargo run
|
||||||
Running `target/debug/diverges`
|
Running `target/debug/diverges`
|
||||||
thread 'main' panicked at 'This function never returns!', hello.rs:2
|
thread 'main' panicked at 'This function never returns!', hello.rs:2
|
||||||
stack backtrace:
|
stack backtrace:
|
||||||
|
|
|
@ -320,7 +320,11 @@ fn default_hook(info: &PanicInfo) {
|
||||||
let log_backtrace = {
|
let log_backtrace = {
|
||||||
let panics = update_panic_count(0);
|
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;
|
let file = info.location.file;
|
||||||
|
@ -347,8 +351,8 @@ fn default_hook(info: &PanicInfo) {
|
||||||
|
|
||||||
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
|
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
|
||||||
|
|
||||||
if log_backtrace {
|
if let Some(format) = log_backtrace {
|
||||||
let _ = backtrace::write(err);
|
let _ = backtrace::print(err, format);
|
||||||
} else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) {
|
} else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) {
|
||||||
let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace.");
|
let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,14 @@
|
||||||
|
|
||||||
use libc;
|
use libc;
|
||||||
use io;
|
use io;
|
||||||
use sys_common::backtrace::output;
|
use sys_common::backtrace::Frame;
|
||||||
|
|
||||||
|
pub use sys_common::gnu::libbacktrace::*;
|
||||||
|
pub struct BacktraceContext;
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn write(w: &mut io::Write) -> io::Result<()> {
|
pub fn unwind_backtrace(frames: &mut [Frame])
|
||||||
output(w, 0, 0 as *mut libc::c_void, None)
|
-> io::Result<(usize, BacktraceContext)>
|
||||||
|
{
|
||||||
|
Ok((0, BacktraceContext))
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,8 @@
|
||||||
/// to symbols. This is a bit of a hokey implementation as-is, but it works for
|
/// 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.
|
/// 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:
|
// tracing impls:
|
||||||
mod tracing;
|
mod tracing;
|
||||||
|
@ -100,3 +101,5 @@ pub mod gnu {
|
||||||
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
|
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BacktraceContext;
|
||||||
|
|
|
@ -9,33 +9,45 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use io;
|
use io;
|
||||||
use io::prelude::*;
|
use intrinsics;
|
||||||
|
use ffi::CStr;
|
||||||
use libc;
|
use libc;
|
||||||
|
use sys::backtrace::BacktraceContext;
|
||||||
|
use sys_common::backtrace::Frame;
|
||||||
|
|
||||||
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
|
pub fn resolve_symname<F>(frame: Frame,
|
||||||
_symaddr: *mut libc::c_void) -> io::Result<()> {
|
callback: F,
|
||||||
use sys_common::backtrace::{output};
|
_: &BacktraceContext) -> io::Result<()>
|
||||||
use intrinsics;
|
where F: FnOnce(Option<&str>) -> io::Result<()>
|
||||||
use ffi::CStr;
|
{
|
||||||
|
unsafe {
|
||||||
#[repr(C)]
|
let mut info: Dl_info = intrinsics::init();
|
||||||
struct Dl_info {
|
let symname = if dladdr(frame.exact_position, &mut info) == 0 {
|
||||||
dli_fname: *const libc::c_char,
|
None
|
||||||
dli_fbase: *mut libc::c_void,
|
} else {
|
||||||
dli_sname: *const libc::c_char,
|
CStr::from_ptr(info.dli_sname).to_str().ok()
|
||||||
dli_saddr: *mut libc::c_void,
|
};
|
||||||
}
|
callback(symname)
|
||||||
extern {
|
|
||||||
fn dladdr(addr: *const libc::c_void,
|
|
||||||
info: *mut Dl_info) -> libc::c_int;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut info: Dl_info = unsafe { intrinsics::init() };
|
|
||||||
if unsafe { dladdr(addr, &mut info) == 0 } {
|
|
||||||
output(w, idx,addr, None)
|
|
||||||
} else {
|
|
||||||
output(w, idx, addr, Some(unsafe {
|
|
||||||
CStr::from_ptr(info.dli_sname).to_bytes()
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn foreach_symbol_fileline<F>(_symbol_addr: Frame,
|
||||||
|
_f: F,
|
||||||
|
_: &BacktraceContext) -> io::Result<bool>
|
||||||
|
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
|
||||||
|
{
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Dl_info {
|
||||||
|
dli_fname: *const libc::c_char,
|
||||||
|
dli_fbase: *mut libc::c_void,
|
||||||
|
dli_sname: *const libc::c_char,
|
||||||
|
dli_saddr: *mut libc::c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
extern {
|
||||||
|
fn dladdr(addr: *const libc::c_void,
|
||||||
|
info: *mut Dl_info) -> libc::c_int;
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// 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",
|
#[cfg(any(target_os = "macos", target_os = "ios",
|
||||||
target_os = "emscripten"))]
|
target_os = "emscripten"))]
|
||||||
|
@ -17,5 +17,6 @@ mod imp;
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "ios",
|
#[cfg(not(any(target_os = "macos", target_os = "ios",
|
||||||
target_os = "emscripten")))]
|
target_os = "emscripten")))]
|
||||||
#[path = "gnu.rs"]
|
mod imp {
|
||||||
mod imp;
|
pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname};
|
||||||
|
}
|
||||||
|
|
|
@ -18,39 +18,32 @@
|
||||||
/// simple to use it should be used only on iOS devices as the only viable
|
/// simple to use it should be used only on iOS devices as the only viable
|
||||||
/// option.
|
/// option.
|
||||||
|
|
||||||
use io::prelude::*;
|
|
||||||
use io;
|
use io;
|
||||||
use libc;
|
use libc;
|
||||||
use mem;
|
use mem;
|
||||||
use sys::mutex::Mutex;
|
use sys::backtrace::BacktraceContext;
|
||||||
|
use sys_common::backtrace::Frame;
|
||||||
|
|
||||||
use super::super::printing::print;
|
#[inline(never)] // if we know this is a function call, we can skip it when
|
||||||
|
// tracing
|
||||||
#[inline(never)]
|
pub fn unwind_backtrace(frames: &mut [Frame])
|
||||||
pub fn write(w: &mut Write) -> io::Result<()> {
|
-> io::Result<(usize, BacktraceContext)>
|
||||||
extern {
|
{
|
||||||
fn backtrace(buf: *mut *mut libc::c_void,
|
const FRAME_LEN: usize = 100;
|
||||||
sz: libc::c_int) -> libc::c_int;
|
assert!(FRAME_LEN >= frames.len());
|
||||||
|
let mut raw_frames = [::std::ptr::null_mut(); FRAME_LEN];
|
||||||
|
let nb_frames = unsafe {
|
||||||
|
backtrace(raw_frames.as_mut_ptr(), raw_frames.len() as libc::c_int)
|
||||||
|
} as usize;
|
||||||
|
for (from, to) in raw_frames.iter().zip(frames.iter_mut()).take(nb_frames) {
|
||||||
|
*to = Frame {
|
||||||
|
exact_position: *from,
|
||||||
|
symbol_addr: *from,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Ok((nb_frames as usize, BacktraceContext))
|
||||||
// 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
|
extern {
|
||||||
static LOCK: Mutex = Mutex::new();
|
fn backtrace(buf: *mut *mut libc::c_void, sz: libc::c_int) -> libc::c_int;
|
||||||
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();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,102 +8,97 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
use io;
|
use io;
|
||||||
use io::prelude::*;
|
|
||||||
use libc;
|
use libc;
|
||||||
use mem;
|
use sys::backtrace::BacktraceContext;
|
||||||
use sys_common::mutex::Mutex;
|
use sys_common::backtrace::Frame;
|
||||||
|
|
||||||
use super::super::printing::print;
|
|
||||||
use unwind as uw;
|
use unwind as uw;
|
||||||
|
|
||||||
|
struct Context<'a> {
|
||||||
|
idx: usize,
|
||||||
|
frames: &'a mut [Frame],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct UnwindError(uw::_Unwind_Reason_Code);
|
||||||
|
|
||||||
|
impl Error for UnwindError {
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
"unexpected return value while unwinding"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::fmt::Display for UnwindError {
|
||||||
|
fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result {
|
||||||
|
write!(f, "{}: {:?}", self.description(), self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(never)] // if we know this is a function call, we can skip it when
|
#[inline(never)] // if we know this is a function call, we can skip it when
|
||||||
// tracing
|
// tracing
|
||||||
pub fn write(w: &mut Write) -> io::Result<()> {
|
pub fn unwind_backtrace(frames: &mut [Frame])
|
||||||
struct Context<'a> {
|
-> io::Result<(usize, BacktraceContext)>
|
||||||
idx: isize,
|
{
|
||||||
writer: &'a mut (Write+'a),
|
let mut cx = Context {
|
||||||
last_error: Option<io::Error>,
|
idx: 0,
|
||||||
}
|
frames: frames,
|
||||||
|
};
|
||||||
// When using libbacktrace, we use some necessary global state, so we
|
let result_unwind = unsafe {
|
||||||
// need to prevent more than one thread from entering this block. This
|
uw::_Unwind_Backtrace(trace_fn,
|
||||||
// is semi-reasonable in terms of printing anyway, and we know that all
|
&mut cx as *mut Context
|
||||||
// I/O done here is blocking I/O, not green I/O, so we don't have to
|
as *mut libc::c_void)
|
||||||
// worry about this being a native vs green mutex.
|
};
|
||||||
static LOCK: Mutex = Mutex::new();
|
// See libunwind:src/unwind/Backtrace.c for the return values.
|
||||||
unsafe {
|
// No, there is no doc.
|
||||||
LOCK.lock();
|
match result_unwind {
|
||||||
|
uw::_URC_END_OF_STACK | uw::_URC_FATAL_PHASE1_ERROR => {
|
||||||
writeln!(w, "stack backtrace:")?;
|
Ok((cx.idx, BacktraceContext))
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 _;
|
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
// dladdr() on osx gets whiny when we use FindEnclosingFunction, and
|
Err(io::Error::new(io::ErrorKind::Other,
|
||||||
// it appears to work fine without it, so we only use
|
UnwindError(result_unwind)))
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we hit an error, stop trying to print more frames
|
|
||||||
if cx.last_error.is_some() { return uw::_URC_FAILURE }
|
|
||||||
|
|
||||||
match print(cx.writer, cx.idx, ip, symaddr) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => { cx.last_error = Some(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep going
|
|
||||||
uw::_URC_NO_REASON
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 _;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) }
|
||||||
|
};
|
||||||
|
|
||||||
|
if cx.idx < cx.frames.len() {
|
||||||
|
cx.frames[cx.idx] = Frame {
|
||||||
|
symbol_addr: symaddr,
|
||||||
|
exact_position: ip,
|
||||||
|
};
|
||||||
|
cx.idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uw::_URC_NO_REASON
|
||||||
|
}
|
||||||
|
|
|
@ -24,37 +24,87 @@
|
||||||
|
|
||||||
#![allow(deprecated)] // dynamic_lib
|
#![allow(deprecated)] // dynamic_lib
|
||||||
|
|
||||||
use io::prelude::*;
|
|
||||||
|
|
||||||
use io;
|
use io;
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use mem;
|
use mem;
|
||||||
use ptr;
|
use ptr;
|
||||||
use sys::c;
|
use sys::c;
|
||||||
use sys::dynamic_lib::DynamicLibrary;
|
use sys::dynamic_lib::DynamicLibrary;
|
||||||
use sys::mutex::Mutex;
|
use sys_common::backtrace::Frame;
|
||||||
|
|
||||||
macro_rules! sym {
|
macro_rules! sym {
|
||||||
($lib:expr, $e:expr, $t:ident) => (
|
($lib:expr, $e:expr, $t:ident) => (
|
||||||
match $lib.symbol($e) {
|
$lib.symbol($e).map(|f| unsafe {
|
||||||
Ok(f) => $crate::mem::transmute::<usize, $t>(f),
|
$crate::mem::transmute::<usize, $t>(f)
|
||||||
Err(..) => return Ok(())
|
})
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_env = "msvc")]
|
|
||||||
#[path = "printing/msvc.rs"]
|
|
||||||
mod printing;
|
|
||||||
|
|
||||||
#[cfg(target_env = "gnu")]
|
|
||||||
#[path = "printing/gnu.rs"]
|
|
||||||
mod printing;
|
mod printing;
|
||||||
|
|
||||||
#[cfg(target_env = "gnu")]
|
#[cfg(target_env = "gnu")]
|
||||||
#[path = "backtrace_gnu.rs"]
|
#[path = "backtrace_gnu.rs"]
|
||||||
pub mod gnu;
|
pub mod gnu;
|
||||||
|
|
||||||
|
pub use self::printing::{resolve_symname, foreach_symbol_fileline};
|
||||||
|
|
||||||
|
pub fn unwind_backtrace(frames: &mut [Frame])
|
||||||
|
-> io::Result<(usize, BacktraceContext)>
|
||||||
|
{
|
||||||
|
let dbghelp = DynamicLibrary::open("dbghelp.dll")?;
|
||||||
|
|
||||||
|
// Fetch the symbols necessary from dbghelp.dll
|
||||||
|
let SymInitialize = sym!(dbghelp, "SymInitialize", SymInitializeFn)?;
|
||||||
|
let SymCleanup = sym!(dbghelp, "SymCleanup", SymCleanupFn)?;
|
||||||
|
let StackWalk64 = sym!(dbghelp, "StackWalk64", StackWalk64Fn)?;
|
||||||
|
|
||||||
|
// Allocate necessary structures for doing the stack walk
|
||||||
|
let process = unsafe { c::GetCurrentProcess() };
|
||||||
|
let thread = unsafe { c::GetCurrentThread() };
|
||||||
|
let mut context: c::CONTEXT = unsafe { mem::zeroed() };
|
||||||
|
unsafe { c::RtlCaptureContext(&mut context) };
|
||||||
|
let mut frame: c::STACKFRAME64 = unsafe { mem::zeroed() };
|
||||||
|
let image = init_frame(&mut frame, &context);
|
||||||
|
|
||||||
|
let backtrace_context = BacktraceContext {
|
||||||
|
handle: process,
|
||||||
|
SymCleanup: SymCleanup,
|
||||||
|
dbghelp: dbghelp,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize this process's symbols
|
||||||
|
let ret = unsafe { SymInitialize(process, ptr::null_mut(), c::TRUE) };
|
||||||
|
if ret != c::TRUE {
|
||||||
|
return Ok((0, backtrace_context))
|
||||||
|
}
|
||||||
|
|
||||||
|
// And now that we're done with all the setup, do the stack walking!
|
||||||
|
// Start from -1 to avoid printing this stack frame, which will
|
||||||
|
// always be exactly the same.
|
||||||
|
let mut i = 0;
|
||||||
|
unsafe {
|
||||||
|
while i < frames.len() &&
|
||||||
|
StackWalk64(image, process, thread, &mut frame, &mut context,
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
ptr::null_mut()) == c::TRUE
|
||||||
|
{
|
||||||
|
let addr = frame.AddrPC.Offset;
|
||||||
|
if addr == frame.AddrReturn.Offset || addr == 0 ||
|
||||||
|
frame.AddrReturn.Offset == 0 { break }
|
||||||
|
|
||||||
|
frames[i] = Frame {
|
||||||
|
symbol_addr: (addr - 1) as *const c_void,
|
||||||
|
exact_position: (addr - 1) as *const c_void,
|
||||||
|
};
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((i, backtrace_context))
|
||||||
|
}
|
||||||
|
|
||||||
type SymInitializeFn =
|
type SymInitializeFn =
|
||||||
unsafe extern "system" fn(c::HANDLE, *mut c_void,
|
unsafe extern "system" fn(c::HANDLE, *mut c_void,
|
||||||
c::BOOL) -> c::BOOL;
|
c::BOOL) -> c::BOOL;
|
||||||
|
@ -68,8 +118,8 @@ type StackWalk64Fn =
|
||||||
*mut c_void, *mut c_void) -> c::BOOL;
|
*mut c_void, *mut c_void) -> c::BOOL;
|
||||||
|
|
||||||
#[cfg(target_arch = "x86")]
|
#[cfg(target_arch = "x86")]
|
||||||
pub fn init_frame(frame: &mut c::STACKFRAME64,
|
fn init_frame(frame: &mut c::STACKFRAME64,
|
||||||
ctx: &c::CONTEXT) -> c::DWORD {
|
ctx: &c::CONTEXT) -> c::DWORD {
|
||||||
frame.AddrPC.Offset = ctx.Eip as u64;
|
frame.AddrPC.Offset = ctx.Eip as u64;
|
||||||
frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
|
frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
|
||||||
frame.AddrStack.Offset = ctx.Esp as u64;
|
frame.AddrStack.Offset = ctx.Esp as u64;
|
||||||
|
@ -80,8 +130,8 @@ pub fn init_frame(frame: &mut c::STACKFRAME64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
pub fn init_frame(frame: &mut c::STACKFRAME64,
|
fn init_frame(frame: &mut c::STACKFRAME64,
|
||||||
ctx: &c::CONTEXT) -> c::DWORD {
|
ctx: &c::CONTEXT) -> c::DWORD {
|
||||||
frame.AddrPC.Offset = ctx.Rip as u64;
|
frame.AddrPC.Offset = ctx.Rip as u64;
|
||||||
frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
|
frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
|
||||||
frame.AddrStack.Offset = ctx.Rsp as u64;
|
frame.AddrStack.Offset = ctx.Rsp as u64;
|
||||||
|
@ -91,73 +141,16 @@ pub fn init_frame(frame: &mut c::STACKFRAME64,
|
||||||
c::IMAGE_FILE_MACHINE_AMD64
|
c::IMAGE_FILE_MACHINE_AMD64
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Cleanup {
|
pub struct BacktraceContext {
|
||||||
handle: c::HANDLE,
|
handle: c::HANDLE,
|
||||||
SymCleanup: SymCleanupFn,
|
SymCleanup: SymCleanupFn,
|
||||||
|
// Only used in printing for msvc and not gnu
|
||||||
|
#[allow(dead_code)]
|
||||||
|
dbghelp: DynamicLibrary,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Cleanup {
|
impl Drop for BacktraceContext {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { (self.SymCleanup)(self.handle); }
|
unsafe { (self.SymCleanup)(self.handle); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(w: &mut Write) -> io::Result<()> {
|
|
||||||
// According to windows documentation, all dbghelp functions are
|
|
||||||
// single-threaded.
|
|
||||||
static LOCK: Mutex = Mutex::new();
|
|
||||||
unsafe {
|
|
||||||
LOCK.lock();
|
|
||||||
let res = _write(w);
|
|
||||||
LOCK.unlock();
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn _write(w: &mut Write) -> io::Result<()> {
|
|
||||||
let dbghelp = match DynamicLibrary::open("dbghelp.dll") {
|
|
||||||
Ok(lib) => lib,
|
|
||||||
Err(..) => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch the symbols necessary from dbghelp.dll
|
|
||||||
let SymInitialize = sym!(dbghelp, "SymInitialize", SymInitializeFn);
|
|
||||||
let SymCleanup = sym!(dbghelp, "SymCleanup", SymCleanupFn);
|
|
||||||
let StackWalk64 = sym!(dbghelp, "StackWalk64", StackWalk64Fn);
|
|
||||||
|
|
||||||
// Allocate necessary structures for doing the stack walk
|
|
||||||
let process = c::GetCurrentProcess();
|
|
||||||
let thread = c::GetCurrentThread();
|
|
||||||
let mut context: c::CONTEXT = mem::zeroed();
|
|
||||||
c::RtlCaptureContext(&mut context);
|
|
||||||
let mut frame: c::STACKFRAME64 = mem::zeroed();
|
|
||||||
let image = init_frame(&mut frame, &context);
|
|
||||||
|
|
||||||
// Initialize this process's symbols
|
|
||||||
let ret = SymInitialize(process, ptr::null_mut(), c::TRUE);
|
|
||||||
if ret != c::TRUE { return Ok(()) }
|
|
||||||
let _c = Cleanup { handle: process, SymCleanup: SymCleanup };
|
|
||||||
|
|
||||||
// And now that we're done with all the setup, do the stack walking!
|
|
||||||
// Start from -1 to avoid printing this stack frame, which will
|
|
||||||
// always be exactly the same.
|
|
||||||
let mut i = -1;
|
|
||||||
write!(w, "stack backtrace:\n")?;
|
|
||||||
while StackWalk64(image, process, thread, &mut frame, &mut context,
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut()) == c::TRUE {
|
|
||||||
let addr = frame.AddrPC.Offset;
|
|
||||||
if addr == frame.AddrReturn.Offset || addr == 0 ||
|
|
||||||
frame.AddrReturn.Offset == 0 { break }
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
if i >= 0 {
|
|
||||||
printing::print(w, i, addr - 1, process, &dbghelp)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -8,4 +8,13 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
pub use sys_common::gnu::libbacktrace::print;
|
#[cfg(target_env = "msvc")]
|
||||||
|
#[path = "msvc.rs"]
|
||||||
|
mod printing;
|
||||||
|
|
||||||
|
#[cfg(target_env = "gnu")]
|
||||||
|
mod printing {
|
||||||
|
pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use self::printing::{foreach_symbol_fileline, resolve_symname};
|
83
src/libstd/sys/windows/backtrace/printing/msvc.rs
Normal file
83
src/libstd/sys/windows/backtrace/printing/msvc.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use ffi::CStr;
|
||||||
|
use io;
|
||||||
|
use libc::{c_ulong, c_int, c_char};
|
||||||
|
use mem;
|
||||||
|
use sys::c;
|
||||||
|
use sys::backtrace::BacktraceContext;
|
||||||
|
use sys_common::backtrace::Frame;
|
||||||
|
|
||||||
|
type SymFromAddrFn =
|
||||||
|
unsafe extern "system" fn(c::HANDLE, u64, *mut u64,
|
||||||
|
*mut c::SYMBOL_INFO) -> c::BOOL;
|
||||||
|
type SymGetLineFromAddr64Fn =
|
||||||
|
unsafe extern "system" fn(c::HANDLE, u64, *mut u32,
|
||||||
|
*mut c::IMAGEHLP_LINE64) -> c::BOOL;
|
||||||
|
|
||||||
|
/// Converts a pointer to symbol to its string value.
|
||||||
|
pub fn resolve_symname<F>(frame: Frame,
|
||||||
|
callback: F,
|
||||||
|
context: &BacktraceContext) -> io::Result<()>
|
||||||
|
where F: FnOnce(Option<&str>) -> io::Result<()>
|
||||||
|
{
|
||||||
|
let SymFromAddr = sym!(&context.dbghelp, "SymFromAddr", SymFromAddrFn)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut info: c::SYMBOL_INFO = mem::zeroed();
|
||||||
|
info.MaxNameLen = c::MAX_SYM_NAME as c_ulong;
|
||||||
|
// the struct size in C. the value is different to
|
||||||
|
// `size_of::<SYMBOL_INFO>() - MAX_SYM_NAME + 1` (== 81)
|
||||||
|
// due to struct alignment.
|
||||||
|
info.SizeOfStruct = 88;
|
||||||
|
|
||||||
|
let mut displacement = 0u64;
|
||||||
|
let ret = SymFromAddr(context.handle,
|
||||||
|
frame.symbol_addr as u64,
|
||||||
|
&mut displacement,
|
||||||
|
&mut info);
|
||||||
|
|
||||||
|
let symname = if ret == c::TRUE {
|
||||||
|
let ptr = info.Name.as_ptr() as *const c_char;
|
||||||
|
CStr::from_ptr(ptr).to_str().ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
callback(symname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn foreach_symbol_fileline<F>(frame: Frame,
|
||||||
|
mut f: F,
|
||||||
|
context: &BacktraceContext)
|
||||||
|
-> io::Result<bool>
|
||||||
|
where F: FnMut(&[u8], c_int) -> io::Result<()>
|
||||||
|
{
|
||||||
|
let SymGetLineFromAddr64 = sym!(&context.dbghelp,
|
||||||
|
"SymGetLineFromAddr64",
|
||||||
|
SymGetLineFromAddr64Fn)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut line: c::IMAGEHLP_LINE64 = mem::zeroed();
|
||||||
|
line.SizeOfStruct = ::mem::size_of::<c::IMAGEHLP_LINE64>() as u32;
|
||||||
|
|
||||||
|
let mut displacement = 0u32;
|
||||||
|
let ret = SymGetLineFromAddr64(context.handle,
|
||||||
|
frame.exact_position as u64,
|
||||||
|
&mut displacement,
|
||||||
|
&mut line);
|
||||||
|
if ret == c::TRUE {
|
||||||
|
let name = CStr::from_ptr(line.Filename).to_bytes();
|
||||||
|
f(name, line.LineNumber as c_int)?;
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file at the top-level directory of this distribution and at
|
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
// option. This file may not be copied, modified, or distributed
|
|
||||||
// except according to those terms.
|
|
||||||
|
|
||||||
use io::prelude::*;
|
|
||||||
use io;
|
|
||||||
use libc::c_void;
|
|
||||||
use sys::c;
|
|
||||||
use sys::dynamic_lib::DynamicLibrary;
|
|
||||||
use sys_common::gnu::libbacktrace;
|
|
||||||
|
|
||||||
pub fn print(w: &mut Write,
|
|
||||||
i: isize,
|
|
||||||
addr: u64,
|
|
||||||
_process: c::HANDLE,
|
|
||||||
_dbghelp: &DynamicLibrary)
|
|
||||||
-> io::Result<()> {
|
|
||||||
let addr = addr as usize as *mut c_void;
|
|
||||||
libbacktrace::print(w, i, addr, addr)
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file at the top-level directory of this distribution and at
|
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
// option. This file may not be copied, modified, or distributed
|
|
||||||
// except according to those terms.
|
|
||||||
|
|
||||||
use ffi::CStr;
|
|
||||||
use io::prelude::*;
|
|
||||||
use io;
|
|
||||||
use libc::{c_ulong, c_int, c_char, c_void};
|
|
||||||
use mem;
|
|
||||||
use sys::c;
|
|
||||||
use sys::dynamic_lib::DynamicLibrary;
|
|
||||||
use sys_common::backtrace::{output, output_fileline};
|
|
||||||
|
|
||||||
type SymFromAddrFn =
|
|
||||||
unsafe extern "system" fn(c::HANDLE, u64, *mut u64,
|
|
||||||
*mut c::SYMBOL_INFO) -> c::BOOL;
|
|
||||||
type SymGetLineFromAddr64Fn =
|
|
||||||
unsafe extern "system" fn(c::HANDLE, u64, *mut u32,
|
|
||||||
*mut c::IMAGEHLP_LINE64) -> c::BOOL;
|
|
||||||
|
|
||||||
pub fn print(w: &mut Write,
|
|
||||||
i: isize,
|
|
||||||
addr: u64,
|
|
||||||
process: c::HANDLE,
|
|
||||||
dbghelp: &DynamicLibrary)
|
|
||||||
-> io::Result<()> {
|
|
||||||
unsafe {
|
|
||||||
let SymFromAddr = sym!(dbghelp, "SymFromAddr", SymFromAddrFn);
|
|
||||||
let SymGetLineFromAddr64 = sym!(dbghelp,
|
|
||||||
"SymGetLineFromAddr64",
|
|
||||||
SymGetLineFromAddr64Fn);
|
|
||||||
|
|
||||||
let mut info: c::SYMBOL_INFO = mem::zeroed();
|
|
||||||
info.MaxNameLen = c::MAX_SYM_NAME as c_ulong;
|
|
||||||
// the struct size in C. the value is different to
|
|
||||||
// `size_of::<SYMBOL_INFO>() - MAX_SYM_NAME + 1` (== 81)
|
|
||||||
// due to struct alignment.
|
|
||||||
info.SizeOfStruct = 88;
|
|
||||||
|
|
||||||
let mut displacement = 0u64;
|
|
||||||
let ret = SymFromAddr(process, addr, &mut displacement, &mut info);
|
|
||||||
|
|
||||||
let name = if ret == c::TRUE {
|
|
||||||
let ptr = info.Name.as_ptr() as *const c_char;
|
|
||||||
Some(CStr::from_ptr(ptr).to_bytes())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
output(w, i, addr as usize as *mut c_void, name)?;
|
|
||||||
|
|
||||||
// Now find out the filename and line number
|
|
||||||
let mut line: c::IMAGEHLP_LINE64 = mem::zeroed();
|
|
||||||
line.SizeOfStruct = ::mem::size_of::<c::IMAGEHLP_LINE64>() as u32;
|
|
||||||
|
|
||||||
let mut displacement = 0u32;
|
|
||||||
let ret = SymGetLineFromAddr64(process, addr, &mut displacement, &mut line);
|
|
||||||
if ret == c::TRUE {
|
|
||||||
output_fileline(w,
|
|
||||||
CStr::from_ptr(line.Filename).to_bytes(),
|
|
||||||
line.LineNumber as c_int,
|
|
||||||
false)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,14 +10,25 @@
|
||||||
|
|
||||||
#![cfg_attr(target_os = "nacl", allow(dead_code))]
|
#![cfg_attr(target_os = "nacl", allow(dead_code))]
|
||||||
|
|
||||||
|
/// Common code for printing the backtrace in the same way across the different
|
||||||
|
/// supported platforms.
|
||||||
|
|
||||||
use env;
|
use env;
|
||||||
use io::prelude::*;
|
use io::prelude::*;
|
||||||
use io;
|
use io;
|
||||||
use libc;
|
use libc;
|
||||||
use str;
|
use str;
|
||||||
use sync::atomic::{self, Ordering};
|
use sync::atomic::{self, Ordering};
|
||||||
|
use path::Path;
|
||||||
|
use sys::mutex::Mutex;
|
||||||
|
use ptr;
|
||||||
|
|
||||||
pub use sys::backtrace::write;
|
pub use sys::backtrace::{
|
||||||
|
unwind_backtrace,
|
||||||
|
resolve_symname,
|
||||||
|
foreach_symbol_fileline,
|
||||||
|
BacktraceContext
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
pub const HEX_WIDTH: usize = 18;
|
pub const HEX_WIDTH: usize = 18;
|
||||||
|
@ -25,45 +36,217 @@ pub const HEX_WIDTH: usize = 18;
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
pub const HEX_WIDTH: usize = 10;
|
pub const HEX_WIDTH: usize = 10;
|
||||||
|
|
||||||
|
/// Represents an item in the backtrace list. See `unwind_backtrace` for how
|
||||||
|
/// it is created.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Frame {
|
||||||
|
/// Exact address of the call that failed.
|
||||||
|
pub exact_position: *const libc::c_void,
|
||||||
|
/// Address of the enclosing function.
|
||||||
|
pub symbol_addr: *const libc::c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Max number of frames to print.
|
||||||
|
const MAX_NB_FRAMES: usize = 100;
|
||||||
|
|
||||||
|
/// Prints the current backtrace.
|
||||||
|
pub fn print(w: &mut Write, format: PrintFormat) -> io::Result<()> {
|
||||||
|
static LOCK: Mutex = Mutex::new();
|
||||||
|
|
||||||
|
// Use a lock to prevent mixed output in multithreading context.
|
||||||
|
// Some platforms also requires it, like `SymFromAddr` on Windows.
|
||||||
|
unsafe {
|
||||||
|
LOCK.lock();
|
||||||
|
let res = _print(w, format);
|
||||||
|
LOCK.unlock();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _print(w: &mut Write, format: PrintFormat) -> io::Result<()> {
|
||||||
|
let mut frames = [Frame {
|
||||||
|
exact_position: ptr::null(),
|
||||||
|
symbol_addr: ptr::null(),
|
||||||
|
}; MAX_NB_FRAMES];
|
||||||
|
let (nb_frames, context) = unwind_backtrace(&mut frames)?;
|
||||||
|
let (skipped_before, skipped_after) =
|
||||||
|
filter_frames(&frames[..nb_frames], format, &context);
|
||||||
|
if format == PrintFormat::Short {
|
||||||
|
writeln!(w, "note: Some details are omitted, \
|
||||||
|
run with `RUST_BACKTRACE=full` for a verbose backtrace.")?;
|
||||||
|
}
|
||||||
|
writeln!(w, "stack backtrace:")?;
|
||||||
|
|
||||||
|
let filtered_frames = &frames[..nb_frames - skipped_after];
|
||||||
|
for (index, frame) in filtered_frames.iter().skip(skipped_before).enumerate() {
|
||||||
|
resolve_symname(*frame, |symname| {
|
||||||
|
output(w, index, *frame, symname, format)
|
||||||
|
}, &context)?;
|
||||||
|
let has_more_filenames = foreach_symbol_fileline(*frame, |file, line| {
|
||||||
|
output_fileline(w, file, line, format)
|
||||||
|
}, &context)?;
|
||||||
|
if has_more_filenames {
|
||||||
|
w.write_all(b" <... and possibly more>")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_frames(frames: &[Frame],
|
||||||
|
format: PrintFormat,
|
||||||
|
context: &BacktraceContext) -> (usize, usize)
|
||||||
|
{
|
||||||
|
if format == PrintFormat::Full {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut skipped_before = 0;
|
||||||
|
for (i, frame) in frames.iter().enumerate() {
|
||||||
|
skipped_before = i;
|
||||||
|
let mut skip = false;
|
||||||
|
|
||||||
|
let _ = resolve_symname(*frame, |symname| {
|
||||||
|
if let Some(mangled_symbol_name) = symname {
|
||||||
|
let magics_begin = [
|
||||||
|
"_ZN3std3sys3imp9backtrace",
|
||||||
|
"_ZN3std10sys_common9backtrace",
|
||||||
|
"_ZN3std9panicking",
|
||||||
|
"_ZN4core9panicking",
|
||||||
|
"rust_begin_unwind",
|
||||||
|
"_ZN4core6result13unwrap_failed",
|
||||||
|
];
|
||||||
|
if !magics_begin.iter().any(|s| mangled_symbol_name.starts_with(s)) {
|
||||||
|
skip = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}, context);
|
||||||
|
|
||||||
|
if skip {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut skipped_after = 0;
|
||||||
|
for (i, frame) in frames.iter().rev().enumerate() {
|
||||||
|
let _ = resolve_symname(*frame, |symname| {
|
||||||
|
if let Some(mangled_symbol_name) = symname {
|
||||||
|
let magics_end = [
|
||||||
|
"_ZN3std9panicking3try7do_call",
|
||||||
|
"__rust_maybe_catch_panic",
|
||||||
|
"__libc_start_main",
|
||||||
|
"__rust_try",
|
||||||
|
"_start",
|
||||||
|
];
|
||||||
|
if magics_end.iter().any(|s| mangled_symbol_name.starts_with(s)) {
|
||||||
|
skipped_after = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
(skipped_before, skipped_after)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controls how the backtrace should be formated.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum PrintFormat {
|
||||||
|
/// Show all the frames with absolute path for files.
|
||||||
|
Full = 2,
|
||||||
|
/// Show only relevant data from the backtrace.
|
||||||
|
Short = 3,
|
||||||
|
}
|
||||||
|
|
||||||
// For now logging is turned off by default, and this function checks to see
|
// 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.
|
// whether the magical environment variable is present to see if it's turned on.
|
||||||
pub fn log_enabled() -> bool {
|
pub fn log_enabled() -> Option<PrintFormat> {
|
||||||
static ENABLED: atomic::AtomicIsize = atomic::AtomicIsize::new(0);
|
static ENABLED: atomic::AtomicIsize = atomic::AtomicIsize::new(0);
|
||||||
match ENABLED.load(Ordering::SeqCst) {
|
match ENABLED.load(Ordering::SeqCst) {
|
||||||
1 => return false,
|
0 => {},
|
||||||
2 => return true,
|
1 => return None,
|
||||||
_ => {}
|
2 => return Some(PrintFormat::Full),
|
||||||
|
3 => return Some(PrintFormat::Short),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = match env::var_os("RUST_BACKTRACE") {
|
let val = match env::var_os("RUST_BACKTRACE") {
|
||||||
Some(x) => if &x == "0" { 1 } else { 2 },
|
Some(x) => if &x == "0" {
|
||||||
None => 1,
|
None
|
||||||
|
} else if &x == "full" {
|
||||||
|
Some(PrintFormat::Full)
|
||||||
|
} else {
|
||||||
|
Some(PrintFormat::Short)
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
};
|
};
|
||||||
ENABLED.store(val, Ordering::SeqCst);
|
ENABLED.store(match val {
|
||||||
val == 2
|
Some(v) => v as isize,
|
||||||
|
None => 1,
|
||||||
|
}, Ordering::SeqCst);
|
||||||
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
// These output functions should now be used everywhere to ensure consistency.
|
/// Print the symbol of the backtrace frame.
|
||||||
pub fn output(w: &mut Write, idx: isize, addr: *mut libc::c_void,
|
///
|
||||||
s: Option<&[u8]>) -> io::Result<()> {
|
/// These output functions should now be used everywhere to ensure consistency.
|
||||||
write!(w, " {:2}: {:2$?} - ", idx, addr, HEX_WIDTH)?;
|
/// You may want to also use `output_fileline`.
|
||||||
match s.and_then(|s| str::from_utf8(s).ok()) {
|
fn output(w: &mut Write, idx: usize, frame: Frame,
|
||||||
Some(string) => demangle(w, string)?,
|
s: Option<&str>, format: PrintFormat) -> io::Result<()> {
|
||||||
None => write!(w, "<unknown>")?,
|
// Remove the `17: 0x0 - <unknown>` line.
|
||||||
|
if format == PrintFormat::Short && frame.exact_position == ptr::null() {
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
w.write_all(&['\n' as u8])
|
match format {
|
||||||
|
PrintFormat::Full => write!(w,
|
||||||
|
" {:2}: {:2$?} - ",
|
||||||
|
idx,
|
||||||
|
frame.exact_position,
|
||||||
|
HEX_WIDTH)?,
|
||||||
|
PrintFormat::Short => write!(w, " {:2}: ", idx)?,
|
||||||
|
}
|
||||||
|
match s {
|
||||||
|
Some(string) => demangle(w, string, format)?,
|
||||||
|
None => w.write_all(b"<unknown>")?,
|
||||||
|
}
|
||||||
|
w.write_all(b"\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print the filename and line number of the backtrace frame.
|
||||||
|
///
|
||||||
|
/// See also `output`.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
|
fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
|
||||||
more: bool) -> io::Result<()> {
|
format: PrintFormat) -> io::Result<()> {
|
||||||
let file = str::from_utf8(file).unwrap_or("<unknown>");
|
|
||||||
// prior line: " ##: {:2$} - func"
|
// prior line: " ##: {:2$} - func"
|
||||||
write!(w, " {:3$}at {}:{}", "", file, line, HEX_WIDTH)?;
|
w.write_all(b"")?;
|
||||||
if more {
|
match format {
|
||||||
write!(w, " <... and possibly more>")?;
|
PrintFormat::Full => write!(w,
|
||||||
|
" {:1$}",
|
||||||
|
"",
|
||||||
|
HEX_WIDTH)?,
|
||||||
|
PrintFormat::Short => write!(w, " ")?,
|
||||||
}
|
}
|
||||||
w.write_all(&['\n' as u8])
|
|
||||||
|
let file = str::from_utf8(file).unwrap_or("<unknown>");
|
||||||
|
let file_path = Path::new(file);
|
||||||
|
let mut already_printed = false;
|
||||||
|
if format == PrintFormat::Short && file_path.is_absolute() {
|
||||||
|
if let Ok(cwd) = env::current_dir() {
|
||||||
|
if let Ok(stripped) = file_path.strip_prefix(&cwd) {
|
||||||
|
if let Some(s) = stripped.to_str() {
|
||||||
|
write!(w, " at ./{}:{}", s, line)?;
|
||||||
|
already_printed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !already_printed {
|
||||||
|
write!(w, " at {}:{}", file, line)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.write_all(b"\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,7 +267,7 @@ pub fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
|
||||||
// Note that this demangler isn't quite as fancy as it could be. We have lots
|
// Note that this demangler isn't quite as fancy as it could be. We have lots
|
||||||
// of other information in our symbols like hashes, version, type information,
|
// of other information in our symbols like hashes, version, type information,
|
||||||
// etc. Additionally, this doesn't handle glue symbols at all.
|
// etc. Additionally, this doesn't handle glue symbols at all.
|
||||||
pub fn demangle(writer: &mut Write, s: &str) -> io::Result<()> {
|
pub fn demangle(writer: &mut Write, s: &str, format: PrintFormat) -> io::Result<()> {
|
||||||
// First validate the symbol. If it doesn't look like anything we're
|
// First validate the symbol. If it doesn't look like anything we're
|
||||||
// expecting, we just print it literally. Note that we must handle non-rust
|
// expecting, we just print it literally. Note that we must handle non-rust
|
||||||
// symbols because we could have any function in the backtrace.
|
// symbols because we could have any function in the backtrace.
|
||||||
|
@ -123,6 +306,22 @@ pub fn demangle(writer: &mut Write, s: &str) -> io::Result<()> {
|
||||||
if !valid {
|
if !valid {
|
||||||
writer.write_all(s.as_bytes())?;
|
writer.write_all(s.as_bytes())?;
|
||||||
} else {
|
} else {
|
||||||
|
// remove the `::hfc2edb670e5eda97` part at the end of the symbol.
|
||||||
|
if format == PrintFormat::Short {
|
||||||
|
// The symbol in still mangled.
|
||||||
|
let mut split = inner.rsplitn(2, "17h");
|
||||||
|
match (split.next(), split.next()) {
|
||||||
|
(Some(addr), rest) => {
|
||||||
|
if addr.len() == 16 &&
|
||||||
|
addr.chars().all(|c| c.is_digit(16))
|
||||||
|
{
|
||||||
|
inner = rest.unwrap_or("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
while !inner.is_empty() {
|
while !inner.is_empty() {
|
||||||
if !first {
|
if !first {
|
||||||
|
@ -208,7 +407,9 @@ mod tests {
|
||||||
use sys_common;
|
use sys_common;
|
||||||
macro_rules! t { ($a:expr, $b:expr) => ({
|
macro_rules! t { ($a:expr, $b:expr) => ({
|
||||||
let mut m = Vec::new();
|
let mut m = Vec::new();
|
||||||
sys_common::backtrace::demangle(&mut m, $a).unwrap();
|
sys_common::backtrace::demangle(&mut m,
|
||||||
|
$a,
|
||||||
|
super::PrintFormat::Full).unwrap();
|
||||||
assert_eq!(String::from_utf8(m).unwrap(), $b);
|
assert_eq!(String::from_utf8(m).unwrap(), $b);
|
||||||
}) }
|
}) }
|
||||||
|
|
||||||
|
|
|
@ -8,186 +8,204 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use io;
|
|
||||||
use io::prelude::*;
|
|
||||||
use libc;
|
use libc;
|
||||||
use sys_common::backtrace::{output, output_fileline};
|
|
||||||
|
|
||||||
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
|
use ffi::CStr;
|
||||||
symaddr: *mut libc::c_void) -> io::Result<()> {
|
use io;
|
||||||
use ffi::CStr;
|
use mem;
|
||||||
use mem;
|
use ptr;
|
||||||
use ptr;
|
use sys::backtrace::BacktraceContext;
|
||||||
|
use sys_common::backtrace::Frame;
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
// libbacktrace.h API
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
type backtrace_syminfo_callback =
|
|
||||||
extern "C" fn(data: *mut libc::c_void,
|
|
||||||
pc: libc::uintptr_t,
|
|
||||||
symname: *const libc::c_char,
|
|
||||||
symval: libc::uintptr_t,
|
|
||||||
symsize: libc::uintptr_t);
|
|
||||||
type backtrace_full_callback =
|
|
||||||
extern "C" fn(data: *mut libc::c_void,
|
|
||||||
pc: libc::uintptr_t,
|
|
||||||
filename: *const libc::c_char,
|
|
||||||
lineno: libc::c_int,
|
|
||||||
function: *const libc::c_char) -> libc::c_int;
|
|
||||||
type backtrace_error_callback =
|
|
||||||
extern "C" fn(data: *mut libc::c_void,
|
|
||||||
msg: *const libc::c_char,
|
|
||||||
errnum: libc::c_int);
|
|
||||||
enum backtrace_state {}
|
|
||||||
|
|
||||||
extern {
|
|
||||||
fn backtrace_create_state(filename: *const libc::c_char,
|
|
||||||
threaded: libc::c_int,
|
|
||||||
error: backtrace_error_callback,
|
|
||||||
data: *mut libc::c_void)
|
|
||||||
-> *mut backtrace_state;
|
|
||||||
fn backtrace_syminfo(state: *mut backtrace_state,
|
|
||||||
addr: libc::uintptr_t,
|
|
||||||
cb: backtrace_syminfo_callback,
|
|
||||||
error: backtrace_error_callback,
|
|
||||||
data: *mut libc::c_void) -> libc::c_int;
|
|
||||||
fn backtrace_pcinfo(state: *mut backtrace_state,
|
|
||||||
addr: libc::uintptr_t,
|
|
||||||
cb: backtrace_full_callback,
|
|
||||||
error: backtrace_error_callback,
|
|
||||||
data: *mut libc::c_void) -> libc::c_int;
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
// helper callbacks
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
type FileLine = (*const libc::c_char, libc::c_int);
|
|
||||||
|
|
||||||
extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
|
|
||||||
_errnum: libc::c_int) {
|
|
||||||
// do nothing for now
|
|
||||||
}
|
|
||||||
extern fn syminfo_cb(data: *mut libc::c_void,
|
|
||||||
_pc: libc::uintptr_t,
|
|
||||||
symname: *const libc::c_char,
|
|
||||||
_symval: libc::uintptr_t,
|
|
||||||
_symsize: libc::uintptr_t) {
|
|
||||||
let slot = data as *mut *const libc::c_char;
|
|
||||||
unsafe { *slot = symname; }
|
|
||||||
}
|
|
||||||
extern fn pcinfo_cb(data: *mut libc::c_void,
|
|
||||||
_pc: libc::uintptr_t,
|
|
||||||
filename: *const libc::c_char,
|
|
||||||
lineno: libc::c_int,
|
|
||||||
_function: *const libc::c_char) -> libc::c_int {
|
|
||||||
if !filename.is_null() {
|
|
||||||
let slot = data as *mut &mut [FileLine];
|
|
||||||
let buffer = unsafe {ptr::read(slot)};
|
|
||||||
|
|
||||||
// if the buffer is not full, add file:line to the buffer
|
|
||||||
// and adjust the buffer for next possible calls to pcinfo_cb.
|
|
||||||
if !buffer.is_empty() {
|
|
||||||
buffer[0] = (filename, lineno);
|
|
||||||
unsafe { ptr::write(slot, &mut buffer[1..]); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
// The libbacktrace API supports creating a state, but it does not
|
|
||||||
// support destroying a state. I personally take this to mean that a
|
|
||||||
// state is meant to be created and then live forever.
|
|
||||||
//
|
|
||||||
// I would love to register an at_exit() handler which cleans up this
|
|
||||||
// state, but libbacktrace provides no way to do so.
|
|
||||||
//
|
|
||||||
// With these constraints, this function has a statically cached state
|
|
||||||
// that is calculated the first time this is requested. Remember that
|
|
||||||
// backtracing all happens serially (one global lock).
|
|
||||||
//
|
|
||||||
// Things don't work so well on not-Linux since libbacktrace can't track
|
|
||||||
// down that executable this is. We at one point used env::current_exe but
|
|
||||||
// it turns out that there are some serious security issues with that
|
|
||||||
// approach.
|
|
||||||
//
|
|
||||||
// Specifically, on certain platforms like BSDs, a malicious actor can cause
|
|
||||||
// an arbitrary file to be placed at the path returned by current_exe.
|
|
||||||
// libbacktrace does not behave defensively in the presence of ill-formed
|
|
||||||
// DWARF information, and has been demonstrated to segfault in at least one
|
|
||||||
// case. There is no evidence at the moment to suggest that a more carefully
|
|
||||||
// constructed file can't cause arbitrary code execution. As a result of all
|
|
||||||
// of this, we don't hint libbacktrace with the path to the current process.
|
|
||||||
unsafe fn init_state() -> *mut backtrace_state {
|
|
||||||
static mut STATE: *mut backtrace_state = ptr::null_mut();
|
|
||||||
if !STATE.is_null() { return STATE }
|
|
||||||
|
|
||||||
let filename = match ::sys::backtrace::gnu::get_executable_filename() {
|
|
||||||
Ok((filename, file)) => {
|
|
||||||
// filename is purposely leaked here since libbacktrace requires
|
|
||||||
// it to stay allocated permanently, file is also leaked so that
|
|
||||||
// the file stays locked
|
|
||||||
let filename_ptr = filename.as_ptr();
|
|
||||||
mem::forget(filename);
|
|
||||||
mem::forget(file);
|
|
||||||
filename_ptr
|
|
||||||
},
|
|
||||||
Err(_) => ptr::null(),
|
|
||||||
};
|
|
||||||
|
|
||||||
STATE = backtrace_create_state(filename, 0, error_cb,
|
|
||||||
ptr::null_mut());
|
|
||||||
STATE
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
// translation
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// backtrace errors are currently swept under the rug, only I/O
|
|
||||||
// errors are reported
|
|
||||||
let state = unsafe { init_state() };
|
|
||||||
if state.is_null() {
|
|
||||||
return output(w, idx, addr, None)
|
|
||||||
}
|
|
||||||
let mut data = ptr::null();
|
|
||||||
let data_addr = &mut data as *mut *const libc::c_char;
|
|
||||||
let ret = unsafe {
|
|
||||||
backtrace_syminfo(state, symaddr as libc::uintptr_t,
|
|
||||||
syminfo_cb, error_cb,
|
|
||||||
data_addr as *mut libc::c_void)
|
|
||||||
};
|
|
||||||
if ret == 0 || data.is_null() {
|
|
||||||
output(w, idx, addr, None)?;
|
|
||||||
} else {
|
|
||||||
output(w, idx, addr, Some(unsafe { CStr::from_ptr(data).to_bytes() }))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn foreach_symbol_fileline<F>(frame: Frame,
|
||||||
|
mut f: F,
|
||||||
|
_: &BacktraceContext) -> io::Result<bool>
|
||||||
|
where F: FnMut(&[u8], libc::c_int) -> io::Result<()>
|
||||||
|
{
|
||||||
// pcinfo may return an arbitrary number of file:line pairs,
|
// pcinfo may return an arbitrary number of file:line pairs,
|
||||||
// in the order of stack trace (i.e. inlined calls first).
|
// in the order of stack trace (i.e. inlined calls first).
|
||||||
// in order to avoid allocation, we stack-allocate a fixed size of entries.
|
// in order to avoid allocation, we stack-allocate a fixed size of entries.
|
||||||
const FILELINE_SIZE: usize = 32;
|
const FILELINE_SIZE: usize = 32;
|
||||||
let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
|
let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
|
||||||
let ret;
|
let ret;
|
||||||
let fileline_count;
|
let fileline_count = {
|
||||||
{
|
let state = unsafe { init_state() };
|
||||||
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
|
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
|
||||||
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
|
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
|
||||||
ret = unsafe {
|
ret = unsafe {
|
||||||
backtrace_pcinfo(state, addr as libc::uintptr_t,
|
backtrace_pcinfo(state,
|
||||||
pcinfo_cb, error_cb,
|
frame.exact_position as libc::uintptr_t,
|
||||||
|
pcinfo_cb,
|
||||||
|
error_cb,
|
||||||
fileline_addr as *mut libc::c_void)
|
fileline_addr as *mut libc::c_void)
|
||||||
};
|
};
|
||||||
fileline_count = FILELINE_SIZE - fileline_win.len();
|
FILELINE_SIZE - fileline_win.len()
|
||||||
}
|
};
|
||||||
if ret == 0 {
|
if ret == 0 {
|
||||||
for (i, &(file, line)) in fileline_buf[..fileline_count].iter().enumerate() {
|
for &(file, line) in &fileline_buf[..fileline_count] {
|
||||||
if file.is_null() { continue; } // just to be sure
|
if file.is_null() { continue; } // just to be sure
|
||||||
let file = unsafe { CStr::from_ptr(file).to_bytes() };
|
let file = unsafe { CStr::from_ptr(file).to_bytes() };
|
||||||
output_fileline(w, file, line, i == FILELINE_SIZE - 1)?;
|
f(file, line)?;
|
||||||
|
}
|
||||||
|
Ok(fileline_count == FILELINE_SIZE)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a pointer to symbol to its string value.
|
||||||
|
pub fn resolve_symname<F>(frame: Frame,
|
||||||
|
callback: F,
|
||||||
|
_: &BacktraceContext) -> io::Result<()>
|
||||||
|
where F: FnOnce(Option<&str>) -> io::Result<()>
|
||||||
|
{
|
||||||
|
let symname = {
|
||||||
|
let state = unsafe { init_state() };
|
||||||
|
if state.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut data = ptr::null();
|
||||||
|
let data_addr = &mut data as *mut *const libc::c_char;
|
||||||
|
let ret = unsafe {
|
||||||
|
backtrace_syminfo(state,
|
||||||
|
frame.symbol_addr as libc::uintptr_t,
|
||||||
|
syminfo_cb,
|
||||||
|
error_cb,
|
||||||
|
data_addr as *mut libc::c_void)
|
||||||
|
};
|
||||||
|
if ret == 0 || data.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
CStr::from_ptr(data).to_str().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
callback(symname)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// libbacktrace.h API
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
type backtrace_syminfo_callback =
|
||||||
|
extern "C" fn(data: *mut libc::c_void,
|
||||||
|
pc: libc::uintptr_t,
|
||||||
|
symname: *const libc::c_char,
|
||||||
|
symval: libc::uintptr_t,
|
||||||
|
symsize: libc::uintptr_t);
|
||||||
|
type backtrace_full_callback =
|
||||||
|
extern "C" fn(data: *mut libc::c_void,
|
||||||
|
pc: libc::uintptr_t,
|
||||||
|
filename: *const libc::c_char,
|
||||||
|
lineno: libc::c_int,
|
||||||
|
function: *const libc::c_char) -> libc::c_int;
|
||||||
|
type backtrace_error_callback =
|
||||||
|
extern "C" fn(data: *mut libc::c_void,
|
||||||
|
msg: *const libc::c_char,
|
||||||
|
errnum: libc::c_int);
|
||||||
|
enum backtrace_state {}
|
||||||
|
#[link(name = "backtrace", kind = "static")]
|
||||||
|
#[cfg(all(not(test), not(cargobuild)))]
|
||||||
|
extern {}
|
||||||
|
|
||||||
|
extern {
|
||||||
|
fn backtrace_create_state(filename: *const libc::c_char,
|
||||||
|
threaded: libc::c_int,
|
||||||
|
error: backtrace_error_callback,
|
||||||
|
data: *mut libc::c_void)
|
||||||
|
-> *mut backtrace_state;
|
||||||
|
fn backtrace_syminfo(state: *mut backtrace_state,
|
||||||
|
addr: libc::uintptr_t,
|
||||||
|
cb: backtrace_syminfo_callback,
|
||||||
|
error: backtrace_error_callback,
|
||||||
|
data: *mut libc::c_void) -> libc::c_int;
|
||||||
|
fn backtrace_pcinfo(state: *mut backtrace_state,
|
||||||
|
addr: libc::uintptr_t,
|
||||||
|
cb: backtrace_full_callback,
|
||||||
|
error: backtrace_error_callback,
|
||||||
|
data: *mut libc::c_void) -> libc::c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// helper callbacks
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type FileLine = (*const libc::c_char, libc::c_int);
|
||||||
|
|
||||||
|
extern fn error_cb(_data: *mut libc::c_void, _msg: *const libc::c_char,
|
||||||
|
_errnum: libc::c_int) {
|
||||||
|
// do nothing for now
|
||||||
|
}
|
||||||
|
extern fn syminfo_cb(data: *mut libc::c_void,
|
||||||
|
_pc: libc::uintptr_t,
|
||||||
|
symname: *const libc::c_char,
|
||||||
|
_symval: libc::uintptr_t,
|
||||||
|
_symsize: libc::uintptr_t) {
|
||||||
|
let slot = data as *mut *const libc::c_char;
|
||||||
|
unsafe { *slot = symname; }
|
||||||
|
}
|
||||||
|
extern fn pcinfo_cb(data: *mut libc::c_void,
|
||||||
|
_pc: libc::uintptr_t,
|
||||||
|
filename: *const libc::c_char,
|
||||||
|
lineno: libc::c_int,
|
||||||
|
_function: *const libc::c_char) -> libc::c_int {
|
||||||
|
if !filename.is_null() {
|
||||||
|
let slot = data as *mut &mut [FileLine];
|
||||||
|
let buffer = unsafe {ptr::read(slot)};
|
||||||
|
|
||||||
|
// if the buffer is not full, add file:line to the buffer
|
||||||
|
// and adjust the buffer for next possible calls to pcinfo_cb.
|
||||||
|
if !buffer.is_empty() {
|
||||||
|
buffer[0] = (filename, lineno);
|
||||||
|
unsafe { ptr::write(slot, &mut buffer[1..]); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// The libbacktrace API supports creating a state, but it does not
|
||||||
|
// support destroying a state. I personally take this to mean that a
|
||||||
|
// state is meant to be created and then live forever.
|
||||||
|
//
|
||||||
|
// I would love to register an at_exit() handler which cleans up this
|
||||||
|
// state, but libbacktrace provides no way to do so.
|
||||||
|
//
|
||||||
|
// With these constraints, this function has a statically cached state
|
||||||
|
// that is calculated the first time this is requested. Remember that
|
||||||
|
// backtracing all happens serially (one global lock).
|
||||||
|
//
|
||||||
|
// Things don't work so well on not-Linux since libbacktrace can't track
|
||||||
|
// down that executable this is. We at one point used env::current_exe but
|
||||||
|
// it turns out that there are some serious security issues with that
|
||||||
|
// approach.
|
||||||
|
//
|
||||||
|
// Specifically, on certain platforms like BSDs, a malicious actor can cause
|
||||||
|
// an arbitrary file to be placed at the path returned by current_exe.
|
||||||
|
// libbacktrace does not behave defensively in the presence of ill-formed
|
||||||
|
// DWARF information, and has been demonstrated to segfault in at least one
|
||||||
|
// case. There is no evidence at the moment to suggest that a more carefully
|
||||||
|
// constructed file can't cause arbitrary code execution. As a result of all
|
||||||
|
// of this, we don't hint libbacktrace with the path to the current process.
|
||||||
|
unsafe fn init_state() -> *mut backtrace_state {
|
||||||
|
static mut STATE: *mut backtrace_state = ptr::null_mut();
|
||||||
|
if !STATE.is_null() { return STATE }
|
||||||
|
|
||||||
|
let filename = match ::sys::backtrace::gnu::get_executable_filename() {
|
||||||
|
Ok((filename, file)) => {
|
||||||
|
// filename is purposely leaked here since libbacktrace requires
|
||||||
|
// it to stay allocated permanently, file is also leaked so that
|
||||||
|
// the file stays locked
|
||||||
|
let filename_ptr = filename.as_ptr();
|
||||||
|
mem::forget(filename);
|
||||||
|
mem::forget(file);
|
||||||
|
filename_ptr
|
||||||
|
},
|
||||||
|
Err(_) => ptr::null(),
|
||||||
|
};
|
||||||
|
|
||||||
|
STATE = backtrace_create_state(filename, 0, error_cb,
|
||||||
|
ptr::null_mut());
|
||||||
|
STATE
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ macro_rules! cfg_if {
|
||||||
use libc::{c_int, c_void, uintptr_t};
|
use libc::{c_int, c_void, uintptr_t};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum _Unwind_Reason_Code {
|
pub enum _Unwind_Reason_Code {
|
||||||
_URC_NO_REASON = 0,
|
_URC_NO_REASON = 0,
|
||||||
_URC_FOREIGN_EXCEPTION_CAUGHT = 1,
|
_URC_FOREIGN_EXCEPTION_CAUGHT = 1,
|
||||||
|
|
|
@ -141,12 +141,12 @@ fn run_test(me: &str) {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
let mut template = Command::new(me);
|
let mut template = Command::new(me);
|
||||||
template.env("RUST_BACKTRACE", "1");
|
template.env("RUST_BACKTRACE", "full");
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
loop {
|
loop {
|
||||||
let out = Command::new(me)
|
let out = Command::new(me)
|
||||||
.env("RUST_BACKTRACE", "1")
|
.env("RUST_BACKTRACE", "full")
|
||||||
.arg(i.to_string()).output().unwrap();
|
.arg(i.to_string()).output().unwrap();
|
||||||
let output = str::from_utf8(&out.stdout).unwrap();
|
let output = str::from_utf8(&out.stdout).unwrap();
|
||||||
let error = str::from_utf8(&out.stderr).unwrap();
|
let error = str::from_utf8(&out.stderr).unwrap();
|
||||||
|
|
|
@ -47,7 +47,7 @@ fn template(me: &str) -> Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expected(fn_name: &str) -> String {
|
fn expected(fn_name: &str) -> String {
|
||||||
format!(" - backtrace::{}", fn_name)
|
format!(" backtrace::{}", fn_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtest(me: &str) {
|
fn runtest(me: &str) {
|
||||||
|
@ -59,6 +59,53 @@ fn runtest(me: &str) {
|
||||||
assert!(s.contains("stack backtrace") && s.contains(&expected("foo")),
|
assert!(s.contains("stack backtrace") && s.contains(&expected("foo")),
|
||||||
"bad output: {}", s);
|
"bad output: {}", s);
|
||||||
|
|
||||||
|
// Make sure than the short version cleans the backtrace.
|
||||||
|
let p = template(me).arg("fail").env("RUST_BACKTRACE", "1").spawn().unwrap();
|
||||||
|
let out = p.wait_with_output().unwrap();
|
||||||
|
assert!(!out.status.success());
|
||||||
|
let s = str::from_utf8(&out.stderr).unwrap();
|
||||||
|
let removed_symbols = &[
|
||||||
|
"std::sys::imp::backtrace",
|
||||||
|
"std::sys_common::backtrace",
|
||||||
|
"std::panicking",
|
||||||
|
"core::panicking",
|
||||||
|
"rust_begin_unwind",
|
||||||
|
"code::result::unwrap_failed",
|
||||||
|
"std::panicking::try::do_call",
|
||||||
|
"__rust_maybe_catch_panic",
|
||||||
|
"__libc_start_main",
|
||||||
|
"__rust_try",
|
||||||
|
"_start",
|
||||||
|
];
|
||||||
|
for symbol in removed_symbols {
|
||||||
|
assert!(!s.contains(symbol),
|
||||||
|
"{} should be removed from the backtrace",
|
||||||
|
symbol);
|
||||||
|
}
|
||||||
|
assert!(s.contains(" 0:"), "the frame number should start at 0");
|
||||||
|
|
||||||
|
// Only on linux for _start and __libc_start_main
|
||||||
|
#[cfg(target_os="linux")]
|
||||||
|
{
|
||||||
|
// Make sure than the short version cleans the backtrace.
|
||||||
|
let p = template(me).arg("fail").env("RUST_BACKTRACE", "full").spawn().unwrap();
|
||||||
|
let out = p.wait_with_output().unwrap();
|
||||||
|
assert!(!out.status.success());
|
||||||
|
let s = str::from_utf8(&out.stderr).unwrap();
|
||||||
|
let should_be_present = &[
|
||||||
|
"std::panicking",
|
||||||
|
"__rust_maybe_catch_panic",
|
||||||
|
"__libc_start_main",
|
||||||
|
"_start",
|
||||||
|
];
|
||||||
|
for symbol in should_be_present {
|
||||||
|
// May give false positive due to inlining.
|
||||||
|
assert!(s.contains(symbol),
|
||||||
|
"the full version of the backtrace should contain {}",
|
||||||
|
symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the stack trace is *not* printed
|
// Make sure the stack trace is *not* printed
|
||||||
// (Remove RUST_BACKTRACE from our own environment, in case developer
|
// (Remove RUST_BACKTRACE from our own environment, in case developer
|
||||||
// is running `make check` with it on.)
|
// is running `make check` with it on.)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue