1
Fork 0

std: Depend on backtrace crate from crates.io

This commit removes all in-tree support for generating backtraces in
favor of depending on the `backtrace` crate on crates.io. This resolves
a very longstanding piece of duplication where the standard library has
long contained the ability to generate a backtrace on panics, but the
code was later extracted and duplicated on crates.io with the
`backtrace` crate. Since that fork each implementation has seen various
improvements one way or another, but typically `backtrace`-the-crate has
lagged behind libstd in one way or another.

The goal here is to remove this duplication of a fairly critical piece
of code and ensure that there's only one source of truth for generating
backtraces between the standard library and the crate on crates.io.
Recently I've been working to bring the `backtrace` crate on crates.io
up to speed with the support in the standard library which includes:

* Support for `StackWalkEx` on MSVC to recover inline frames with
  debuginfo.
* Using `libbacktrace` by default on MinGW targets.
* Supporting `libbacktrace` on OSX as an option.
* Ensuring all the requisite support in `backtrace`-the-crate compiles
  with `#![no_std]`.
* Updating the `libbacktrace` implementation in `backtrace`-the-crate to
  initialize the global state with the correct filename where necessary.

After reviewing the code in libstd the `backtrace` crate should be at
exact feature parity with libstd today. The backtraces generated should
have the same symbols and same number of frames in general, and there's
not known divergence from libstd currently.

Note that one major difference between libstd's backtrace support and
the `backtrace` crate is that on OSX the crates.io crate enables the
`coresymbolication` feature by default. This feature, however, uses
private internal APIs that aren't published for OSX. While they provide
more accurate backtraces this isn't appropriate for libstd distributed
as a binary, so libstd's dependency on the `backtrace` crate explicitly
disables this feature and forces OSX to use `libbacktrace` as a
symbolication strategy.

The long-term goal of this refactoring is to eventually move us towards
a world where we can drop `libbacktrace` entirely and simply use Gimli
and the surrounding crates for backtrace support. That's still aways off
but hopefully will much more easily enabled by having the source of
truth for backtraces live in crates.io!

Procedurally if we go forward with this I'd like to transfer the
`backtrace-rs` crate to the rust-lang GitHub organization as well, but I
figured I'd hold off on that until we get closer to merging.
This commit is contained in:
Alex Crichton 2019-05-15 07:30:15 -07:00
parent 02f5786a32
commit d1040fe329
35 changed files with 209 additions and 2164 deletions

View file

