```text
$ RUST_BACKTRACE=1 ./diverges
thread 'main' panicked at 'This function never returns!', hello.rs:2
+Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
+stack backtrace:
+ hello::diverges
+ at ./hello.rs:2
+ hello::main
+ at ./hello.rs:6
+```
+
+If you want the complete backtrace and filenames:
+
+```text
+$ RUST_BACKTRACE=full ./diverges
+thread 'main' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r
2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w
`RUST_BACKTRACE` also works with Cargo’s `run` command:
```text
-$ RUST_BACKTRACE=1 cargo run
+$ RUST_BACKTRACE=full cargo run
Running `target/debug/diverges`
thread 'main' panicked at 'This function never returns!', hello.rs:2
stack backtrace:
let log_backtrace = {
let panics = update_panic_count(0);
- panics >= 2 || backtrace::log_enabled()
+ if panics >= 2 {
+ Some(backtrace::PrintFormat::Full)
+ } else {
+ backtrace::log_enabled()
+ }
};
let file = info.location.file;
static FIRST_PANIC: AtomicBool = AtomicBool::new(true);
- if log_backtrace {
- let _ = backtrace::write(err);
+ if let Some(format) = log_backtrace {
+ let _ = backtrace::print(err, format);
} else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) {
let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace.");
}
use libc;
use io;
-use sys_common::backtrace::output;
+use sys_common::backtrace::Frame;
+
+pub use sys_common::gnu::libbacktrace::*;
+pub struct BacktraceContext;
#[inline(never)]
-pub fn write(w: &mut io::Write) -> io::Result<()> {
- output(w, 0, 0 as *mut libc::c_void, None)
+pub fn unwind_backtrace(frames: &mut [Frame])
+ -> io::Result<(usize, BacktraceContext)>
+{
+ Ok((0, BacktraceContext))
}
/// to symbols. This is a bit of a hokey implementation as-is, but it works for
/// all unix platforms we support right now, so it at least gets the job done.
-pub use self::tracing::write;
+pub use self::tracing::unwind_backtrace;
+pub use self::printing::{foreach_symbol_fileline, resolve_symname};
// tracing impls:
mod tracing;
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
}
}
+
+pub struct BacktraceContext;
// except according to those terms.
use io;
-use io::prelude::*;
+use intrinsics;
+use ffi::CStr;
use libc;
+use sys::backtrace::BacktraceContext;
+use sys_common::backtrace::Frame;
-pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
- _symaddr: *mut libc::c_void) -> io::Result<()> {
- use sys_common::backtrace::{output};
- use intrinsics;
- use ffi::CStr;
-
- #[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;
+pub fn resolve_symname<F>(frame: Frame,
+ callback: F,
+ _: &BacktraceContext) -> io::Result<()>
+ where F: FnOnce(Option<&str>) -> io::Result<()>
+{
+ unsafe {
+ let mut info: Dl_info = intrinsics::init();
+ let symname = if dladdr(frame.exact_position, &mut info) == 0 {
+ None
+ } else {
+ CStr::from_ptr(info.dli_sname).to_str().ok()
+ };
+ callback(symname)
}
+}
- 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;
}
+++ /dev/null
-// 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.
-
-pub use sys_common::gnu::libbacktrace::print;
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-pub use self::imp::print;
+pub use self::imp::{foreach_symbol_fileline, resolve_symname};
#[cfg(any(target_os = "macos", target_os = "ios",
target_os = "emscripten"))]
#[cfg(not(any(target_os = "macos", target_os = "ios",
target_os = "emscripten")))]
-#[path = "gnu.rs"]
-mod imp;
+mod imp {
+ pub use sys_common::gnu::libbacktrace::{foreach_symbol_fileline, resolve_symname};
+}
/// simple to use it should be used only on iOS devices as the only viable
/// option.
-use io::prelude::*;
use io;
use libc;
use mem;
-use sys::mutex::Mutex;
+use sys::backtrace::BacktraceContext;
+use sys_common::backtrace::Frame;
-use super::super::printing::print;
-
-#[inline(never)]
-pub fn write(w: &mut Write) -> io::Result<()> {
- extern {
- fn backtrace(buf: *mut *mut libc::c_void,
- sz: libc::c_int) -> libc::c_int;
+#[inline(never)] // if we know this is a function call, we can skip it when
+ // tracing
+pub fn unwind_backtrace(frames: &mut [Frame])
+ -> io::Result<(usize, BacktraceContext)>
+{
+ const FRAME_LEN: usize = 100;
+ 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
- static LOCK: Mutex = Mutex::new();
- unsafe {
- LOCK.lock();
-
- writeln!(w, "stack backtrace:")?;
- // 100 lines should be enough
- const SIZE: usize = 100;
- let mut buf: [*mut libc::c_void; SIZE] = mem::zeroed();
- let cnt = backtrace(buf.as_mut_ptr(), SIZE as libc::c_int) as usize;
-
- // skipping the first one as it is write itself
- for i in 1..cnt {
- print(w, i as isize, buf[i], buf[i])?
- }
- LOCK.unlock();
- }
- Ok(())
+extern {
+ fn backtrace(buf: *mut *mut libc::c_void, sz: libc::c_int) -> libc::c_int;
}
// option. This file may not be copied, modified, or distributed
// except according to those terms.
+use error::Error;
use io;
-use io::prelude::*;
use libc;
-use mem;
-use sys_common::mutex::Mutex;
+use sys::backtrace::BacktraceContext;
+use sys_common::backtrace::Frame;
-use super::super::printing::print;
use unwind as uw;
-#[inline(never)] // if we know this is a function call, we can skip it when
- // tracing
-pub fn write(w: &mut Write) -> io::Result<()> {
- struct Context<'a> {
- idx: isize,
- writer: &'a mut (Write+'a),
- last_error: Option<io::Error>,
- }
+struct Context<'a> {
+ idx: usize,
+ frames: &'a mut [Frame],
+}
- // When using libbacktrace, we use some necessary global state, so we
- // need to prevent more than one thread from entering this block. This
- // is semi-reasonable in terms of printing anyway, and we know that all
- // I/O done here is blocking I/O, not green I/O, so we don't have to
- // worry about this being a native vs green mutex.
- static LOCK: Mutex = Mutex::new();
- unsafe {
- LOCK.lock();
+#[derive(Debug)]
+struct UnwindError(uw::_Unwind_Reason_Code);
- writeln!(w, "stack backtrace:")?;
+impl Error for UnwindError {
+ fn description(&self) -> &'static str {
+ "unexpected return value while unwinding"
+ }
+}
- let mut cx = Context { writer: w, last_error: None, idx: 0 };
- let ret = match {
- uw::_Unwind_Backtrace(trace_fn,
- &mut cx as *mut Context as *mut libc::c_void)
- } {
- uw::_URC_NO_REASON => {
- match cx.last_error {
- Some(err) => Err(err),
- None => Ok(())
- }
- }
- _ => Ok(()),
- };
- LOCK.unlock();
- return ret
+impl ::fmt::Display for UnwindError {
+ fn fmt(&self, f: &mut ::fmt::Formatter) -> ::fmt::Result {
+ write!(f, "{}: {:?}", self.description(), self.0)
}
+}
- extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
- arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
- let cx: &mut Context = unsafe { mem::transmute(arg) };
- let mut ip_before_insn = 0;
- let mut ip = unsafe {
- uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
- };
- if !ip.is_null() && ip_before_insn == 0 {
- // this is a non-signaling frame, so `ip` refers to the address
- // after the calling instruction. account for that.
- ip = (ip as usize - 1) as *mut _;
+#[inline(never)] // if we know this is a function call, we can skip it when
+ // tracing
+pub fn unwind_backtrace(frames: &mut [Frame])
+ -> io::Result<(usize, BacktraceContext)>
+{
+ let mut cx = Context {
+ idx: 0,
+ frames: frames,
+ };
+ let result_unwind = unsafe {
+ uw::_Unwind_Backtrace(trace_fn,
+ &mut cx as *mut Context
+ as *mut libc::c_void)
+ };
+ // See libunwind:src/unwind/Backtrace.c for the return values.
+ // No, there is no doc.
+ match result_unwind {
+ uw::_URC_END_OF_STACK | uw::_URC_FATAL_PHASE1_ERROR => {
+ Ok((cx.idx, BacktraceContext))
}
-
- // dladdr() on osx gets whiny when we use FindEnclosingFunction, and
- // it appears to work fine without it, so we only use
- // FindEnclosingFunction on non-osx platforms. In doing so, we get a
- // slightly more accurate stack trace in the process.
- //
- // This is often because panic involves the last instruction of a
- // function being "call std::rt::begin_unwind", with no ret
- // instructions after it. This means that the return instruction
- // pointer points *outside* of the calling function, and by
- // unwinding it we go back to the original function.
- let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
- ip
- } else {
- unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
- };
-
- // Don't print out the first few frames (they're not user frames)
- cx.idx += 1;
- if cx.idx <= 0 { return uw::_URC_NO_REASON }
- // Don't print ginormous backtraces
- if cx.idx > 100 {
- match write!(cx.writer, " ... <frames omitted>\n") {
- Ok(()) => {}
- Err(e) => { cx.last_error = Some(e); }
- }
- return uw::_URC_FAILURE
+ _ => {
+ Err(io::Error::new(io::ErrorKind::Other,
+ UnwindError(result_unwind)))
}
+ }
+}
- // Once we hit an error, stop trying to print more frames
- if cx.last_error.is_some() { return uw::_URC_FAILURE }
+extern fn trace_fn(ctx: *mut uw::_Unwind_Context,
+ arg: *mut libc::c_void) -> uw::_Unwind_Reason_Code {
+ let cx = unsafe { &mut *(arg as *mut Context) };
+ let mut ip_before_insn = 0;
+ let mut ip = unsafe {
+ uw::_Unwind_GetIPInfo(ctx, &mut ip_before_insn) as *mut libc::c_void
+ };
+ if !ip.is_null() && ip_before_insn == 0 {
+ // this is a non-signaling frame, so `ip` refers to the address
+ // after the calling instruction. account for that.
+ ip = (ip as usize - 1) as *mut _;
+ }
- match print(cx.writer, cx.idx, ip, symaddr) {
- Ok(()) => {}
- Err(e) => { cx.last_error = Some(e); }
- }
+ // dladdr() on osx gets whiny when we use FindEnclosingFunction, and
+ // it appears to work fine without it, so we only use
+ // FindEnclosingFunction on non-osx platforms. In doing so, we get a
+ // slightly more accurate stack trace in the process.
+ //
+ // This is often because panic involves the last instruction of a
+ // function being "call std::rt::begin_unwind", with no ret
+ // instructions after it. This means that the return instruction
+ // pointer points *outside* of the calling function, and by
+ // unwinding it we go back to the original function.
+ let symaddr = if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
+ ip
+ } else {
+ unsafe { uw::_Unwind_FindEnclosingFunction(ip) }
+ };
- // keep going
- uw::_URC_NO_REASON
+ if cx.idx < cx.frames.len() {
+ cx.frames[cx.idx] = Frame {
+ symbol_addr: symaddr,
+ exact_position: ip,
+ };
+ cx.idx += 1;
}
+
+ uw::_URC_NO_REASON
}
+++ /dev/null
-// 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.
-
-//! As always, windows has something very different than unix, we mainly want
-//! to avoid having to depend too much on libunwind for windows.
-//!
-//! If you google around, you'll find a fair bit of references to built-in
-//! functions to get backtraces on windows. It turns out that most of these are
-//! in an external library called dbghelp. I was unable to find this library
-//! via `-ldbghelp`, but it is apparently normal to do the `dlopen` equivalent
-//! of it.
-//!
-//! You'll also find that there's a function called CaptureStackBackTrace
-//! mentioned frequently (which is also easy to use), but sadly I didn't have a
-//! copy of that function in my mingw install (maybe it was broken?). Instead,
-//! this takes the route of using StackWalk64 in order to walk the stack.
-
-#![allow(deprecated)] // dynamic_lib
-
-use io::prelude::*;
-
-use io;
-use libc::c_void;
-use mem;
-use ptr;
-use sys::c;
-use sys::dynamic_lib::DynamicLibrary;
-use sys::mutex::Mutex;
-
-macro_rules! sym {
- ($lib:expr, $e:expr, $t:ident) => (
- match $lib.symbol($e) {
- Ok(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;
-
-#[cfg(target_env = "gnu")]
-#[path = "backtrace_gnu.rs"]
-pub mod gnu;
-
-type SymInitializeFn =
- unsafe extern "system" fn(c::HANDLE, *mut c_void,
- c::BOOL) -> c::BOOL;
-type SymCleanupFn =
- unsafe extern "system" fn(c::HANDLE) -> c::BOOL;
-
-type StackWalk64Fn =
- unsafe extern "system" fn(c::DWORD, c::HANDLE, c::HANDLE,
- *mut c::STACKFRAME64, *mut c::CONTEXT,
- *mut c_void, *mut c_void,
- *mut c_void, *mut c_void) -> c::BOOL;
-
-#[cfg(target_arch = "x86")]
-pub fn init_frame(frame: &mut c::STACKFRAME64,
- ctx: &c::CONTEXT) -> c::DWORD {
- frame.AddrPC.Offset = ctx.Eip as u64;
- frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
- frame.AddrStack.Offset = ctx.Esp as u64;
- frame.AddrStack.Mode = c::ADDRESS_MODE::AddrModeFlat;
- frame.AddrFrame.Offset = ctx.Ebp as u64;
- frame.AddrFrame.Mode = c::ADDRESS_MODE::AddrModeFlat;
- c::IMAGE_FILE_MACHINE_I386
-}
-
-#[cfg(target_arch = "x86_64")]
-pub fn init_frame(frame: &mut c::STACKFRAME64,
- ctx: &c::CONTEXT) -> c::DWORD {
- frame.AddrPC.Offset = ctx.Rip as u64;
- frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
- frame.AddrStack.Offset = ctx.Rsp as u64;
- frame.AddrStack.Mode = c::ADDRESS_MODE::AddrModeFlat;
- frame.AddrFrame.Offset = ctx.Rbp as u64;
- frame.AddrFrame.Mode = c::ADDRESS_MODE::AddrModeFlat;
- c::IMAGE_FILE_MACHINE_AMD64
-}
-
-struct Cleanup {
- handle: c::HANDLE,
- SymCleanup: SymCleanupFn,
-}
-
-impl Drop for Cleanup {
- fn drop(&mut self) {
- 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(())
-}
--- /dev/null
+// 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;
+use sys::c;
+use libc::c_char;
+use path::PathBuf;
+use fs::{OpenOptions, File};
+use sys::ext::fs::OpenOptionsExt;
+use sys::handle::Handle;
+use super::super::{fill_utf16_buf, os2path, to_u16s, wide_char_to_multi_byte};
+
+fn query_full_process_image_name() -> io::Result<PathBuf> {
+ unsafe {
+ let process_handle = Handle::new(c::OpenProcess(c::PROCESS_QUERY_INFORMATION,
+ c::FALSE,
+ c::GetCurrentProcessId()));
+ fill_utf16_buf(|buf, mut sz| {
+ if c::QueryFullProcessImageNameW(process_handle.raw(), 0, buf, &mut sz) == 0 {
+ 0
+ } else {
+ sz
+ }
+ }, os2path)
+ }
+}
+
+fn lock_and_get_executable_filename() -> io::Result<(PathBuf, File)> {
+ // We query the current image name, open the file without FILE_SHARE_DELETE so it
+ // can't be moved and then get the current image name again. If the names are the
+ // same than we have successfully locked the file
+ let image_name1 = query_full_process_image_name()?;
+ let file = OpenOptions::new()
+ .read(true)
+ .share_mode(c::FILE_SHARE_READ | c::FILE_SHARE_WRITE)
+ .open(&image_name1)?;
+ let image_name2 = query_full_process_image_name()?;
+
+ if image_name1 != image_name2 {
+ return Err(io::Error::new(io::ErrorKind::Other,
+ "executable moved while trying to lock it"));
+ }
+
+ Ok((image_name1, file))
+}
+
+// Get the executable filename for libbacktrace
+// This returns the path in the ANSI code page and a File which should remain open
+// for as long as the path should remain valid
+pub fn get_executable_filename() -> io::Result<(Vec<c_char>, File)> {
+ let (executable, file) = lock_and_get_executable_filename()?;
+ let u16_executable = to_u16s(executable.into_os_string())?;
+ Ok((wide_char_to_multi_byte(c::CP_ACP, c::WC_NO_BEST_FIT_CHARS,
+ &u16_executable, true)?, file))
+}
--- /dev/null
+// 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.
+
+//! As always, windows has something very different than unix, we mainly want
+//! to avoid having to depend too much on libunwind for windows.
+//!
+//! If you google around, you'll find a fair bit of references to built-in
+//! functions to get backtraces on windows. It turns out that most of these are
+//! in an external library called dbghelp. I was unable to find this library
+//! via `-ldbghelp`, but it is apparently normal to do the `dlopen` equivalent
+//! of it.
+//!
+//! You'll also find that there's a function called CaptureStackBackTrace
+//! mentioned frequently (which is also easy to use), but sadly I didn't have a
+//! copy of that function in my mingw install (maybe it was broken?). Instead,
+//! this takes the route of using StackWalk64 in order to walk the stack.
+
+#![allow(deprecated)] // dynamic_lib
+
+use io;
+use libc::c_void;
+use mem;
+use ptr;
+use sys::c;
+use sys::dynamic_lib::DynamicLibrary;
+use sys_common::backtrace::Frame;
+
+macro_rules! sym {
+ ($lib:expr, $e:expr, $t:ident) => (
+ $lib.symbol($e).map(|f| unsafe {
+ $crate::mem::transmute::<usize, $t>(f)
+ })
+ )
+}
+
+mod printing;
+
+#[cfg(target_env = "gnu")]
+#[path = "backtrace_gnu.rs"]
+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 =
+ unsafe extern "system" fn(c::HANDLE, *mut c_void,
+ c::BOOL) -> c::BOOL;
+type SymCleanupFn =
+ unsafe extern "system" fn(c::HANDLE) -> c::BOOL;
+
+type StackWalk64Fn =
+ unsafe extern "system" fn(c::DWORD, c::HANDLE, c::HANDLE,
+ *mut c::STACKFRAME64, *mut c::CONTEXT,
+ *mut c_void, *mut c_void,
+ *mut c_void, *mut c_void) -> c::BOOL;
+
+#[cfg(target_arch = "x86")]
+fn init_frame(frame: &mut c::STACKFRAME64,
+ ctx: &c::CONTEXT) -> c::DWORD {
+ frame.AddrPC.Offset = ctx.Eip as u64;
+ frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
+ frame.AddrStack.Offset = ctx.Esp as u64;
+ frame.AddrStack.Mode = c::ADDRESS_MODE::AddrModeFlat;
+ frame.AddrFrame.Offset = ctx.Ebp as u64;
+ frame.AddrFrame.Mode = c::ADDRESS_MODE::AddrModeFlat;
+ c::IMAGE_FILE_MACHINE_I386
+}
+
+#[cfg(target_arch = "x86_64")]
+fn init_frame(frame: &mut c::STACKFRAME64,
+ ctx: &c::CONTEXT) -> c::DWORD {
+ frame.AddrPC.Offset = ctx.Rip as u64;
+ frame.AddrPC.Mode = c::ADDRESS_MODE::AddrModeFlat;
+ frame.AddrStack.Offset = ctx.Rsp as u64;
+ frame.AddrStack.Mode = c::ADDRESS_MODE::AddrModeFlat;
+ frame.AddrFrame.Offset = ctx.Rbp as u64;
+ frame.AddrFrame.Mode = c::ADDRESS_MODE::AddrModeFlat;
+ c::IMAGE_FILE_MACHINE_AMD64
+}
+
+pub struct BacktraceContext {
+ handle: c::HANDLE,
+ SymCleanup: SymCleanupFn,
+ // Only used in printing for msvc and not gnu
+ #[allow(dead_code)]
+ dbghelp: DynamicLibrary,
+}
+
+impl Drop for BacktraceContext {
+ fn drop(&mut self) {
+ unsafe { (self.SymCleanup)(self.handle); }
+ }
+}
--- /dev/null
+// 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.
+
+#[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};
--- /dev/null
+// 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)
+ }
+}
+++ /dev/null
-// 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;
-use sys::c;
-use libc::c_char;
-use path::PathBuf;
-use fs::{OpenOptions, File};
-use sys::ext::fs::OpenOptionsExt;
-use sys::handle::Handle;
-use super::super::{fill_utf16_buf, os2path, to_u16s, wide_char_to_multi_byte};
-
-fn query_full_process_image_name() -> io::Result<PathBuf> {
- unsafe {
- let process_handle = Handle::new(c::OpenProcess(c::PROCESS_QUERY_INFORMATION,
- c::FALSE,
- c::GetCurrentProcessId()));
- fill_utf16_buf(|buf, mut sz| {
- if c::QueryFullProcessImageNameW(process_handle.raw(), 0, buf, &mut sz) == 0 {
- 0
- } else {
- sz
- }
- }, os2path)
- }
-}
-
-fn lock_and_get_executable_filename() -> io::Result<(PathBuf, File)> {
- // We query the current image name, open the file without FILE_SHARE_DELETE so it
- // can't be moved and then get the current image name again. If the names are the
- // same than we have successfully locked the file
- let image_name1 = query_full_process_image_name()?;
- let file = OpenOptions::new()
- .read(true)
- .share_mode(c::FILE_SHARE_READ | c::FILE_SHARE_WRITE)
- .open(&image_name1)?;
- let image_name2 = query_full_process_image_name()?;
-
- if image_name1 != image_name2 {
- return Err(io::Error::new(io::ErrorKind::Other,
- "executable moved while trying to lock it"));
- }
-
- Ok((image_name1, file))
-}
-
-// Get the executable filename for libbacktrace
-// This returns the path in the ANSI code page and a File which should remain open
-// for as long as the path should remain valid
-pub fn get_executable_filename() -> io::Result<(Vec<c_char>, File)> {
- let (executable, file) = lock_and_get_executable_filename()?;
- let u16_executable = to_u16s(executable.into_os_string())?;
- Ok((wide_char_to_multi_byte(c::CP_ACP, c::WC_NO_BEST_FIT_CHARS,
- &u16_executable, true)?, file))
-}
+++ /dev/null
-// 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)
-}
+++ /dev/null
-// 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(())
- }
- }
-}
#![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 io::prelude::*;
use io;
use libc;
use str;
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")]
pub const HEX_WIDTH: usize = 18;
#[cfg(target_pointer_width = "32")]
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
// 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);
match ENABLED.load(Ordering::SeqCst) {
- 1 => return false,
- 2 => return true,
- _ => {}
+ 0 => {},
+ 1 => return None,
+ 2 => return Some(PrintFormat::Full),
+ 3 => return Some(PrintFormat::Short),
+ _ => unreachable!(),
}
let val = match env::var_os("RUST_BACKTRACE") {
- Some(x) => if &x == "0" { 1 } else { 2 },
- None => 1,
+ Some(x) => if &x == "0" {
+ None
+ } else if &x == "full" {
+ Some(PrintFormat::Full)
+ } else {
+ Some(PrintFormat::Short)
+ },
+ None => None,
};
- ENABLED.store(val, Ordering::SeqCst);
- val == 2
+ ENABLED.store(match val {
+ Some(v) => v as isize,
+ None => 1,
+ }, Ordering::SeqCst);
+ val
}
-// These output functions should now be used everywhere to ensure consistency.
-pub fn output(w: &mut Write, idx: isize, addr: *mut libc::c_void,
- s: Option<&[u8]>) -> io::Result<()> {
- write!(w, " {:2}: {:2$?} - ", idx, addr, HEX_WIDTH)?;
- match s.and_then(|s| str::from_utf8(s).ok()) {
- Some(string) => demangle(w, string)?,
- None => write!(w, "<unknown>")?,
+/// Print the symbol of the backtrace frame.
+///
+/// These output functions should now be used everywhere to ensure consistency.
+/// You may want to also use `output_fileline`.
+fn output(w: &mut Write, idx: usize, frame: Frame,
+ s: Option<&str>, format: PrintFormat) -> io::Result<()> {
+ // Remove the `17: 0x0 - <unknown>` line.
+ if format == PrintFormat::Short && frame.exact_position == ptr::null() {
+ return Ok(());
+ }
+ match format {
+ PrintFormat::Full => write!(w,
+ " {:2}: {:2$?} - ",
+ idx,
+ frame.exact_position,
+ HEX_WIDTH)?,
+ PrintFormat::Short => write!(w, " {:2}: ", idx)?,
}
- w.write_all(&['\n' as u8])
+ 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)]
-pub fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
- more: bool) -> io::Result<()> {
- let file = str::from_utf8(file).unwrap_or("<unknown>");
+fn output_fileline(w: &mut Write, file: &[u8], line: libc::c_int,
+ format: PrintFormat) -> io::Result<()> {
// prior line: " ##: {:2$} - func"
- write!(w, " {:3$}at {}:{}", "", file, line, HEX_WIDTH)?;
- if more {
- write!(w, " <... and possibly more>")?;
+ w.write_all(b"")?;
+ match format {
+ 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")
}
// 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,
// 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
// expecting, we just print it literally. Note that we must handle non-rust
// symbols because we could have any function in the backtrace.
if !valid {
writer.write_all(s.as_bytes())?;
} 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;
while !inner.is_empty() {
if !first {
use sys_common;
macro_rules! t { ($a:expr, $b:expr) => ({
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);
}) }
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-use io;
-use io::prelude::*;
use libc;
-use sys_common::backtrace::{output, output_fileline};
-
-pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
- symaddr: *mut libc::c_void) -> io::Result<()> {
- use ffi::CStr;
- use mem;
- use ptr;
-
- ////////////////////////////////////////////////////////////////////////
- // 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() }))?;
- }
+use ffi::CStr;
+use io;
+use mem;
+use ptr;
+use sys::backtrace::BacktraceContext;
+use sys_common::backtrace::Frame;
+
+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,
// in the order of stack trace (i.e. inlined calls first).
// in order to avoid allocation, we stack-allocate a fixed size of entries.
const FILELINE_SIZE: usize = 32;
let mut fileline_buf = [(ptr::null(), -1); FILELINE_SIZE];
let ret;
- let fileline_count;
- {
+ let fileline_count = {
+ let state = unsafe { init_state() };
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
ret = unsafe {
- backtrace_pcinfo(state, addr as libc::uintptr_t,
- pcinfo_cb, error_cb,
+ backtrace_pcinfo(state,
+ frame.exact_position as libc::uintptr_t,
+ pcinfo_cb,
+ error_cb,
fileline_addr as *mut libc::c_void)
};
- fileline_count = FILELINE_SIZE - fileline_win.len();
- }
+ FILELINE_SIZE - fileline_win.len()
+ };
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
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
}
use libc::{c_int, c_void, uintptr_t};
#[repr(C)]
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Debug, Copy, Clone, PartialEq)]
pub enum _Unwind_Reason_Code {
_URC_NO_REASON = 0,
_URC_FOREIGN_EXCEPTION_CAUGHT = 1,
use std::process::Command;
let mut template = Command::new(me);
- template.env("RUST_BACKTRACE", "1");
+ template.env("RUST_BACKTRACE", "full");
let mut i = 0;
loop {
let out = Command::new(me)
- .env("RUST_BACKTRACE", "1")
+ .env("RUST_BACKTRACE", "full")
.arg(i.to_string()).output().unwrap();
let output = str::from_utf8(&out.stdout).unwrap();
let error = str::from_utf8(&out.stderr).unwrap();
}
fn expected(fn_name: &str) -> String {
- format!(" - backtrace::{}", fn_name)
+ format!(" backtrace::{}", fn_name)
}
fn runtest(me: &str) {
assert!(s.contains("stack backtrace") && s.contains(&expected("foo")),
"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
// (Remove RUST_BACKTRACE from our own environment, in case developer
// is running `make check` with it on.)