From 11b8dda7190039b0f77c3919d8dff52104f6726e Mon Sep 17 00:00:00 2001 From: Dave Tucker Date: Fri, 24 Jun 2022 15:20:27 +0100 Subject: [PATCH] aya: Implement USDT probes Signed-off-by: Dave Tucker --- .github/workflows/build-aya.yml | 2 +- aya/Cargo.toml | 1 + aya/src/bpf.rs | 10 +- aya/src/obj/mod.rs | 7 +- aya/src/programs/cgroup_skb.rs | 2 +- aya/src/programs/cgroup_sock.rs | 2 +- aya/src/programs/cgroup_sock_addr.rs | 2 +- aya/src/programs/cgroup_sockopt.rs | 2 +- aya/src/programs/cgroup_sysctl.rs | 7 +- aya/src/programs/extension.rs | 34 +- aya/src/programs/kprobe.rs | 6 +- aya/src/programs/mod.rs | 16 + aya/src/programs/perf_attach.rs | 86 +++-- aya/src/programs/perf_event.rs | 23 +- aya/src/programs/probe.rs | 22 +- aya/src/programs/sk_lookup.rs | 2 +- aya/src/programs/trace_point.rs | 9 +- aya/src/programs/uprobe.rs | 282 +--------------- aya/src/programs/usdt.rs | 474 +++++++++++++++++++++++++++ aya/src/programs/utils.rs | 283 +++++++++++++++- aya/src/programs/xdp.rs | 7 +- aya/src/sys/bpf.rs | 15 +- aya/src/sys/perf_event.rs | 5 + 23 files changed, 949 insertions(+), 350 deletions(-) create mode 100644 aya/src/programs/usdt.rs diff --git a/.github/workflows/build-aya.yml b/.github/workflows/build-aya.yml index 8f57b1de..a2bcc21c 100644 --- a/.github/workflows/build-aya.yml +++ b/.github/workflows/build-aya.yml @@ -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 diff --git a/aya/Cargo.toml b/aya/Cargo.toml index 8c43a401..20d3c145 100644 --- a/aya/Cargo.toml +++ b/aya/Cargo.toml @@ -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" diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 86d476a5..ab14626e 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -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) -> Result { diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 4cf11415..c7dd6d6c 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -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(),)), diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs index 17d69063..e4c00a97 100644 --- a/aya/src/programs/cgroup_skb.rs +++ b/aya/src/programs/cgroup_skb.rs @@ -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, diff --git a/aya/src/programs/cgroup_sock.rs b/aya/src/programs/cgroup_sock.rs index 17bc68ee..4b16d094 100644 --- a/aya/src/programs/cgroup_sock.rs +++ b/aya/src/programs/cgroup_sock.rs @@ -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, diff --git a/aya/src/programs/cgroup_sock_addr.rs b/aya/src/programs/cgroup_sock_addr.rs index aa8b71fd..c9a96ad2 100644 --- a/aya/src/programs/cgroup_sock_addr.rs +++ b/aya/src/programs/cgroup_sock_addr.rs @@ -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, diff --git a/aya/src/programs/cgroup_sockopt.rs b/aya/src/programs/cgroup_sockopt.rs index 14df5fc6..e63144a7 100644 --- a/aya/src/programs/cgroup_sockopt.rs +++ b/aya/src/programs/cgroup_sockopt.rs @@ -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, diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs index f4a3f352..7be26876 100644 --- a/aya/src/programs/cgroup_sysctl.rs +++ b/aya/src/programs/cgroup_sysctl.rs @@ -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( diff --git a/aya/src/programs/extension.rs b/aya/src/programs/extension.rs index 75296244..2af56000 100644 --- a/aya/src/programs/extension.rs +++ b/aya/src/programs/extension.rs @@ -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))) } diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index 81bb783f..c80bae06 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -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. diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 7f392f7a..9eecdc97 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -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 diff --git a/aya/src/programs/perf_attach.rs b/aya/src/programs/perf_attach.rs index 7bd15f23..8445603e 100644 --- a/aya/src/programs/perf_attach.rs +++ b/aya/src/programs/perf_attach.rs @@ -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(::Id), + PerfLinkId(::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>( - data: &mut ProgramData, +pub(crate) fn perf_attach( + prog_fd: RawFd, fd: RawFd, -) -> Result { - perf_attach_either(data, fd, None, None) + cookie: Option, +) -> Result { + 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>( - data: &mut ProgramData, +pub(crate) fn perf_attach_debugfs( + prog_fd: RawFd, fd: RawFd, probe_kind: ProbeKind, event_alias: String, -) -> Result { - perf_attach_either(data, fd, Some(probe_kind), Some(event_alias)) +) -> Result { + perf_attach_either(prog_fd, fd, Some(probe_kind), Some(event_alias)) } -fn perf_attach_either>( - data: &mut ProgramData, +fn perf_attach_either( + prog_fd: RawFd, fd: RawFd, probe_kind: Option, event_alias: Option, -) -> Result { - let prog_fd = data.fd_or_err()?; +) -> Result { 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>( } })?; - data.links.insert( - PerfLink { - perf_fd: fd, - probe_kind, - event_alias, - } - .into(), - ) + Ok(PerfLinkInner::PerfLink(PerfLink { + perf_fd: fd, + probe_kind, + event_alias, + })) } diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs index 0b8e7ae7..406fa275 100644 --- a/aya/src/programs/perf_event.rs +++ b/aya/src/programs/perf_event.rs @@ -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, + pub(crate) data: ProgramData, } impl PerfEvent { @@ -140,7 +141,7 @@ impl PerfEvent { config: u64, scope: PerfEventScope, sample_policy: SamplePolicy, - ) -> Result { + ) -> Result { 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 { + pub fn take_link(&mut self, link_id: PerfEventLinkId) -> Result { 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 +); diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index 55d9da93..b33f0cbe 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -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>( +pub(crate) fn attach>( program_data: &mut ProgramData, kind: ProbeKind, fn_name: &str, @@ -49,12 +49,19 @@ pub(crate) fn attach>( 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, + ref_cnt_offset: Option, ) -> Result { 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, diff --git a/aya/src/programs/sk_lookup.rs b/aya/src/programs/sk_lookup.rs index a64e062a..2ae41f35 100644 --- a/aya/src/programs/sk_lookup.rs +++ b/aya/src/programs/sk_lookup.rs @@ -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, diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs index f75e00f7..71cb147e 100644 --- a/aya/src/programs/trace_point.rs +++ b/aya/src/programs/trace_point.rs @@ -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( diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 527f833a..7d015a4f 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -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::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, -} - -impl LdSoCache { - pub fn load>(path: T) -> Result { - let data = fs::read(path)?; - Self::parse(&data) - } - - fn parse(data: &[u8]) -> Result { - let mut cursor = Cursor::new(data); - - let read_u32 = |cursor: &mut Cursor<_>| -> Result { - let mut buf = [0u8; mem::size_of::()]; - cursor.read_exact(&mut buf)?; - - Ok(u32::from_ne_bytes(buf)) - }; - - let read_i32 = |cursor: &mut Cursor<_>| -> Result { - let mut buf = [0u8; mem::size_of::()]; - 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::()); - - 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 { - 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, - paths: HashMap, -} - -impl ProcMap { - fn new(pid: pid_t) -> Result { - 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, 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, -} - -impl ProcMapEntry { - fn parse(line: &str) -> Result { - 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); - } -} diff --git a/aya/src/programs/usdt.rs b/aya/src/programs/usdt.rs new file mode 100644 index 00000000..aef69ab1 --- /dev/null +++ b/aya/src/programs/usdt.rs @@ -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, +} + +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>( + &mut self, + mut spec_map: crate::maps::Array, + mut ip_to_spec_map: crate::maps::HashMap, + tp_provider: &str, + tp_name: &str, + target: T, + pid: Option, + ) -> Result { + 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 { + self.data.take_link(link_id) + } +} + +/// The identifer of a MultiPerfLink. +#[derive(Debug, Hash, Eq, PartialEq)] +pub struct MultiPerfLinkId(Vec); + +/// The attachment type of USDT programs. +#[derive(Debug)] +pub struct MultiPerfLink { + perf_links: Vec, +} + +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, + }, + + /// 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, + }, + + /// 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, +) -> Result, UsdtError> { + let file = fs::read(path)?; + let data = &*file; + if let Ok(elf) = object::elf::FileHeader32::parse(data) { + if mem::size_of::() != 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::() != 8 { + return Err(UsdtError::Unsupported); + } + return collect_usdts_from_elf(elf, data, provider, name, pid); + } + Err(UsdtError::Unsupported) +} + +fn collect_usdts_from_elf>( + elf: &Elf, + data: &[u8], + provider: &str, + name: &str, + pid: Option, +) -> Result, 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 = 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::(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::(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>( + 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 { + let mut cursor = Cursor::new(data); + let read_u64 = |cursor: &mut Cursor<_>| -> Result { + let mut buf = [0u8; mem::size_of::()]; + 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 { + 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"); + } +} diff --git a/aya/src/programs/utils.rs b/aya/src/programs/utils.rs index 56af2a0f..187c3dc1 100644 --- a/aya/src/programs/utils.rs +++ b/aya/src/programs/utils.rs @@ -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>( 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::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, +} + +impl LdSoCache { + pub fn load>(path: T) -> Result { + let data = fs::read(path)?; + Self::parse(&data) + } + + fn parse(data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); + + let read_u32 = |cursor: &mut Cursor<_>| -> Result { + let mut buf = [0u8; mem::size_of::()]; + cursor.read_exact(&mut buf)?; + + Ok(u32::from_ne_bytes(buf)) + }; + + let read_i32 = |cursor: &mut Cursor<_>| -> Result { + let mut buf = [0u8; mem::size_of::()]; + 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::()); + + 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 { + 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, + paths: HashMap, +} + +impl ProcMap { + pub(crate) fn new(pid: pid_t) -> Result { + 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, 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, +} + +impl ProcMapEntry { + fn parse(line: &str) -> Result { + 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); + } +} diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs index a979bc08..d10919aa 100644 --- a/aya/src/programs/xdp.rs +++ b/aya/src/programs/xdp.rs @@ -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)))) diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index f60ba2cb..fc1274fd 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -334,6 +334,7 @@ pub(crate) fn bpf_link_create( target_fd: RawFd, attach_type: bpf_attach_type, btf_id: Option, + cookie: Option, flags: u32, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; @@ -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; diff --git a/aya/src/sys/perf_event.rs b/aya/src/sys/perf_event.rs index 68eddd35..8f762d4e 100644 --- a/aya/src/sys/perf_event.rs +++ b/aya/src/sys/perf_event.rs @@ -67,6 +67,7 @@ pub(crate) fn perf_event_open_probe( name: &str, offset: u64, pid: Option, + ref_cnt_offset: Option, ) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; @@ -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::() as u32;