@ -2,40 +2,17 @@
/// supported platforms.
use crate::env;
use crate::io::prelude::*;
use crate::io;
use crate::io::prelude::*;
use crate::mem;
use crate::path::{self, Path};
use crate::ptr;
use crate::str;
use crate::sync::atomic::{self, Ordering};
use crate::sys::mutex::Mutex;
use rustc_demangle::demangle;
use backtrace::{BytesOrWideString, Frame, Symbol};
pub use crate::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 u8,
/// Address of the enclosing function.
pub symbol_addr: *const u8,
/// Which inlined function is this frame referring to
pub inline_context: u32,
}
pub const HEX_WIDTH: usize = 2 + 2 * mem::size_of::<usize>();
/// Max number of frames to print.
const MAX_NB_FRAMES: usize = 100;
@ -49,7 +26,7 @@ pub fn print(w: &mut dyn Write, format: PrintFormat) -> io::Result<()> {
// test mode immediately return here to optimize away any references to the
// libbacktrace symbols
if cfg!(test) {
return Ok(())
return Ok(());
}
// Use a lock to prevent mixed output in multithreading context.
@ -63,75 +40,39 @@ pub fn print(w: &mut dyn Write, format: PrintFormat) -> io::Result<()> {
}
fn _print(w: &mut dyn Write, format: PrintFormat) -> io::Result<()> {
let mut frames = [Frame {
exact_position: ptr::null(),
symbol_addr: ptr::null(),
inline_context: 0,
}; MAX_NB_FRAMES];
let (nb_frames, context) = unwind_backtrace(&mut frames)?;
let (skipped_before, skipped_after) =
filter_frames(&frames[..nb_frames], format, &context);
if skipped_before + skipped_after > 0 {
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>")?;
}
let mut printer = Printer::new(format, w);
unsafe {
backtrace::trace_unsynchronized(|frame| {
let mut hit = false;
backtrace::resolve_frame_unsynchronized(frame, |symbol| {
hit = true;
printer.output(frame, Some(symbol));
});
if !hit {
printer.output(frame, None);
}
!printer.done
});
}
if printer.skipped {
writeln!(
w,
"note: Some details are omitted, \
run with `RUST_BACKTRACE=full` for a verbose backtrace."
)?;
}
Ok(())
}
/// Returns a number of frames to remove at the beginning and at the end of the
/// backtrace, according to the backtrace format.
fn filter_frames(frames: &[Frame],
format: PrintFormat,
context: &BacktraceContext) -> (usize, usize)
{
if format == PrintFormat::Full {
return (0, 0);
}
let skipped_before = 0;
let skipped_after = frames.len() - frames.iter().position(|frame| {
let mut is_marker = false;
let _ = resolve_symname(*frame, |symname| {
if let Some(mangled_symbol_name) = symname {
// Use grep to find the concerned functions
if mangled_symbol_name.contains("__rust_begin_short_backtrace") {
is_marker = true;
}
}
Ok(())
}, context);
is_marker
}).unwrap_or(frames.len());
if skipped_before + skipped_after >= frames.len() {
// Avoid showing completely empty backtraces
return (0, 0);
}
(skipped_before, skipped_after)
}
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
where F: FnOnce() -> T, F: Send, T: Send
where
F: FnOnce() -> T,
F: Send,
T: Send,
{
f()
}
@ -156,7 +97,7 @@ pub fn log_enabled() -> Option<PrintFormat> {
_ => return Some(PrintFormat::Full),
}
let val = env::var_os("RUST_BACKTRACE").and_then(|x|
let val = env::var_os("RUST_BACKTRACE").and_then(|x| {
if &x == "0" {
None
} else if &x == "full" {
@ -164,80 +105,141 @@ pub fn log_enabled() -> Option<PrintFormat> {
} else {
Some(PrintFormat::Short)
}
});
ENABLED.store(
match val {
Some(v) => v as isize,
None => 1,
},
Ordering::SeqCst,
);
ENABLED.store(match val {
Some(v) => v as isize,
None => 1,
}, Ordering::SeqCst);
val
}
/// Prints 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 dyn 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)?,
}
match s {
Some(string) => {
let symbol = demangle(string);
match format {
PrintFormat::Full => write!(w, "{}", symbol)?,
// strip the trailing hash if short mode
PrintFormat::Short => write!(w, "{:#}", symbol)?,
}
}
None => w.write_all(b"<unknown>")?,
}
w.write_all(b"\n")
struct Printer<'a, 'b> {
format: PrintFormat,
done: bool,
skipped: bool,
idx: usize,
out: &'a mut (dyn Write + 'b),
}
/// Prints the filename and line number of the backtrace frame.
///
/// See also `output`.
#[allow(dead_code)]
fn output_fileline(w: &mut dyn Write,
file: &[u8],
line: u32,
format: PrintFormat) -> io::Result<()> {
// prior line: " ##: {:2$} - func"
w.write_all(b"")?;
match format {
PrintFormat::Full => write!(w,
" {:1$}",
"",
HEX_WIDTH)?,
PrintFormat::Short => write!(w, " ")?,
impl<'a, 'b> Printer<'a, 'b> {
fn new(format: PrintFormat, out: &'a mut (dyn Write + 'b)) -> Printer<'a, 'b> {
Printer { format, done: false, skipped: false, idx: 0, out }
}
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 .{}{}:{}", path::MAIN_SEPARATOR, s, line)?;
already_printed = true;
/// Prints 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(&mut self, frame: &Frame, symbol: Option<&Symbol>) {
if self.idx > MAX_NB_FRAMES {
self.done = true;
self.skipped = true;
return;
}
if self._output(frame, symbol).is_err() {
self.done = true;
}
self.idx += 1;
}
fn _output(&mut self, frame: &Frame, symbol: Option<&Symbol>) -> io::Result<()> {
if self.format == PrintFormat::Short {
if let Some(sym) = symbol.and_then(|s| s.name()).and_then(|s| s.as_str()) {
if sym.contains("__rust_begin_short_backtrace") {
self.skipped = true;
self.done = true;
return Ok(());
}
}
// Remove the `17: 0x0 - <unknown>` line.
if self.format == PrintFormat::Short && frame.ip() == ptr::null_mut() {
self.skipped = true;
return Ok(());
}
}
match self.format {
PrintFormat::Full => {
write!(self.out, " {:2}: {:2$?} - ", self.idx, frame.ip(), HEX_WIDTH)?
}
PrintFormat::Short => write!(self.out, " {:2}: ", self.idx)?,
}
match symbol.and_then(|s| s.name()) {
Some(symbol) => {
match self.format {
PrintFormat::Full => write!(self.out, "{}", symbol)?,
// strip the trailing hash if short mode
PrintFormat::Short => write!(self.out, "{:#}", symbol)?,
}
}
None => self.out.write_all(b"<unknown>")?,
}
self.out.write_all(b"\n")?;
if let Some(sym) = symbol {
self.output_fileline(sym)?;
}
Ok(())
}
/// Prints the filename and line number of the backtrace frame.
///
/// See also `output`.
fn output_fileline(&mut self, symbol: &Symbol) -> io::Result<()> {
#[cfg(windows)]
let path_buf;
let file = match symbol.filename_raw() {
#[cfg(unix)]
Some(BytesOrWideString::Bytes(bytes)) => {
use crate::os::unix::prelude::*;
Path::new(crate::ffi::OsStr::from_bytes(bytes))
}
#[cfg(not(unix))]
Some(BytesOrWideString::Bytes(bytes)) => {
Path::new(crate::str::from_utf8(bytes).unwrap_or("<unknown>"))
}
#[cfg(windows)]
Some(BytesOrWideString::Wide(wide)) => {
use crate::os::windows::prelude::*;
path_buf = crate::ffi::OsString::from_wide(wide);
Path::new(&path_buf)
}
#[cfg(not(windows))]
Some(BytesOrWideString::Wide(_wide)) => {
Path::new("<unknown>")
}
None => return Ok(()),
};
let line = match symbol.lineno() {
Some(line) => line,
None => return Ok(()),
};
// prior line: " ##: {:2$} - func"
self.out.write_all(b"")?;
match self.format {
PrintFormat::Full => write!(self.out, " {:1$}", "", HEX_WIDTH)?,
PrintFormat::Short => write!(self.out, " ")?,
}
let mut already_printed = false;
if self.format == PrintFormat::Short && file.is_absolute() {
if let Ok(cwd) = env::current_dir() {
if let Ok(stripped) = file.strip_prefix(&cwd) {
if let Some(s) = stripped.to_str() {
write!(self.out, " at .{}{}:{}", path::MAIN_SEPARATOR, s, line)?;
already_printed = true;
}
}
}
}
}
if !already_printed {
write!(w, " at {}:{}", file, line)?;
}
if !already_printed {
write!(self.out, " at {}:{}", file.display(), line)?;
}
w.write_all(b"\n")
self.out.write_all(b"\n")
}
}