aya: Implement USDT probes

Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
pull/329/head
Dave Tucker 2 years ago
parent eb05c18140
commit 11b8dda719

@ -42,7 +42,7 @@ jobs:
env:
RUST_BACKTRACE: full
run: |
cross test --verbose --target ${{matrix.arch}}
cross test --verbose --target ${{matrix.arch}} --all-features
test:
runs-on: ubuntu-20.04

@ -21,6 +21,7 @@ parking_lot = { version = "0.12.0", features = ["send_guard"] }
tokio = { version = "1.2.0", features = ["macros", "rt", "rt-multi-thread", "net"], optional = true }
async-io = { version = "1.3", optional = true }
log = "0.4"
aya-common = { version = "0.1.0", path = "../aya-common", features = ["user"] }
[dev-dependencies]
matches = "0.1.8"

@ -25,7 +25,7 @@ use crate::{
BtfTracePoint, CgroupSkb, CgroupSkbAttachType, CgroupSock, CgroupSockAddr, CgroupSockopt,
CgroupSysctl, Extension, FEntry, FExit, KProbe, LircMode2, Lsm, PerfEvent, ProbeKind,
Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, SkLookup, SkMsg, SkSkb,
SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp,
SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Usdt, Xdp,
},
sys::{
bpf_load_btf, bpf_map_freeze, bpf_map_update_elem_ptr, is_bpf_cookie_supported,
@ -643,6 +643,9 @@ impl<'a> BpfLoader<'a> {
attach_type: *attach_type,
})
}
ProgramSection::Usdt { .. } => Program::Usdt(Usdt {
data: ProgramData::new(prog_name, obj, btf_fd, verifier_log_level),
}),
}
};
(name, program)
@ -934,6 +937,11 @@ pub enum BpfError {
#[error("program error")]
/// A program error
ProgramError(#[from] ProgramError),
/// Required map not found
#[error("required map {0} not found. did you enable the usdt feature (aya) or include usdt.bpf.h (libbpf)?")]
/// A program error
MissingMap(String),
}
fn load_btf(raw_btf: Vec<u8>) -> Result<RawFd, BtfError> {

@ -293,6 +293,9 @@ pub enum ProgramSection {
name: String,
attach_type: CgroupSockAttachType,
},
Usdt {
name: String,
},
}
impl ProgramSection {
@ -326,6 +329,7 @@ impl ProgramSection {
ProgramSection::Extension { name } => name,
ProgramSection::SkLookup { name } => name,
ProgramSection::CgroupSock { name, .. } => name,
ProgramSection::Usdt { name } => name,
}
}
}
@ -349,6 +353,7 @@ impl FromStr for ProgramSection {
"kprobe" => KProbe { name },
"kretprobe" => KRetProbe { name },
"uprobe" => UProbe { name },
"usdt" => Usdt { name },
"uretprobe" => URetProbe { name },
"xdp" => Xdp { name },
"tp_btf" => BtfTracePoint { name },
@ -1684,7 +1689,7 @@ mod tests {
buf.extend(&map_data);
buf.extend(&map_data);
// throw in some padding
buf.extend(&[0, 0, 0, 0]);
buf.extend([0, 0, 0, 0]);
buf.extend(&map_data);
assert_matches!(
obj.parse_section(fake_section(BpfSectionKind::Maps, "maps", buf.as_slice(),)),

@ -97,7 +97,7 @@ impl CgroupSkb {
};
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,

@ -73,7 +73,7 @@ impl CgroupSock {
let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,

@ -74,7 +74,7 @@ impl CgroupSockAddr {
let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,

@ -71,7 +71,7 @@ impl CgroupSockopt {
let attach_type = self.data.expected_attach_type.unwrap();
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, 0).map_err(
let link_fd = bpf_link_create(prog_fd, cgroup_fd, attach_type, None, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,

@ -66,12 +66,11 @@ impl CgroupSysctl {
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 7, 0) {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
let link_fd = bpf_link_create(prog_fd, cgroup_fd, BPF_CGROUP_SYSCTL, None, None, 0)
.map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
},
)? as RawFd;
})? as RawFd;
self.data
.links
.insert(CgroupSysctlLink(CgroupSysctlLinkInner::Fd(FdLink::new(

@ -90,11 +90,18 @@ impl Extension {
let target_fd = self.data.attach_prog_fd.ok_or(ProgramError::NotLoaded)?;
let btf_id = self.data.attach_btf_id.ok_or(ProgramError::NotLoaded)?;
// the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
let link_fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_INGRESS, Some(btf_id), 0)
.map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
})? as RawFd;
let link_fd = bpf_link_create(
prog_fd,
target_fd,
BPF_CGROUP_INET_INGRESS,
Some(btf_id),
None,
0,
)
.map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
})? as RawFd;
self.data.links.insert(ExtensionLink(FdLink::new(link_fd)))
}
@ -118,11 +125,18 @@ impl Extension {
let (_, btf_id) = get_btf_info(target_fd, func_name)?;
let prog_fd = self.data.fd_or_err()?;
// the attach type must be set as 0, which is bpf_attach_type::BPF_CGROUP_INET_INGRESS
let link_fd = bpf_link_create(prog_fd, target_fd, BPF_CGROUP_INET_INGRESS, Some(btf_id), 0)
.map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
})? as RawFd;
let link_fd = bpf_link_create(
prog_fd,
target_fd,
BPF_CGROUP_INET_INGRESS,
Some(btf_id),
None,
0,
)
.map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
})? as RawFd;
self.data.links.insert(ExtensionLink(FdLink::new(link_fd)))
}

