mirror of https://github.com/aya-rs/aya
aya: Implement USDT probes
Signed-off-by: Dave Tucker <dave@dtucker.co.uk>pull/329/merge^2
parent
eb05c18140
commit
11b8dda719
@ -0,0 +1,474 @@
|
|||||||
|
//! User statically-defined tracepoints.
|
||||||
|
use aya_common::{UsdtSpec, USDT_MAX_SPEC_COUNT};
|
||||||
|
use libc::pid_t;
|
||||||
|
use object::{elf::*, read::elf::*, Endianness};
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
convert::TryInto,
|
||||||
|
ffi::CStr,
|
||||||
|
fs,
|
||||||
|
io::{self, BufRead, Cursor, Read},
|
||||||
|
mem,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
generated::{bpf_prog_type::BPF_PROG_TYPE_KPROBE, BPF_NOEXIST},
|
||||||
|
maps::{MapError, MapRefMut},
|
||||||
|
programs::{
|
||||||
|
define_link_wrapper, load_program,
|
||||||
|
perf_attach::{perf_attach, PerfLinkIdInner, PerfLinkInner},
|
||||||
|
probe::create_as_probe,
|
||||||
|
utils::{LD_SO_CACHE, LD_SO_CACHE_FILE},
|
||||||
|
Link, ProbeKind, ProgramData, ProgramError,
|
||||||
|
},
|
||||||
|
Pod, FEATURES,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::programs::utils::{ProcMap, ProcMapError};
|
||||||
|
|
||||||
|
unsafe impl Pod for UsdtSpec {}
|
||||||
|
|
||||||
|
/// Name of the map used for USDT specs.
|
||||||
|
pub const USDT_SPEC_MAP: &str = "__bpf_usdt_specs";
|
||||||
|
/// Name of the map used for USDT to IP mappings.
|
||||||
|
pub const USDT_IP_TO_SPEC_MAP: &str = "__bpf_usdt_ip_to_spec_id";
|
||||||
|
|
||||||
|
/// A user statically-defined tracepoint
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[doc(alias = "BPF_PROG_TYPE_KPROBE")]
|
||||||
|
pub struct Usdt {
|
||||||
|
pub(crate) data: ProgramData<UsdtLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Usdt {
|
||||||
|
/// Loads the program inside the kernel.
|
||||||
|
pub fn load(&mut self) -> Result<(), ProgramError> {
|
||||||
|
load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches the program.
|
||||||
|
///
|
||||||
|
/// Attaches the uprobe to the tracepoint `tp_provider`/`tp_name` defined in the `target`.
|
||||||
|
/// If `pid` is not `None`, the program executes only when the target
|
||||||
|
/// function is executed by the given `pid`.
|
||||||
|
///
|
||||||
|
/// The `target` argument can be an absolute path to a binary or library, or
|
||||||
|
/// a library name (eg: `"libc"`).
|
||||||
|
///
|
||||||
|
/// The returned value can be used to detach, see [Usdt::detach].
|
||||||
|
pub fn attach<T: AsRef<Path>>(
|
||||||
|
&mut self,
|
||||||
|
mut spec_map: crate::maps::Array<MapRefMut, UsdtSpec>,
|
||||||
|
mut ip_to_spec_map: crate::maps::HashMap<MapRefMut, i64, u32>,
|
||||||
|
tp_provider: &str,
|
||||||
|
tp_name: &str,
|
||||||
|
target: T,
|
||||||
|
pid: Option<pid_t>,
|
||||||
|
) -> Result<UsdtLinkId, ProgramError> {
|
||||||
|
let target = target.as_ref();
|
||||||
|
let target_str = &*target.as_os_str().to_string_lossy();
|
||||||
|
|
||||||
|
let mut path = if let Some(pid) = pid {
|
||||||
|
let proc_map_libs =
|
||||||
|
ProcMap::new(pid).map_err(|e| UsdtError::ProcMapError { pid, source: e })?;
|
||||||
|
proc_map_libs
|
||||||
|
.find_by_name(target_str)
|
||||||
|
.map_err(|io_error| UsdtError::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| UsdtError::InvalidLdSoCache {
|
||||||
|
io_error: error.clone(),
|
||||||
|
})?;
|
||||||
|
cache.resolve(target_str)
|
||||||
|
}
|
||||||
|
.map(String::from)
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = path.ok_or(UsdtError::InvalidTarget {
|
||||||
|
path: target.to_owned(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let tracepoints = collect_usdts(&path, tp_provider, tp_name, pid)?;
|
||||||
|
let mut perf_links = vec![];
|
||||||
|
let mut spec_ids = VecDeque::with_capacity(USDT_MAX_SPEC_COUNT as usize);
|
||||||
|
for i in 0..USDT_MAX_SPEC_COUNT {
|
||||||
|
spec_ids.push_back(i)
|
||||||
|
}
|
||||||
|
let mut spec_id_map = HashMap::new();
|
||||||
|
for t in tracepoints {
|
||||||
|
let id = if spec_id_map.contains_key(&t.args) {
|
||||||
|
*(spec_id_map.get(&t.args).unwrap())
|
||||||
|
} else {
|
||||||
|
let id = spec_ids.pop_front().unwrap();
|
||||||
|
spec_id_map.insert(t.args.clone(), id);
|
||||||
|
spec_map.set(id, t.spec, 0)?;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
let mut cookie = Some(id as u64);
|
||||||
|
if !FEATURES.bpf_cookie {
|
||||||
|
cookie.take();
|
||||||
|
if let Err(MapError::SyscallError { call, io_error }) =
|
||||||
|
ip_to_spec_map.insert(t.abs_ip.try_into().unwrap(), id, BPF_NOEXIST.into())
|
||||||
|
{
|
||||||
|
if io_error.raw_os_error().unwrap() != (-libc::EEXIST) {
|
||||||
|
return Err(ProgramError::MapError(MapError::SyscallError {
|
||||||
|
call,
|
||||||
|
io_error,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let fd = create_as_probe(ProbeKind::UProbe, &path, t.rel_ip, pid, Some(t.sem_off))?;
|
||||||
|
let link = perf_attach(self.data.fd_or_err()?, fd, cookie)?;
|
||||||
|
perf_links.push(link);
|
||||||
|
}
|
||||||
|
let link = UsdtLink(MultiPerfLink { perf_links });
|
||||||
|
self.data.links.insert(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detaches the program.
|
||||||
|
///
|
||||||
|
/// See [UProbe::attach].
|
||||||
|
pub fn detach(&mut self, link_id: UsdtLinkId) -> Result<(), ProgramError> {
|
||||||
|
self.data.links.remove(link_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes ownership of the link referenced by the provided link_id.
|
||||||
|
///
|
||||||
|
/// The link will be detached on `Drop` and the caller is now responsible
|
||||||
|
/// for managing its lifetime.
|
||||||
|
pub fn take_link(&mut self, link_id: UsdtLinkId) -> Result<UsdtLink, ProgramError> {
|
||||||
|
self.data.take_link(link_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The identifer of a MultiPerfLink.
|
||||||
|
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||||
|
pub struct MultiPerfLinkId(Vec<PerfLinkIdInner>);
|
||||||
|
|
||||||
|
/// The attachment type of USDT programs.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MultiPerfLink {
|
||||||
|
perf_links: Vec<PerfLinkInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Link for MultiPerfLink {
|
||||||
|
type Id = MultiPerfLinkId;
|
||||||
|
|
||||||
|
fn id(&self) -> Self::Id {
|
||||||
|
let ids = self.perf_links.iter().map(|p| p.id()).collect();
|
||||||
|
MultiPerfLinkId(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detach(self) -> Result<(), ProgramError> {
|
||||||
|
for l in self.perf_links {
|
||||||
|
l.detach()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_link_wrapper!(
|
||||||
|
/// The link used by [Usdt] programs.
|
||||||
|
UsdtLink,
|
||||||
|
/// The type returned by [Usdt::attach]. Can be passed to [Usdt::detach].
|
||||||
|
UsdtLinkId,
|
||||||
|
MultiPerfLink,
|
||||||
|
MultiPerfLinkId
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The type returned when attaching an [`UProbe`] fails.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UsdtError {
|
||||||
|
/// There was an error parsing `/etc/ld.so.cache`.
|
||||||
|
#[error("error reading `{}` file", LD_SO_CACHE_FILE)]
|
||||||
|
InvalidLdSoCache {
|
||||||
|
/// the original [`io::Error`]
|
||||||
|
#[source]
|
||||||
|
io_error: Arc<io::Error>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The target program could not be found.
|
||||||
|
#[error("could not resolve uprobe target `{path}`")]
|
||||||
|
InvalidTarget {
|
||||||
|
/// path to target
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// There was an error resolving the target symbol.
|
||||||
|
#[error("error resolving symbol")]
|
||||||
|
SymbolError {
|
||||||
|
/// symbol name
|
||||||
|
symbol: String,
|
||||||
|
/// the original error
|
||||||
|
#[source]
|
||||||
|
error: Box<dyn std::error::Error + Send + Sync>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// There was an error accessing `filename`.
|
||||||
|
#[error("`{filename}`")]
|
||||||
|
FileError {
|
||||||
|
/// The file name
|
||||||
|
filename: String,
|
||||||
|
/// The [`io::Error`] returned from the file operation
|
||||||
|
#[source]
|
||||||
|
io_error: io::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Unsupported file type
|
||||||
|
#[error("unsupported file type")]
|
||||||
|
Unsupported,
|
||||||
|
|
||||||
|
/// An [`io::Error`]
|
||||||
|
#[error("io error")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
|
||||||
|
/// An [`object::Error`]
|
||||||
|
#[error("error parsing ELF")]
|
||||||
|
Object(#[from] object::Error),
|
||||||
|
|
||||||
|
/// Can't find matching offset in shard libs
|
||||||
|
#[error("can't find matching offset in shared libs")]
|
||||||
|
OffsetError,
|
||||||
|
|
||||||
|
/// Section is not executable
|
||||||
|
#[error("section is not executable")]
|
||||||
|
NoExec,
|
||||||
|
|
||||||
|
/// Segment is not found
|
||||||
|
#[error("segment not found")]
|
||||||
|
SegmentNotFound,
|
||||||
|
|
||||||
|
/// BPF Cookies are not supported
|
||||||
|
#[error("bpf cookies are required to support attachment without a pid")]
|
||||||
|
NoCookie,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_usdts(
|
||||||
|
path: &str,
|
||||||
|
provider: &str,
|
||||||
|
name: &str,
|
||||||
|
pid: Option<pid_t>,
|
||||||
|
) -> Result<Vec<UsdtTarget>, UsdtError> {
|
||||||
|
let file = fs::read(path)?;
|
||||||
|
let data = &*file;
|
||||||
|
if let Ok(elf) = object::elf::FileHeader32::parse(data) {
|
||||||
|
if mem::size_of::<usize>() != 4 {
|
||||||
|
return Err(UsdtError::Unsupported);
|
||||||
|
}
|
||||||
|
return collect_usdts_from_elf(elf, data, provider, name, pid);
|
||||||
|
} else if let Ok(elf) = object::elf::FileHeader64::parse(data) {
|
||||||
|
if mem::size_of::<usize>() != 8 {
|
||||||
|
return Err(UsdtError::Unsupported);
|
||||||
|
}
|
||||||
|
return collect_usdts_from_elf(elf, data, provider, name, pid);
|
||||||
|
}
|
||||||
|
Err(UsdtError::Unsupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_usdts_from_elf<Elf: FileHeader<Endian = Endianness>>(
|
||||||
|
elf: &Elf,
|
||||||
|
data: &[u8],
|
||||||
|
provider: &str,
|
||||||
|
name: &str,
|
||||||
|
pid: Option<pid_t>,
|
||||||
|
) -> Result<Vec<UsdtTarget>, UsdtError> {
|
||||||
|
let endian = elf.endian()?;
|
||||||
|
let sections = elf.sections(endian, data)?;
|
||||||
|
let program_headers = elf.program_headers(endian, data)?;
|
||||||
|
let mut results = vec![];
|
||||||
|
let mut base_addr: Option<u64> = None;
|
||||||
|
if let Some((_, base_section)) = sections.section_by_name(endian, b".stapsdt.base") {
|
||||||
|
base_addr = Some(base_section.sh_addr(endian).into())
|
||||||
|
};
|
||||||
|
if let Some((_, notes_section)) = sections.section_by_name(endian, b".note.stapsdt") {
|
||||||
|
if let Some(mut notes) = notes_section.notes(endian, data)? {
|
||||||
|
while let Ok(Some(note)) = notes.next() {
|
||||||
|
if note.name() != b"stapsdt" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if note.n_type(endian) != 3 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let note_data = note.desc();
|
||||||
|
let n = UsdtNote::parse(endian, note_data)?;
|
||||||
|
if n.provider != provider || n.name != name {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut abs_ip = n.loc_addr;
|
||||||
|
if let Some(addr) = base_addr {
|
||||||
|
abs_ip += addr - n.base_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seg = find_segment_by_address::<Elf>(program_headers, endian, abs_ip)
|
||||||
|
.ok_or(UsdtError::SegmentNotFound)?;
|
||||||
|
if seg.p_flags(endian) & PF_X == 0 {
|
||||||
|
return Err(UsdtError::NoExec);
|
||||||
|
}
|
||||||
|
let rel_ip = abs_ip - seg.p_vaddr(endian).into() + seg.p_offset(endian).into();
|
||||||
|
|
||||||
|
// If attaching to a sharef library and bpf cookies are not supported.
|
||||||
|
// Abs address of attach points are required
|
||||||
|
if elf.e_type(endian) == ET_DYN && !FEATURES.bpf_cookie {
|
||||||
|
if pid.is_none() {
|
||||||
|
return Err(UsdtError::NoCookie);
|
||||||
|
}
|
||||||
|
let proc_map_libs =
|
||||||
|
ProcMap::new(pid.unwrap()).map_err(|e| UsdtError::ProcMapError {
|
||||||
|
pid: pid.unwrap(),
|
||||||
|
source: e,
|
||||||
|
})?;
|
||||||
|
let res = proc_map_libs
|
||||||
|
.find_by_offset(rel_ip)
|
||||||
|
.ok_or(UsdtError::OffsetError)?;
|
||||||
|
abs_ip = res.address - res.offset + rel_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sem_off = 0;
|
||||||
|
if n.sem_addr != 0x0 {
|
||||||
|
// semaphore refcnt support was in 4.20, which is min supported version so we assume its supported
|
||||||
|
let seg = find_segment_by_address::<Elf>(program_headers, endian, n.sem_addr)
|
||||||
|
.ok_or(UsdtError::SegmentNotFound)?;
|
||||||
|
if seg.p_flags(endian) & PF_X == 0 {
|
||||||
|
return Err(UsdtError::NoExec);
|
||||||
|
}
|
||||||
|
sem_off = n.sem_addr - seg.p_vaddr(endian).into() + seg.p_offset(endian).into();
|
||||||
|
}
|
||||||
|
let spec = n.args.parse().unwrap();
|
||||||
|
results.push(UsdtTarget {
|
||||||
|
abs_ip,
|
||||||
|
rel_ip,
|
||||||
|
sem_off,
|
||||||
|
args: n.args,
|
||||||
|
spec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_segment_by_address<Elf: FileHeader<Endian = Endianness>>(
|
||||||
|
program_headers: &[Elf::ProgramHeader],
|
||||||
|
endian: Endianness,
|
||||||
|
addr: u64,
|
||||||
|
) -> Option<&Elf::ProgramHeader> {
|
||||||
|
program_headers.iter().find(|&header| {
|
||||||
|
header.p_vaddr(endian).into() < addr
|
||||||
|
&& addr < (header.p_vaddr(endian).into() + header.p_memsz(endian).into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct UsdtTarget {
|
||||||
|
abs_ip: u64,
|
||||||
|
rel_ip: u64,
|
||||||
|
sem_off: u64,
|
||||||
|
args: String,
|
||||||
|
spec: UsdtSpec,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct UsdtNote {
|
||||||
|
loc_addr: u64,
|
||||||
|
base_addr: u64,
|
||||||
|
sem_addr: u64,
|
||||||
|
provider: String,
|
||||||
|
name: String,
|
||||||
|
args: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsdtNote {
|
||||||
|
pub(crate) fn parse(endianness: Endianness, data: &[u8]) -> Result<UsdtNote, UsdtError> {
|
||||||
|
let mut cursor = Cursor::new(data);
|
||||||
|
let read_u64 = |cursor: &mut Cursor<_>| -> Result<u64, io::Error> {
|
||||||
|
let mut buf = [0u8; mem::size_of::<u64>()];
|
||||||
|
cursor.read_exact(&mut buf)?;
|
||||||
|
match endianness {
|
||||||
|
Endianness::Big => Ok(u64::from_be_bytes(buf)),
|
||||||
|
Endianness::Little => Ok(u64::from_le_bytes(buf)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let read_string = |cursor: &mut Cursor<_>| -> Result<String, io::Error> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
cursor.read_until(b'\0', &mut buf)?;
|
||||||
|
Ok(CStr::from_bytes_with_nul(&buf)
|
||||||
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string())
|
||||||
|
};
|
||||||
|
let loc_addr = read_u64(&mut cursor)?;
|
||||||
|
let base_addr = read_u64(&mut cursor)?;
|
||||||
|
let sem_addr = read_u64(&mut cursor)?;
|
||||||
|
let provider = read_string(&mut cursor)?;
|
||||||
|
let name = read_string(&mut cursor)?;
|
||||||
|
let args = read_string(&mut cursor)?;
|
||||||
|
|
||||||
|
let res = UsdtNote {
|
||||||
|
loc_addr,
|
||||||
|
base_addr,
|
||||||
|
sem_addr,
|
||||||
|
provider,
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_stapsdt() {
|
||||||
|
/*
|
||||||
|
/usr/bin/mariadb: file format elf64-x86-64
|
||||||
|
|
||||||
|
Contents of section .note.stapsdt:
|
||||||
|
0000 08000000 34000000 03000000 73746170 ....4.......stap
|
||||||
|
0010 73647400 34a10d00 00000000 382e3600 sdt.4.......8.6.
|
||||||
|
0020 00000000 00000000 00000000 6c696267 ............libg
|
||||||
|
0030 63630075 6e77696e 64003840 25726469 cc.unwind.8@%rdi
|
||||||
|
0040 20384025 72736900 8@%rsi
|
||||||
|
*/
|
||||||
|
let data: &[u8] = &[
|
||||||
|
0x34, 0xa1, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x2e, 0x36, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x69, 0x62, 0x67,
|
||||||
|
0x63, 0x63, 0x00, 0x75, 0x6e, 0x77, 0x69, 0x6e, 0x64, 0x00, 0x38, 0x40, 0x25, 0x72,
|
||||||
|
0x64, 0x69, 0x20, 0x38, 0x40, 0x25, 0x72, 0x73, 0x69, 0x00,
|
||||||
|
];
|
||||||
|
let n = UsdtNote::parse(Endianness::Little, data).unwrap();
|
||||||
|
assert_eq!(n.loc_addr, 0xda134);
|
||||||
|
assert_eq!(n.base_addr, 0x362e38);
|
||||||
|
assert_eq!(n.sem_addr, 0x0);
|
||||||
|
assert_eq!(n.provider, "libgcc");
|
||||||
|
assert_eq!(n.name, "unwind");
|
||||||
|
assert_eq!(n.args, "8@%rdi 8@%rsi");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue