pub mod cache;
pub mod source;
mod symbolizer;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::path::Path;
use std::str;
cfg_apk! {
pub use symbolizer::ApkDispatch;
pub use symbolizer::ApkMemberInfo;
}
pub use symbolizer::Builder;
pub use symbolizer::ProcessDispatch;
pub use symbolizer::ProcessMemberInfo;
pub use symbolizer::Symbolizer;
pub(crate) use symbolizer::symbolize_with_resolver;
pub(crate) use symbolizer::Resolver;
pub use crate::maps::EntryPath as ProcessMemberPath;
pub use crate::maps::PathName as ProcessMemberType;
use crate::Addr;
use crate::Result;
#[derive(Debug)]
#[non_exhaustive]
pub enum FindSymOpts {
Basic,
CodeInfo,
CodeInfoAndInlined,
}
impl FindSymOpts {
#[inline]
pub(crate) fn code_info(&self) -> bool {
match self {
Self::Basic => false,
Self::CodeInfo | Self::CodeInfoAndInlined => true,
}
}
#[inline]
pub(crate) fn inlined_fns(&self) -> bool {
match self {
Self::Basic | Self::CodeInfo => false,
Self::CodeInfoAndInlined => true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Input<T> {
AbsAddr(T),
VirtOffset(T),
FileOffset(T),
}
impl<T> Input<T> {
#[inline]
pub fn map<F, U>(&self, f: F) -> Input<U>
where
T: Copy,
F: FnOnce(T) -> U,
{
match self {
Self::AbsAddr(x) => Input::AbsAddr(f(*x)),
Self::VirtOffset(x) => Input::VirtOffset(f(*x)),
Self::FileOffset(x) => Input::FileOffset(f(*x)),
}
}
#[inline]
pub fn as_inner_ref(&self) -> &T {
match self {
Self::AbsAddr(x) | Self::VirtOffset(x) | Self::FileOffset(x) => x,
}
}
#[inline]
pub fn into_inner(self) -> T {
match self {
Self::AbsAddr(x) | Self::VirtOffset(x) | Self::FileOffset(x) => x,
}
}
}
#[cfg(test)]
impl<T> Input<&[T]>
where
T: Copy,
{
fn try_to_single(&self) -> Option<Input<T>> {
match self {
Self::AbsAddr([addr]) => Some(Input::AbsAddr(*addr)),
Self::VirtOffset([addr]) => Some(Input::VirtOffset(*addr)),
Self::FileOffset([offset]) => Some(Input::FileOffset(*offset)),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CodeInfo<'src> {
pub dir: Option<Cow<'src, Path>>,
pub file: Cow<'src, OsStr>,
pub line: Option<u32>,
pub column: Option<u16>,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl CodeInfo<'_> {
#[inline]
pub fn to_path(&self) -> Cow<'_, Path> {
self.dir.as_ref().map_or_else(
|| Cow::Borrowed(Path::new(&self.file)),
|dir| Cow::Owned(dir.join(&self.file)),
)
}
pub fn into_owned(self) -> CodeInfo<'static> {
let Self {
dir,
file,
line,
column,
_non_exhaustive: (),
} = self;
CodeInfo {
dir: dir.map(|dir| Cow::Owned(dir.into_owned())),
file: Cow::Owned(file.into_owned()),
line,
column,
_non_exhaustive: (),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct InlinedFn<'src> {
pub name: Cow<'src, str>,
pub code_info: Option<CodeInfo<'src>>,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl InlinedFn<'_> {
pub fn into_owned(self) -> InlinedFn<'static> {
let Self {
name,
code_info,
_non_exhaustive: (),
} = self;
InlinedFn {
name: Cow::Owned(name.into_owned()),
code_info: code_info.map(CodeInfo::into_owned),
_non_exhaustive: (),
}
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
#[non_exhaustive]
pub enum SrcLang {
#[default]
Unknown,
Cpp,
Rust,
}
#[derive(Debug, PartialEq)]
pub struct ResolvedSym<'src> {
pub name: &'src str,
pub module: Option<&'src OsStr>,
pub addr: Addr,
pub size: Option<usize>,
pub lang: SrcLang,
pub code_info: Option<Box<CodeInfo<'src>>>,
pub inlined: Box<[InlinedFn<'src>]>,
#[doc(hidden)]
pub _non_exhaustive: (),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Sym<'src> {
pub name: Cow<'src, str>,
pub module: Option<Cow<'src, OsStr>>,
pub addr: Addr,
pub offset: usize,
pub size: Option<usize>,
pub code_info: Option<Box<CodeInfo<'src>>>,
pub inlined: Box<[InlinedFn<'src>]>,
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl Sym<'_> {
pub fn into_owned(self) -> Sym<'static> {
let Self {
name,
module,
addr,
offset,
size,
code_info,
inlined,
_non_exhaustive,
} = self;
Sym {
name: Cow::Owned(name.into_owned()),
module: module.map(|module| Cow::Owned(module.into_owned())),
addr,
offset,
size,
code_info: code_info.map(|info| Box::new(info.into_owned())),
inlined: Vec::from(inlined)
.into_iter()
.map(InlinedFn::into_owned)
.collect::<Box<[_]>>(),
_non_exhaustive: (),
}
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Reason {
Unmapped,
InvalidFileOffset,
MissingComponent,
MissingSyms,
Unsupported,
UnknownAddr,
IgnoredError,
}
impl Reason {
#[doc(hidden)]
#[inline]
pub fn as_bytes(&self) -> &'static [u8] {
match self {
Self::Unmapped => b"absolute address not found in virtual memory map of process\0",
Self::InvalidFileOffset => b"file offset does not map to a valid piece of code/data\0",
Self::MissingComponent => b"proc maps entry has no component\0",
Self::MissingSyms => b"symbolization source has no or no relevant symbols\0",
Self::Unsupported => b"address belongs to unsupported entity\0",
Self::UnknownAddr => b"address not found in symbolization source\0",
Self::IgnoredError => b"an error occurred but was ignored in batch mode\0",
}
}
}
impl Display for Reason {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let cstr = self.as_bytes();
let s = unsafe { str::from_utf8_unchecked(&cstr[..cstr.len() - 1]) };
f.write_str(s)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Symbolized<'src> {
Sym(Sym<'src>),
Unknown(Reason),
}
impl<'src> Symbolized<'src> {
#[inline]
pub fn as_sym(&self) -> Option<&Sym<'src>> {
match self {
Self::Sym(sym) => Some(sym),
Self::Unknown(..) => None,
}
}
#[inline]
pub fn into_sym(self) -> Option<Sym<'src>> {
match self {
Self::Sym(sym) => Some(sym),
Self::Unknown(..) => None,
}
}
}
#[doc(hidden)]
pub trait AsSymbolize {
fn as_symbolize(&self) -> &dyn Symbolize;
}
pub trait Symbolize
where
Self: AsSymbolize + Debug,
{
fn find_sym(&self, addr: Addr, opts: &FindSymOpts) -> Result<Result<ResolvedSym<'_>, Reason>>;
}
impl<S> AsSymbolize for S
where
S: Symbolize,
{
fn as_symbolize(&self) -> &dyn Symbolize {
self
}
}
pub trait Resolve: Symbolize + TranslateFileOffset {}
impl<R> Resolve for R where R: Symbolize + TranslateFileOffset {}
pub trait TranslateFileOffset
where
Self: Debug,
{
fn file_offset_to_virt_offset(&self, file_offset: u64) -> Result<Option<Addr>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_repr() {
let lang = SrcLang::default();
assert_ne!(format!("{lang:?}"), "");
let input = Input::FileOffset(0x1337);
assert_ne!(format!("{input:?}"), "");
let code_info = CodeInfo {
dir: Some(Cow::Borrowed(Path::new("/tmp/some-dir"))),
file: Cow::Borrowed(OsStr::new("test.c")),
line: Some(1337),
column: None,
_non_exhaustive: (),
};
let sym = Sym {
name: Cow::Borrowed("test"),
module: Some(Cow::Borrowed(OsStr::new("module"))),
addr: 1337,
offset: 42,
size: None,
code_info: None,
inlined: Box::new([InlinedFn {
name: Cow::Borrowed("inlined_test"),
code_info: Some(code_info.clone()),
_non_exhaustive: (),
}]),
_non_exhaustive: (),
};
assert_ne!(format!("{sym:?}"), "");
let symbolized = Symbolized::Sym(sym);
assert_ne!(format!("{symbolized:?}"), "");
}
#[test]
fn display_repr() {
assert_eq!(
Reason::MissingSyms.to_string(),
"symbolization source has no or no relevant symbols"
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn sym_size() {
assert_eq!(size_of::<Sym>(), 104);
}
#[test]
fn owned_conversion() {
let sym = Sym {
name: Cow::Borrowed("test"),
module: Some(Cow::Borrowed(OsStr::new("module"))),
addr: 1337,
offset: 42,
size: None,
code_info: None,
inlined: Box::new([InlinedFn {
name: Cow::Borrowed("inlined_test"),
code_info: Some(CodeInfo {
dir: Some(Cow::Borrowed(Path::new("/tmp/some-dir"))),
file: Cow::Borrowed(OsStr::new("test.c")),
line: Some(1337),
column: None,
_non_exhaustive: (),
}),
_non_exhaustive: (),
}]),
_non_exhaustive: (),
};
assert_eq!(sym, sym.clone().into_owned());
}
#[test]
fn input_mapping() {
fn test<F>(f: F)
where
F: Fn(usize) -> Input<usize>,
{
let input = f(0x1337);
let input = input.map(|x| 2 * x);
assert_eq!(input, f(2 * 0x1337));
assert_eq!(input.into_inner(), 2 * 0x1337);
}
for variant in [Input::AbsAddr, Input::VirtOffset, Input::FileOffset] {
let () = test(variant);
}
}
#[test]
fn symbolized_unknown_conversions() {
let symbolized = Symbolized::Unknown(Reason::UnknownAddr);
assert_eq!(symbolized.as_sym(), None);
assert_eq!(symbolized.into_sym(), None);
}
}