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:
Yamakaky 2016-12-04 16:38:27 -05:00
parent e0044bd389
commit d50e4cc064
No known key found for this signature in database
GPG key ID: 1F5120C66C0B64F7
19 changed files with 804 additions and 526 deletions

View file

@ -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 Cargos `run` command: `RUST_BACKTRACE` also works with Cargos `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:

View file

@ -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.");
} }

View file

@ -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))
} }

View file

@ -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 fn get_executable_filename() -> io::Result<(Vec<c_char>, fs::File)> {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
} }
} }
pub struct BacktraceContext;

View file

@ -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;
}

View file

@ -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 @@
#[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};
}

View file

@ -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(())
} }

View file

@ -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
}

View file

@ -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 @@ macro_rules! sym {
*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(())
}

View file

@ -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};

View 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)
}
}

View file

@ -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)
}

View file

@ -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(())
}
}
}

View file

@ -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 @@
#[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);
}) } }) }

View file

@ -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
} }

View file

@ -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,

View file

@ -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();

View file

@ -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.)