use std::alloc::alloc;
use std::alloc::dealloc;
use std::alloc::Layout;
use std::borrow::Cow;
use std::ffi::CString;
use std::ffi::OsString;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::mem;
use std::mem::size_of;
use std::mem::ManuallyDrop;
use std::os::raw::c_char;
use std::os::unix::ffi::OsStringExt as _;
use std::path::PathBuf;
use std::ptr;
use blazesym::normalize::Apk;
use blazesym::normalize::Elf;
use blazesym::normalize::NormalizeOpts;
use blazesym::normalize::Normalizer;
use blazesym::normalize::Reason;
use blazesym::normalize::Unknown;
use blazesym::normalize::UserMeta;
use blazesym::normalize::UserOutput;
use blazesym::symbolize::Sym;
use blazesym::Addr;
use crate::blaze_err;
#[cfg(doc)]
use crate::blaze_err_last;
use crate::blaze_sym;
use crate::blaze_symbolize_inlined_fn;
use crate::convert_sym;
use crate::set_last_err;
use crate::util::slice_from_user_array;
use crate::util::DynSize as _;
pub type blaze_normalizer = Normalizer;
#[repr(C)]
#[derive(Debug)]
pub struct blaze_normalizer_opts {
pub type_size: usize,
pub use_procmap_query: bool,
pub cache_vmas: bool,
pub build_ids: bool,
pub cache_build_ids: bool,
pub reserved: [u8; 20],
}
impl Default for blaze_normalizer_opts {
fn default() -> Self {
Self {
type_size: size_of::<Self>(),
use_procmap_query: false,
cache_vmas: false,
build_ids: false,
cache_build_ids: false,
reserved: [0; 20],
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_normalize_opts {
pub type_size: usize,
pub sorted_addrs: bool,
pub map_files: bool,
pub apk_to_elf: bool,
pub reserved: [u8; 21],
}
impl Default for blaze_normalize_opts {
fn default() -> Self {
Self {
type_size: size_of::<Self>(),
sorted_addrs: false,
map_files: false,
apk_to_elf: false,
reserved: [0; 21],
}
}
}
impl From<blaze_normalize_opts> for NormalizeOpts {
fn from(opts: blaze_normalize_opts) -> Self {
let blaze_normalize_opts {
type_size: _,
sorted_addrs,
map_files,
apk_to_elf,
reserved: _,
} = opts;
Self {
sorted_addrs,
map_files,
apk_to_elf,
_non_exhaustive: (),
}
}
}
#[no_mangle]
pub extern "C" fn blaze_normalizer_new() -> *mut blaze_normalizer {
let normalizer = Normalizer::new();
let normalizer_box = Box::new(normalizer);
let () = set_last_err(blaze_err::OK);
Box::into_raw(normalizer_box)
}
#[no_mangle]
pub unsafe extern "C" fn blaze_normalizer_new_opts(
opts: *const blaze_normalizer_opts,
) -> *mut blaze_normalizer {
if !input_zeroed!(opts, blaze_normalizer_opts) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null_mut()
}
let opts = input_sanitize!(opts, blaze_normalizer_opts);
let blaze_normalizer_opts {
type_size: _,
use_procmap_query,
cache_vmas,
build_ids,
cache_build_ids,
reserved: _,
} = opts;
let normalizer = Normalizer::builder()
.enable_procmap_query(use_procmap_query)
.enable_vma_caching(cache_vmas)
.enable_build_ids(build_ids)
.enable_build_id_caching(cache_build_ids)
.build();
let normalizer_box = Box::new(normalizer);
let () = set_last_err(blaze_err::OK);
Box::into_raw(normalizer_box)
}
#[no_mangle]
pub unsafe extern "C" fn blaze_normalizer_free(normalizer: *mut blaze_normalizer) {
if !normalizer.is_null() {
drop(unsafe { Box::from_raw(normalizer) });
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_normalized_output {
pub output: u64,
pub meta_idx: usize,
pub reserved: [u8; 16],
}
impl From<(u64, usize)> for blaze_normalized_output {
fn from((output, meta_idx): (u64, usize)) -> Self {
Self {
output,
meta_idx,
reserved: [0; 16],
}
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct blaze_user_meta_kind(u8);
impl blaze_user_meta_kind {
pub const UNKNOWN: Self = Self(0);
pub const APK: Self = Self(1);
pub const ELF: Self = Self(2);
pub const SYM: Self = Self(3);
#[deprecated]
pub const BLAZE_USER_META_UNKNOWN: Self = Self(0);
#[deprecated]
pub const BLAZE_USER_META_APK: Self = Self(1);
#[deprecated]
pub const BLAZE_USER_META_ELF: Self = Self(2);
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_user_meta_apk {
pub path: *mut c_char,
pub reserved: [u8; 16],
}
impl blaze_user_meta_apk {
fn from(other: Apk) -> ManuallyDrop<Self> {
let Apk {
path,
_non_exhaustive: (),
} = other;
let slf = Self {
path: CString::new(path.into_os_string().into_vec())
.expect("encountered path with NUL bytes")
.into_raw(),
reserved: [0; 16],
};
ManuallyDrop::new(slf)
}
unsafe fn free(self) {
let Self { path, reserved: _ } = self;
let _apk = Apk {
path: PathBuf::from(OsString::from_vec(
unsafe { CString::from_raw(path) }.into_bytes(),
)),
_non_exhaustive: (),
};
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_user_meta_elf {
pub path: *mut c_char,
pub build_id_len: usize,
pub build_id: *mut u8,
pub reserved: [u8; 16],
}
impl blaze_user_meta_elf {
fn from(other: Elf) -> ManuallyDrop<Self> {
let Elf {
path,
build_id,
_non_exhaustive: (),
} = other;
let slf = Self {
path: CString::new(path.into_os_string().into_vec())
.expect("encountered path with NUL bytes")
.into_raw(),
build_id_len: build_id
.as_ref()
.map(|build_id| build_id.len())
.unwrap_or(0),
build_id: build_id
.map(|build_id| {
unsafe {
Box::into_raw(build_id.to_vec().into_boxed_slice())
.as_mut()
.unwrap()
.as_mut_ptr()
}
})
.unwrap_or_else(ptr::null_mut),
reserved: [0; 16],
};
ManuallyDrop::new(slf)
}
unsafe fn free(self) {
let Self {
path,
build_id_len,
build_id,
reserved: _,
} = self;
let _elf = Elf {
path: PathBuf::from(OsString::from_vec(
unsafe { CString::from_raw(path) }.into_bytes(),
)),
build_id: (!build_id.is_null()).then(|| unsafe {
Cow::Owned(
Box::<[u8]>::from_raw(ptr::slice_from_raw_parts_mut(build_id, build_id_len))
.into_vec(),
)
}),
_non_exhaustive: (),
};
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_user_meta_sym {
pub sym: *const blaze_sym,
pub reserved: [u8; 16],
}
impl blaze_user_meta_sym {
fn from(sym: Sym) -> ManuallyDrop<Self> {
let strtab_size = sym.c_str_size();
let buf_size = mem::size_of::<u64>()
+ mem::size_of::<blaze_sym>()
+ sym.inlined.len() * mem::size_of::<blaze_symbolize_inlined_fn>()
+ strtab_size;
let buf = unsafe { alloc(Layout::from_size_align(buf_size, 8).unwrap()) };
assert!(!buf.is_null());
unsafe { *(buf as *mut u64) = buf_size as u64 };
let sym_buf = unsafe { buf.add(mem::size_of::<u64>()) }.cast::<blaze_sym>();
let mut inlined_last = unsafe { sym_buf.add(1) }.cast::<blaze_symbolize_inlined_fn>();
let mut cstr_last = unsafe { inlined_last.add(sym.inlined.len()) }.cast::<c_char>();
let sym_ref = unsafe { &mut *sym_buf };
let () = convert_sym(&sym, sym_ref, &mut inlined_last, &mut cstr_last);
let slf = Self {
sym: sym_buf,
reserved: [0; 16],
};
ManuallyDrop::new(slf)
}
unsafe fn free(self) {
let Self { sym, reserved: _ } = self;
let buf = unsafe { sym.byte_sub(mem::size_of::<u64>()).cast::<u8>().cast_mut() };
let size = unsafe { *(buf as *mut u64) } as usize;
let () = unsafe { dealloc(buf, Layout::from_size_align(size, 8).unwrap()) };
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct blaze_normalize_reason(u8);
impl blaze_normalize_reason {
pub const UNMAPPED: Self = Self(0);
pub const MISSING_COMPONENT: Self = Self(1);
pub const UNSUPPORTED: Self = Self(2);
pub const INVALID_FILE_OFFSET: Self = Self(3);
pub const MISSING_SYMS: Self = Self(4);
pub const UNKNOWN_ADDR: Self = Self(5);
pub const IGNORED_ERROR: Self = Self(7);
}
impl From<Reason> for blaze_normalize_reason {
fn from(reason: Reason) -> Self {
match reason {
Reason::Unmapped => Self::UNMAPPED,
Reason::MissingComponent => Self::MISSING_COMPONENT,
Reason::Unsupported => Self::UNSUPPORTED,
Reason::InvalidFileOffset => Self::INVALID_FILE_OFFSET,
Reason::MissingSyms => Self::MISSING_SYMS,
Reason::UnknownAddr => Self::UNKNOWN_ADDR,
Reason::IgnoredError => Self::IGNORED_ERROR,
_ => unreachable!(),
}
}
}
#[no_mangle]
pub extern "C" fn blaze_normalize_reason_str(reason: blaze_normalize_reason) -> *const c_char {
match reason {
blaze_normalize_reason::UNMAPPED => Reason::Unmapped.as_bytes().as_ptr().cast(),
blaze_normalize_reason::MISSING_COMPONENT => {
Reason::MissingComponent.as_bytes().as_ptr().cast()
}
blaze_normalize_reason::UNSUPPORTED => Reason::Unsupported.as_bytes().as_ptr().cast(),
blaze_normalize_reason::INVALID_FILE_OFFSET => {
Reason::InvalidFileOffset.as_bytes().as_ptr().cast()
}
blaze_normalize_reason::MISSING_SYMS => Reason::MissingSyms.as_bytes().as_ptr().cast(),
blaze_normalize_reason::UNKNOWN_ADDR => Reason::UnknownAddr.as_bytes().as_ptr().cast(),
blaze_normalize_reason::IGNORED_ERROR => Reason::IgnoredError.as_bytes().as_ptr().cast(),
_ => b"unknown reason\0".as_ptr().cast(),
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_user_meta_unknown {
pub reason: blaze_normalize_reason,
pub reserved: [u8; 15],
}
impl blaze_user_meta_unknown {
fn from(other: Unknown) -> ManuallyDrop<Self> {
let Unknown {
reason,
_non_exhaustive: (),
} = other;
let slf = Self {
reason: reason.into(),
reserved: [0; 15],
};
ManuallyDrop::new(slf)
}
fn free(self) {
let Self {
reason: _,
reserved: _,
} = self;
}
}
#[repr(C)]
pub union blaze_user_meta_variant {
pub apk: ManuallyDrop<blaze_user_meta_apk>,
pub elf: ManuallyDrop<blaze_user_meta_elf>,
pub sym: ManuallyDrop<blaze_user_meta_sym>,
pub unknown: ManuallyDrop<blaze_user_meta_unknown>,
}
impl Debug for blaze_user_meta_variant {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct(stringify!(blaze_user_meta_variant)).finish()
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_user_meta {
pub kind: blaze_user_meta_kind,
pub unused: [u8; 7],
pub variant: blaze_user_meta_variant,
pub reserved: [u8; 16],
}
impl blaze_user_meta {
fn from(other: UserMeta) -> ManuallyDrop<Self> {
let slf = match other {
UserMeta::Apk(apk) => Self {
kind: blaze_user_meta_kind::APK,
unused: [0; 7],
variant: blaze_user_meta_variant {
apk: blaze_user_meta_apk::from(apk),
},
reserved: [0; 16],
},
UserMeta::Elf(elf) => Self {
kind: blaze_user_meta_kind::ELF,
unused: [0; 7],
variant: blaze_user_meta_variant {
elf: blaze_user_meta_elf::from(elf),
},
reserved: [0; 16],
},
UserMeta::Sym(sym) => Self {
kind: blaze_user_meta_kind::SYM,
unused: [0; 7],
variant: blaze_user_meta_variant {
sym: blaze_user_meta_sym::from(sym),
},
reserved: [0; 16],
},
UserMeta::Unknown(unknown) => Self {
kind: blaze_user_meta_kind::UNKNOWN,
unused: [0; 7],
variant: blaze_user_meta_variant {
unknown: blaze_user_meta_unknown::from(unknown),
},
reserved: [0; 16],
},
_ => unreachable!(),
};
ManuallyDrop::new(slf)
}
unsafe fn free(self) {
match self.kind {
blaze_user_meta_kind::APK => unsafe {
ManuallyDrop::into_inner(self.variant.apk).free()
},
blaze_user_meta_kind::ELF => unsafe {
ManuallyDrop::into_inner(self.variant.elf).free()
},
blaze_user_meta_kind::SYM => unsafe {
ManuallyDrop::into_inner(self.variant.sym).free()
},
blaze_user_meta_kind::UNKNOWN => {
ManuallyDrop::into_inner(unsafe { self.variant.unknown }).free()
}
_ => {
debug_assert!(false)
}
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct blaze_normalized_user_output {
pub meta_cnt: usize,
pub metas: *mut blaze_user_meta,
pub output_cnt: usize,
pub outputs: *mut blaze_normalized_output,
pub reserved: [u8; 16],
}
impl blaze_normalized_user_output {
fn from(other: UserOutput) -> ManuallyDrop<Self> {
let slf = Self {
meta_cnt: other.meta.len(),
metas: unsafe {
Box::into_raw(
other
.meta
.into_iter()
.map(blaze_user_meta::from)
.map(ManuallyDrop::into_inner)
.collect::<Vec<_>>()
.into_boxed_slice(),
)
.as_mut()
.unwrap()
.as_mut_ptr()
},
output_cnt: other.outputs.len(),
outputs: unsafe {
Box::into_raw(
other
.outputs
.into_iter()
.map(blaze_normalized_output::from)
.collect::<Vec<_>>()
.into_boxed_slice(),
)
.as_mut()
.unwrap()
.as_mut_ptr()
},
reserved: [0; 16],
};
ManuallyDrop::new(slf)
}
}
unsafe fn blaze_normalize_user_addrs_impl(
normalizer: *const blaze_normalizer,
pid: u32,
addrs: *const Addr,
addr_cnt: usize,
opts: &NormalizeOpts,
) -> *mut blaze_normalized_user_output {
let normalizer = unsafe { &*normalizer };
let addrs = unsafe { slice_from_user_array(addrs, addr_cnt) };
let result = normalizer.normalize_user_addrs_opts(pid.into(), &addrs, opts);
match result {
Ok(output) => {
let output_box = Box::new(ManuallyDrop::into_inner(
blaze_normalized_user_output::from(output),
));
let () = set_last_err(blaze_err::OK);
Box::into_raw(output_box)
}
Err(err) => {
let () = set_last_err(err.kind().into());
ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn blaze_normalize_user_addrs(
normalizer: *const blaze_normalizer,
pid: u32,
addrs: *const Addr,
addr_cnt: usize,
) -> *mut blaze_normalized_user_output {
let opts = NormalizeOpts::default();
unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
}
#[no_mangle]
pub unsafe extern "C" fn blaze_normalize_user_addrs_opts(
normalizer: *const blaze_normalizer,
pid: u32,
addrs: *const Addr,
addr_cnt: usize,
opts: *const blaze_normalize_opts,
) -> *mut blaze_normalized_user_output {
if !input_zeroed!(opts, blaze_normalize_opts) {
let () = set_last_err(blaze_err::INVALID_INPUT);
return ptr::null_mut()
}
let opts = input_sanitize!(opts, blaze_normalize_opts);
let opts = NormalizeOpts::from(opts);
unsafe { blaze_normalize_user_addrs_impl(normalizer, pid, addrs, addr_cnt, &opts) }
}
#[no_mangle]
pub unsafe extern "C" fn blaze_user_output_free(output: *mut blaze_normalized_user_output) {
if output.is_null() {
return
}
let user_output = unsafe { Box::from_raw(output) };
let addr_metas = unsafe {
Box::<[blaze_user_meta]>::from_raw(ptr::slice_from_raw_parts_mut(
user_output.metas,
user_output.meta_cnt,
))
}
.into_vec();
let _norm_addrs = unsafe {
Box::<[blaze_normalized_output]>::from_raw(ptr::slice_from_raw_parts_mut(
user_output.outputs,
user_output.output_cnt,
))
}
.into_vec();
for addr_meta in addr_metas {
let () = unsafe { addr_meta.free() };
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
use std::io;
use std::path::Path;
use blazesym::helper::read_elf_build_id;
use blazesym::Mmap;
use blazesym::__private::find_the_answer_fn;
use blazesym::__private::zip;
use test_tag::tag;
use crate::blaze_err_last;
#[tag(miri)]
#[test]
#[cfg(target_pointer_width = "64")]
fn type_sizes() {
assert_eq!(size_of::<blaze_normalizer_opts>(), 32);
assert_eq!(size_of::<blaze_normalize_opts>(), 32);
assert_eq!(size_of::<blaze_user_meta_apk>(), 24);
assert_eq!(size_of::<blaze_user_meta_elf>(), 40);
assert_eq!(size_of::<blaze_user_meta_sym>(), 24);
assert_eq!(size_of::<blaze_user_meta_unknown>(), 16);
}
#[tag(miri)]
#[test]
fn debug_repr() {
let output = blaze_normalized_output {
output: 0x1337,
meta_idx: 1,
reserved: [0; 16],
};
assert_eq!(
format!("{output:?}"),
"blaze_normalized_output { output: 4919, meta_idx: 1, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }"
);
let meta_kind = blaze_user_meta_kind::APK;
assert_eq!(format!("{meta_kind:?}"), "blaze_user_meta_kind(1)");
let apk = blaze_user_meta_apk {
path: ptr::null_mut(),
reserved: [0; 16],
};
assert_eq!(
format!("{apk:?}"),
"blaze_user_meta_apk { path: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
);
let elf = blaze_user_meta_elf {
path: ptr::null_mut(),
build_id_len: 0,
build_id: ptr::null_mut(),
reserved: [0; 16],
};
assert_eq!(
format!("{elf:?}"),
"blaze_user_meta_elf { path: 0x0, build_id_len: 0, build_id: 0x0, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
);
let unknown = blaze_user_meta_unknown {
reason: blaze_normalize_reason::UNMAPPED,
reserved: [0; 15],
};
assert_eq!(
format!("{unknown:?}"),
"blaze_user_meta_unknown { reason: blaze_normalize_reason(0), reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
);
let meta = blaze_user_meta {
kind: blaze_user_meta_kind::UNKNOWN,
unused: [0; 7],
variant: blaze_user_meta_variant {
unknown: ManuallyDrop::new(blaze_user_meta_unknown {
reason: blaze_normalize_reason::UNMAPPED,
reserved: [0; 15],
}),
},
reserved: [0; 16],
};
assert_eq!(
format!("{meta:?}"),
"blaze_user_meta { kind: blaze_user_meta_kind(0), unused: [0, 0, 0, 0, 0, 0, 0], variant: blaze_user_meta_variant, reserved: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }",
);
let normalized = blaze_normalized_user_output {
meta_cnt: 0,
metas: ptr::null_mut(),
output_cnt: 0,
outputs: ptr::null_mut(),
reserved: [0; 16],
};
assert_eq!(
format!("{normalized:?}"),
"blaze_normalized_user_output { meta_cnt: 0, metas: 0x0, output_cnt: 0, outputs: 0x0, reserved: [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_normalize_reason::UNMAPPED),
(
Reason::MissingComponent,
blaze_normalize_reason::MISSING_COMPONENT,
),
(Reason::Unsupported, blaze_normalize_reason::UNSUPPORTED),
(
Reason::InvalidFileOffset,
blaze_normalize_reason::INVALID_FILE_OFFSET,
),
(Reason::MissingSyms, blaze_normalize_reason::MISSING_SYMS),
(Reason::UnknownAddr, blaze_normalize_reason::UNKNOWN_ADDR),
(Reason::IgnoredError, blaze_normalize_reason::IGNORED_ERROR),
];
for (reason, expected) in data {
assert_eq!(blaze_normalize_reason::from(reason), expected);
let cstr = unsafe { CStr::from_ptr(blaze_normalize_reason_str(expected)) };
let expected = CStr::from_bytes_with_nul(reason.as_bytes()).unwrap();
assert_eq!(cstr, expected);
}
}
#[tag(miri)]
#[test]
fn unknown_conversion() {
let unknown = Unknown {
reason: Reason::Unsupported,
_non_exhaustive: (),
};
let unknown_c = blaze_user_meta_unknown::from(unknown.clone());
let () = ManuallyDrop::into_inner(unknown_c).free();
let meta = UserMeta::Unknown(unknown);
let meta_c = blaze_user_meta::from(meta);
let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
}
#[tag(miri)]
#[test]
fn apk_conversion() {
let apk = Apk {
path: PathBuf::from("/tmp/archive.apk"),
_non_exhaustive: (),
};
let apk_c = blaze_user_meta_apk::from(apk.clone());
let () = unsafe { ManuallyDrop::into_inner(apk_c).free() };
let meta = UserMeta::Apk(apk);
let meta_c = blaze_user_meta::from(meta);
let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
}
#[tag(miri)]
#[test]
fn elf_conversion() {
let elf = Elf {
path: PathBuf::from("/tmp/file.so"),
build_id: Some(Cow::Borrowed(&[0x01, 0x02, 0x03, 0x04])),
_non_exhaustive: (),
};
let elf_c = blaze_user_meta_elf::from(elf.clone());
let () = unsafe { ManuallyDrop::into_inner(elf_c).free() };
let meta = UserMeta::Elf(elf);
let meta_c = blaze_user_meta::from(meta);
let () = unsafe { ManuallyDrop::into_inner(meta_c).free() };
}
#[tag(miri)]
#[test]
fn normalizer_creation() {
let normalizer = blaze_normalizer_new();
let () = unsafe { blaze_normalizer_free(normalizer) };
}
#[test]
fn normalize_user_addrs() {
fn test(normalizer: *const blaze_normalizer) {
let addrs = [
0x0,
libc::atexit as *const () as Addr,
libc::chdir as *const () as Addr,
libc::fopen as *const () as Addr,
elf_conversion as *const () as Addr,
normalize_user_addrs as *const () as Addr,
];
let result = unsafe {
blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
};
assert_ne!(result, ptr::null_mut());
let normalized = unsafe { &*result };
assert_eq!(normalized.meta_cnt, 3);
assert_eq!(normalized.output_cnt, 6);
let meta = unsafe { normalized.metas.read() };
assert_eq!(meta.kind, blaze_user_meta_kind::UNKNOWN);
assert_eq!(
unsafe { meta.variant.unknown.reason },
blaze_normalize_reason::UNMAPPED
);
let () = unsafe { blaze_user_output_free(result) };
}
let normalizer = blaze_normalizer_new();
assert_ne!(normalizer, ptr::null_mut());
test(normalizer);
let () = unsafe { blaze_normalizer_free(normalizer) };
let opts = blaze_normalizer_opts {
cache_vmas: true,
..Default::default()
};
let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
assert_ne!(normalizer, ptr::null_mut());
test(normalizer);
test(normalizer);
let () = unsafe { blaze_normalizer_free(normalizer) };
}
fn test_normalize_user_addrs_sorted(use_procmap_query: bool) {
let mut addrs = [
libc::atexit as *const () as Addr,
libc::chdir as *const () as Addr,
libc::fopen as *const () as Addr,
elf_conversion as *const () as Addr,
normalize_user_addrs as *const () as Addr,
];
let () = addrs.sort();
let opts = blaze_normalizer_opts {
use_procmap_query,
..Default::default()
};
let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
assert_ne!(normalizer, ptr::null_mut());
let opts = blaze_normalize_opts {
sorted_addrs: true,
..Default::default()
};
let result = unsafe {
blaze_normalize_user_addrs_opts(
normalizer,
0,
addrs.as_slice().as_ptr(),
addrs.len(),
&opts,
)
};
assert_ne!(result, ptr::null_mut());
let normalized = unsafe { &*result };
assert_eq!(normalized.meta_cnt, 2);
assert_eq!(normalized.output_cnt, 5);
let () = unsafe { blaze_user_output_free(result) };
let () = unsafe { blaze_normalizer_free(normalizer) };
}
#[test]
fn normalize_user_addrs_sorted_proc_maps() {
test_normalize_user_addrs_sorted(false)
}
#[test]
#[ignore = "test requires PROCMAP_QUERY ioctl kernel support"]
fn normalize_user_addrs_sorted_ioctl() {
test_normalize_user_addrs_sorted(true)
}
#[test]
fn normalize_user_addrs_unsorted_failure() {
let mut addrs = [
libc::atexit as *const () as Addr,
libc::chdir as *const () as Addr,
libc::fopen as *const () as Addr,
elf_conversion as *const () as Addr,
normalize_user_addrs as *const () as Addr,
];
let () = addrs.sort_by(|addr1, addr2| addr1.cmp(addr2).reverse());
let normalizer = blaze_normalizer_new();
assert_ne!(normalizer, ptr::null_mut());
let opts = blaze_normalize_opts {
sorted_addrs: true,
..Default::default()
};
let result = unsafe {
blaze_normalize_user_addrs_opts(
normalizer,
0,
addrs.as_slice().as_ptr(),
addrs.len(),
&opts,
)
};
assert_eq!(result, ptr::null_mut());
assert_eq!(blaze_err_last(), blaze_err::INVALID_INPUT);
let () = unsafe { blaze_normalizer_free(normalizer) };
}
#[test]
#[cfg_attr(
not(target_pointer_width = "64"),
ignore = "loads 64 bit shared object"
)]
fn normalize_build_id_reading() {
fn test(read_build_ids: bool) {
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 opts = blaze_normalizer_opts {
build_ids: read_build_ids,
..Default::default()
};
let normalizer = unsafe { blaze_normalizer_new_opts(&opts) };
assert!(!normalizer.is_null());
let opts = blaze_normalize_opts {
sorted_addrs: true,
..Default::default()
};
let addrs = [the_answer_addr as Addr];
let result = unsafe {
blaze_normalize_user_addrs_opts(
normalizer,
0,
addrs.as_slice().as_ptr(),
addrs.len(),
&opts,
)
};
assert!(!result.is_null());
let normalized = unsafe { &*result };
assert_eq!(normalized.meta_cnt, 1);
assert_eq!(normalized.output_cnt, 1);
let rc = unsafe { libc::dlclose(handle) };
assert_eq!(rc, 0, "{}", io::Error::last_os_error());
let output = unsafe { &*normalized.outputs.add(0) };
let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
let elf = unsafe { &meta.variant.elf };
assert!(!elf.path.is_null());
let path = unsafe { CStr::from_ptr(elf.path) };
assert_eq!(path, so_cstr.as_ref());
if read_build_ids {
let expected = read_elf_build_id(&test_so).unwrap().unwrap();
let build_id = unsafe { slice_from_user_array(elf.build_id, elf.build_id_len) };
assert_eq!(build_id, expected.as_ref());
} else {
assert!(elf.build_id.is_null());
}
let () = unsafe { blaze_user_output_free(result) };
let () = unsafe { blaze_normalizer_free(normalizer) };
}
test(true);
test(false);
}
#[test]
fn normalize_custom_so_in_zip() {
let test_zip = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("data")
.join("test.zip");
let so_name = "libtest-so.so";
let mmap = Mmap::builder().exec().open(&test_zip).unwrap();
let archive = zip::Archive::with_mmap(mmap.clone()).unwrap();
let so = archive
.entries()
.find_map(|entry| {
let entry = entry.unwrap();
(entry.path == Path::new(so_name)).then_some(entry)
})
.unwrap();
let elf_mmap = mmap
.constrain(so.data_offset..so.data_offset + so.data.len() as u64)
.unwrap();
let (_sym, the_answer_addr) = find_the_answer_fn(&elf_mmap);
let normalizer = blaze_normalizer_new();
assert!(!normalizer.is_null());
let addrs = [the_answer_addr];
let opts = blaze_normalize_opts {
apk_to_elf: true,
..Default::default()
};
let result = unsafe {
blaze_normalize_user_addrs_opts(
normalizer,
0,
addrs.as_slice().as_ptr(),
addrs.len(),
&opts,
)
};
assert_ne!(result, ptr::null_mut());
let normalized = unsafe { &*result };
assert_eq!(normalized.meta_cnt, 1);
assert_eq!(normalized.output_cnt, 1);
let output = unsafe { &*normalized.outputs.add(0) };
let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
assert_eq!(meta.kind, blaze_user_meta_kind::ELF);
let elf = unsafe { &meta.variant.elf };
let path = unsafe { CStr::from_ptr(elf.path) };
assert!(path.to_str().unwrap().ends_with(so_name), "{path:?}");
let () = unsafe { blaze_user_output_free(result) };
let () = unsafe { blaze_normalizer_free(normalizer) };
}
#[cfg(linux)]
#[cfg(target_pointer_width = "64")]
#[test]
fn normalize_local_vdso_address() {
use libc::gettimeofday;
let addrs = [
normalize_local_vdso_address as *const () as Addr,
gettimeofday as *const () as Addr,
];
let normalizer = blaze_normalizer_new();
assert!(!normalizer.is_null());
let result = unsafe {
blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
};
assert_ne!(result, ptr::null_mut());
let normalized = unsafe { &*result };
assert_eq!(normalized.meta_cnt, 2);
assert_eq!(normalized.output_cnt, 2);
let output = unsafe { &*normalized.outputs.add(1) };
let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
assert_eq!(meta.kind, blaze_user_meta_kind::SYM);
let sym = unsafe { &*meta.variant.sym.sym };
let name = unsafe { CStr::from_ptr(sym.name) };
assert!(name.to_str().unwrap().ends_with("gettimeofday"), "{name:?}");
let () = unsafe { blaze_user_output_free(result) };
let () = unsafe { blaze_normalizer_free(normalizer) };
}
#[cfg(linux)]
#[cfg(target_pointer_width = "64")]
#[test]
fn normalize_invalid_vdso_address() {
use blazesym::__private::find_vdso_range;
let vdso = find_vdso_range();
let addrs = [vdso.start];
let normalizer = blaze_normalizer_new();
assert!(!normalizer.is_null());
let result = unsafe {
blaze_normalize_user_addrs(normalizer, 0, addrs.as_slice().as_ptr(), addrs.len())
};
assert_ne!(result, ptr::null_mut());
let normalized = unsafe { &*result };
assert_eq!(normalized.meta_cnt, 1);
assert_eq!(normalized.output_cnt, 1);
let output = unsafe { &*normalized.outputs };
let meta = unsafe { &*normalized.metas.add(output.meta_idx) };
match meta.kind {
blaze_user_meta_kind::UNKNOWN | blaze_user_meta_kind::SYM => (),
_ => panic!("encountered unexpected meta kind: {:?}", meta.kind),
}
let () = unsafe { blaze_user_output_free(result) };
let () = unsafe { blaze_normalizer_free(normalizer) };
}
}