aya: parse complete entries from /proc/$pid/maps

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
Signed-off-by: Tamir Duberstein <tamird@gmail.com>
pull/848/head
Dave Tucker 3 years ago committed by Tamir Duberstein
parent de0b7cee8d
commit ea76e0f62d

@ -141,17 +141,25 @@ impl UProbe {
}
}
fn resolve_attach_path<'a, 'b, 'c>(
fn resolve_attach_path<'a, 'b, 'c, T>(
target: &'a Path,
proc_map: Option<&'b ProcMap>,
proc_map: Option<&'b ProcMap<T>>,
) -> Result<&'c Path, UProbeError>
where
'a: 'c,
'b: 'c,
T: AsRef<[u8]>,
{
proc_map
.and_then(|proc_map| proc_map.find_lib(target))
.map(Ok)
.and_then(|proc_map| {
proc_map
.find_library_path_by_name(target)
.map_err(|source| UProbeError::ProcMap {
pid: proc_map.pid,
source,
})
.transpose()
})
.or_else(|| target.is_absolute().then(|| Ok(target)))
.or_else(|| {
LD_SO_CACHE
@ -182,11 +190,19 @@ fn test_resolve_attach_path() {
// Now let's resolve the path to libc. It should exist in the current process's memory map and
// then in the ld.so.cache.
let libc_path = resolve_attach_path("libc".as_ref(), Some(&proc_map)).unwrap();
let libc_path = libc_path.to_str().unwrap();
let libc_path = resolve_attach_path("libc".as_ref(), Some(&proc_map)).unwrap_or_else(|err| {
match err.source() {
Some(source) => panic!("{err}: {source}"),
None => panic!("{err}"),
}
});
// Make sure we got a path that contains libc.
assert!(libc_path.contains("libc"), "libc_path: {}", libc_path);
assert_matches::assert_matches!(
libc_path.to_str(),
Some(libc_path) if libc_path.contains("libc"),
"libc_path: {}", libc_path.display()
);
}
define_link_wrapper!(
@ -260,47 +276,197 @@ pub enum UProbeError {
#[source]
io_error: io::Error,
},
/// There was en error fetching the memory map for `pid`.
#[error("error fetching libs for {pid}")]
ProcMap {
/// The pid.
pid: i32,
/// The [`ProcMapError`] that caused the error.
#[source]
source: ProcMapError,
},
}
/// Error reading from /proc/pid/maps.
#[derive(Debug, Error)]
pub enum ProcMapError {
/// Unable to read /proc/pid/maps.
#[error(transparent)]
ReadFile(#[from] io::Error),
/// Error parsing a line of /proc/pid/maps.
#[error("could not parse {:?}", OsStr::from_bytes(line))]
ParseLine {
/// The line that could not be parsed.
line: Vec<u8>,
},
}
struct ProcMap {
data: Vec<u8>,
/// 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)]
struct ProcMapEntry<'a> {
#[cfg_attr(not(test), expect(dead_code))]
address: u64,
#[cfg_attr(not(test), expect(dead_code))]
address_end: u64,
#[cfg_attr(not(test), expect(dead_code))]
perms: &'a OsStr,
#[cfg_attr(not(test), expect(dead_code))]
offset: u64,
#[cfg_attr(not(test), expect(dead_code))]
dev: &'a OsStr,
#[cfg_attr(not(test), expect(dead_code))]
inode: u32,
path: Option<&'a Path>,
}
impl ProcMap {
impl<'a> ProcMapEntry<'a> {
fn parse(line: &'a [u8]) -> Result<Self, ProcMapError> {
use std::os::unix::ffi::OsStrExt as _;
let err = || ProcMapError::ParseLine {
line: line.to_vec(),
};
let mut parts = line
.split(|b| b.is_ascii_whitespace())
.filter(|part| !part.is_empty());
let mut next = || parts.next().ok_or_else(err);
let (start, end) = {
let addr = next()?;
let mut addr_parts = addr.split(|b| *b == b'-');
let mut next = || {
addr_parts
.next()
.ok_or(())
.and_then(|part| {
let s =
std::str::from_utf8(part).map_err(|std::str::Utf8Error { .. }| ())?;
let n = u64::from_str_radix(s, 16)
.map_err(|std::num::ParseIntError { .. }| ())?;
Ok(n)
})
.map_err(|()| err())
};
let start = next()?;
let end = next()?;
if let Some(_part) = addr_parts.next() {
return Err(err());
}
(start, end)
};
let perms = next()?;
let perms = OsStr::from_bytes(perms);
let offset = next()?;
let offset = std::str::from_utf8(offset).map_err(|std::str::Utf8Error { .. }| err())?;
let offset =
u64::from_str_radix(offset, 16).map_err(|std::num::ParseIntError { .. }| err())?;
let dev = next()?;
let dev = OsStr::from_bytes(dev);
let inode = next()?;
let inode = std::str::from_utf8(inode).map_err(|std::str::Utf8Error { .. }| err())?;
let inode = inode
.parse()
.map_err(|std::num::ParseIntError { .. }| err())?;
let path = parts
.next()
.and_then(|path| match path {
[b'[', .., b']'] => None,
path => {
let path = Path::new(OsStr::from_bytes(path));
if !path.is_absolute() {
Some(Err(err()))
} else {
Some(Ok(path))
}
}
})
.transpose()?;
if let Some(_part) = parts.next() {
return Err(err());
}
Ok(Self {
address: start,
address_end: end,
perms,
offset,
dev,
inode,
path,
})
}
}
/// The memory maps of a process.
///
/// This is read from /proc/`pid`/maps.
///
/// The information here may be used to resolve addresses to paths.
struct ProcMap<T> {
pid: pid_t,
data: T,
}
impl ProcMap<Vec<u8>> {
fn new(pid: pid_t) -> Result<Self, UProbeError> {
let filename = PathBuf::from(format!("/proc/{pid}/maps"));
let data = fs::read(&filename)
.map_err(|io_error| UProbeError::FileError { filename, io_error })?;
Ok(Self { data })
Ok(Self { pid, data })
}
}
fn libs(&self) -> impl Iterator<Item = (&OsStr, &Path)> {
let Self { data } = self;
impl<T: AsRef<[u8]>> ProcMap<T> {
fn libs(&self) -> impl Iterator<Item = Result<ProcMapEntry<'_>, ProcMapError>> {
let Self { pid: _, data } = self;
data.split(|&b| b == b'\n').filter_map(|line| {
line.split(|b| b.is_ascii_whitespace())
.filter(|p| !p.is_empty())
.next_back()
.and_then(|path| {
let path = Path::new(OsStr::from_bytes(path));
path.is_absolute()
.then_some(())
.and_then(|()| path.file_name())
.map(|file_name| (file_name, path))
})
})
data.as_ref()
.split(|&b| b == b'\n')
.map(ProcMapEntry::parse)
}
fn find_lib(&self, lib: &Path) -> Option<&Path> {
// 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: &Path) -> Result<Option<&Path>, ProcMapError> {
let lib = lib.as_os_str();
let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib);
self.libs().find_map(|(file_name, path)| {
file_name.strip_prefix(lib).and_then(|suffix| {
(suffix.starts_with(OsStr::new(".so")) || suffix.starts_with(OsStr::new("-")))
.then_some(path)
})
})
for entry in self.libs() {
let ProcMapEntry {
address: _,
address_end: _,
perms: _,
offset: _,
dev: _,
inode: _,
path,
} = entry?;
if let Some(path) = path {
if let Some(filename) = path.file_name() {
if let Some(suffix) = filename.strip_prefix(lib) {
if suffix.is_empty()
|| suffix.starts_with(OsStr::new(".so"))
|| suffix.starts_with(OsStr::new("-"))
{
return Ok(Some(path));
}
}
}
}
}
Ok(None)
}
}
@ -569,7 +735,7 @@ fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError>
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use object::{Architecture, BinaryFormat, Endianness, write::SectionKind};
use super::*;
@ -736,4 +902,141 @@ mod tests {
Err(ResolveSymbolError::BuildIdMismatch(_))
));
}
#[test]
fn test_parse_proc_map_entry_shared_lib() {
assert_matches!(
ProcMapEntry::parse(b"7ffd6fbea000-7ffd6fbec000 r-xp 00000000 00:00 0 [vdso]"),
Ok(ProcMapEntry {
address: 0x7ffd6fbea000,
address_end: 0x7ffd6fbec000,
perms,
offset: 0,
dev,
inode: 0,
path: None,
}) if perms == "r-xp" && dev == "00:00"
);
}
#[test]
fn test_parse_proc_map_entry_absolute_path() {
assert_matches!(
ProcMapEntry::parse(b"7f1bca83a000-7f1bca83c000 rw-p 00036000 fd:01 2895508 /usr/lib64/ld-linux-x86-64.so.2"),
Ok(ProcMapEntry {
address: 0x7f1bca83a000,
address_end: 0x7f1bca83c000,
perms,
offset: 0x00036000,
dev,
inode: 2895508,
path: Some(path),
}) if perms == "rw-p" && dev == "fd:01" && path == Path::new("/usr/lib64/ld-linux-x86-64.so.2")
);
}
#[test]
fn test_parse_proc_map_entry_all_zeros() {
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0"),
Ok(ProcMapEntry {
address: 0x7f1bca5f9000,
address_end: 0x7f1bca601000,
perms,
offset: 0,
dev,
inode: 0,
path: None,
}) if perms == "rw-p" && dev == "00:00"
);
}
#[test]
fn test_parse_proc_map_entry_parse_errors() {
assert_matches!(
ProcMapEntry::parse(b"zzzz-7ffd6fbea000 r-xp 00000000 00:00 0 [vdso]"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"zzzz-7ffd6fbea000 r-xp 00000000 00:00 0 [vdso]"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 r-xp zzzz 00:00 0 [vdso]"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 r-xp 00000000 00:00 zzzz [vdso]"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f90007ffd6fbea000 r-xp 00000000 00:00 0 [vdso]"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 r-xp 00000000"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000-deadbeef rw-p 00000000 00:00 0"),
Err(ProcMapError::ParseLine { line: _ })
);
assert_matches!(
ProcMapEntry::parse(b"7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0 deadbeef"),
Err(ProcMapError::ParseLine { line: _ })
);
}
#[test]
fn test_proc_map_find_lib_by_name() {
let proc_map_libs = ProcMap {
pid: 0xdead,
data: b"7fc4a9800000-7fc4a98ad000 r--p 00000000 00:24 18147308 /usr/lib64/libcrypto.so.3.0.9",
};
assert_matches!(
proc_map_libs.find_library_path_by_name(Path::new("libcrypto.so.3.0.9")),
Ok(Some(path)) if path == Path::new("/usr/lib64/libcrypto.so.3.0.9")
);
}
#[test]
fn test_proc_map_find_lib_by_partial_name() {
let proc_map_libs = ProcMap {
pid: 0xdead,
data: b"7fc4a9800000-7fc4a98ad000 r--p 00000000 00:24 18147308 /usr/lib64/libcrypto.so.3.0.9",
};
assert_matches!(
proc_map_libs.find_library_path_by_name(Path::new("libcrypto")),
Ok(Some(path)) if path == Path::new("/usr/lib64/libcrypto.so.3.0.9")
);
}
#[test]
fn test_proc_map_with_multiple_lib_entries() {
let proc_map_libs = ProcMap {
pid: 0xdead,
data: br#"
7f372868000-7f3722869000 r--p 00000000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2
7f3722869000-7f372288f000 r-xp 00001000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2
7f372288f000-7f3722899000 r--p 00027000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2
7f3722899000-7f372289b000 r--p 00030000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2
7f372289b000-7f372289d000 rw-p 00032000 00:24 18097875 /usr/lib64/ld-linux-x86-64.so.2
"#
.trim_ascii(),
};
assert_matches!(
proc_map_libs.find_library_path_by_name(Path::new("ld-linux-x86-64.so.2")),
Ok(Some(path)) if path == Path::new("/usr/lib64/ld-linux-x86-64.so.2")
);
}
}

@ -6581,6 +6581,42 @@ pub fn aya::programs::trace_point::TracePointLinkId::borrow_mut(&mut self) -> &m
impl<T> core::convert::From<T> 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::ParseLine
pub aya::programs::uprobe::ProcMapError::ParseLine::line: alloc::vec::Vec<u8>
pub aya::programs::uprobe::ProcMapError::ReadFile(std::io::error::Error)
impl core::convert::From<std::io::error::Error> for aya::programs::uprobe::ProcMapError
pub fn aya::programs::uprobe::ProcMapError::from(source: std::io::error::Error) -> Self
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::Debug for aya::programs::uprobe::ProcMapError
pub fn aya::programs::uprobe::ProcMapError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
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::marker::Freeze for aya::programs::uprobe::ProcMapError
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<T, U> core::convert::Into<U> for aya::programs::uprobe::ProcMapError where U: core::convert::From<T>
pub fn aya::programs::uprobe::ProcMapError::into(self) -> U
impl<T, U> core::convert::TryFrom<U> for aya::programs::uprobe::ProcMapError where U: core::convert::Into<T>
pub type aya::programs::uprobe::ProcMapError::Error = core::convert::Infallible
pub fn aya::programs::uprobe::ProcMapError::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
impl<T, U> core::convert::TryInto<U> for aya::programs::uprobe::ProcMapError where U: core::convert::TryFrom<T>
pub type aya::programs::uprobe::ProcMapError::Error = <U as core::convert::TryFrom<T>>::Error
pub fn aya::programs::uprobe::ProcMapError::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
impl<T> 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<T> 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<T> core::borrow::Borrow<T> for aya::programs::uprobe::ProcMapError where T: ?core::marker::Sized
pub fn aya::programs::uprobe::ProcMapError::borrow(&self) -> &T
impl<T> core::borrow::BorrowMut<T> for aya::programs::uprobe::ProcMapError where T: ?core::marker::Sized
pub fn aya::programs::uprobe::ProcMapError::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya::programs::uprobe::ProcMapError
pub fn aya::programs::uprobe::ProcMapError::from(t: T) -> T
pub enum aya::programs::uprobe::UProbeAttachLocation<'a>
pub aya::programs::uprobe::UProbeAttachLocation::AbsoluteOffset(u64)
pub aya::programs::uprobe::UProbeAttachLocation::Symbol(&'a str)
@ -6619,6 +6655,9 @@ pub aya::programs::uprobe::UProbeError::InvalidLdSoCache
pub aya::programs::uprobe::UProbeError::InvalidLdSoCache::io_error: &'static std::io::error::Error
pub aya::programs::uprobe::UProbeError::InvalidTarget
pub aya::programs::uprobe::UProbeError::InvalidTarget::path: std::path::PathBuf
pub aya::programs::uprobe::UProbeError::ProcMap
pub aya::programs::uprobe::UProbeError::ProcMap::pid: i32
pub aya::programs::uprobe::UProbeError::ProcMap::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
@ -7928,6 +7967,9 @@ pub aya::programs::UProbeError::InvalidLdSoCache
pub aya::programs::UProbeError::InvalidLdSoCache::io_error: &'static std::io::error::Error
pub aya::programs::UProbeError::InvalidTarget
pub aya::programs::UProbeError::InvalidTarget::path: std::path::PathBuf
pub aya::programs::UProbeError::ProcMap
pub aya::programs::UProbeError::ProcMap::pid: i32
pub aya::programs::UProbeError::ProcMap::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

Loading…
Cancel
Save