From d50e4cc0640e54a64d0f7ccb05a77fd4a2fe0741 Mon Sep 17 00:00:00 2001 From: Yamakaky Date: Sun, 4 Dec 2016 16:38:27 -0500 Subject: [PATCH] Improve backtrace formating while panicking. - `RUST_BACKTRACE=full` prints all the informations (old behaviour) - `RUST_BACKTRACE=(0|no)` disables the backtrace. - `RUST_BACKTRACE=` (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. --- src/doc/book/src/functions.md | 15 +- src/libstd/panicking.rs | 10 +- src/libstd/sys/redox/backtrace.rs | 11 +- src/libstd/sys/unix/backtrace/mod.rs | 5 +- .../sys/unix/backtrace/printing/dladdr.rs | 64 ++-- src/libstd/sys/unix/backtrace/printing/mod.rs | 7 +- .../unix/backtrace/tracing/backtrace_fn.rs | 53 ++- .../sys/unix/backtrace/tracing/gcc_s.rs | 171 +++++---- .../windows/{ => backtrace}/backtrace_gnu.rs | 0 .../{backtrace.rs => backtrace/mod.rs} | 151 ++++---- .../backtrace/printing/mod.rs} | 11 +- .../sys/windows/backtrace/printing/msvc.rs | 83 +++++ src/libstd/sys/windows/printing/gnu.rs | 26 -- src/libstd/sys/windows/printing/msvc.rs | 73 ---- src/libstd/sys_common/backtrace.rs | 253 +++++++++++-- src/libstd/sys_common/gnu/libbacktrace.rs | 342 +++++++++--------- src/libunwind/libunwind.rs | 2 +- src/test/run-pass/backtrace-debuginfo.rs | 4 +- src/test/run-pass/backtrace.rs | 49 ++- 19 files changed, 804 insertions(+), 526 deletions(-) rename src/libstd/sys/windows/{ => backtrace}/backtrace_gnu.rs (100%) rename src/libstd/sys/windows/{backtrace.rs => backtrace/mod.rs} (63%) rename src/libstd/sys/{unix/backtrace/printing/gnu.rs => windows/backtrace/printing/mod.rs} (63%) create mode 100644 src/libstd/sys/windows/backtrace/printing/msvc.rs delete mode 100644 src/libstd/sys/windows/printing/gnu.rs delete mode 100644 src/libstd/sys/windows/printing/msvc.rs diff --git a/src/doc/book/src/functions.md b/src/doc/book/src/functions.md index eff77a54d83..96c8e9f5d68 100644 --- a/src/doc/book/src/functions.md +++ b/src/doc/book/src/functions.md @@ -230,6 +230,19 @@ If you want more information, you can get a backtrace by setting the ```text $ RUST_BACKTRACE=1 ./diverges thread 'main' panicked at 'This function never returns!', hello.rs:2 +Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +stack backtrace: + hello::diverges + at ./hello.rs:2 + hello::main + at ./hello.rs:6 +``` + +If you want the complete backtrace and filenames: + +```text +$ RUST_BACKTRACE=full ./diverges +thread 'main' panicked at 'This function never returns!', hello.rs:2 stack backtrace: 1: 0x7f402773a829 - sys::backtrace::write::h0942de78b6c02817K8r 2: 0x7f402773d7fc - panicking::on_panic::h3f23f9d0b5f4c91bu9w @@ -262,7 +275,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace. `RUST_BACKTRACE` also works with Cargo’s `run` command: ```text -$ RUST_BACKTRACE=1 cargo run +$ RUST_BACKTRACE=full cargo run Running `target/debug/diverges` thread 'main' panicked at 'This function never returns!', hello.rs:2 stack backtrace: diff --git a/src/libstd/panicking.rs b/src/libstd/panicking.rs index 3fba49345e6..3b5a1cffc7a 100644 --- a/src/libstd/panicking.rs +++ b/src/libstd/panicking.rs @@ -320,7 +320,11 @@ fn default_hook(info: &PanicInfo) { let log_backtrace = { let panics = update_panic_count(0); - panics >= 2 || backtrace::log_enabled() + if panics >= 2 { + Some(backtrace::PrintFormat::Full) + } else { + backtrace::log_enabled() + } }; let file = info.location.file; @@ -347,8 +351,8 @@ fn default_hook(info: &PanicInfo) { static FIRST_PANIC: AtomicBool = AtomicBool::new(true); - if log_backtrace { - let _ = backtrace::write(err); + if let Some(format) = log_backtrace { + let _ = backtrace::print(err, format); } else if FIRST_PANIC.compare_and_swap(true, false, Ordering::SeqCst) { let _ = writeln!(err, "note: Run with `RUST_BACKTRACE=1` for a backtrace."); } diff --git a/src/libstd/sys/redox/backtrace.rs b/src/libstd/sys/redox/backtrace.rs index 6f53841502a..961148fb6b4 100644 --- a/src/libstd/sys/redox/backtrace.rs +++ b/src/libstd/sys/redox/backtrace.rs @@ -10,9 +10,14 @@ use libc; use io; -use sys_common::backtrace::output; +use sys_common::backtrace::Frame; + +pub use sys_common::gnu::libbacktrace::*; +pub struct BacktraceContext; #[inline(never)] -pub fn write(w: &mut io::Write) -> io::Result<()> { - output(w, 0, 0 as *mut libc::c_void, None) +pub fn unwind_backtrace(frames: &mut [Frame]) + -> io::Result<(usize, BacktraceContext)> +{ + Ok((0, BacktraceContext)) } diff --git a/src/libstd/sys/unix/backtrace/mod.rs b/src/libstd/sys/unix/backtrace/mod.rs index 1eef89bf66f..29d4012dcdf 100644 --- a/src/libstd/sys/unix/backtrace/mod.rs +++ b/src/libstd/sys/unix/backtrace/mod.rs @@ -83,7 +83,8 @@ /// to symbols. This is a bit of a hokey implementation as-is, but it works for /// all unix platforms we support right now, so it at least gets the job done. -pub use self::tracing::write; +pub use self::tracing::unwind_backtrace; +pub use self::printing::{foreach_symbol_fileline, resolve_symname}; // tracing impls: mod tracing; @@ -100,3 +101,5 @@ pub fn get_executable_filename() -> io::Result<(Vec, fs::File)> { Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) } } + +pub struct BacktraceContext; diff --git a/src/libstd/sys/unix/backtrace/printing/dladdr.rs b/src/libstd/sys/unix/backtrace/printing/dladdr.rs index d9b759dc673..05a071a7978 100644 --- a/src/libstd/sys/unix/backtrace/printing/dladdr.rs +++ b/src/libstd/sys/unix/backtrace/printing/dladdr.rs @@ -9,33 +9,45 @@ // 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; - } - - 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 resolve_symname(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) } } + +pub fn foreach_symbol_fileline(_symbol_addr: Frame, + _f: F, + _: &BacktraceContext) -> io::Result + 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; +} diff --git a/src/libstd/sys/unix/backtrace/printing/mod.rs b/src/libstd/sys/unix/backtrace/printing/mod.rs index 02e53854727..1ae82e01100 100644 --- a/src/libstd/sys/unix/backtrace/printing/mod.rs +++ b/src/libstd/sys/unix/backtrace/printing/mod.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -pub use self::imp::print; +pub use self::imp::{foreach_symbol_fileline, resolve_symname}; #[cfg(any(target_os = "macos", target_os = "ios", target_os = "emscripten"))] @@ -17,5 +17,6 @@ #[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}; +} diff --git a/src/libstd/sys/unix/backtrace/tracing/backtrace_fn.rs b/src/libstd/sys/unix/backtrace/tracing/backtrace_fn.rs index ca2e70b5003..fd46b8b9cf0 100644 --- a/src/libstd/sys/unix/backtrace/tracing/backtrace_fn.rs +++ b/src/libstd/sys/unix/backtrace/tracing/backtrace_fn.rs @@ -18,39 +18,32 @@ /// 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, + }; } - - // 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(()) + Ok((nb_frames as usize, BacktraceContext)) +} + +extern { + fn backtrace(buf: *mut *mut libc::c_void, sz: libc::c_int) -> libc::c_int; } diff --git a/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs b/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs index c1b45620ab0..8691fe55e7c 100644 --- a/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs +++ b/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs @@ -8,102 +8,97 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use error::Error; use io; -use io::prelude::*; use libc; -use mem; -use sys_common::mutex::Mutex; +use sys::backtrace::BacktraceContext; +use sys_common::backtrace::Frame; -use super::super::printing::print; use unwind as uw; +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 // tracing -pub fn write(w: &mut Write) -> io::Result<()> { - struct Context<'a> { - idx: isize, - writer: &'a mut (Write+'a), - last_error: Option, - } - - // 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(); - - writeln!(w, "stack backtrace:")?; - - 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 _; +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, " ... \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 } - - 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 +} diff --git a/src/libstd/sys/windows/backtrace_gnu.rs b/src/libstd/sys/windows/backtrace/backtrace_gnu.rs similarity index 100% rename from src/libstd/sys/windows/backtrace_gnu.rs rename to src/libstd/sys/windows/backtrace/backtrace_gnu.rs diff --git a/src/libstd/sys/windows/backtrace.rs b/src/libstd/sys/windows/backtrace/mod.rs similarity index 63% rename from src/libstd/sys/windows/backtrace.rs rename to src/libstd/sys/windows/backtrace/mod.rs index 94aaf439f3d..3c3fd8d3e4a 100644 --- a/src/libstd/sys/windows/backtrace.rs +++ b/src/libstd/sys/windows/backtrace/mod.rs @@ -24,37 +24,87 @@ #![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; +use sys_common::backtrace::Frame; macro_rules! sym { ($lib:expr, $e:expr, $t:ident) => ( - match $lib.symbol($e) { - Ok(f) => $crate::mem::transmute::(f), - Err(..) => return Ok(()) - } + $lib.symbol($e).map(|f| unsafe { + $crate::mem::transmute::(f) + }) ) } -#[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; +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; @@ -68,8 +118,8 @@ macro_rules! sym { *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 { +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; @@ -80,8 +130,8 @@ pub fn init_frame(frame: &mut c::STACKFRAME64, } #[cfg(target_arch = "x86_64")] -pub fn init_frame(frame: &mut c::STACKFRAME64, - ctx: &c::CONTEXT) -> c::DWORD { +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; @@ -91,73 +141,16 @@ pub fn init_frame(frame: &mut c::STACKFRAME64, c::IMAGE_FILE_MACHINE_AMD64 } -struct Cleanup { +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 Cleanup { +impl Drop for BacktraceContext { 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(()) -} diff --git a/src/libstd/sys/unix/backtrace/printing/gnu.rs b/src/libstd/sys/windows/backtrace/printing/mod.rs similarity index 63% rename from src/libstd/sys/unix/backtrace/printing/gnu.rs rename to src/libstd/sys/windows/backtrace/printing/mod.rs index fb06fbedaf5..3e566f6e2bd 100644 --- a/src/libstd/sys/unix/backtrace/printing/gnu.rs +++ b/src/libstd/sys/windows/backtrace/printing/mod.rs @@ -8,4 +8,13 @@ // option. This file may not be copied, modified, or distributed // 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}; diff --git a/src/libstd/sys/windows/backtrace/printing/msvc.rs b/src/libstd/sys/windows/backtrace/printing/msvc.rs new file mode 100644 index 00000000000..3107d784324 --- /dev/null +++ b/src/libstd/sys/windows/backtrace/printing/msvc.rs @@ -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 or the MIT license +// , 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(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::() - 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(frame: Frame, + mut f: F, + context: &BacktraceContext) + -> io::Result + 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::() 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) + } +} diff --git a/src/libstd/sys/windows/printing/gnu.rs b/src/libstd/sys/windows/printing/gnu.rs deleted file mode 100644 index be2d5273c07..00000000000 --- a/src/libstd/sys/windows/printing/gnu.rs +++ /dev/null @@ -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 or the MIT license -// , 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) -} diff --git a/src/libstd/sys/windows/printing/msvc.rs b/src/libstd/sys/windows/printing/msvc.rs deleted file mode 100644 index 9c29ac4082a..00000000000 --- a/src/libstd/sys/windows/printing/msvc.rs +++ /dev/null @@ -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 or the MIT license -// , 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::() - 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::() 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(()) - } - } -} diff --git a/src/libstd/sys_common/backtrace.rs b/src/libstd/sys_common/backtrace.rs index a8540fed928..a19f7954e8f 100644 --- a/src/libstd/sys_common/backtrace.rs +++ b/src/libstd/sys_common/backtrace.rs @@ -10,14 +10,25 @@ #![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; @@ -25,45 +36,217 @@ #[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 { 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, "")?, +/// 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 - ` 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"")?, + } + 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(""); +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(""); + 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 // 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. @@ -123,6 +306,22 @@ pub fn demangle(writer: &mut Write, s: &str) -> io::Result<()> { 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 { @@ -208,7 +407,9 @@ mod tests { 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); }) } diff --git a/src/libstd/sys_common/gnu/libbacktrace.rs b/src/libstd/sys_common/gnu/libbacktrace.rs index 0bdbeddb112..1ea5cca44c7 100644 --- a/src/libstd/sys_common/gnu/libbacktrace.rs +++ b/src/libstd/sys_common/gnu/libbacktrace.rs @@ -8,186 +8,204 @@ // 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(frame: Frame, + mut f: F, + _: &BacktraceContext) -> io::Result +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(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 } diff --git a/src/libunwind/libunwind.rs b/src/libunwind/libunwind.rs index 7fb58373251..e22f6702672 100644 --- a/src/libunwind/libunwind.rs +++ b/src/libunwind/libunwind.rs @@ -18,7 +18,7 @@ macro_rules! cfg_if { 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, diff --git a/src/test/run-pass/backtrace-debuginfo.rs b/src/test/run-pass/backtrace-debuginfo.rs index 626eccfc9ec..88fee9ed25b 100644 --- a/src/test/run-pass/backtrace-debuginfo.rs +++ b/src/test/run-pass/backtrace-debuginfo.rs @@ -141,12 +141,12 @@ fn run_test(me: &str) { 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(); diff --git a/src/test/run-pass/backtrace.rs b/src/test/run-pass/backtrace.rs index 834ce984e66..dcdf82682f3 100644 --- a/src/test/run-pass/backtrace.rs +++ b/src/test/run-pass/backtrace.rs @@ -47,7 +47,7 @@ fn template(me: &str) -> Command { } fn expected(fn_name: &str) -> String { - format!(" - backtrace::{}", fn_name) + format!(" backtrace::{}", fn_name) } fn runtest(me: &str) { @@ -59,6 +59,53 @@ 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.)