blob: 6cd050242dd950e6090f725a969dc157193418a0 [file] [log] [blame]
use backtrace_sys::backtrace_state;
use crate::ffi::CStr;
use crate::io;
use crate::mem;
use crate::ptr;
use crate::sys::backtrace::BacktraceContext;
use crate::sys_common::backtrace::Frame;
pub fn foreach_symbol_fileline<F>(frame: Frame,
mut f: F,
_: &BacktraceContext) -> io::Result<bool>
where F: FnMut(&[u8], u32) -> 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(), !0); FILELINE_SIZE];
let ret;
let fileline_count = {
let state = unsafe { init_state() };
if state.is_null() {
return Err(io::Error::new(
io::ErrorKind::Other,
"failed to allocate libbacktrace state")
)
}
let mut fileline_win: &mut [FileLine] = &mut fileline_buf;
let fileline_addr = &mut fileline_win as *mut &mut [FileLine];
ret = unsafe {
backtrace_sys::backtrace_pcinfo(
state,
frame.exact_position as libc::uintptr_t,
pcinfo_cb,
error_cb,
fileline_addr as *mut libc::c_void,
)
};
FILELINE_SIZE - fileline_win.len()
};
if ret == 0 {
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() };
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() {
return Err(io::Error::new(
io::ErrorKind::Other,
"failed to allocate libbacktrace state")
)
}
let mut data: *const libc::c_char = ptr::null();
let data_addr = &mut data as *mut *const libc::c_char;
let ret = unsafe {
backtrace_sys::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)
}
////////////////////////////////////////////////////////////////////////
// helper callbacks
////////////////////////////////////////////////////////////////////////
type FileLine = (*const libc::c_char, u32);
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 as u32);
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 crate::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_sys::backtrace_create_state(
filename,
0,
error_cb,
ptr::null_mut(),
);
STATE
}