diff options
| author | Yamakaky <yamakaky@yamaworld.fr> | 2016-12-04 16:38:27 -0500 |
|---|---|---|
| committer | Yamakaky <yamakaky@yamaworld.fr> | 2017-02-15 14:24:37 -0500 |
| commit | d50e4cc0640e54a64d0f7ccb05a77fd4a2fe0741 (patch) | |
| tree | 2c403c3c5fb8e02b5d5bbe493eec5375c47fd137 /src/libstd/sys_common | |
| parent | e0044bd3896456afb346d06e91a97ac515930ccf (diff) | |
| download | rust-d50e4cc0640e54a64d0f7ccb05a77fd4a2fe0741.tar.gz rust-d50e4cc0640e54a64d0f7ccb05a77fd4a2fe0741.zip | |
Improve backtrace formating while panicking.
- `RUST_BACKTRACE=full` prints all the informations (old behaviour) - `RUST_BACKTRACE=(0|no)` disables the backtrace. - `RUST_BACKTRACE=<everything else>` (including `1`) shows a simplified backtrace, without the function addresses and with cleaned filenames and symbols. Also removes some unneded frames at the beginning and the end. Fixes #37783. PR is #38165.
Diffstat (limited to 'src/libstd/sys_common')
| -rw-r--r-- | src/libstd/sys_common/backtrace.rs | 253 | ||||
| -rw-r--r-- | src/libstd/sys_common/gnu/libbacktrace.rs | 344 |
2 files changed, 408 insertions, 189 deletions
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 @@ 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") } @@ -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<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 } |
