use std::borrow::Cow;
use std::cell::OnceCell;
use std::fs::File;
use std::ops::Range;
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "apk")]
use crate::apk::create_apk_elf_path;
use crate::elf::ElfParser;
use crate::elf::StaticMem;
use crate::file_cache::FileCache;
use crate::insert_map::InsertMap;
use crate::maps;
use crate::util;
#[cfg(feature = "tracing")]
use crate::util::Hexify;
use crate::util::OnceCellExt as _;
use crate::vdso::create_vdso_parser;
#[cfg(feature = "apk")]
use crate::zip;
use crate::Addr;
use crate::Error;
use crate::ErrorExt as _;
use crate::IntoError as _;
use crate::Mmap;
use crate::Pid;
use crate::Result;
use super::buildid::read_build_id;
use super::buildid::read_elf_build_id_from_mmap;
use super::buildid::BuildId;
use super::ioctl::query_procmap;
use super::user;
use super::user::normalize_sorted_user_addrs_with_entries;
use super::user::UserOutput;
use super::NormalizeOpts;
#[derive(Clone, Debug)]
pub struct Output<M> {
pub outputs: Vec<(u64, usize)>,
pub meta: Vec<M>,
}
#[derive(Clone, Debug)]
pub struct Builder {
use_procmap_query: bool,
cache_vmas: bool,
build_ids: bool,
cache_build_ids: bool,
}
impl Builder {
pub fn enable_procmap_query(mut self, enable: bool) -> Builder {
self.use_procmap_query = enable;
self
}
pub fn enable_vma_caching(mut self, enable: bool) -> Builder {
self.cache_vmas = enable;
self
}
pub fn enable_build_ids(mut self, enable: bool) -> Builder {
self.build_ids = enable;
self
}
pub fn enable_build_id_caching(mut self, enable: bool) -> Builder {
self.cache_build_ids = enable;
self
}
pub fn build(self) -> Normalizer {
let Builder {
use_procmap_query,
cache_vmas,
build_ids,
cache_build_ids,
} = self;
Normalizer {
use_procmap_query,
cache_vmas,
build_ids,
cache_build_ids: build_ids && cache_build_ids,
#[cfg(feature = "apk")]
apk_cache: FileCache::default(),
vdso_parser: OnceCell::new(),
entry_cache: InsertMap::new(),
build_id_cache: FileCache::default(),
}
}
}
impl Default for Builder {
fn default() -> Self {
Self {
use_procmap_query: false,
cache_vmas: false,
build_ids: true,
cache_build_ids: false,
}
}
}
#[derive(Debug)]
pub struct Normalizer {
use_procmap_query: bool,
cache_vmas: bool,
build_ids: bool,
cache_build_ids: bool,
#[cfg(feature = "apk")]
apk_cache: FileCache<(
zip::Archive,
InsertMap<Range<u64>, Option<BuildId<'static>>>,
)>,
vdso_parser: OnceCell<Box<ElfParser<StaticMem>>>,
entry_cache: InsertMap<Pid, Box<[maps::MapsEntry]>>,
build_id_cache: FileCache<Option<BuildId<'static>>>,
}
impl Normalizer {
#[inline]
pub fn new() -> Self {
Builder::default().build()
}
#[inline]
pub fn builder() -> Builder {
Builder::default()
}
fn normalize_user_addrs_impl<'slf, A, E, M>(
&'slf self,
pid: Pid,
addrs: A,
entries: E,
opts: &NormalizeOpts,
) -> Result<UserOutput<'slf>>
where
A: ExactSizeIterator<Item = Addr> + Clone,
E: FnMut(Addr) -> Option<Result<M>>,
M: AsRef<maps::MapsEntry>,
{
let addrs_cnt = addrs.len();
let mut handler = user::NormalizationHandler::new(self, opts, pid, addrs_cnt);
let () = normalize_sorted_user_addrs_with_entries(addrs, entries, &mut handler)?;
debug_assert_eq!(handler.normalized.outputs.len(), addrs_cnt);
Ok(handler.normalized)
}
fn normalize_user_addrs_iter<A>(
&self,
addrs: A,
pid: Pid,
opts: &NormalizeOpts,
) -> Result<UserOutput<'_>>
where
A: ExactSizeIterator<Item = Addr> + Clone,
{
if self.use_procmap_query {
let path = format!("/proc/{pid}/maps");
let file = File::open(&path)
.with_context(|| format!("failed to open `{path}` for reading"))?;
if !self.cache_vmas {
let entries =
move |addr| query_procmap(&file, pid, addr, self.build_ids).transpose();
self.normalize_user_addrs_impl(pid, addrs, entries, opts)
} else {
let entries = self.entry_cache.get_or_try_insert(pid, || {
let mut entries = Vec::new();
let mut next_addr = 0;
while let Some(entry) = query_procmap(&file, pid, next_addr, self.build_ids)? {
next_addr = entry.range.end;
if maps::filter_relevant(&entry) {
let () = entries.push(entry);
}
}
Ok(entries.into_boxed_slice())
})?;
let mut entry_iter = entries.iter().map(Ok);
let entries = |_addr| entry_iter.next();
self.normalize_user_addrs_impl(pid, addrs, entries, opts)
}
} else {
if !self.cache_vmas {
let mut entry_iter = maps::parse_filtered(pid)?;
let entries = |_addr| entry_iter.next();
self.normalize_user_addrs_impl(pid, addrs, entries, opts)
} else {
let parsed = self.entry_cache.get_or_try_insert(pid, || {
let parsed = maps::parse_filtered(pid)?.collect::<Result<Box<_>>>()?;
Ok(parsed)
})?;
let mut entry_iter = parsed.iter().map(Ok);
let entries = |_addr| entry_iter.next();
self.normalize_user_addrs_impl(pid, addrs, entries, opts)
}
}
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(pid = ?pid, addrs = ?Hexify(addrs)), err))]
pub fn normalize_user_addrs_opts(
&self,
pid: Pid,
addrs: &[Addr],
opts: &NormalizeOpts,
) -> Result<UserOutput<'_>> {
if opts.sorted_addrs {
self.normalize_user_addrs_iter(addrs.iter().copied(), pid, opts)
} else {
util::with_ordered_elems(
addrs,
|normalized: &mut UserOutput| normalized.outputs.as_mut_slice(),
|sorted_addrs| self.normalize_user_addrs_iter(sorted_addrs, pid, opts),
)
}
}
pub fn normalize_user_addrs(&self, pid: Pid, addrs: &[Addr]) -> Result<UserOutput<'_>> {
self.normalize_user_addrs_opts(pid, addrs, &NormalizeOpts::default())
}
#[cfg_attr(feature = "tracing", crate::log::instrument(err, skip_all, fields(path = ?path)))]
pub(crate) fn read_build_id(&self, path: &Path) -> Result<Option<BuildId<'static>>> {
let build_id = if self.build_ids {
if self.cache_build_ids {
let (file, cell) = self.build_id_cache.entry(path)?;
cell.get_or_try_init_(|| {
let parser = ElfParser::from_file(file, path.to_path_buf().into_os_string())?;
let build_id =
read_build_id(&parser)?.map(|build_id| Cow::Owned(build_id.to_vec()));
Result::<_, Error>::Ok(build_id)
})?
.clone()
} else {
let parser = ElfParser::open(path)?;
read_build_id(&parser)?.map(|build_id| Cow::Owned(build_id.to_vec()))
}
} else {
debug_assert!(!self.cache_build_ids);
None
};
Ok(build_id)
}
#[cfg(feature = "apk")]
pub(crate) fn translate_apk_to_elf(
&self,
apk_file_off: u64,
apk_path: &Path,
) -> Result<Option<(u64, PathBuf, Option<BuildId<'static>>)>> {
let (file, cell) = self.apk_cache.entry(apk_path)?;
let (apk, elf_build_ids) = cell.get_or_try_init_(|| {
let mmap = Mmap::builder()
.map(file)
.with_context(|| format!("failed to memory map `{}`", apk_path.display()))?;
let apk = zip::Archive::with_mmap(mmap)
.with_context(|| format!("failed to open zip file `{}`", apk_path.display()))?;
let elf_build_ids = InsertMap::new();
Result::<_, Error>::Ok((apk, elf_build_ids))
})?;
for apk_entry in apk.entries() {
let apk_entry = apk_entry
.with_context(|| format!("failed to iterate `{}` members", apk_path.display()))?;
let bounds = apk_entry.data_offset..apk_entry.data_offset + apk_entry.data.len() as u64;
if bounds.contains(&apk_file_off) {
let elf_build_id = if self.build_ids {
let mmap = apk
.mmap()
.constrain(bounds.clone())
.ok_or_invalid_input(|| {
format!(
"invalid APK entry data bounds ({bounds:?}) in {}",
apk_path.display()
)
})?;
if self.cache_build_ids {
elf_build_ids
.get_or_try_insert(bounds.clone(), || {
read_elf_build_id_from_mmap(&mmap)
})?
.clone()
} else {
read_elf_build_id_from_mmap(&mmap)?
}
} else {
None
};
let elf_off = apk_file_off - apk_entry.data_offset;
let elf_path = create_apk_elf_path(apk_path, apk_entry.path);
return Ok(Some((elf_off, elf_path, elf_build_id)))
}
}
Ok(None)
}
pub(crate) fn vdso_parser<'slf>(
&'slf self,
pid: Pid,
range: &Range<Addr>,
) -> Result<&'slf ElfParser<StaticMem>> {
let parser = self.vdso_parser.get_or_try_init_(|| {
let parser = create_vdso_parser(pid, range)?;
Result::<_, Error>::Ok(Box::new(parser))
})?;
Ok(parser)
}
}
impl Default for Normalizer {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_initialization() {
let _normalizer = Normalizer::default();
}
}