Improve backtrace formating while panicking.
- `RUST_BACKTRACE=full` prints all the informations (old behaviour) - `RUST_BACKTRACE=(0|no)` disables the backtrace. - `RUST_BACKTRACE=<everything else>` (including `1`) shows a simplified backtrace, without the function addresses and with cleaned filenames and symbols. Also removes some unneded frames at the beginning and the end. Fixes #37783. PR is #38165.
This commit is contained in:
@@ -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(());
|
||||
}
|
||||
w.write_all(&['\n' as u8])
|
||||
match format {
|
||||
PrintFormat::Full => write!(w,
|
||||
" {:2}: {:2$?} - ",
|
||||
idx,
|
||||
frame.exact_position,
|
||||
HEX_WIDTH)?,
|
||||
PrintFormat::Short => write!(w, " {:2}: ", idx)?,
|
||||
}
|
||||
match s {
|
||||
Some(string) => demangle(w, string, format)?,
|
||||
None => w.write_all(b"<unknown>")?,
|
||||
}
|
||||
w.write_all(b"\n")
|
||||
}
|
||||
|
||||
/// Print the filename and line number of the backtrace frame.
|
||||
///
|
||||
/// See also `output`.
|
||||
#[allow(dead_code)]
|
||||
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);
|
||||
}) }
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user