@ -6,7 +6,7 @@ use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
programs::{
define_link_wrapper, load_program,
perf_attach::{PerfLink, PerfLinkId},
perf_attach::{PerfLinkIdInner, PerfLinkInner},
probe::{attach, ProbeKind},
ProgramData, ProgramError,
},
@ -90,8 +90,8 @@ define_link_wrapper!(
KProbeLink,
/// The type returned by [KProbe::attach]. Can be passed to [KProbe::detach].
KProbeLinkId,
PerfLink,
PerfLinkId
PerfLinkInner,
PerfLinkIdInner
);
/// The type returned when attaching a [`KProbe`] fails.

@ -60,6 +60,7 @@ pub mod tc;
pub mod tp_btf;
pub mod trace_point;
pub mod uprobe;
pub mod usdt;
mod utils;
pub mod xdp;
@ -98,6 +99,7 @@ pub use tc::{SchedClassifier, TcAttachType, TcError};
pub use tp_btf::BtfTracePoint;
pub use trace_point::{TracePoint, TracePointError};
pub use uprobe::{UProbe, UProbeError};
pub use usdt::{Usdt, UsdtError};
pub use xdp::{Xdp, XdpError, XdpFlags};
use crate::{
@ -198,6 +200,10 @@ pub enum ProgramError {
#[error(transparent)]
Btf(#[from] BtfError),
/// An error occurred while working with a Usdt program.
#[error(transparent)]
Usdt(#[from] UsdtError),
/// The program is not attached.
#[error("the program name `{name}` is invalid")]
InvalidName {
@ -265,6 +271,8 @@ pub enum Program {
SkLookup(SkLookup),
/// A [`CgroupSock`] program
CgroupSock(CgroupSock),
/// A [`Usdt`] program
Usdt(Usdt),
}
impl Program {
@ -295,6 +303,7 @@ impl Program {
Program::CgroupSockAddr(_) => BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
Program::SkLookup(_) => BPF_PROG_TYPE_SK_LOOKUP,
Program::CgroupSock(_) => BPF_PROG_TYPE_CGROUP_SOCK,
Program::Usdt(_) => BPF_PROG_TYPE_KPROBE,
}
}
@ -324,6 +333,7 @@ impl Program {
Program::CgroupSockAddr(p) => p.pin(path),
Program::SkLookup(p) => p.pin(path),
Program::CgroupSock(p) => p.pin(path),
Program::Usdt(p) => p.pin(path),
}
}
@ -353,6 +363,7 @@ impl Program {
Program::CgroupSockAddr(p) => p.unload(),
Program::SkLookup(p) => p.unload(),
Program::CgroupSock(p) => p.unload(),
Program::Usdt(p) => p.unload(),
}
}
@ -385,6 +396,7 @@ impl Program {
Program::CgroupSockAddr(p) => p.fd(),
Program::SkLookup(p) => p.fd(),
Program::CgroupSock(p) => p.fd(),
Program::Usdt(p) => p.fd(),
}
}
}
@ -637,6 +649,7 @@ impl_program_unload!(
SkLookup,
SockOps,
CgroupSock,
Usdt,
);
macro_rules! impl_fd {
@ -676,6 +689,7 @@ impl_fd!(
SkLookup,
SockOps,
CgroupSock,
Usdt,
);
macro_rules! impl_program_pin{
@ -720,6 +734,7 @@ impl_program_pin!(
SkLookup,
SockOps,
CgroupSock,
Usdt,
);
macro_rules! impl_try_from_program {
@ -774,6 +789,7 @@ impl_try_from_program!(
CgroupSockAddr,
SkLookup,
CgroupSock,
Usdt,
);
/// Provides information about a loaded program, like name, id and statistics

@ -3,11 +3,44 @@ use libc::close;
use std::os::unix::io::RawFd;
use crate::{
programs::{probe::detach_debug_fs, Link, ProbeKind, ProgramData, ProgramError},
sys::perf_event_ioctl,
PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_SET_BPF,
generated::bpf_attach_type::BPF_PERF_EVENT,
programs::{probe::detach_debug_fs, Link, ProbeKind, ProgramError},
sys::{bpf_link_create, perf_event_ioctl},
FEATURES, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, PERF_EVENT_IOC_SET_BPF,
};
use crate::programs::links::FdLink;
#[derive(Debug, Hash, Eq, PartialEq)]
pub(crate) enum PerfLinkIdInner {
FdLinkId(<FdLink as Link>::Id),
PerfLinkId(<PerfLink as Link>::Id),
}
#[derive(Debug)]
pub(crate) enum PerfLinkInner {
FdLink(FdLink),
PerfLink(PerfLink),
}
impl Link for PerfLinkInner {
type Id = PerfLinkIdInner;
fn id(&self) -> Self::Id {
match self {
PerfLinkInner::FdLink(link) => PerfLinkIdInner::FdLinkId(link.id()),
PerfLinkInner::PerfLink(link) => PerfLinkIdInner::PerfLinkId(link.id()),
}
}
fn detach(self) -> Result<(), ProgramError> {
match self {
PerfLinkInner::FdLink(link) => link.detach(),
PerfLinkInner::PerfLink(link) => link.detach(),
}
}
}
/// The identifer of a PerfLink.
#[derive(Debug, Hash, Eq, PartialEq)]
pub struct PerfLinkId(RawFd);
@ -41,29 +74,39 @@ impl Link for PerfLink {
}
}
pub(crate) fn perf_attach<T: Link + From<PerfLink>>(
data: &mut ProgramData<T>,
pub(crate) fn perf_attach(
prog_fd: RawFd,
fd: RawFd,
) -> Result<T::Id, ProgramError> {
perf_attach_either(data, fd, None, None)
cookie: Option<u64>,
) -> Result<PerfLinkInner, ProgramError> {
if FEATURES.bpf_perf_link {
let link_fd = bpf_link_create(prog_fd, fd, BPF_PERF_EVENT, None, cookie, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
},
)? as RawFd;
Ok(PerfLinkInner::FdLink(FdLink::new(link_fd)))
} else {
perf_attach_either(prog_fd, fd, None, None)
}
}
pub(crate) fn perf_attach_debugfs<T: Link + From<PerfLink>>(
data: &mut ProgramData<T>,
pub(crate) fn perf_attach_debugfs(
prog_fd: RawFd,
fd: RawFd,
probe_kind: ProbeKind,
event_alias: String,
) -> Result<T::Id, ProgramError> {
perf_attach_either(data, fd, Some(probe_kind), Some(event_alias))
) -> Result<PerfLinkInner, ProgramError> {
perf_attach_either(prog_fd, fd, Some(probe_kind), Some(event_alias))
}
fn perf_attach_either<T: Link + From<PerfLink>>(
data: &mut ProgramData<T>,
fn perf_attach_either(
prog_fd: RawFd,
fd: RawFd,
probe_kind: Option<ProbeKind>,
event_alias: Option<String>,
) -> Result<T::Id, ProgramError> {
let prog_fd = data.fd_or_err()?;
) -> Result<PerfLinkInner, ProgramError> {
perf_event_ioctl(fd, PERF_EVENT_IOC_SET_BPF, prog_fd).map_err(|(_, io_error)| {
ProgramError::SyscallError {
call: "PERF_EVENT_IOC_SET_BPF".to_owned(),
@ -77,12 +120,9 @@ fn perf_attach_either<T: Link + From<PerfLink>>(
}
})?;
data.links.insert(
PerfLink {
perf_fd: fd,
probe_kind,
event_alias,
}
.into(),
)
Ok(PerfLinkInner::PerfLink(PerfLink {
perf_fd: fd,
probe_kind,
event_alias,
}))
}

@ -12,8 +12,9 @@ use crate::{
},
},
programs::{
links::define_link_wrapper,
load_program, perf_attach,
perf_attach::{PerfLink, PerfLinkId},
perf_attach::{PerfLinkIdInner, PerfLinkInner},
ProgramData, ProgramError,
},
sys::perf_event_open,
@ -118,7 +119,7 @@ pub enum PerfEventScope {
#[derive(Debug)]
#[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")]
pub struct PerfEvent {
pub(crate) data: ProgramData<PerfLink>,
pub(crate) data: ProgramData<PerfEventLink>,
}
impl PerfEvent {
@ -140,7 +141,7 @@ impl PerfEvent {
config: u64,
scope: PerfEventScope,
sample_policy: SamplePolicy,
) -> Result<PerfLinkId, ProgramError> {
) -> Result<PerfEventLinkId, ProgramError> {
let (sample_period, sample_frequency) = match sample_policy {
SamplePolicy::Period(period) => (period, None),
SamplePolicy::Frequency(frequency) => (0, Some(frequency)),
@ -167,13 +168,14 @@ impl PerfEvent {
io_error,
})? as i32;
perf_attach(&mut self.data, fd)
let link = perf_attach(self.data.fd_or_err()?, fd, None)?;
self.data.links.insert(PerfEventLink(link))
}
/// Detaches the program.
///
/// See [PerfEvent::attach].
pub fn detach(&mut self, link_id: PerfLinkId) -> Result<(), ProgramError> {
pub fn detach(&mut self, link_id: PerfEventLinkId) -> Result<(), ProgramError> {
self.data.links.remove(link_id)
}
@ -181,7 +183,16 @@ impl PerfEvent {
///
/// 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: PerfLinkId) -> Result<PerfLink, ProgramError> {
pub fn take_link(&mut self, link_id: PerfEventLinkId) -> Result<PerfEventLink, ProgramError> {
self.data.take_link(link_id)
}
}
define_link_wrapper!(
/// The link used by [PerfEvent] programs.
PerfEventLink,
/// The type returned by [PerfEvent::attach]. Can be passed to [PerfEvent::detach].
PerfEventLinkId,
PerfLinkInner,
PerfLinkIdInner
);

@ -7,7 +7,7 @@ use std::{
use crate::{
programs::{
kprobe::KProbeError, perf_attach, perf_attach::PerfLink, perf_attach_debugfs,
kprobe::KProbeError, perf_attach, perf_attach::PerfLinkInner, perf_attach_debugfs,
trace_point::read_sys_fs_trace_point_id, uprobe::UProbeError, Link, ProgramData,
ProgramError,
},
@ -36,7 +36,7 @@ impl ProbeKind {
}
}
pub(crate) fn attach<T: Link + From<PerfLink>>(
pub(crate) fn attach<T: Link + From<PerfLinkInner>>(
program_data: &mut ProgramData<T>,
kind: ProbeKind,
fn_name: &str,
@ -49,12 +49,19 @@ pub(crate) fn attach<T: Link + From<PerfLink>>(
if k_ver < (4, 17, 0) {
let (fd, event_alias) = create_as_trace_point(kind, fn_name, offset, pid)?;
return perf_attach_debugfs(program_data, fd, kind, event_alias);
let link = T::from(perf_attach_debugfs(
program_data.fd_or_err()?,
fd,
kind,
event_alias,
)?);
return program_data.links.insert(link);
};
let fd = create_as_probe(kind, fn_name, offset, pid)?;
let fd = create_as_probe(kind, fn_name, offset, pid, None)?;
perf_attach(program_data, fd)
let link = T::from(perf_attach(program_data.fd_or_err()?, fd, None)?);
program_data.links.insert(link)
}
pub(crate) fn detach_debug_fs(kind: ProbeKind, event_alias: &str) -> Result<(), ProgramError> {
@ -70,11 +77,12 @@ pub(crate) fn detach_debug_fs(kind: ProbeKind, event_alias: &str) -> Result<(),
Ok(())
}
fn create_as_probe(
pub(crate) fn create_as_probe(
kind: ProbeKind,
fn_name: &str,
offset: u64,
pid: Option<pid_t>,
ref_cnt_offset: Option<u64>,
) -> Result<i32, ProgramError> {
use ProbeKind::*;
@ -97,7 +105,7 @@ fn create_as_probe(
_ => None,
};
let fd = perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid).map_err(
let fd = perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid, ref_cnt_offset).map_err(
|(_code, io_error)| ProgramError::SyscallError {
call: "perf_event_open".to_owned(),
io_error,

@ -64,7 +64,7 @@ impl SkLookup {
let prog_fd = self.data.fd_or_err()?;
let netns_fd = netns.as_raw_fd();
let link_fd = bpf_link_create(prog_fd, netns_fd, BPF_SK_LOOKUP, None, 0).map_err(
let link_fd = bpf_link_create(prog_fd, netns_fd, BPF_SK_LOOKUP, None, None, 0).map_err(
|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,

@ -6,7 +6,7 @@ use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT,
programs::{
define_link_wrapper, load_program,
perf_attach::{perf_attach, PerfLink, PerfLinkId},
perf_attach::{perf_attach, PerfLinkIdInner, PerfLinkInner},
ProgramData, ProgramError,
},
sys::perf_event_open_trace_point,
@ -85,7 +85,8 @@ impl TracePoint {
}
})? as i32;
perf_attach(&mut self.data, fd)
let link = TracePointLink(perf_attach(self.data.fd_or_err()?, fd, None)?);
self.data.links.insert(link)
}
/// Detaches from a trace point.
@ -109,8 +110,8 @@ define_link_wrapper!(
TracePointLink,
/// The type returned by [TracePoint::attach]. Can be passed to [TracePoint::detach].
TracePointLinkId,
PerfLink,
PerfLinkId
PerfLinkInner,
PerfLinkIdInner
);
pub(crate) fn read_sys_fs_trace_point_id(

@ -1,14 +1,8 @@
//! User space probes.
use libc::pid_t;
use object::{Object, ObjectSymbol};
use std::{
collections::HashMap,
error::Error,
ffi::CStr,
fs,
io::{self, BufRead, Cursor, Read},
mem,
os::raw::c_char,
io,
path::{Path, PathBuf},
sync::Arc,
};
@ -18,20 +12,13 @@ use crate::{
generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE,
programs::{
define_link_wrapper, load_program,
perf_attach::{PerfLink, PerfLinkId},
perf_attach::{PerfLinkIdInner, PerfLinkInner},
probe::{attach, ProbeKind},
utils::{resolve_symbol, ProcMap, ProcMapError, LD_SO_CACHE, LD_SO_CACHE_FILE},
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";
/// An user space probe.
///
/// User probes are eBPF programs that can be attached to any userspace
@ -148,8 +135,8 @@ define_link_wrapper!(
UProbeLink,
/// The type returned by [UProbe::attach]. Can be passed to [UProbe::detach].
UProbeLinkId,
PerfLink,
PerfLinkId
PerfLinkInner,
PerfLinkIdInner
);
/// The type returned when attaching an [`UProbe`] fails.
@ -200,262 +187,3 @@ pub enum UProbeError {
source: ProcMapError,
},
}
#[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).map_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: 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()))
}
/// Error reading from /proc/pid/maps
#[derive(Debug, Error)]
pub enum ProcMapError {
/// An [`io::Error`]
#[error(transparent)]
IoError(io::Error),
/// Error parsing a line of /proc/pid/maps
#[error("proc map entry parse error")]
ParseError,
}
pub(crate) struct ProcMap {
_entries: Vec<ProcMapEntry>,
paths: HashMap<String, String>,
}
impl ProcMap {
fn new(pid: pid_t) -> Result<Self, ProcMapError> {
let maps_file = format!("/proc/{}/maps", pid);
let data = fs::read_to_string(maps_file).map_err(ProcMapError::IoError)?;
let mut entries = vec![];
let mut paths = HashMap::new();
for line in data.lines() {
let entry = ProcMapEntry::parse(line)?;
if let Some(path) = &entry.path {
let p = PathBuf::from(path);
let key = p.file_name().unwrap().to_string_lossy().into_owned();
let value = p.to_string_lossy().to_string();
paths.insert(key, value);
}
entries.push(entry);
}
Ok(ProcMap {
_entries: entries,
paths,
})
}
fn find_by_name(&self, lib: &str) -> Result<Option<String>, io::Error> {
let ret = if lib.contains(".so") {
self.paths.iter().find(|(k, _)| k.as_str().starts_with(lib))
} else {
let lib = lib.to_string();
let lib1 = lib.clone() + ".so";
let lib2 = lib + "-";
self.paths
.iter()
.find(|(k, _)| k.starts_with(&lib1) || k.starts_with(&lib2))
};
Ok(ret.map(|(_, v)| v.clone()))
}
}
pub(crate) struct ProcMapEntry {
_address: u64,
_address_end: u64,
_perms: String,
_offset: u64,
_dev: String,
_inode: u32,
path: Option<String>,
}
impl ProcMapEntry {
fn parse(line: &str) -> Result<Self, ProcMapError> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 5 {
return Err(ProcMapError::ParseError);
}
let addr_parts: Vec<&str> = parts[0].split('-').collect();
let address =
u64::from_str_radix(addr_parts[0], 16).map_err(|_| ProcMapError::ParseError)?;
let address_end =
u64::from_str_radix(addr_parts[1], 16).map_err(|_| ProcMapError::ParseError)?;
let perms = parts[1];
let offset = u64::from_str_radix(parts[2], 16).map_err(|_| ProcMapError::ParseError)?;
let dev = parts[3];
let inode = parts[4].parse().map_err(|_| ProcMapError::ParseError)?;
let path = if parts.len() == 6 {
if parts[5].starts_with('/') {
Some(parts[5].to_string())
} else {
None
}
} else {
None
};
Ok(ProcMapEntry {
_address: address,
_address_end: address_end,
_perms: perms.to_string(),
_offset: offset,
_dev: dev.to_string(),
_inode: inode,
path,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_proc_map_entry_from_str_1() {
let s = "7ffd6fbea000-7ffd6fbec000 r-xp 00000000 00:00 0 [vdso]";
let proc_map = ProcMapEntry::parse(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_from_str_2() {
let s = "7f1bca83a000-7f1bca83c000 rw-p 00036000 fd:01 2895508 /usr/lib64/ld-linux-x86-64.so.2";
let proc_map = ProcMapEntry::parse(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_from_str_3() {
let s = "7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0";
let proc_map = ProcMapEntry::parse(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);
}
}

@ -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");
}
}

@ -1,5 +1,17 @@
//! Common functions shared between multiple eBPF program types.
use std::{ffi::CStr, os::unix::io::RawFd};
use libc::pid_t;
use object::{Object, ObjectSymbol};
use std::{
collections::HashMap,
ffi::CStr,
fs,
io::{self, BufRead, Cursor, Read},
mem,
os::{raw::c_char, unix::prelude::RawFd},
path::{Path, PathBuf},
sync::Arc,
};
use thiserror::Error;
use crate::{
programs::{FdLink, Link, ProgramData, ProgramError},
@ -22,3 +34,272 @@ pub(crate) fn attach_raw_tracepoint<T: Link + From<FdLink>>(
program_data.links.insert(FdLink::new(pfd).into())
}
pub(crate) const LD_SO_CACHE_FILE: &str = "/etc/ld.so.cache";
lazy_static! {
pub(crate) 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(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).map_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: 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)]
pub(crate) enum ResolveSymbolError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("error parsing ELF")]
Object(#[from] object::Error),
#[error("unknown symbol `{0}`")]
Unknown(String),
}
pub(crate) 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()))
}
/// Error reading from /proc/pid/maps
#[derive(Debug, Error)]
pub enum ProcMapError {
/// An [`io::Error`]
#[error(transparent)]
IoError(io::Error),
/// Error parsing a line of /proc/pid/maps
#[error("proc map entry parse error")]
ParseError,
}
pub(crate) struct ProcMap {
entries: Vec<ProcMapEntry>,
paths: HashMap<String, String>,
}
impl ProcMap {
pub(crate) fn new(pid: pid_t) -> Result<Self, ProcMapError> {
let maps_file = format!("/proc/{}/maps", pid);
let data = fs::read_to_string(maps_file).map_err(ProcMapError::IoError)?;
let mut entries = vec![];
let mut paths = HashMap::new();
for line in data.lines() {
let entry = ProcMapEntry::parse(line)?;
if let Some(path) = &entry.path {
let p = PathBuf::from(path);
let key = p.file_name().unwrap().to_string_lossy().into_owned();
let value = p.to_string_lossy().to_string();
paths.insert(key, value);
}
entries.push(entry);
}
Ok(ProcMap { entries, paths })
}
pub(crate) fn find_by_name(&self, lib: &str) -> Result<Option<String>, io::Error> {
let ret = if lib.contains(".so") {
self.paths.iter().find(|(k, _)| k.as_str().starts_with(lib))
} else {
let lib = lib.to_string();
let lib1 = lib.clone() + ".so";
let lib2 = lib + "-";
self.paths
.iter()
.find(|(k, _)| k.starts_with(&lib1) || k.starts_with(&lib2))
};
Ok(ret.map(|(_, v)| v.clone()))
}
pub(crate) fn find_by_offset(&self, offset: u64) -> Option<&ProcMapEntry> {
self.entries
.iter()
.find(|&e| e.offset <= offset && offset < e.offset + (e.address_end - e.address))
}
}
pub(crate) struct ProcMapEntry {
pub address: u64,
pub address_end: u64,
_perms: String,
pub offset: u64,
_dev: String,
_inode: u32,
pub path: Option<String>,
}
impl ProcMapEntry {
fn parse(line: &str) -> Result<Self, ProcMapError> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 5 {
return Err(ProcMapError::ParseError);
}
let addr_parts: Vec<&str> = parts[0].split('-').collect();
let address =
u64::from_str_radix(addr_parts[0], 16).map_err(|_| ProcMapError::ParseError)?;
let address_end =
u64::from_str_radix(addr_parts[1], 16).map_err(|_| ProcMapError::ParseError)?;
let perms = parts[1];
let offset = u64::from_str_radix(parts[2], 16).map_err(|_| ProcMapError::ParseError)?;
let dev = parts[3];
let inode = parts[4].parse().map_err(|_| ProcMapError::ParseError)?;
let path = if parts.len() == 6 {
if parts[5].starts_with('/') {
Some(parts[5].to_string())
} else {
None
}
} else {
None
};
Ok(ProcMapEntry {
address,
address_end,
_perms: perms.to_string(),
offset,
_dev: dev.to_string(),
_inode: inode,
path,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_proc_map_entry_from_str_1() {
let s = "7ffd6fbea000-7ffd6fbec000 r-xp 00000000 00:00 0 [vdso]";
let proc_map = ProcMapEntry::parse(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_from_str_2() {
let s = "7f1bca83a000-7f1bca83c000 rw-p 00036000 fd:01 2895508 /usr/lib64/ld-linux-x86-64.so.2";
let proc_map = ProcMapEntry::parse(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_from_str_3() {
let s = "7f1bca5f9000-7f1bca601000 rw-p 00000000 00:00 0";
let proc_map = ProcMapEntry::parse(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);
}
}

@ -109,12 +109,11 @@ impl Xdp {
let k_ver = kernel_version().unwrap();
if k_ver >= (5, 9, 0) {
let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, flags.bits).map_err(
|(_, io_error)| ProgramError::SyscallError {
let link_fd = bpf_link_create(prog_fd, if_index, BPF_XDP, None, None, flags.bits)
.map_err(|(_, io_error)| ProgramError::SyscallError {
call: "bpf_link_create".to_owned(),
io_error,
},
)? as RawFd;
})? as RawFd;
self.data
.links
.insert(XdpLink(XdpLinkInner::FdLink(FdLink::new(link_fd))))

@ -334,6 +334,7 @@ pub(crate) fn bpf_link_create(
target_fd: RawFd,
attach_type: bpf_attach_type,
btf_id: Option<u32>,
cookie: Option<u64>,
flags: u32,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<bpf_attr>() };
@ -345,6 +346,9 @@ pub(crate) fn bpf_link_create(
if let Some(btf_id) = btf_id {
attr.link_create.__bindgen_anon_2.target_btf_id = btf_id;
}
if let Some(cookie) = cookie {
attr.link_create.__bindgen_anon_2.perf_event.bpf_cookie = cookie;
}
sys_bpf(bpf_cmd::BPF_LINK_CREATE, &attr)
}
@ -589,9 +593,14 @@ pub(crate) fn is_perf_link_supported() -> bool {
u.prog_type = bpf_prog_type::BPF_PROG_TYPE_TRACEPOINT as u32;
if let Ok(fd) = sys_bpf(bpf_cmd::BPF_PROG_LOAD, &attr) {
if let Err((code, _)) =
bpf_link_create(fd as i32, -1, bpf_attach_type::BPF_PERF_EVENT, None, 0)
{
if let Err((code, _)) = bpf_link_create(
fd as i32,
-1,
bpf_attach_type::BPF_PERF_EVENT,
None,
None,
0,
) {
if code == (-libc::EBADF).into() {
unsafe { libc::close(fd as i32) };
return true;

@ -67,6 +67,7 @@ pub(crate) fn perf_event_open_probe(
name: &str,
offset: u64,
pid: Option<pid_t>,
ref_cnt_offset: Option<u64>,
) -> SysResult {
let mut attr = unsafe { mem::zeroed::<perf_event_attr>() };
@ -74,6 +75,10 @@ pub(crate) fn perf_event_open_probe(
attr.config = 1 << ret_bit;
}
if let Some(ref_cnt_offset) = ref_cnt_offset {
attr.config |= ref_cnt_offset << 32;
}
let c_name = CString::new(name).unwrap();
attr.size = mem::size_of::<perf_event_attr>() as u32;

Loading…
Cancel
Save