blob: b3fc0b8ed2709f0a8da660438d9c331e2f1516cb [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License..
use crate::marker;
use crate::mem;
use crate::ptr;
use crate::slice;
use crate::sys::backtrace::{self, Bomb, BytesOrWideString};
use crate::sys_common::backtrace::{ResolveWhat, SymbolName};
use sgx_trts::enclave;
use sgx_libc::{self, c_char, c_int, c_void, uintptr_t};
use sgx_backtrace_sys as bt;
use sgx_unwind as uw;
pub enum Symbol<'a> {
Syminfo {
pc: uintptr_t,
symname: *const c_char,
_marker: marker::PhantomData<&'a ()>,
},
Pcinfo {
pc: uintptr_t,
filename: *const c_char,
lineno: c_int,
function: *const c_char,
symname: *const c_char,
},
}
impl Symbol<'_> {
pub fn name(&self) -> Option<SymbolName<'_>> {
let symbol = |ptr: *const c_char| unsafe {
if ptr.is_null() {
None
} else {
let len = sgx_libc::strlen(ptr);
Some(SymbolName::new(slice::from_raw_parts(
ptr as *const u8,
len,
)))
}
};
match *self {
Symbol::Syminfo { symname, .. } => symbol(symname),
Symbol::Pcinfo {
function, symname, ..
} => {
// If possible prefer the `function` name which comes from
// debuginfo and can typically be more accurate for inline
// frames for example. If that's not present though fall back to
// the symbol table name specified in `symname`.
//
// Note that sometimes `function` can feel somewhat less
// accurate, for example being listed as `try<i32,closure>`
// isntead of `std::panicking::try::do_call`. It's not really
// clear why, but overall the `function` name seems more accurate.
if let Some(sym) = symbol(function) {
return Some(sym);
}
symbol(symname)
}
}
}
pub fn name_bytes(&self) -> Option<&[u8]> {
let symbol = |ptr: *const c_char| unsafe {
if ptr.is_null() {
None
} else {
let len = sgx_libc::strlen(ptr);
Some(slice::from_raw_parts(ptr as *const u8, len))
}
};
match *self {
Symbol::Syminfo { symname, .. } => symbol(symname),
Symbol::Pcinfo {
function, symname, ..
} => {
// If possible prefer the `function` name which comes from
// debuginfo and can typically be more accurate for inline
// frames for example. If that's not present though fall back to
// the symbol table name specified in `symname`.
//
// Note that sometimes `function` can feel somewhat less
// accurate, for example being listed as `try<i32,closure>`
// isntead of `std::panicking::try::do_call`. It's not really
// clear why, but overall the `function` name seems more accurate.
if let Some(sym) = symbol(function) {
return Some(sym);
}
symbol(symname)
}
}
}
pub fn addr(&self) -> Option<*mut c_void> {
let pc = match *self {
Symbol::Syminfo { pc, .. } => pc,
Symbol::Pcinfo { pc, .. } => pc,
};
if pc == 0 {
None
} else {
Some(pc as *mut _)
}
}
fn filename_bytes(&self) -> Option<&[u8]> {
match *self {
Symbol::Syminfo { .. } => None,
Symbol::Pcinfo { filename, .. } => {
let ptr = filename as *const u8;
if ptr.is_null() {
return None;
}
unsafe {
let len = sgx_libc::strlen(filename);
Some(slice::from_raw_parts(ptr, len))
}
}
}
}
pub fn filename_raw(&self) -> Option<BytesOrWideString<'_>> {
self.filename_bytes().map(BytesOrWideString::Bytes)
}
pub fn lineno(&self) -> Option<u32> {
match *self {
Symbol::Syminfo { .. } => None,
Symbol::Pcinfo { lineno, .. } => Some(lineno as u32),
}
}
pub fn colno(&self) -> Option<u32> {
None
}
}
extern "C" fn error_cb(_data: *mut c_void, _msg: *const c_char, _errnum: c_int) {
// do nothing for now
}
/// Type of the `data` pointer passed into `syminfo_cb`
struct SyminfoState<'a> {
cb: &'a mut (dyn FnMut(&super::Symbol) + 'a),
pc: usize,
}
static ENCLAVE_ENTRY_NAME: &str = "enclave_entry\0";
extern "C" fn syminfo_cb(
data: *mut c_void,
pc: uintptr_t,
symname: *const c_char,
_symval: uintptr_t,
_symsize: uintptr_t,
) {
let state = unsafe { init_state() };
if state.is_null() {
return;
}
let mut bomb = Bomb::new(true);
// Once this callback is invoked from `backtrace_syminfo` when we start
// resolving we go further to call `backtrace_pcinfo`. The
// `backtrace_pcinfo` function will consult debug information and attemp tto
// do things like recover file/line information as well as inlined frames.
// Note though that `backtrace_pcinfo` can fail or not do much if there's
// not debug info, so if that happens we're sure to call the callback with
// at least one symbol from the `syminfo_cb`.
unsafe {
let syminfo_state = &mut *(data as *mut SyminfoState<'_>);
let mut pcinfo_state = PcinfoState {
symname,
called: false,
cb: syminfo_state.cb,
};
bt::backtrace_pcinfo(
state,
syminfo_state.pc as uintptr_t,
pcinfo_cb,
error_cb,
&mut pcinfo_state as *mut _ as *mut _,
);
if !pcinfo_state.called {
let mut symname = symname;
if symname.is_null() {
let sym_address =
uw::_Unwind_FindEnclosingFunction((pc + 1) as *mut c_void) as usize;
let enclave_entry = enclave::rsgx_get_enclave_entry();
if sym_address == enclave_entry || (sym_address - 0x04) == enclave_entry {
//0x04 endbr64
symname = ENCLAVE_ENTRY_NAME as *const _ as *const c_char
}
}
let inner = Symbol::Syminfo {
pc,
symname,
_marker: marker::PhantomData,
};
(pcinfo_state.cb)(&super::Symbol {
name: inner.name_bytes().map(|m| m.to_vec()),
addr: inner.addr(),
filename: inner.filename_bytes().map(|m| m.to_vec()),
lineno: inner.lineno(),
colno: inner.colno(),
});
}
}
bomb.set(false);
}
/// Type of the `data` pointer passed into `pcinfo_cb`
struct PcinfoState<'a> {
cb: &'a mut (dyn FnMut(&super::Symbol) + 'a),
symname: *const c_char,
called: bool,
}
extern "C" fn pcinfo_cb(
data: *mut c_void,
pc: uintptr_t,
filename: *const c_char,
lineno: c_int,
function: *const c_char,
) -> c_int {
if filename.is_null() || function.is_null() {
return -1;
}
let mut bomb = Bomb::new(true);
unsafe {
let state = &mut *(data as *mut PcinfoState);
state.called = true;
let inner = Symbol::Pcinfo {
pc,
filename,
lineno,
symname: state.symname,
function,
};
(state.cb)(&super::Symbol {
name: inner.name_bytes().map(|m| m.to_vec()),
addr: inner.addr(),
filename: inner.filename_bytes().map(|m| m.to_vec()),
lineno: inner.lineno(),
colno: inner.colno(),
});
}
bomb.set(false);
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).
//
// Note the lack of synchronization here is due to the requirement that
// `resolve` is externally synchronized.
unsafe fn init_state() -> *mut bt::backtrace_state {
static mut STATE: *mut bt::backtrace_state = 0 as *mut _;
if !STATE.is_null() {
return STATE;
}
let filename = match backtrace::gnu::get_enclave_filename() {
Ok(filename) => {
// filename is purposely leaked here since libbacktrace requires
// it to stay allocated permanently.
let filename_ptr = filename.as_ptr();
mem::forget(filename);
filename_ptr
}
Err(_) => return ptr::null_mut(),
};
STATE = bt::backtrace_create_state(
filename.cast(),
// Don't exercise threadsafe capabilities of libbacktrace since
// we're always calling it in a synchronized fashion.
0,
error_cb,
ptr::null_mut(), // no extra data
);
STATE
}
pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
let symaddr = what.address_or_ip() as usize;
// backtrace errors are currently swept under the rug
let state = init_state();
if state.is_null() {
return;
}
// Call the `backtrace_syminfo` API which (from reading the code)
// should call `syminfo_cb` exactly once (or fail with an error
// presumably). We then handle more within the `syminfo_cb`.
//
// Note that we do this since `syminfo` will consult the symbol table,
// finding symbol names even if there's no debug information in the binary.
let mut syminfo_state = SyminfoState { pc: symaddr, cb };
bt::backtrace_syminfo(
state,
symaddr as uintptr_t,
syminfo_cb,
error_cb,
&mut syminfo_state as *mut _ as *mut _,
);
}
pub unsafe fn clear_symbol_cache() {}