mirror of https://github.com/aya-rs/aya
aya: split aya::programs::probe into ::kprobe and ::uprobe & add docs
parent
d9634ae945
commit
ae863bc663
@ -0,0 +1,54 @@
|
||||
//! Kernel space probes.
|
||||
//!
|
||||
//! Kernel probes are eBPF programs that can be attached to almost any function inside the kernel.
|
||||
use libc::pid_t;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
|
||||
programs::{
|
||||
load_program,
|
||||
probe::{attach, ProbeKind},
|
||||
LinkRef, ProgramData, ProgramError,
|
||||
},
|
||||
};
|
||||
|
||||
/// A `kprobe` program.
|
||||
#[derive(Debug)]
|
||||
pub struct KProbe {
|
||||
pub(crate) data: ProgramData,
|
||||
pub(crate) kind: ProbeKind,
|
||||
}
|
||||
|
||||
impl KProbe {
|
||||
/// Loads the program inside the kernel.
|
||||
///
|
||||
/// See also [`Program::load`](crate::programs::Program::load).
|
||||
pub fn load(&mut self) -> Result<(), ProgramError> {
|
||||
load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.data.name.to_string()
|
||||
}
|
||||
|
||||
pub fn attach(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
offset: u64,
|
||||
pid: Option<pid_t>,
|
||||
) -> Result<LinkRef, ProgramError> {
|
||||
attach(&mut self.data, self.kind, fn_name, offset, pid)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum KProbeError {
|
||||
#[error("`{filename}`")]
|
||||
FileError {
|
||||
filename: String,
|
||||
#[source]
|
||||
io_error: io::Error,
|
||||
},
|
||||
}
|
@ -0,0 +1,266 @@
|
||||
//! User space probes.
|
||||
use libc::pid_t;
|
||||
use object::{Object, ObjectSymbol};
|
||||
use std::{
|
||||
error::Error,
|
||||
ffi::CStr,
|
||||
fs,
|
||||
io::{self, BufRead, Cursor, Read},
|
||||
mem,
|
||||
os::raw::c_char,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
|
||||
programs::{
|
||||
load_program,
|
||||
probe::{attach, ProbeKind},
|
||||
LinkRef, ProgramData, ProgramError,
|
||||
},
|
||||
};
|
||||
|
||||
const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache";
|
||||
|
||||
lazy_static! {
|
||||
static ref LD_SO_CACHE: Result<LdSoCache, Arc<io::Error>> =
|
||||
LdSoCache::load(LD_SO_CACHE_FILE).map_err(Arc::new);
|
||||
}
|
||||
const LD_SO_CACHE_HEADER: &str = "glibc-ld.so.cache1.1";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UProbe {
|
||||
pub(crate) data: ProgramData,
|
||||
pub(crate) kind: ProbeKind,
|
||||
}
|
||||
|
||||
impl UProbe {
|
||||
pub fn load(&mut self) -> Result<(), ProgramError> {
|
||||
load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.data.name.to_string()
|
||||
}
|
||||
|
||||
pub fn attach<T: AsRef<Path>>(
|
||||
&mut self,
|
||||
fn_name: Option<&str>,
|
||||
offset: u64,
|
||||
target: T,
|
||||
pid: Option<pid_t>,
|
||||
) -> Result<LinkRef, ProgramError> {
|
||||
let target = target.as_ref();
|
||||
let target_str = &*target.as_os_str().to_string_lossy();
|
||||
|
||||
let mut path = if let Some(pid) = pid {
|
||||
find_lib_in_proc_maps(pid, &target_str).map_err(|io_error| UProbeError::FileError {
|
||||
filename: format!("/proc/{}/maps", pid),
|
||||
io_error,
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if path.is_none() {
|
||||
path = if target.is_absolute() {
|
||||
Some(target_str)
|
||||
} else {
|
||||
let cache =
|
||||
LD_SO_CACHE
|
||||
.as_ref()
|
||||
.map_err(|error| UProbeError::InvalidLdSoCache {
|
||||
io_error: error.clone(),
|
||||
})?;
|
||||
cache.resolve(target_str)
|
||||
}
|
||||
.map(String::from)
|
||||
};
|
||||
|
||||
let path = path.ok_or(UProbeError::InvalidTarget {
|
||||
path: target.to_owned(),
|
||||
})?;
|
||||
|
||||
let sym_offset = if let Some(fn_name) = fn_name {
|
||||
resolve_symbol(&path, fn_name).map_err(|error| UProbeError::SymbolError {
|
||||
symbol: fn_name.to_string(),
|
||||
error: Box::new(error),
|
||||
})?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
attach(&mut self.data, self.kind, &path, sym_offset + offset, pid)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UProbeError {
|
||||
#[error("error reading `{}` file", LD_SO_CACHE_FILE)]
|
||||
InvalidLdSoCache {
|
||||
#[source]
|
||||
io_error: Arc<io::Error>,
|
||||
},
|
||||
|
||||
#[error("could not resolve uprobe target `{path}`")]
|
||||
InvalidTarget { path: PathBuf },
|
||||
|
||||
#[error("error resolving symbol")]
|
||||
SymbolError {
|
||||
symbol: String,
|
||||
#[source]
|
||||
error: Box<dyn Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error("`{filename}`")]
|
||||
FileError {
|
||||
filename: String,
|
||||
#[source]
|
||||
io_error: io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
fn proc_maps_libs(pid: pid_t) -> Result<Vec<(String, String)>, io::Error> {
|
||||
let maps_file = format!("/proc/{}/maps", pid);
|
||||
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<Option<String>, io::Error> {
|
||||
let libs = proc_maps_libs(pid)?;
|
||||
|
||||
let ret = if lib.contains(".so") {
|
||||
libs.iter().find(|(k, _)| k.as_str().starts_with(lib))
|
||||
} else {
|
||||
let lib = lib.to_string();
|
||||
let lib1 = lib.clone() + ".so";
|
||||
let lib2 = lib + "-";
|
||||
libs.iter()
|
||||
.find(|(k, _)| k.starts_with(&lib1) || k.starts_with(&lib2))
|
||||
};
|
||||
|
||||
Ok(ret.map(|(_, v)| v.clone()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CacheEntry {
|
||||
key: String,
|
||||
value: String,
|
||||
flags: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LdSoCache {
|
||||
entries: Vec<CacheEntry>,
|
||||
}
|
||||
|
||||
impl LdSoCache {
|
||||
pub fn load<T: AsRef<Path>>(path: T) -> Result<Self, io::Error> {
|
||||
let data = fs::read(path)?;
|
||||
Self::parse(&data)
|
||||
}
|
||||
|
||||
fn parse(data: &[u8]) -> Result<Self, io::Error> {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
let read_u32 = |cursor: &mut Cursor<_>| -> Result<u32, io::Error> {
|
||||
let mut buf = [0u8; mem::size_of::<u32>()];
|
||||
cursor.read_exact(&mut buf)?;
|
||||
|
||||
Ok(u32::from_ne_bytes(buf))
|
||||
};
|
||||
|
||||
let read_i32 = |cursor: &mut Cursor<_>| -> Result<i32, io::Error> {
|
||||
let mut buf = [0u8; mem::size_of::<i32>()];
|
||||
cursor.read_exact(&mut buf)?;
|
||||
|
||||
Ok(i32::from_ne_bytes(buf))
|
||||
};
|
||||
|
||||
let mut buf = [0u8; LD_SO_CACHE_HEADER.len()];
|
||||
cursor.read_exact(&mut buf)?;
|
||||
let header = std::str::from_utf8(&buf).or(Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid ld.so.cache header",
|
||||
)))?;
|
||||
if header != LD_SO_CACHE_HEADER {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid ld.so.cache header",
|
||||
));
|
||||
}
|
||||
|
||||
let num_entries = read_u32(&mut cursor)?;
|
||||
let _str_tab_len = read_u32(&mut cursor)?;
|
||||
cursor.consume(5 * mem::size_of::<u32>());
|
||||
|
||||
let mut entries = Vec::new();
|
||||
for _ in 0..num_entries {
|
||||
let flags = read_i32(&mut cursor)?;
|
||||
let k_pos = read_u32(&mut cursor)? as usize;
|
||||
let v_pos = read_u32(&mut cursor)? as usize;
|
||||
cursor.consume(12);
|
||||
let key =
|
||||
unsafe { CStr::from_ptr(cursor.get_ref()[k_pos..].as_ptr() as *const c_char) }
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let value =
|
||||
unsafe { CStr::from_ptr(cursor.get_ref()[v_pos..].as_ptr() as *const c_char) }
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
entries.push(CacheEntry { key, value, flags });
|
||||
}
|
||||
|
||||
Ok(LdSoCache { entries })
|
||||
}
|
||||
|
||||
pub fn resolve(&self, lib: &str) -> Option<&str> {
|
||||
let lib = if !lib.contains(".so") {
|
||||
lib.to_string() + ".so"
|
||||
} else {
|
||||
lib.to_string()
|
||||
};
|
||||
self.entries
|
||||
.iter()
|
||||
.find(|entry| entry.key.starts_with(&lib))
|
||||
.map(|entry| entry.value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum ResolveSymbolError {
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error("error parsing ELF")]
|
||||
Object(#[from] object::Error),
|
||||
|
||||
#[error("unknown symbol `{0}`")]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
fn resolve_symbol(path: &str, symbol: &str) -> Result<u64, ResolveSymbolError> {
|
||||
let data = fs::read(path)?;
|
||||
let obj = object::read::File::parse(&data)?;
|
||||
|
||||
obj.dynamic_symbols()
|
||||
.chain(obj.symbols())
|
||||
.find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false))
|
||||
.map(|s| s.address())
|
||||
.ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))
|
||||
}
|
Loading…
Reference in New Issue