diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index e40bada26..32318da7e 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -3,13 +3,16 @@ use libc::pid_t; use object::{Object, ObjectSection, ObjectSymbol}; use std::{ borrow::Cow, + collections::HashMap, error::Error, ffi::CStr, fs, io::{self, BufRead, Cursor, Read}, mem, + num::ParseIntError, os::{fd::AsFd as _, raw::c_char}, path::{Path, PathBuf}, + str::FromStr, sync::Arc, }; use thiserror::Error; @@ -136,12 +139,17 @@ fn resolve_attach_path>( }; let target_str = target.to_str().ok_or_else(invalid_target)?; pid.and_then(|pid| { - find_lib_in_proc_maps(pid, target_str) - .map_err(|io_error| UProbeError::FileError { - filename: format!("/proc/{pid}/maps"), - io_error, + ProcMap::new(pid) + .map_err(|source| UProbeError::ProcMapError { pid, source }) + .and_then(|proc_map_libs| { + proc_map_libs + .find_library_path_by_name(target_str) + .map_err(|io_error| UProbeError::FileError { + filename: format!("/proc/{pid}/maps"), + io_error, + }) + .map(|v| v.map(Cow::Owned)) }) - .map(|v| v.map(Cow::Owned)) .transpose() }) .or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target_str)))) @@ -216,7 +224,7 @@ pub enum UProbeError { /// There was an error parsing `/etc/ld.so.cache`. #[error("error reading `{}` file", LD_SO_CACHE_FILE)] InvalidLdSoCache { - /// the original [`io::Error`] + /// the original [`io::Error`]. #[source] io_error: Arc, }, @@ -224,16 +232,16 @@ pub enum UProbeError { /// The target program could not be found. #[error("could not resolve uprobe target `{path}`")] InvalidTarget { - /// path to target + /// path to target. path: PathBuf, }, /// There was an error resolving the target symbol. #[error("error resolving symbol")] SymbolError { - /// symbol name + /// symbol name. symbol: String, - /// the original error + /// the original error. #[source] error: Box, }, @@ -241,49 +249,23 @@ pub enum UProbeError { /// There was an error accessing `filename`. #[error("`{filename}`")] FileError { - /// The file name + /// The file name. filename: String, - /// The [`io::Error`] returned from the file operation + /// The [`io::Error`] returned from the file operation. #[source] io_error: io::Error, }, -} - -fn proc_maps_libs(pid: pid_t) -> Result, io::Error> { - let maps_file = format!("/proc/{pid}/maps"); - let data = fs::read_to_string(maps_file)?; - - Ok(data - .lines() - .filter_map(|line| { - let line = line.split_whitespace().last()?; - if line.starts_with('/') { - let path = PathBuf::from(line); - let key = path.file_name().unwrap().to_string_lossy().into_owned(); - Some((key, path.to_string_lossy().to_string())) - } else { - None - } - }) - .collect()) -} - -fn find_lib_in_proc_maps(pid: pid_t, lib: &str) -> Result, io::Error> { - let libs = proc_maps_libs(pid)?; - - let ret = if lib.contains(".so") { - libs.into_iter().find(|(k, _)| k.as_str().starts_with(lib)) - } else { - libs.into_iter().find(|(k, _)| { - k.strip_prefix(lib) - .map(|k| k.starts_with(".so") || k.starts_with('-')) - .unwrap_or_default() - }) - }; - Ok(ret.map(|(_, v)| v)) + /// There was en error resolving a path. + #[error("error fetching libs for {pid}")] + ProcMapError { + /// The pid. + pid: i32, + /// The [`ProcMapError`] that caused the error. + #[source] + source: ProcMapError, + }, } - #[derive(Debug)] pub(crate) struct CacheEntry { key: String, @@ -450,3 +432,279 @@ fn resolve_symbol(path: &str, symbol: &str) -> Result { Ok(sym.address() - section.address() + offset) } } + +/// Error reading from /proc/pid/maps. +#[derive(Debug, Error)] +pub enum ProcMapError { + /// Unable to read /proc/pid/maps. + #[error(transparent)] + ReadError(io::Error), + + /// Error parsing an integer. + #[error(transparent)] + IntError(#[from] ParseIntError), + + /// Error parsing a line of /proc/pid/maps. + #[error("proc map entry parse error")] + ParseError, +} + +/// The memory maps of a process. +/// +/// This is read from /proc/`pid`/maps. +/// +/// The information here may be used to resolve addresses to paths. +pub struct ProcMap { + entries: Vec, + libraries: HashMap, +} + +impl ProcMap { + /// Create a new [`ProcMap`] from a given pid. + pub fn new(pid: pid_t) -> Result { + let maps_file = format!("/proc/{}/maps", pid); + let data = fs::read_to_string(maps_file).map_err(ProcMapError::ReadError)?; + let mut entries = vec![]; + let mut libraries = HashMap::new(); + for line in data.lines() { + let entry = ProcMapEntry::from_str(line)?; + if let Some(path) = &entry.path { + let p = PathBuf::from(path); + let filename = p.file_name().unwrap().to_string_lossy().into_owned(); + let library_path = p.to_string_lossy().to_string(); + libraries.entry(filename).or_insert(library_path); + } + entries.push(entry); + } + Ok(ProcMap { entries, libraries }) + } + + // Find the full path of a library by its name. + // + // This isn't part of the public API since it's really only useful for + // attaching uprobes. + fn find_library_path_by_name(&self, lib: &str) -> Result, io::Error> { + let ret = if lib.contains(".so") { + self.libraries + .iter() + .find(|(k, _)| k.as_str().starts_with(lib)) + } else { + self.libraries.iter().find(|(k, _)| { + k.strip_prefix(lib) + .map(|k| k.starts_with(".so") || k.starts_with('-')) + .unwrap_or_default() + }) + }; + + Ok(ret.map(|(_, v)| v.clone())) + } + + /// Iterate parsed memory map entries for the process. + /// + /// This is useful to resolve instruction pointers to a the shared object + /// they belong to. + pub fn entries(&self) -> impl Iterator { + self.entries.iter() + } +} + +/// A entry that has been parsed from /proc/`pid`/maps. +/// +/// This contains information about a mapped portion of memory +/// for the process, ranging from address to address_end. +#[derive(Debug)] +pub struct ProcMapEntry { + address: u64, + address_end: u64, + perms: String, + offset: u64, + dev: String, + inode: u32, + path: Option, +} + +impl ProcMapEntry { + /// The start address of the mapped memory. + pub fn address(&self) -> u64 { + self.address + } + + /// The end address of the mapped memory + pub fn address_end(&self) -> u64 { + self.address_end + } + + /// The permissions of the mapped memory. + pub fn perms(&self) -> &str { + &self.perms + } + + /// The offset of the mapped memory. + pub fn offset(&self) -> u64 { + self.offset + } + + /// The device of the mapped memory. + pub fn dev(&self) -> &str { + &self.dev + } + + /// The inode of the mapped memory. + pub fn inode(&self) -> u32 { + self.inode + } + + /// The destination path of the mapped memory. + pub fn path(&self) -> Option<&str> { + self.path.as_deref() + } +} + +impl FromStr for ProcMapEntry { + type Err = ProcMapError; + + fn from_str(line: &str) -> Result { + let mut parts = line.split_whitespace(); + let mut next = || parts.next().ok_or(ProcMapError::ParseError); + let (address, address_end) = next()? + .split_once('-') + .ok_or(ProcMapError::ParseError) + .map(|(a, b)| { + let start = u64::from_str_radix(a, 16).map_err(ProcMapError::IntError); + let end = u64::from_str_radix(b, 16).map_err(ProcMapError::IntError); + (start, end) + })?; + let perms = next()?; + let offset = u64::from_str_radix(next()?, 16).map_err(ProcMapError::IntError)?; + let dev = next()?; + let inode = next()?.parse().map_err(ProcMapError::IntError)?; + let path = parts.next().and_then(|s| { + if s.starts_with('/') { + Some(s.to_string()) + } else { + None + } + }); + Ok(ProcMapEntry { + address: address?, + address_end: address_end?, + perms: perms.to_string(), + offset, + dev: dev.to_string(), + inode, + path, + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use assert_matches::assert_matches; + + #[test] + fn test_parse_proc_map_entry_shared_lib() { + let s = "7ffd6fbea000-7ffd6fbec000 r-xp 00000000 00:00 0 [vdso]"; + let proc_map = ProcMapEntry::from_str(s).unwrap(); + assert_eq!(proc_map.address, 0x7ffd6fbea000); + assert_eq!(proc_map.address_end, 0x7ffd6fbec000); + assert_eq!(proc_map.perms, "r-xp"); + assert_eq!(proc_map.offset, 0x0); + assert_eq!(proc_map.dev, "00:00"); + assert_eq!(proc_map.inode, 0); + assert_eq!(proc_map.path, None); + } + + #[test] + fn test_parse_proc_map_entry_absolute_path() { + let s = "7f1bca83a000-7f1bca83c000 rw-p 00036000 fd:01 2895508 /usr/lib64/ld-linux-x86-64.so.2"; + let proc_map = ProcMapEntry::from_str(s).unwrap(); + assert_eq!(proc_map.address, 0x7f1bca83a000); + assert_eq!(proc_map.address_end, 0x7f1bca83c000); + assert_eq!(proc_map.perms, "rw-p"); + assert_eq!(proc_map.offset, 0x00036000); + assert_eq!(proc_map.dev, "fd:01"); + assert_eq!(proc_map.inode, 2895508); + assert_eq!( + proc_map.path, + Some("/usr/lib64/ld-linux-x86-64.so.2".to_string()) + ); + } + + #[test] + fn test_parse_proc_map_entry_all_zeros() { + let s = "7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0"; + let proc_map = ProcMapEntry::from_str(s).unwrap(); + assert_eq!(proc_map.address, 0x7f1bca5f9000); + assert_eq!(proc_map.address_end, 0x7f1bca601000); + assert_eq!(proc_map.perms, "rw-p"); + assert_eq!(proc_map.offset, 0x0); + assert_eq!(proc_map.dev, "00:00"); + assert_eq!(proc_map.inode, 0); + assert_eq!(proc_map.path, None); + } + + #[test] + fn test_parse_proc_map_entry_parse_errors() { + assert_matches!( + ProcMapEntry::from_str( + "zzzz-7ffd6fbea000 r-xp 00000000 00:00 0 [vdso]" + ), + Err(ProcMapError::IntError(_)) + ); + + assert_matches!( + ProcMapEntry::from_str( + "zzzz-7ffd6fbea000 r-xp 00000000 00:00 0 [vdso]" + ), + Err(ProcMapError::IntError(_)) + ); + + assert_matches!( + ProcMapEntry::from_str( + "7f1bca5f9000-7f1bca601000 r-xp zzzz 00:00 0 [vdso]" + ), + Err(ProcMapError::IntError(_)) + ); + + assert_matches!( + ProcMapEntry::from_str( + "7f1bca5f9000-7f1bca601000 r-xp 00000000 00:00 zzzz [vdso]" + ), + Err(ProcMapError::IntError(_)) + ); + + assert_matches!( + ProcMapEntry::from_str( + "7f1bca5f90007ffd6fbea000 r-xp 00000000 00:00 0 [vdso]" + ), + Err(ProcMapError::ParseError) + ); + + assert_matches!( + ProcMapEntry::from_str("7f1bca5f9000-7f1bca601000 r-xp 00000000"), + Err(ProcMapError::ParseError) + ); + } + + #[test] + fn test_proc_map_find_lib_by_name() { + let entry = ProcMapEntry::from_str( + "7fc4a9800000-7fc4a98ad000 r--p 00000000 00:24 18147308 /usr/lib64/libcrypto.so.3.0.9", + ).unwrap(); + + let proc_map_libs = ProcMap { + entries: vec![entry], + libraries: HashMap::from([( + "libcrypto.so.3.0.9".to_owned(), + "/usr/lib64/libcrypto.so.3.0.9".to_owned(), + )]), + }; + assert_eq!( + proc_map_libs + .find_library_path_by_name("libcrypto.so.3.0.9") + .unwrap(), + Some("/usr/lib64/libcrypto.so.3.0.9".to_owned()) + ); + } +} diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 7adc9b63d..c63159417 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -4319,6 +4319,40 @@ pub fn aya::programs::trace_point::TracePointLinkId::borrow_mut(&mut self) -> &m impl core::convert::From for aya::programs::trace_point::TracePointLinkId pub fn aya::programs::trace_point::TracePointLinkId::from(t: T) -> T pub mod aya::programs::uprobe +pub enum aya::programs::uprobe::ProcMapError +pub aya::programs::uprobe::ProcMapError::IoError(std::io::error::Error) +pub aya::programs::uprobe::ProcMapError::ParseError +impl core::error::Error for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> +impl core::fmt::Display for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::fmt(&self, __formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Debug for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Send for aya::programs::uprobe::ProcMapError +impl core::marker::Sync for aya::programs::uprobe::ProcMapError +impl core::marker::Unpin for aya::programs::uprobe::ProcMapError +impl !core::panic::unwind_safe::RefUnwindSafe for aya::programs::uprobe::ProcMapError +impl !core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::ProcMapError +impl core::any::Provider for aya::programs::uprobe::ProcMapError where E: core::error::Error + core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::provide<'a>(&'a self, demand: &mut core::any::Demand<'a>) +impl core::convert::Into for aya::programs::uprobe::ProcMapError where U: core::convert::From +pub fn aya::programs::uprobe::ProcMapError::into(self) -> U +impl core::convert::TryFrom for aya::programs::uprobe::ProcMapError where U: core::convert::Into +pub type aya::programs::uprobe::ProcMapError::Error = core::convert::Infallible +pub fn aya::programs::uprobe::ProcMapError::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::uprobe::ProcMapError where U: core::convert::TryFrom +pub type aya::programs::uprobe::ProcMapError::Error = >::Error +pub fn aya::programs::uprobe::ProcMapError::try_into(self) -> core::result::Result>::Error> +impl alloc::string::ToString for aya::programs::uprobe::ProcMapError where T: core::fmt::Display + core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::to_string(&self) -> alloc::string::String +impl core::any::Any for aya::programs::uprobe::ProcMapError where T: 'static + core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::uprobe::ProcMapError where T: core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::uprobe::ProcMapError where T: core::marker::Sized +pub fn aya::programs::uprobe::ProcMapError::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapError::from(t: T) -> T pub enum aya::programs::uprobe::UProbeError pub aya::programs::uprobe::UProbeError::FileError pub aya::programs::uprobe::UProbeError::FileError::filename: alloc::string::String @@ -4327,6 +4361,9 @@ pub aya::programs::uprobe::UProbeError::InvalidLdSoCache pub aya::programs::uprobe::UProbeError::InvalidLdSoCache::io_error: alloc::sync::Arc pub aya::programs::uprobe::UProbeError::InvalidTarget pub aya::programs::uprobe::UProbeError::InvalidTarget::path: std::path::PathBuf +pub aya::programs::uprobe::UProbeError::ProcMapError +pub aya::programs::uprobe::UProbeError::ProcMapError::pid: i32 +pub aya::programs::uprobe::UProbeError::ProcMapError::source: aya::programs::uprobe::ProcMapError pub aya::programs::uprobe::UProbeError::SymbolError pub aya::programs::uprobe::UProbeError::SymbolError::error: alloc::boxed::Box<(dyn core::error::Error + core::marker::Send + core::marker::Sync)> pub aya::programs::uprobe::UProbeError::SymbolError::symbol: alloc::string::String @@ -4363,6 +4400,64 @@ impl core::borrow::BorrowMut for aya::programs::uprobe::UProbeError where pub fn aya::programs::uprobe::UProbeError::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya::programs::uprobe::UProbeError pub fn aya::programs::uprobe::UProbeError::from(t: T) -> T +pub struct aya::programs::uprobe::ProcMap +impl aya::programs::uprobe::ProcMap +pub fn aya::programs::uprobe::ProcMap::entries(&self) -> impl core::iter::traits::iterator::Iterator +pub fn aya::programs::uprobe::ProcMap::new(pid: libc::unix::pid_t) -> core::result::Result +impl core::marker::Send for aya::programs::uprobe::ProcMap +impl core::marker::Sync for aya::programs::uprobe::ProcMap +impl core::marker::Unpin for aya::programs::uprobe::ProcMap +impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::uprobe::ProcMap +impl core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::ProcMap +impl core::convert::Into for aya::programs::uprobe::ProcMap where U: core::convert::From +pub fn aya::programs::uprobe::ProcMap::into(self) -> U +impl core::convert::TryFrom for aya::programs::uprobe::ProcMap where U: core::convert::Into +pub type aya::programs::uprobe::ProcMap::Error = core::convert::Infallible +pub fn aya::programs::uprobe::ProcMap::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::uprobe::ProcMap where U: core::convert::TryFrom +pub type aya::programs::uprobe::ProcMap::Error = >::Error +pub fn aya::programs::uprobe::ProcMap::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya::programs::uprobe::ProcMap where T: 'static + core::marker::Sized +pub fn aya::programs::uprobe::ProcMap::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::uprobe::ProcMap where T: core::marker::Sized +pub fn aya::programs::uprobe::ProcMap::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::uprobe::ProcMap where T: core::marker::Sized +pub fn aya::programs::uprobe::ProcMap::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::programs::uprobe::ProcMap +pub fn aya::programs::uprobe::ProcMap::from(t: T) -> T +pub struct aya::programs::uprobe::ProcMapEntry +impl aya::programs::uprobe::ProcMapEntry +pub fn aya::programs::uprobe::ProcMapEntry::address(&self) -> u64 +pub fn aya::programs::uprobe::ProcMapEntry::address_end(&self) -> u64 +pub fn aya::programs::uprobe::ProcMapEntry::dev(&self) -> &str +pub fn aya::programs::uprobe::ProcMapEntry::inode(&self) -> u32 +pub fn aya::programs::uprobe::ProcMapEntry::offset(&self) -> u64 +pub fn aya::programs::uprobe::ProcMapEntry::path(&self) -> core::option::Option<&str> +pub fn aya::programs::uprobe::ProcMapEntry::perms(&self) -> &str +impl core::str::traits::FromStr for aya::programs::uprobe::ProcMapEntry +pub type aya::programs::uprobe::ProcMapEntry::Err = aya::programs::uprobe::ProcMapError +pub fn aya::programs::uprobe::ProcMapEntry::from_str(line: &str) -> core::result::Result +impl core::marker::Send for aya::programs::uprobe::ProcMapEntry +impl core::marker::Sync for aya::programs::uprobe::ProcMapEntry +impl core::marker::Unpin for aya::programs::uprobe::ProcMapEntry +impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::uprobe::ProcMapEntry +impl core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::ProcMapEntry +impl core::convert::Into for aya::programs::uprobe::ProcMapEntry where U: core::convert::From +pub fn aya::programs::uprobe::ProcMapEntry::into(self) -> U +impl core::convert::TryFrom for aya::programs::uprobe::ProcMapEntry where U: core::convert::Into +pub type aya::programs::uprobe::ProcMapEntry::Error = core::convert::Infallible +pub fn aya::programs::uprobe::ProcMapEntry::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::uprobe::ProcMapEntry where U: core::convert::TryFrom +pub type aya::programs::uprobe::ProcMapEntry::Error = >::Error +pub fn aya::programs::uprobe::ProcMapEntry::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya::programs::uprobe::ProcMapEntry where T: 'static + core::marker::Sized +pub fn aya::programs::uprobe::ProcMapEntry::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::uprobe::ProcMapEntry where T: core::marker::Sized +pub fn aya::programs::uprobe::ProcMapEntry::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::uprobe::ProcMapEntry where T: core::marker::Sized +pub fn aya::programs::uprobe::ProcMapEntry::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::programs::uprobe::ProcMapEntry +pub fn aya::programs::uprobe::ProcMapEntry::from(t: T) -> T pub struct aya::programs::uprobe::UProbe impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::attach>(&mut self, fn_name: core::option::Option<&str>, offset: u64, target: T, pid: core::option::Option) -> core::result::Result @@ -5454,6 +5549,9 @@ pub aya::programs::UProbeError::InvalidLdSoCache pub aya::programs::UProbeError::InvalidLdSoCache::io_error: alloc::sync::Arc pub aya::programs::UProbeError::InvalidTarget pub aya::programs::UProbeError::InvalidTarget::path: std::path::PathBuf +pub aya::programs::UProbeError::ProcMapError +pub aya::programs::UProbeError::ProcMapError::pid: i32 +pub aya::programs::UProbeError::ProcMapError::source: aya::programs::uprobe::ProcMapError pub aya::programs::UProbeError::SymbolError pub aya::programs::UProbeError::SymbolError::error: alloc::boxed::Box<(dyn core::error::Error + core::marker::Send + core::marker::Sync)> pub aya::programs::UProbeError::SymbolError::symbol: alloc::string::String