|
|
@ -4,7 +4,7 @@ use object::{Object, ObjectSection, ObjectSymbol};
|
|
|
|
use std::{
|
|
|
|
use std::{
|
|
|
|
borrow::Cow,
|
|
|
|
borrow::Cow,
|
|
|
|
error::Error,
|
|
|
|
error::Error,
|
|
|
|
ffi::CStr,
|
|
|
|
ffi::{CStr, OsStr, OsString},
|
|
|
|
fs,
|
|
|
|
fs,
|
|
|
|
io::{self, BufRead, Cursor, Read},
|
|
|
|
io::{self, BufRead, Cursor, Read},
|
|
|
|
mem,
|
|
|
|
mem,
|
|
|
@ -49,6 +49,39 @@ pub struct UProbe {
|
|
|
|
pub(crate) kind: ProbeKind,
|
|
|
|
pub(crate) kind: ProbeKind,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trait OsStringExt {
|
|
|
|
|
|
|
|
fn starts_with(&self, needle: &OsStr) -> bool;
|
|
|
|
|
|
|
|
fn ends_with(&self, needle: &OsStr) -> bool;
|
|
|
|
|
|
|
|
fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr>;
|
|
|
|
|
|
|
|
fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr>;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl OsStringExt for OsStr {
|
|
|
|
|
|
|
|
fn starts_with(&self, needle: &OsStr) -> bool {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes().starts_with(needle.as_bytes())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn ends_with(&self, needle: &OsStr) -> bool {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes().ends_with(needle.as_bytes())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn strip_prefix(&self, prefix: &OsStr) -> Option<&OsStr> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes()
|
|
|
|
|
|
|
|
.strip_prefix(prefix.as_bytes())
|
|
|
|
|
|
|
|
.map(OsStr::from_bytes)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn strip_suffix(&self, suffix: &OsStr) -> Option<&OsStr> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
self.as_bytes()
|
|
|
|
|
|
|
|
.strip_suffix(suffix.as_bytes())
|
|
|
|
|
|
|
|
.map(OsStr::from_bytes)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl UProbe {
|
|
|
|
impl UProbe {
|
|
|
|
/// Loads the program inside the kernel.
|
|
|
|
/// Loads the program inside the kernel.
|
|
|
|
pub fn load(&mut self) -> Result<(), ProgramError> {
|
|
|
|
pub fn load(&mut self) -> Result<(), ProgramError> {
|
|
|
@ -127,16 +160,15 @@ impl UProbe {
|
|
|
|
fn resolve_attach_path<T: AsRef<Path>>(
|
|
|
|
fn resolve_attach_path<T: AsRef<Path>>(
|
|
|
|
target: &T,
|
|
|
|
target: &T,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
pid: Option<pid_t>,
|
|
|
|
) -> Result<Cow<'_, str>, UProbeError> {
|
|
|
|
) -> Result<Cow<'_, Path>, UProbeError> {
|
|
|
|
// Look up the path for the target. If it there is a pid, and the target is a library name
|
|
|
|
// Look up the path for the target. If it there is a pid, and the target is a library name
|
|
|
|
// that is in the process's memory map, use the path of that library. Otherwise, use the target as-is.
|
|
|
|
// that is in the process's memory map, use the path of that library. Otherwise, use the target as-is.
|
|
|
|
let target = target.as_ref();
|
|
|
|
let target = target.as_ref();
|
|
|
|
let invalid_target = || UProbeError::InvalidTarget {
|
|
|
|
let invalid_target = || UProbeError::InvalidTarget {
|
|
|
|
path: target.to_owned(),
|
|
|
|
path: target.to_owned(),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let target_str = target.to_str().ok_or_else(invalid_target)?;
|
|
|
|
|
|
|
|
pid.and_then(|pid| {
|
|
|
|
pid.and_then(|pid| {
|
|
|
|
find_lib_in_proc_maps(pid, target_str)
|
|
|
|
find_lib_in_proc_maps(pid, target)
|
|
|
|
.map_err(|io_error| UProbeError::FileError {
|
|
|
|
.map_err(|io_error| UProbeError::FileError {
|
|
|
|
filename: format!("/proc/{pid}/maps"),
|
|
|
|
filename: format!("/proc/{pid}/maps"),
|
|
|
|
io_error,
|
|
|
|
io_error,
|
|
|
@ -144,14 +176,14 @@ fn resolve_attach_path<T: AsRef<Path>>(
|
|
|
|
.map(|v| v.map(Cow::Owned))
|
|
|
|
.map(|v| v.map(Cow::Owned))
|
|
|
|
.transpose()
|
|
|
|
.transpose()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target_str))))
|
|
|
|
.or_else(|| target.is_absolute().then(|| Ok(Cow::Borrowed(target))))
|
|
|
|
.or_else(|| {
|
|
|
|
.or_else(|| {
|
|
|
|
LD_SO_CACHE
|
|
|
|
LD_SO_CACHE
|
|
|
|
.as_ref()
|
|
|
|
.as_ref()
|
|
|
|
.map_err(|error| UProbeError::InvalidLdSoCache {
|
|
|
|
.map_err(|error| UProbeError::InvalidLdSoCache {
|
|
|
|
io_error: error.clone(),
|
|
|
|
io_error: error.clone(),
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.map(|cache| cache.resolve(target_str).map(Cow::Borrowed))
|
|
|
|
.map(|cache| cache.resolve(target).map(Cow::Borrowed))
|
|
|
|
.transpose()
|
|
|
|
.transpose()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| Err(invalid_target()))
|
|
|
|
.unwrap_or_else(|| Err(invalid_target()))
|
|
|
@ -172,6 +204,7 @@ fn test_resolve_attach_path() {
|
|
|
|
// Now let's resolve the path to libc. It should exist in the current process's memory map and
|
|
|
|
// 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.
|
|
|
|
// then in the ld.so.cache.
|
|
|
|
let libc_path = resolve_attach_path(&"libc", Some(pid)).unwrap();
|
|
|
|
let libc_path = resolve_attach_path(&"libc", Some(pid)).unwrap();
|
|
|
|
|
|
|
|
let libc_path = libc_path.to_str().unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure we got a path that contains libc.
|
|
|
|
// Make sure we got a path that contains libc.
|
|
|
|
assert!(libc_path.contains("libc"), "libc_path: {}", libc_path);
|
|
|
|
assert!(libc_path.contains("libc"), "libc_path: {}", libc_path);
|
|
|
@ -249,45 +282,55 @@ pub enum UProbeError {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn proc_maps_libs(pid: pid_t) -> Result<Vec<(String, String)>, io::Error> {
|
|
|
|
fn proc_maps_libs(pid: pid_t) -> Result<Vec<(OsString, PathBuf)>, io::Error> {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
|
|
|
|
let maps_file = format!("/proc/{pid}/maps");
|
|
|
|
let maps_file = format!("/proc/{pid}/maps");
|
|
|
|
let data = fs::read_to_string(maps_file)?;
|
|
|
|
let data = fs::read(maps_file)?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(data
|
|
|
|
let libs = data
|
|
|
|
.lines()
|
|
|
|
.split(|b| b == &b'\n')
|
|
|
|
.filter_map(|line| {
|
|
|
|
.filter_map(|mut line| {
|
|
|
|
let line = line.split_whitespace().last()?;
|
|
|
|
loop {
|
|
|
|
if line.starts_with('/') {
|
|
|
|
if let [stripped @ .., c] = line {
|
|
|
|
let path = PathBuf::from(line);
|
|
|
|
if c.is_ascii_whitespace() {
|
|
|
|
let key = path.file_name().unwrap().to_string_lossy().into_owned();
|
|
|
|
line = stripped;
|
|
|
|
Some((key, path.to_string_lossy().to_string()))
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
let path = line.split(|b| b.is_ascii_whitespace()).last()?;
|
|
|
|
|
|
|
|
let path = Path::new(OsStr::from_bytes(path));
|
|
|
|
|
|
|
|
path.is_absolute()
|
|
|
|
|
|
|
|
.then(|| {
|
|
|
|
|
|
|
|
path.file_name()
|
|
|
|
|
|
|
|
.map(|file_name| (file_name.to_owned(), path.to_owned()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect())
|
|
|
|
.flatten()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
Ok(libs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn find_lib_in_proc_maps(pid: pid_t, lib: &str) -> Result<Option<String>, io::Error> {
|
|
|
|
fn find_lib_in_proc_maps(pid: pid_t, lib: &Path) -> Result<Option<PathBuf>, io::Error> {
|
|
|
|
let libs = proc_maps_libs(pid)?;
|
|
|
|
let libs = proc_maps_libs(pid)?;
|
|
|
|
|
|
|
|
|
|
|
|
let ret = if lib.contains(".so") {
|
|
|
|
let lib = lib.as_os_str();
|
|
|
|
libs.into_iter().find(|(k, _)| k.as_str().starts_with(lib))
|
|
|
|
let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(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))
|
|
|
|
Ok(libs.into_iter().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)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub(crate) struct CacheEntry {
|
|
|
|
pub(crate) struct CacheEntry {
|
|
|
|
key: String,
|
|
|
|
key: OsString,
|
|
|
|
value: String,
|
|
|
|
value: OsString,
|
|
|
|
_flags: i32,
|
|
|
|
_flags: i32,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -368,11 +411,16 @@ impl LdSoCache {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let read_str = |pos| {
|
|
|
|
let read_str = |pos| {
|
|
|
|
|
|
|
|
use std::os::unix::ffi::OsStrExt as _;
|
|
|
|
|
|
|
|
OsStr::from_bytes(
|
|
|
|
unsafe {
|
|
|
|
unsafe {
|
|
|
|
CStr::from_ptr(cursor.get_ref()[offset + pos..].as_ptr() as *const c_char)
|
|
|
|
CStr::from_ptr(
|
|
|
|
|
|
|
|
cursor.get_ref()[offset + pos..].as_ptr() as *const c_char
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.to_string_lossy()
|
|
|
|
.to_bytes(),
|
|
|
|
.into_owned()
|
|
|
|
)
|
|
|
|
|
|
|
|
.to_owned()
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let key = read_str(k_pos);
|
|
|
|
let key = read_str(k_pos);
|
|
|
@ -389,16 +437,18 @@ impl LdSoCache {
|
|
|
|
Ok(LdSoCache { entries })
|
|
|
|
Ok(LdSoCache { entries })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn resolve(&self, lib: &str) -> Option<&str> {
|
|
|
|
pub fn resolve(&self, lib: &Path) -> Option<&Path> {
|
|
|
|
let lib = if !lib.contains(".so") {
|
|
|
|
let lib = lib.as_os_str();
|
|
|
|
lib.to_string() + ".so"
|
|
|
|
let lib = lib.strip_suffix(OsStr::new(".so")).unwrap_or(lib);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
lib.to_string()
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
self.entries
|
|
|
|
self.entries
|
|
|
|
.iter()
|
|
|
|
.iter()
|
|
|
|
.find(|entry| entry.key.starts_with(&lib))
|
|
|
|
.find_map(|CacheEntry { key, value, _flags }| {
|
|
|
|
.map(|entry| entry.value.as_str())
|
|
|
|
key.strip_prefix(lib).and_then(|suffix| {
|
|
|
|
|
|
|
|
suffix
|
|
|
|
|
|
|
|
.starts_with(OsStr::new(".so"))
|
|
|
|
|
|
|
|
.then_some(Path::new(value.as_os_str()))
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -420,7 +470,7 @@ enum ResolveSymbolError {
|
|
|
|
SectionFileRangeNone(String, Result<String, object::Error>),
|
|
|
|
SectionFileRangeNone(String, Result<String, object::Error>),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn resolve_symbol(path: &str, symbol: &str) -> Result<u64, ResolveSymbolError> {
|
|
|
|
fn resolve_symbol(path: &Path, symbol: &str) -> Result<u64, ResolveSymbolError> {
|
|
|
|
let data = fs::read(path)?;
|
|
|
|
let data = fs::read(path)?;
|
|
|
|
let obj = object::read::File::parse(&*data)?;
|
|
|
|
let obj = object::read::File::parse(&*data)?;
|
|
|
|
|
|
|
|
|
|
|
|