use std::alloc::alloc;
use std::alloc::dealloc;
use std::alloc::Layout;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fmt::Debug;
use std::mem;
use std::mem::ManuallyDrop;
use std::ops::Deref as _;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt as _;
use std::os::unix::ffi::OsStringExt as _;
use std::path::PathBuf;
use std::ptr;
#[cfg(doc)]
use blazesym::inspect;
use blazesym::inspect::source::Elf;
use blazesym::inspect::source::Source;
use blazesym::inspect::Inspector;
use blazesym::inspect::SymInfo;
use blazesym::Addr;
use blazesym::SymType;
use crate::blaze_err;
#[cfg(doc)]
use crate::blaze_err_last;
use crate::from_cstr;
use crate::set_last_err;
use crate::util::slice_from_user_array;
use crate::util::DynSize as _;
pub type blaze_inspector = Inspector;
#[repr(C)]
#[derive(Debug)]
pub struct blaze_inspect_elf_src {
pub type_size: usize,
pub path: *const c_char,
pub debug_syms: bool,
pub reserved: [u8; 23],
}
impl Default for blaze_inspect_elf_src {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
path: ptr::null(),
debug_syms: false,
reserved: [0; 23],
}
}
}
#[cfg_attr(not(test), allow(unused))]
impl blaze_inspect_elf_src {
fn from(other: Elf) -> ManuallyDrop<Self> {
let Elf {
path,
debug_syms,
_non_exhaustive: (),
} = other;
let slf = Self {
path: CString::new(path.into_os_string().into_vec())
.expect("encountered path with NUL bytes")
.into_raw(),
debug_syms,
..Default::default()
};
ManuallyDrop::new(slf)
}
unsafe fn free(self) {
let Self {
type_size: _,
path,
debug_syms,
reserved: _,
} = self;
let _elf = Elf {
path: PathBuf::from(OsString::from_vec(
unsafe { CString::from_raw(path as *mut _) }.into_bytes(),
)),
debug_syms,
_non_exhaustive: (),
};
}
}
impl From<blaze_inspect_elf_src> for Elf {
fn from(other: blaze_inspect_elf_src) -> Self {
let blaze_inspect_elf_src {
type_size: _,
path,
debug_syms,
reserved: _,
} = other;
Self {
path: unsafe { from_cstr(path) },
debug_syms,
_non_exhaustive: (),
}
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct blaze_sym_type(u8);
impl blaze_sym_type {
pub const UNDEF: Self = Self(0);
pub const FUNC: Self = Self(1);
pub const VAR: Self = Self(2);
}
impl From<SymType> for blaze_sym_type {
fn from(other: SymType) -> Self {
match other {
SymType::Undefined => Self::UNDEF,
SymType::Function => Self::FUNC,
SymType::Variable => Self::VAR,
_ => unreachable!(),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_sym_info {
pub name: *const c_char,
pub addr: Addr,
pub size: isize,
pub file_offset: u64,
pub module: *const c_char,
pub sym_type: blaze_sym_type,
pub reserved: [u8; 23],
}
fn convert_syms_list_to_c(syms_list: Vec<Vec<SymInfo>>) -> *const *const blaze_sym_info {
let sym_cnt = syms_list.iter().map(|syms| syms.len() + 1).sum::<usize>();
let str_buf_sz = syms_list.c_str_size();
let array_sz = (mem::size_of::<*const u64>() * syms_list.len()).div_ceil(mem::size_of::<u64>())
* mem::size_of::<u64>();
let sym_buf_sz = mem::size_of::<blaze_sym_info>() * sym_cnt;
let buf_size = mem::size_of::<u64>() + array_sz + sym_buf_sz + str_buf_sz;
let buf = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
if buf.is_null() {
return ptr::null()
}
unsafe { *buf.cast::<u64>() = buf_size as u64 };
let raw_buf = unsafe { buf.add(mem::size_of::<u64>()) };
let mut syms_ptr = raw_buf as *mut *mut blaze_sym_info;
let mut sym_ptr = unsafe { raw_buf.add(array_sz) } as *mut blaze_sym_info;
let mut str_ptr = unsafe { raw_buf.add(array_sz + sym_buf_sz) } as *mut c_char;
for syms in syms_list {
unsafe { *syms_ptr = sym_ptr };
for SymInfo {
name,
addr,
size,
sym_type,
file_offset,
module,
_non_exhaustive: (),
} in syms
{
let name_ptr = str_ptr.cast();
unsafe { ptr::copy_nonoverlapping(name.as_ptr().cast(), str_ptr, name.len()) };
str_ptr = unsafe { str_ptr.add(name.len()) };
unsafe { *str_ptr = 0 };
str_ptr = unsafe { str_ptr.add(1) };
let module = if let Some(fname) = module.as_ref() {
let fname = AsRef::<OsStr>::as_ref(fname.deref()).as_bytes();
let obj_fname_ptr = str_ptr;
unsafe { ptr::copy_nonoverlapping(fname.as_ptr().cast(), str_ptr, fname.len()) };
str_ptr = unsafe { str_ptr.add(fname.len()) };
unsafe { *str_ptr = 0 };
str_ptr = unsafe { str_ptr.add(1) };
obj_fname_ptr
} else {
ptr::null()
};
unsafe {
(*sym_ptr) = blaze_sym_info {
name: name_ptr,
addr,
size: size
.map(|size| isize::try_from(size).unwrap_or(isize::MAX))
.unwrap_or(-1),
sym_type: sym_type.into(),
file_offset: file_offset.unwrap_or(0),
module,
reserved: [0; 23],
}
};
sym_ptr = unsafe { sym_ptr.add(1) };
}
unsafe {
(*sym_ptr) = blaze_sym_info {
name: ptr::null(),
addr: 0,
size: 0,
sym_type: blaze_sym_type::UNDEF,
file_offset: 0,
module: ptr::null(),
reserved: [0; 23],
}
};
sym_ptr = unsafe { sym_ptr.add(1) };
syms_ptr = unsafe { syms_ptr.add(1) };
}
raw_buf as *const *const blaze_sym_info
}
#[no_mangle]
pub unsafe extern "C" fn blaze_inspect_syms_elf(
inspector: *const blaze_inspector,
src: *const blaze_inspect_elf_src,
names: *const *const c_char,
name_cnt: usize,
) -> *const *const blaze_sym_info {
if !input_zeroed!(src, blaze_inspect_elf_src) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_inspect_elf_src);
let inspector = unsafe { &*inspector };
let src = Source::Elf(Elf::from(src));
let names = unsafe { slice_from_user_array(names, name_cnt) };
let names = names
.iter()
.map(|&p| {
unsafe { CStr::from_ptr(p) }.to_str().unwrap()
})
.collect::<Vec<_>>();
let result = inspector.lookup(&src, &names);
match result {
Ok(syms) => {
let result = convert_syms_list_to_c(syms);
if result.is_null() {
let () = set_last_err(blaze_err::OUT_OF_MEMORY);
} else {
let () = set_last_err(blaze_err::OK);
}
result
}
Err(err) => {
let () = set_last_err(err.kind().into());
ptr::null()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_inspect_syms_free(syms: *const *const blaze_sym_info) {
if syms.is_null() {
return
}
let buf = unsafe { syms.byte_sub(mem::size_of::<u64>()).cast::<u8>().cast_mut() };
let size = unsafe { *buf.cast::<u64>() } as usize;
unsafe { dealloc(buf, Layout::from_size_align(size, 8).unwrap()) };
}
#[no_mangle]
pub extern "C" fn blaze_inspector_new() -> *mut blaze_inspector {
let inspector = Inspector::new();
let inspector_box = Box::new(inspector);
let () = set_last_err(blaze_err::OK);
Box::into_raw(inspector_box)
}
#[no_mangle]
pub unsafe extern "C" fn blaze_inspector_free(inspector: *mut blaze_inspector) {
if !inspector.is_null() {
drop(unsafe { Box::from_raw(inspector) });
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::MaybeUninit;
use std::path::Path;
use std::ptr::addr_of;
use std::slice;
use test_log::test;
use test_tag::tag;
use crate::blaze_err_last;
#[tag(miri)]
#[test]
#[cfg(target_pointer_width = "64")]
fn type_sizes() {
assert_eq!(mem::size_of::<blaze_inspect_elf_src>(), 40);
assert_eq!(mem::size_of::<blaze_sym_info>(), 64);
}
#[tag(miri)]
#[test]
fn debug_repr() {
let elf = blaze_inspect_elf_src {
type_size: 24,
path: ptr::null(),
debug_syms: true,
reserved: [0; 23],
};
assert_eq!(
format!("{elf:?}"),
"blaze_inspect_elf_src { type_size: 24, path: 0x0, debug_syms: true, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
let info = blaze_sym_info {
name: ptr::null(),
addr: 42,
size: 1337,
file_offset: 31,
module: ptr::null(),
sym_type: blaze_sym_type::VAR,
reserved: [0; 23],
};
assert_eq!(
format!("{info:?}"),
"blaze_sym_info { name: 0x0, addr: 42, size: 1337, file_offset: 31, module: 0x0, sym_type: blaze_sym_type(2), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
}
#[tag(miri)]
#[test]
fn elf_src_validity() {
#[repr(C)]
struct elf_src_with_ext {
type_size: usize,
_path: *const c_char,
debug_syms: bool,
reserved: [u8; 23],
foobar: bool,
reserved2: [u8; 7],
}
assert!(mem::size_of::<blaze_inspect_elf_src>() < mem::size_of::<elf_src_with_ext>());
let mut src = MaybeUninit::<elf_src_with_ext>::uninit();
let () = unsafe {
ptr::write_bytes(
src.as_mut_ptr().cast::<u8>(),
0,
mem::size_of::<elf_src_with_ext>(),
)
};
let mut src = unsafe { src.assume_init() };
src.type_size = mem::size_of::<elf_src_with_ext>();
src.debug_syms = true;
let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
assert!(input_zeroed!(src_ptr, blaze_inspect_elf_src));
src.reserved[0] = 1;
let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
src.reserved[0] = 0;
src.type_size = mem::size_of::<usize>() - 1;
let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
src.type_size = mem::size_of::<elf_src_with_ext>();
src.foobar = true;
let src_ptr = addr_of!(src).cast::<blaze_inspect_elf_src>();
assert!(!input_zeroed!(src_ptr, blaze_inspect_elf_src));
}
#[tag(miri)]
#[test]
fn syms_list_conversion() {
fn test(syms: Vec<Vec<SymInfo>>) {
let copy = syms.clone();
let ptr = convert_syms_list_to_c(syms);
assert!(!ptr.is_null());
for (i, list) in copy.into_iter().enumerate() {
for (j, sym) in list.into_iter().enumerate() {
let c_sym = unsafe { &(*(*ptr.add(i)).add(j)) };
assert_eq!(
unsafe { CStr::from_ptr(c_sym.name) }.to_bytes(),
CString::new(sym.name.deref()).unwrap().to_bytes()
);
assert_eq!(c_sym.addr, sym.addr);
assert_eq!(
c_sym.size,
sym.size
.map(|size| isize::try_from(size).unwrap_or(isize::MAX))
.unwrap_or(-1)
);
assert_eq!(c_sym.sym_type, blaze_sym_type::from(sym.sym_type));
assert_eq!(Some(c_sym.file_offset), sym.file_offset);
assert_eq!(
unsafe { CStr::from_ptr(c_sym.module) }.to_bytes(),
CString::new(sym.module.as_deref().unwrap().to_os_string().into_vec())
.unwrap()
.to_bytes()
);
}
}
let () = unsafe { blaze_inspect_syms_free(ptr) };
}
let syms = vec![];
test(syms);
let syms = vec![vec![SymInfo {
name: "sym1".into(),
addr: 0xdeadbeef,
size: Some(42),
sym_type: SymType::Function,
file_offset: Some(1337),
module: Some(OsStr::new("/tmp/foobar.so").into()),
_non_exhaustive: (),
}]];
test(syms);
let syms = vec![vec![
SymInfo {
name: "sym1".into(),
addr: 0xdeadbeef,
size: Some(42),
sym_type: SymType::Function,
file_offset: Some(1337),
module: Some(OsStr::new("/tmp/foobar.so").into()),
_non_exhaustive: (),
},
SymInfo {
name: "sym2".into(),
addr: 0xdeadbeef + 52,
size: Some(45),
sym_type: SymType::Undefined,
file_offset: Some(1338),
module: Some(OsStr::new("other.so").into()),
_non_exhaustive: (),
},
]];
test(syms);
let syms = vec![
vec![SymInfo {
name: "sym1".into(),
addr: 0xdeadbeef,
size: Some(42),
sym_type: SymType::Function,
file_offset: Some(1337),
module: Some(OsStr::new("/tmp/foobar.so").into()),
_non_exhaustive: (),
}],
vec![SymInfo {
name: "sym2".into(),
addr: 0xdeadbeef + 52,
size: Some(45),
sym_type: SymType::Undefined,
file_offset: Some(1338),
module: Some(OsStr::new("other.so").into()),
_non_exhaustive: (),
}],
];
test(syms);
let sym = SymInfo {
name: "sym1".into(),
addr: 0xdeadbeef,
size: Some(42),
sym_type: SymType::Function,
file_offset: Some(1337),
module: Some(OsStr::new("/tmp/foobar.so").into()),
_non_exhaustive: (),
};
let syms = vec![(0..200).map(|_| sym.clone()).collect()];
test(syms);
let syms = (0..200).map(|_| vec![sym.clone()]).collect();
test(syms);
}
#[tag(miri)]
#[test]
fn inspector_creation() {
let inspector = blaze_inspector_new();
let () = unsafe { blaze_inspector_free(inspector) };
}
#[test]
fn non_zero_reserved() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs.bin");
let mut src = blaze_inspect_elf_src::from(Elf::new(path));
src.reserved[1] = 1;
let factorial = CString::new("factorial").unwrap();
let names = [factorial.as_ptr()];
let inspector = blaze_inspector_new();
let result =
unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
let () = unsafe { ManuallyDrop::into_inner(src).free() };
assert_eq!(result, ptr::null());
assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
let () = unsafe { blaze_inspector_free(inspector) };
}
#[test]
fn non_present_file() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("does-not-exist");
let src = blaze_inspect_elf_src::from(Elf::new(path));
let factorial = CString::new("factorial").unwrap();
let names = [factorial.as_ptr()];
let inspector = blaze_inspector_new();
let result =
unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
let () = unsafe { ManuallyDrop::into_inner(src).free() };
assert_eq!(result, ptr::null());
assert_eq!(blaze_err_last(), blaze_err::NOT_FOUND);
let () = unsafe { blaze_inspector_free(inspector) };
}
#[test]
fn lookup_dwarf() {
let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let src = blaze_inspect_elf_src::from(Elf::new(test_dwarf));
let factorial = CString::new("factorial").unwrap();
let names = [factorial.as_ptr()];
let inspector = blaze_inspector_new();
let result =
unsafe { blaze_inspect_syms_elf(inspector, &*src, names.as_ptr(), names.len()) };
let () = unsafe { ManuallyDrop::into_inner(src).free() };
assert!(!result.is_null());
let sym_infos = unsafe { slice::from_raw_parts(result, names.len()) };
let sym_info = unsafe { &*sym_infos[0] };
assert_eq!(
unsafe { CStr::from_ptr(sym_info.name) },
CStr::from_bytes_with_nul(b"factorial\0").unwrap()
);
assert_eq!(sym_info.addr, 0x2000200);
let () = unsafe { blaze_inspect_syms_free(result) };
let () = unsafe { blaze_inspector_free(inspector) };
}
}