use std::alloc::alloc;
use std::alloc::dealloc;
use std::alloc::Layout;
use std::ffi::CStr;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::mem;
use std::ops::Deref as _;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStrExt as _;
use std::path::Path;
use std::path::PathBuf;
use std::ptr;
use blazesym::symbolize::cache;
use blazesym::symbolize::source::Elf;
use blazesym::symbolize::source::GsymData;
use blazesym::symbolize::source::GsymFile;
use blazesym::symbolize::source::Kernel;
use blazesym::symbolize::source::Process;
use blazesym::symbolize::source::Source;
use blazesym::symbolize::CodeInfo;
use blazesym::symbolize::Input;
use blazesym::symbolize::Reason;
use blazesym::symbolize::Sym;
use blazesym::symbolize::Symbolized;
use blazesym::symbolize::Symbolizer;
use blazesym::Addr;
use blazesym::MaybeDefault;
use crate::blaze_err;
#[cfg(doc)]
use crate::blaze_err_last;
use crate::set_last_err;
use crate::util::slice_from_aligned_user_array;
use crate::util::slice_from_user_array;
use crate::util::DynSize as _;
#[repr(C)]
#[derive(Debug)]
pub struct blaze_cache_src_elf {
pub type_size: usize,
pub path: *const c_char,
pub reserved: [u8; 16],
}
impl Default for blaze_cache_src_elf {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
path: ptr::null(),
reserved: [0; 16],
}
}
}
impl From<blaze_cache_src_elf> for cache::Elf {
fn from(elf: blaze_cache_src_elf) -> Self {
let blaze_cache_src_elf {
type_size: _,
path,
reserved: _,
} = elf;
Self {
path: unsafe { from_cstr(path) },
_non_exhaustive: (),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_cache_src_process {
pub type_size: usize,
pub pid: u32,
pub cache_vmas: bool,
pub reserved: [u8; 19],
}
impl Default for blaze_cache_src_process {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
pid: 0,
cache_vmas: false,
reserved: [0; 19],
}
}
}
impl From<blaze_cache_src_process> for cache::Process {
fn from(process: blaze_cache_src_process) -> Self {
let blaze_cache_src_process {
type_size: _,
pid,
cache_vmas,
reserved: _,
} = process;
Self {
pid: pid.into(),
cache_vmas,
_non_exhaustive: (),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_src_elf {
pub type_size: usize,
pub path: *const c_char,
pub debug_syms: bool,
pub reserved: [u8; 23],
}
impl Default for blaze_symbolize_src_elf {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
path: ptr::null(),
debug_syms: false,
reserved: [0; 23],
}
}
}
impl From<blaze_symbolize_src_elf> for Elf {
fn from(elf: blaze_symbolize_src_elf) -> Self {
let blaze_symbolize_src_elf {
type_size: _,
path,
debug_syms,
reserved: _,
} = elf;
Self {
path: unsafe { from_cstr(path) },
debug_syms,
_non_exhaustive: (),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_src_kernel {
pub type_size: usize,
pub kallsyms: *const c_char,
pub vmlinux: *const c_char,
pub debug_syms: bool,
pub reserved: [u8; 23],
}
impl Default for blaze_symbolize_src_kernel {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
kallsyms: ptr::null(),
vmlinux: ptr::null(),
debug_syms: false,
reserved: [0; 23],
}
}
}
impl From<blaze_symbolize_src_kernel> for Kernel {
fn from(kernel: blaze_symbolize_src_kernel) -> Self {
fn to_maybe_path(path: *const c_char) -> MaybeDefault<PathBuf> {
if !path.is_null() {
let path = unsafe { from_cstr(path) };
if path.as_os_str().is_empty() {
MaybeDefault::None
} else {
MaybeDefault::Some(path)
}
} else {
MaybeDefault::Default
}
}
let blaze_symbolize_src_kernel {
type_size: _,
kallsyms,
vmlinux,
debug_syms,
reserved: _,
} = kernel;
Self {
kallsyms: to_maybe_path(kallsyms),
vmlinux: to_maybe_path(vmlinux),
kaslr_offset: None,
debug_syms,
_non_exhaustive: (),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_src_process {
pub type_size: usize,
pub pid: u32,
pub debug_syms: bool,
pub perf_map: bool,
pub no_map_files: bool,
pub no_vdso: bool,
pub reserved: [u8; 16],
}
impl Default for blaze_symbolize_src_process {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
pid: 0,
debug_syms: false,
perf_map: false,
no_map_files: false,
no_vdso: false,
reserved: [0; 16],
}
}
}
impl From<blaze_symbolize_src_process> for Process {
fn from(process: blaze_symbolize_src_process) -> Self {
let blaze_symbolize_src_process {
type_size: _,
pid,
debug_syms,
perf_map,
no_map_files,
no_vdso,
reserved: _,
} = process;
Self {
pid: pid.into(),
debug_syms,
perf_map,
map_files: !no_map_files,
vdso: !no_vdso,
_non_exhaustive: (),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_src_gsym_data {
pub type_size: usize,
pub data: *const u8,
pub data_len: usize,
pub reserved: [u8; 16],
}
impl Default for blaze_symbolize_src_gsym_data {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
data: ptr::null(),
data_len: 0,
reserved: [0; 16],
}
}
}
impl From<blaze_symbolize_src_gsym_data> for GsymData<'_> {
fn from(gsym: blaze_symbolize_src_gsym_data) -> Self {
let blaze_symbolize_src_gsym_data {
type_size: _,
data,
data_len,
reserved: _,
} = gsym;
Self {
data: unsafe { slice_from_aligned_user_array(data, data_len) },
_non_exhaustive: (),
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_src_gsym_file {
pub type_size: usize,
pub path: *const c_char,
pub reserved: [u8; 16],
}
impl Default for blaze_symbolize_src_gsym_file {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
path: ptr::null(),
reserved: [0; 16],
}
}
}
impl From<blaze_symbolize_src_gsym_file> for GsymFile {
fn from(gsym: blaze_symbolize_src_gsym_file) -> Self {
let blaze_symbolize_src_gsym_file {
type_size: _,
path,
reserved: _,
} = gsym;
Self {
path: unsafe { from_cstr(path) },
_non_exhaustive: (),
}
}
}
pub type blaze_symbolizer = Symbolizer;
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct blaze_symbolize_reason(u8);
impl blaze_symbolize_reason {
pub const SUCCESS: Self = Self(0);
pub const UNMAPPED: Self = Self(1);
pub const INVALID_FILE_OFFSET: Self = Self(2);
pub const MISSING_COMPONENT: Self = Self(3);
pub const MISSING_SYMS: Self = Self(4);
pub const UNKNOWN_ADDR: Self = Self(5);
pub const UNSUPPORTED: Self = Self(6);
pub const IGNORED_ERROR: Self = Self(7);
}
impl From<Reason> for blaze_symbolize_reason {
fn from(reason: Reason) -> Self {
match reason {
Reason::Unmapped => Self::UNMAPPED,
Reason::InvalidFileOffset => Self::INVALID_FILE_OFFSET,
Reason::MissingComponent => Self::MISSING_COMPONENT,
Reason::MissingSyms => Self::MISSING_SYMS,
Reason::Unsupported => Self::UNSUPPORTED,
Reason::UnknownAddr => Self::UNKNOWN_ADDR,
Reason::IgnoredError => Self::IGNORED_ERROR,
_ => unreachable!(),
}
}
}
#[no_mangle]
pub extern "C" fn blaze_symbolize_reason_str(reason: blaze_symbolize_reason) -> *const c_char {
match reason {
blaze_symbolize_reason::SUCCESS => b"success\0".as_ptr().cast(),
blaze_symbolize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
blaze_symbolize_reason::INVALID_FILE_OFFSET => {
Reason::InvalidFileOffset.as_bytes().as_ptr().cast()
}
blaze_symbolize_reason::MISSING_COMPONENT => {
Reason::MissingComponent.as_bytes().as_ptr().cast()
}
blaze_symbolize_reason::MISSING_SYMS => Reason::MissingSyms.as_bytes().as_ptr().cast(),
blaze_symbolize_reason::UNKNOWN_ADDR => Reason::UnknownAddr.as_bytes().as_ptr().cast(),
blaze_symbolize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
blaze_symbolize_reason::IGNORED_ERROR => Reason::IgnoredError.as_bytes().as_ptr().cast(),
_ => b"unknown reason\0".as_ptr().cast(),
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_code_info {
pub dir: *const c_char,
pub file: *const c_char,
pub line: u32,
pub column: u16,
pub reserved: [u8; 10],
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolize_inlined_fn {
pub name: *const c_char,
pub code_info: blaze_symbolize_code_info,
pub reserved: [u8; 8],
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_sym {
pub name: *const c_char,
pub module: *const c_char,
pub addr: Addr,
pub offset: usize,
pub size: isize,
pub code_info: blaze_symbolize_code_info,
pub inlined_cnt: usize,
pub inlined: *const blaze_symbolize_inlined_fn,
pub reason: blaze_symbolize_reason,
pub reserved: [u8; 15],
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_syms {
pub cnt: usize,
pub syms: [blaze_sym; 0],
}
pub(crate) unsafe fn from_cstr(cstr: *const c_char) -> PathBuf {
Path::new(OsStr::from_bytes(
unsafe { CStr::from_ptr(cstr) }.to_bytes(),
))
.to_path_buf()
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_symbolizer_opts {
pub type_size: usize,
pub debug_dirs: *const *const c_char,
pub debug_dirs_len: usize,
pub auto_reload: bool,
pub code_info: bool,
pub inlined_fns: bool,
pub demangle: bool,
pub reserved: [u8; 20],
}
impl Default for blaze_symbolizer_opts {
fn default() -> Self {
Self {
type_size: mem::size_of::<Self>(),
debug_dirs: ptr::null(),
debug_dirs_len: 0,
auto_reload: false,
code_info: false,
inlined_fns: false,
demangle: false,
reserved: [0; 20],
}
}
}
#[no_mangle]
pub extern "C" fn blaze_symbolizer_new() -> *mut blaze_symbolizer {
let symbolizer = Symbolizer::new();
let symbolizer_box = Box::new(symbolizer);
let () = set_last_err(blaze_err::OK);
Box::into_raw(symbolizer_box)
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolizer_new_opts(
opts: *const blaze_symbolizer_opts,
) -> *mut blaze_symbolizer {
if !input_zeroed!(opts, blaze_symbolizer_opts) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null_mut()
}
let opts = input_sanitize!(opts, blaze_symbolizer_opts);
let blaze_symbolizer_opts {
type_size: _,
debug_dirs,
debug_dirs_len: _debug_dirs_len,
auto_reload,
code_info,
inlined_fns,
demangle,
reserved: _,
} = opts;
let builder = Symbolizer::builder()
.enable_auto_reload(auto_reload)
.enable_code_info(code_info)
.enable_inlined_fns(inlined_fns)
.enable_demangling(demangle);
let builder = if debug_dirs.is_null() {
builder
} else {
#[cfg(feature = "dwarf")]
{
let slice = unsafe { slice_from_user_array(debug_dirs, _debug_dirs_len) };
let iter = slice.iter().map(|cstr| {
Path::new(OsStr::from_bytes(
unsafe { CStr::from_ptr(cstr.cast()) }.to_bytes(),
))
});
builder.set_debug_dirs(Some(iter))
}
#[cfg(not(feature = "dwarf"))]
{
builder
}
};
let symbolizer = builder.build();
let symbolizer_box = Box::new(symbolizer);
let () = set_last_err(blaze_err::OK);
Box::into_raw(symbolizer_box)
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolizer_free(symbolizer: *mut blaze_symbolizer) {
if !symbolizer.is_null() {
drop(unsafe { Box::from_raw(symbolizer) });
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_cache_elf(
symbolizer: *mut blaze_symbolizer,
cache: *const blaze_cache_src_elf,
) {
if !input_zeroed!(cache, blaze_cache_src_elf) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return
}
let cache = input_sanitize!(cache, blaze_cache_src_elf);
let cache = cache::Cache::from(cache::Elf::from(cache));
let symbolizer = unsafe { &*symbolizer };
let result = symbolizer.cache(&cache);
let err = result
.map(|()| blaze_err::OK)
.unwrap_or_else(|err| err.kind().into());
let () = set_last_err(err);
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_cache_process(
symbolizer: *mut blaze_symbolizer,
cache: *const blaze_cache_src_process,
) {
if !input_zeroed!(cache, blaze_cache_src_process) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return
}
let cache = input_sanitize!(cache, blaze_cache_src_process);
let cache = cache::Cache::from(cache::Process::from(cache));
let symbolizer = unsafe { &*symbolizer };
let result = symbolizer.cache(&cache);
let err = result
.map(|()| blaze_err::OK)
.unwrap_or_else(|err| err.kind().into());
let () = set_last_err(err);
}
fn make_cstr(src: &OsStr, cstr_last: &mut *mut c_char) -> *mut c_char {
let cstr = *cstr_last;
unsafe { ptr::copy_nonoverlapping(src.as_bytes().as_ptr(), cstr as *mut u8, src.len()) };
unsafe { *cstr.add(src.len()) = 0 };
*cstr_last = unsafe { cstr_last.add(src.len() + 1) };
cstr
}
fn convert_code_info(
code_info_in: Option<&CodeInfo>,
code_info_out: &mut blaze_symbolize_code_info,
cstr_last: &mut *mut c_char,
) {
code_info_out.dir = code_info_in
.as_ref()
.and_then(|info| {
info.dir
.as_ref()
.map(|d| make_cstr(d.as_os_str(), cstr_last))
})
.unwrap_or_else(ptr::null_mut);
code_info_out.file = code_info_in
.as_ref()
.map(|info| make_cstr(&info.file, cstr_last))
.unwrap_or_else(ptr::null_mut);
code_info_out.line = code_info_in
.as_ref()
.and_then(|info| info.line)
.unwrap_or(0);
code_info_out.column = code_info_in
.as_ref()
.and_then(|info| info.column)
.unwrap_or(0);
}
pub(crate) fn convert_sym(
sym: &Sym,
sym_ref: &mut blaze_sym,
inlined_last: &mut *mut blaze_symbolize_inlined_fn,
cstr_last: &mut *mut c_char,
) {
let name_ptr = make_cstr(OsStr::new(sym.name.as_ref()), cstr_last);
let module_ptr = sym
.module
.as_deref()
.map(|module| make_cstr(module, cstr_last))
.unwrap_or(ptr::null_mut());
sym_ref.name = name_ptr;
sym_ref.module = module_ptr;
sym_ref.addr = sym.addr;
sym_ref.offset = sym.offset;
sym_ref.size = sym
.size
.map(|size| isize::try_from(size).unwrap_or(isize::MAX))
.unwrap_or(-1);
convert_code_info(sym.code_info.as_deref(), &mut sym_ref.code_info, cstr_last);
sym_ref.inlined_cnt = sym.inlined.len();
sym_ref.inlined = *inlined_last;
sym_ref.reason = blaze_symbolize_reason::SUCCESS;
for inlined in sym.inlined.iter() {
let inlined_ref = unsafe { &mut **inlined_last };
let name_ptr = make_cstr(OsStr::new(inlined.name.as_ref()), cstr_last);
inlined_ref.name = name_ptr;
convert_code_info(
inlined.code_info.as_ref(),
&mut inlined_ref.code_info,
cstr_last,
);
*inlined_last = unsafe { inlined_last.add(1) };
}
}
fn convert_symbolizedresults_to_c(results: Vec<Symbolized>) -> *const blaze_syms {
let (strtab_size, inlined_fn_cnt) = results.iter().fold((0, 0), |acc, sym| match sym {
Symbolized::Sym(sym) => (acc.0 + sym.c_str_size(), acc.1 + sym.inlined.len()),
Symbolized::Unknown(..) => acc,
});
let buf_size = mem::size_of::<u64>()
+ strtab_size
+ mem::size_of::<blaze_syms>()
+ mem::size_of::<blaze_sym>() * results.len()
+ mem::size_of::<blaze_symbolize_inlined_fn>() * inlined_fn_cnt;
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 syms_buf = unsafe { buf.add(mem::size_of::<u64>()) };
let syms_ptr = syms_buf.cast::<blaze_syms>();
let mut syms_last = unsafe { (*syms_ptr).syms.as_mut_ptr() };
let mut inlined_last = unsafe {
syms_buf.add(mem::size_of::<blaze_syms>() + mem::size_of::<blaze_sym>() * results.len())
} as *mut blaze_symbolize_inlined_fn;
let mut cstr_last = unsafe {
syms_buf.add(
mem::size_of::<blaze_syms>()
+ mem::size_of::<blaze_sym>() * results.len()
+ mem::size_of::<blaze_symbolize_inlined_fn>() * inlined_fn_cnt,
)
} as *mut c_char;
unsafe { (*syms_ptr).cnt = results.len() };
for sym in results {
match sym {
Symbolized::Sym(sym) => {
let sym_ref = unsafe { &mut *syms_last };
let () = convert_sym(&sym, sym_ref, &mut inlined_last, &mut cstr_last);
}
Symbolized::Unknown(reason) => {
let () = unsafe { syms_last.write_bytes(0, 1) };
let sym_ref = unsafe { &mut *syms_last };
sym_ref.reason = reason.into();
}
}
syms_last = unsafe { syms_last.add(1) };
}
syms_ptr
}
unsafe fn blaze_symbolize_impl(
symbolizer: *mut blaze_symbolizer,
src: Source<'_>,
inputs: Input<*const u64>,
input_cnt: usize,
) -> *const blaze_syms {
let symbolizer = unsafe { &*symbolizer };
let addrs = unsafe { slice_from_user_array(*inputs.as_inner_ref(), input_cnt) };
let input = match inputs {
Input::AbsAddr(..) => Input::AbsAddr(addrs.deref()),
Input::VirtOffset(..) => Input::VirtOffset(addrs.deref()),
Input::FileOffset(..) => Input::FileOffset(addrs.deref()),
};
let result = symbolizer.symbolize(&src, input);
match result {
Ok(results) if results.is_empty() => {
let () = set_last_err(blaze_err::OK);
ptr::null()
}
Ok(results) => {
let result = convert_symbolizedresults_to_c(results);
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_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_process_abs_addrs(
symbolizer: *mut blaze_symbolizer,
src: *const blaze_symbolize_src_process,
abs_addrs: *const Addr,
abs_addr_cnt: usize,
) -> *const blaze_syms {
if !input_zeroed!(src, blaze_symbolize_src_process) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_symbolize_src_process);
let src = Source::from(Process::from(src));
unsafe { blaze_symbolize_impl(symbolizer, src, Input::AbsAddr(abs_addrs), abs_addr_cnt) }
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_kernel_abs_addrs(
symbolizer: *mut blaze_symbolizer,
src: *const blaze_symbolize_src_kernel,
abs_addrs: *const Addr,
abs_addr_cnt: usize,
) -> *const blaze_syms {
if !input_zeroed!(src, blaze_symbolize_src_kernel) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_symbolize_src_kernel);
let src = Source::from(Kernel::from(src));
unsafe { blaze_symbolize_impl(symbolizer, src, Input::AbsAddr(abs_addrs), abs_addr_cnt) }
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_elf_virt_offsets(
symbolizer: *mut blaze_symbolizer,
src: *const blaze_symbolize_src_elf,
virt_offsets: *const Addr,
virt_offset_cnt: usize,
) -> *const blaze_syms {
if !input_zeroed!(src, blaze_symbolize_src_elf) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_symbolize_src_elf);
let src = Source::from(Elf::from(src));
unsafe {
blaze_symbolize_impl(
symbolizer,
src,
Input::VirtOffset(virt_offsets),
virt_offset_cnt,
)
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_elf_file_offsets(
symbolizer: *mut blaze_symbolizer,
src: *const blaze_symbolize_src_elf,
file_offsets: *const Addr,
file_offset_cnt: usize,
) -> *const blaze_syms {
if !input_zeroed!(src, blaze_symbolize_src_elf) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_symbolize_src_elf);
let src = Source::from(Elf::from(src));
unsafe {
blaze_symbolize_impl(
symbolizer,
src,
Input::FileOffset(file_offsets),
file_offset_cnt,
)
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_gsym_data_virt_offsets(
symbolizer: *mut blaze_symbolizer,
src: *const blaze_symbolize_src_gsym_data,
virt_offsets: *const Addr,
virt_offset_cnt: usize,
) -> *const blaze_syms {
if !input_zeroed!(src, blaze_symbolize_src_gsym_data) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_symbolize_src_gsym_data);
let src = Source::from(GsymData::from(src));
unsafe {
blaze_symbolize_impl(
symbolizer,
src,
Input::VirtOffset(virt_offsets),
virt_offset_cnt,
)
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_symbolize_gsym_file_virt_offsets(
symbolizer: *mut blaze_symbolizer,
src: *const blaze_symbolize_src_gsym_file,
virt_offsets: *const Addr,
virt_offset_cnt: usize,
) -> *const blaze_syms {
if !input_zeroed!(src, blaze_symbolize_src_gsym_file) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null()
}
let src = input_sanitize!(src, blaze_symbolize_src_gsym_file);
let src = Source::from(GsymFile::from(src));
unsafe {
blaze_symbolize_impl(
symbolizer,
src,
Input::VirtOffset(virt_offsets),
virt_offset_cnt,
)
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_syms_free(syms: *const blaze_syms) {
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()) };
}
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Cow;
use std::ffi::CString;
use std::fs::copy;
use std::fs::read as read_file;
use std::fs::remove_file;
use std::hint::black_box;
use std::io::Error;
use std::os::unix::ffi::OsStringExt as _;
use std::slice;
use blazesym::inspect;
use blazesym::normalize;
use blazesym::symbolize::InlinedFn;
use blazesym::symbolize::Reason;
use blazesym::Pid;
use tempfile::tempdir;
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_cache_src_elf>(), 32);
assert_eq!(mem::size_of::<blaze_cache_src_process>(), 32);
assert_eq!(mem::size_of::<blaze_symbolize_src_elf>(), 40);
assert_eq!(mem::size_of::<blaze_symbolize_src_kernel>(), 48);
assert_eq!(mem::size_of::<blaze_symbolize_src_process>(), 32);
assert_eq!(mem::size_of::<blaze_symbolize_src_gsym_data>(), 40);
assert_eq!(mem::size_of::<blaze_symbolize_src_gsym_file>(), 32);
assert_eq!(mem::size_of::<blaze_symbolizer_opts>(), 48);
assert_eq!(mem::size_of::<blaze_symbolize_code_info>(), 32);
assert_eq!(mem::size_of::<blaze_symbolize_inlined_fn>(), 48);
assert_eq!(mem::size_of::<blaze_sym>(), 104);
}
#[tag(miri)]
#[test]
fn debug_repr() {
let elf = blaze_symbolize_src_elf {
type_size: 24,
..Default::default()
};
assert_eq!(
format!("{elf:?}"),
"blaze_symbolize_src_elf { type_size: 24, path: 0x0, debug_syms: false, 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 kernel = blaze_symbolize_src_kernel {
type_size: 32,
debug_syms: true,
..Default::default()
};
assert_eq!(
format!("{kernel:?}"),
"blaze_symbolize_src_kernel { type_size: 32, kallsyms: 0x0, vmlinux: 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 process = blaze_symbolize_src_process {
type_size: 16,
pid: 1337,
debug_syms: true,
..Default::default()
};
assert_eq!(
format!("{process:?}"),
"blaze_symbolize_src_process { type_size: 16, pid: 1337, debug_syms: true, perf_map: false, no_map_files: false, no_vdso: false, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
let gsym_data = blaze_symbolize_src_gsym_data {
type_size: 24,
data: ptr::null(),
data_len: 0,
reserved: [0; 16],
};
assert_eq!(
format!("{gsym_data:?}"),
"blaze_symbolize_src_gsym_data { type_size: 24, data: 0x0, data_len: 0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
let gsym_file = blaze_symbolize_src_gsym_file {
type_size: 16,
path: ptr::null(),
reserved: [0; 16],
};
assert_eq!(
format!("{gsym_file:?}"),
"blaze_symbolize_src_gsym_file { type_size: 16, path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
let sym = blaze_sym {
name: ptr::null(),
module: ptr::null(),
addr: 0x1337,
offset: 24,
size: 16,
code_info: blaze_symbolize_code_info {
dir: ptr::null(),
file: ptr::null(),
line: 42,
column: 1,
reserved: [0; 10],
},
inlined_cnt: 0,
inlined: ptr::null(),
reason: blaze_symbolize_reason::UNSUPPORTED,
reserved: [0; 15],
};
assert_eq!(
format!("{sym:?}"),
"blaze_sym { name: 0x0, module: 0x0, addr: 4919, offset: 24, size: 16, code_info: blaze_symbolize_code_info { dir: 0x0, file: 0x0, line: 42, column: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, inlined_cnt: 0, inlined: 0x0, reason: blaze_symbolize_reason(6), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
let inlined = blaze_symbolize_inlined_fn {
name: ptr::null(),
code_info: blaze_symbolize_code_info {
dir: ptr::null(),
file: ptr::null(),
line: 42,
column: 1,
reserved: [0; 10],
},
reserved: [0; 8],
};
assert_eq!(
format!("{inlined:?}"),
"blaze_symbolize_inlined_fn { name: 0x0, code_info: blaze_symbolize_code_info { dir: 0x0, file: 0x0, line: 42, column: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, reserved: [0, 0, 0, 0, 0, 0, 0, 0] }"
);
let syms = blaze_syms { cnt: 0, syms: [] };
assert_eq!(format!("{syms:?}"), "blaze_syms { cnt: 0, syms: [] }");
let opts = blaze_symbolizer_opts {
type_size: 16,
demangle: true,
..Default::default()
};
assert_eq!(
format!("{opts:?}"),
"blaze_symbolizer_opts { type_size: 16, debug_dirs: 0x0, debug_dirs_len: 0, auto_reload: false, code_info: false, inlined_fns: false, demangle: true, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
}
#[tag(miri)]
#[test]
fn reason_stringification() {
let data = [
(Reason::Unmapped, blaze_symbolize_reason::UNMAPPED),
(
Reason::InvalidFileOffset,
blaze_symbolize_reason::INVALID_FILE_OFFSET,
),
(
Reason::MissingComponent,
blaze_symbolize_reason::MISSING_COMPONENT,
),
(Reason::MissingSyms, blaze_symbolize_reason::MISSING_SYMS),
(Reason::Unsupported, blaze_symbolize_reason::UNSUPPORTED),
(Reason::UnknownAddr, blaze_symbolize_reason::UNKNOWN_ADDR),
(Reason::IgnoredError, blaze_symbolize_reason::IGNORED_ERROR),
];
for (reason, expected) in data {
assert_eq!(blaze_symbolize_reason::from(reason), expected);
let cstr = unsafe { CStr::from_ptr(blaze_symbolize_reason_str(expected)) };
let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
assert_eq!(cstr, expected);
}
}
#[tag(miri)]
#[test]
fn kernel_conversion() {
let kernel = blaze_symbolize_src_kernel::default();
let kernel = Kernel::from(kernel);
assert_eq!(kernel.kallsyms, MaybeDefault::Default);
assert_eq!(kernel.vmlinux, MaybeDefault::Default);
let kernel = blaze_symbolize_src_kernel {
kallsyms: b"\0" as *const _ as *const c_char,
vmlinux: b"\0" as *const _ as *const c_char,
..Default::default()
};
let kernel = Kernel::from(kernel);
assert_eq!(kernel.kallsyms, MaybeDefault::None);
assert_eq!(kernel.vmlinux, MaybeDefault::None);
let kernel = blaze_symbolize_src_kernel {
kallsyms: b"/proc/kallsyms\0" as *const _ as *const c_char,
vmlinux: b"/boot/vmlinux\0" as *const _ as *const c_char,
debug_syms: false,
..Default::default()
};
let kernel = Kernel::from(kernel);
assert_eq!(
kernel.kallsyms,
MaybeDefault::Some(PathBuf::from("/proc/kallsyms"))
);
assert_eq!(
kernel.vmlinux,
MaybeDefault::Some(PathBuf::from("/boot/vmlinux"))
);
}
#[tag(miri)]
#[test]
fn cache_process_conversion() {
let process = blaze_cache_src_process {
pid: 42,
cache_vmas: false,
..Default::default()
};
let process = cache::Process::from(process);
assert_eq!(process.pid, Pid::from(42))
}
#[tag(miri)]
#[test]
fn symbol_conversion() {
fn touch<X: Clone>(x: &X) {
let x = x.clone();
let _x = black_box(x);
}
fn touch_cstr(s: *const c_char) {
if !s.is_null() {
let s = unsafe { CStr::from_ptr(s) }.to_bytes();
let _x = black_box(s);
}
}
fn touch_code_info(code_info: &blaze_symbolize_code_info) {
let blaze_symbolize_code_info {
dir,
file,
line,
column,
reserved: _,
} = code_info;
let _x = touch_cstr(*dir);
let _x = touch_cstr(*file);
let _x = touch(line);
let _x = touch(column);
}
fn touch_syms(syms: *const blaze_syms) {
let syms = unsafe { &*syms };
for i in 0..syms.cnt {
let sym = unsafe { &*syms.syms.as_slice().as_ptr().add(i) };
let blaze_sym {
name,
module,
addr,
offset,
size,
code_info,
inlined_cnt,
inlined,
reason,
reserved: _,
} = sym;
let () = touch_cstr(*name);
let () = touch_cstr(*module);
let _x = touch(addr);
let _x = touch(offset);
let _x = touch(size);
let () = touch_code_info(code_info);
for j in 0..*inlined_cnt {
let inlined_fn = unsafe { &*inlined.add(j) };
let blaze_symbolize_inlined_fn {
name,
code_info,
reserved: _,
} = inlined_fn;
let () = touch_cstr(*name);
let () = touch_code_info(code_info);
}
let () = touch(reason);
}
}
let results = vec![];
let syms = convert_symbolizedresults_to_c(results);
assert!(!syms.is_null());
let () = touch_syms(syms);
let () = unsafe { blaze_syms_free(syms) };
let results = vec![Symbolized::Sym(Sym {
name: "test".into(),
module: Some(Cow::from(OsStr::new("module"))),
addr: 0x1337,
offset: 0x1338,
size: Some(42),
code_info: Some(Box::new(CodeInfo {
dir: None,
file: OsStr::new("a-file").into(),
line: Some(42),
column: Some(43),
_non_exhaustive: (),
})),
inlined: vec![InlinedFn {
name: "inlined_fn".into(),
code_info: Some(CodeInfo {
dir: Some(Path::new("/some/dir").into()),
file: OsStr::new("another-file").into(),
line: Some(42),
column: Some(43),
_non_exhaustive: (),
}),
_non_exhaustive: (),
}]
.into_boxed_slice(),
_non_exhaustive: (),
})];
let syms = convert_symbolizedresults_to_c(results);
assert!(!syms.is_null());
let () = touch_syms(syms);
let () = unsafe { blaze_syms_free(syms) };
let results = vec![
Symbolized::Unknown(Reason::UnknownAddr),
Symbolized::Sym(Sym {
name: "test".into(),
module: Some(Cow::from(OsStr::new("module"))),
addr: 0x1337,
offset: 0x1338,
size: None,
code_info: None,
inlined: vec![InlinedFn {
name: "inlined_fn".into(),
code_info: None,
_non_exhaustive: (),
}]
.into_boxed_slice(),
_non_exhaustive: (),
}),
Symbolized::Unknown(Reason::InvalidFileOffset),
];
let syms = convert_symbolizedresults_to_c(results);
assert!(!syms.is_null());
let () = touch_syms(syms);
let () = unsafe { blaze_syms_free(syms) };
}
#[tag(miri)]
#[test]
fn symbolizer_creation() {
let symbolizer = blaze_symbolizer_new();
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[tag(miri)]
#[test]
fn symbolizer_creation_with_opts() {
let opts = blaze_symbolizer_opts {
demangle: true,
..Default::default()
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_elf_dwarf_gsym() {
fn test<F>(symbolize: F, has_code_info: bool)
where
F: FnOnce(*mut blaze_symbolizer, *const Addr, usize) -> *const blaze_syms,
{
let symbolizer = blaze_symbolizer_new();
let addrs = [0x2000200];
let result = symbolize(symbolizer, addrs.as_ptr(), addrs.len());
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"factorial\0").unwrap()
);
assert_eq!(sym.reason, blaze_symbolize_reason::SUCCESS);
assert_eq!(sym.addr, 0x2000200);
assert_eq!(sym.offset, 0);
assert!(sym.size > 0);
if has_code_info {
assert!(!sym.code_info.dir.is_null());
assert!(!sym.code_info.file.is_null());
assert_eq!(
unsafe { CStr::from_ptr(sym.code_info.file) },
CStr::from_bytes_with_nul(b"test-stable-addrs.c\0").unwrap()
);
assert_eq!(sym.code_info.line, 10);
} else {
assert!(sym.code_info.dir.is_null());
assert!(sym.code_info.file.is_null());
assert_eq!(sym.code_info.line, 0);
}
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs-no-dwarf.bin");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: true,
..Default::default()
};
let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs, addr_cnt)
};
test(symbolize, false);
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: true,
..Default::default()
};
let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs, addr_cnt)
};
test(symbolize, true);
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs.gsym");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let gsym_src = blaze_symbolize_src_gsym_file {
path: path_c.as_ptr(),
..Default::default()
};
let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
blaze_symbolize_gsym_file_virt_offsets(symbolizer, &gsym_src, addrs, addr_cnt)
};
test(symbolize, true);
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs.gsym");
let data = read_file(path).unwrap();
let gsym_src = blaze_symbolize_src_gsym_data {
data: data.as_ptr(),
data_len: data.len(),
..Default::default()
};
let symbolize = |symbolizer, addrs, addr_cnt| unsafe {
blaze_symbolize_gsym_data_virt_offsets(symbolizer, &gsym_src, addrs, addr_cnt)
};
test(symbolize, true);
}
#[test]
#[cfg_attr(
not(target_pointer_width = "64"),
ignore = "loads 64 bit shared object"
)]
fn symbolize_elf_file_offset() {
let test_so = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("libtest-so.so")
.canonicalize()
.unwrap();
let so_cstr = CString::new(test_so.clone().into_os_string().into_vec()).unwrap();
let handle = unsafe { libc::dlopen(so_cstr.as_ptr(), libc::RTLD_NOW) };
assert!(!handle.is_null());
let the_answer_addr = unsafe { libc::dlsym(handle, "the_answer\0".as_ptr().cast()) };
assert!(!the_answer_addr.is_null());
let normalizer = normalize::Normalizer::new();
let normalized = normalizer
.normalize_user_addrs(Pid::Slf, [the_answer_addr as Addr].as_slice())
.unwrap();
assert_eq!(normalized.outputs.len(), 1);
assert_eq!(normalized.meta.len(), 1);
let rc = unsafe { libc::dlclose(handle) };
assert_eq!(rc, 0, "{}", Error::last_os_error());
let output = normalized.outputs[0];
let meta = &normalized.meta[output.1];
assert_eq!(meta.as_elf().unwrap().path, test_so);
let symbolizer = blaze_symbolizer_new();
let elf_src = blaze_symbolize_src_elf {
path: so_cstr.as_ptr(),
..Default::default()
};
let offsets = [output.0];
let result = unsafe {
blaze_symbolize_elf_file_offsets(
symbolizer,
&elf_src,
offsets.as_slice().as_ptr(),
offsets.len(),
)
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert!(!sym.name.is_null());
assert!(!sym.module.is_null());
assert!(sym.size > 0);
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"the_answer\0").unwrap()
);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[tag(other_os)]
#[test]
fn symbolize_elf_cached() {
let dir = tempdir().unwrap();
let path__ = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs.bin");
let path = dir.path().join("test-stable-addrs-temporary.bin");
let _count = copy(&path__, &path).unwrap();
let symbolizer = blaze_symbolizer_new();
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let cache = blaze_cache_src_elf {
path: path_c.as_ptr(),
..Default::default()
};
let () = unsafe { blaze_symbolize_cache_elf(symbolizer, &cache) };
assert_eq!(blaze_err_last(), blaze_err::OK);
let () = remove_file(&path).unwrap();
let src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: false,
..Default::default()
};
let addrs = [0x2000200];
let result = unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"factorial\0").unwrap()
);
assert_eq!(sym.reason, blaze_symbolize_reason::SUCCESS);
assert_eq!(sym.addr, 0x2000200);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_dwarf_demangle() {
fn test(path: &Path, addr: Addr) -> Result<(), ()> {
let opts = blaze_symbolizer_opts {
code_info: true,
inlined_fns: true,
..Default::default()
};
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: true,
..Default::default()
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let addrs = [addr];
let result = unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
let name = unsafe { CStr::from_ptr(sym.name) };
assert!(
name.to_str().unwrap().contains("test13test_function"),
"{name:?}"
);
if sym.inlined_cnt == 0 {
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
return Err(())
}
assert_eq!(sym.inlined_cnt, 1);
let name = unsafe { CStr::from_ptr((*sym.inlined).name) };
assert!(
name.to_str().unwrap().contains("test12inlined_call"),
"{name:?}"
);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
let opts = blaze_symbolizer_opts {
code_info: true,
inlined_fns: true,
demangle: true,
..Default::default()
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let addrs = [addr];
let result = unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"test::test_function\0").unwrap()
);
assert_eq!(unsafe { CStr::from_ptr(sym.module) }, &*path_c);
assert_eq!(sym.inlined_cnt, 1);
assert_eq!(
unsafe { CStr::from_ptr((*sym.inlined).name) },
CStr::from_bytes_with_nul(b"test::inlined_call\0").unwrap()
);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
Ok(())
}
let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-rs.bin");
let elf = inspect::source::Elf::new(&test_dwarf);
let src = inspect::source::Source::Elf(elf);
let inspector = inspect::Inspector::new();
let results = inspector
.lookup(&src, &["_RNvCs69hjMPjVIJK_4test13test_function"])
.unwrap()
.into_iter()
.flatten()
.collect::<Vec<_>>();
assert!(!results.is_empty());
let addr = results[0].addr;
let src = Source::Elf(Elf::new(&test_dwarf));
let symbolizer = Symbolizer::builder().enable_demangling(false).build();
let result = symbolizer
.symbolize_single(&src, Input::VirtOffset(addr))
.unwrap()
.into_sym()
.unwrap();
let addr = result.addr;
let size = result.size.unwrap() as u64;
for inst_addr in addr..addr + size {
if test(&test_dwarf, inst_addr).is_ok() {
return
}
}
panic!("failed to find inlined function call");
}
#[test]
fn symbolize_in_process() {
let process_src = blaze_symbolize_src_process {
pid: 0,
debug_syms: true,
perf_map: true,
..Default::default()
};
let symbolizer = blaze_symbolizer_new();
let addrs = [blaze_symbolizer_new as *const () as Addr];
let result = unsafe {
blaze_symbolize_process_abs_addrs(symbolizer, &process_src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"blaze_symbolizer_new\0").unwrap()
);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_in_process_cached() {
let symbolizer = blaze_symbolizer_new();
let cache = blaze_cache_src_process {
pid: 0,
cache_vmas: true,
..Default::default()
};
let () = unsafe { blaze_symbolize_cache_process(symbolizer, &cache) };
assert_eq!(blaze_err_last(), blaze_err::OK);
let src = blaze_symbolize_src_process {
pid: 0,
debug_syms: true,
perf_map: true,
..Default::default()
};
let addrs = [blaze_symbolizer_new as *const () as Addr];
let result = unsafe {
blaze_symbolize_process_abs_addrs(symbolizer, &src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"blaze_symbolizer_new\0").unwrap()
);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_in_kernel() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("kallsyms");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let src = blaze_symbolize_src_kernel {
kallsyms: path_c.as_ptr(),
vmlinux: b"\0" as *const _ as *const c_char,
..Default::default()
};
let symbolizer = blaze_symbolizer_new();
let addrs = [0xc080a470];
let result = unsafe {
blaze_symbolize_kernel_abs_addrs(symbolizer, &src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(
unsafe { CStr::from_ptr(sym.name) },
CStr::from_bytes_with_nul(b"init_task\0").unwrap()
);
assert_eq!(sym.module, ptr::null_mut());
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_elf_non_existent() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("does-not-actually-exist.bin");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: true,
..Default::default()
};
let symbolizer = blaze_symbolizer_new();
let addrs = [blaze_symbolizer_new as *const () as Addr];
let result = unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
};
assert!(result.is_null());
assert_eq!(blaze_err_last(), blaze_err::NOT_FOUND);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_no_debug_dirs() {
let dir = tempdir().unwrap();
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs-stripped-with-link.bin");
let dst = dir.path().join("test-stable-addrs-stripped-with-link.bin");
let _count = copy(path, &dst).unwrap();
let debug_dirs = [];
let opts = blaze_symbolizer_opts {
debug_dirs: debug_dirs.as_ptr(),
debug_dirs_len: debug_dirs.len(),
code_info: true,
inlined_fns: true,
demangle: true,
..Default::default()
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let path_c = CString::new(dst.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: true,
..Default::default()
};
let addrs = [0x2000200];
let result = unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
assert_eq!(sym.name, ptr::null());
assert_eq!(sym.module, ptr::null());
assert_eq!(sym.reason, blaze_symbolize_reason::MISSING_SYMS);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
#[test]
fn symbolize_configurable_debug_dirs() {
let debug_dir1 = tempdir().unwrap();
let debug_dir1_c = CString::new(debug_dir1.path().to_str().unwrap()).unwrap();
let debug_dir2 = tempdir().unwrap();
let debug_dir2_c = CString::new(debug_dir2.path().to_str().unwrap()).unwrap();
let src = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs-dwarf-only.dbg");
let dst = debug_dir2.path().join("test-stable-addrs-dwarf-only.dbg");
let _count = copy(src, dst).unwrap();
let debug_dirs = [debug_dir1_c.as_ptr(), debug_dir2_c.as_ptr()];
let opts = blaze_symbolizer_opts {
debug_dirs: debug_dirs.as_ptr(),
debug_dirs_len: debug_dirs.len(),
code_info: true,
inlined_fns: true,
demangle: true,
..Default::default()
};
let symbolizer = unsafe { blaze_symbolizer_new_opts(&opts) };
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test-stable-addrs-stripped-with-link.bin");
let path_c = CString::new(path.to_str().unwrap()).unwrap();
let elf_src = blaze_symbolize_src_elf {
path: path_c.as_ptr(),
debug_syms: true,
..Default::default()
};
let addrs = [0x2000200];
let result = unsafe {
blaze_symbolize_elf_virt_offsets(symbolizer, &elf_src, addrs.as_ptr(), addrs.len())
};
assert!(!result.is_null());
let result = unsafe { &*result };
assert_eq!(result.cnt, 1);
let syms = unsafe { slice::from_raw_parts(result.syms.as_ptr(), result.cnt) };
let sym = &syms[0];
let name = unsafe { CStr::from_ptr(sym.name) };
assert_eq!(name.to_str().unwrap(), "factorial");
let module = unsafe { CStr::from_ptr(sym.module) };
assert!(
module
.to_str()
.unwrap()
.ends_with("test-stable-addrs-stripped-with-link.bin"),
"{module:?}"
);
let () = unsafe { blaze_syms_free(result) };
let () = unsafe { blaze_symbolizer_free(symbolizer) };
}
}