From f75577c5c734258998e72260c9e037ef86c0d4ad Mon Sep 17 00:00:00 2001 From: swananan Date: Mon, 15 Dec 2025 22:01:59 +0800 Subject: [PATCH] aya: add multi-uprobe attach support Introduce UProbe::attach_multi with UProbeMultiAttachOptions and per-location cookies. Require #[uprobe(multi)] programs (BPF_TRACE_UPROBE_MULTI) and guard unsupported kernels. Refactor symbol resolution into a single pass to batch lookups efficiently and add integration tests covering multi-attach success and rejecting non-multi programs --- aya-ebpf-macros/src/uprobe.rs | 7 + aya-obj/src/obj.rs | 38 +- aya/src/bpf.rs | 30 +- aya/src/programs/mod.rs | 1 - aya/src/programs/uprobe.rs | 421 ++++++++++++++++-- aya/src/sys/bpf.rs | 36 +- test/integration-ebpf/Cargo.toml | 4 + test/integration-ebpf/src/uprobe_multi.rs | 22 + test/integration-test/src/lib.rs | 1 + test/integration-test/src/tests.rs | 1 + .../src/tests/uprobe_multi.rs | 166 +++++++ xtask/public-api/aya-obj.txt | 4 + xtask/public-api/aya.txt | 92 +++- 13 files changed, 772 insertions(+), 51 deletions(-) create mode 100644 test/integration-ebpf/src/uprobe_multi.rs create mode 100644 test/integration-test/src/tests/uprobe_multi.rs diff --git a/aya-ebpf-macros/src/uprobe.rs b/aya-ebpf-macros/src/uprobe.rs index 8005c50c..297d17c9 100644 --- a/aya-ebpf-macros/src/uprobe.rs +++ b/aya-ebpf-macros/src/uprobe.rs @@ -29,6 +29,7 @@ pub(crate) struct UProbe { offset: Option, item: ItemFn, sleepable: bool, + multi: bool, } impl UProbe { @@ -48,6 +49,7 @@ impl UProbe { .transpose() .map_err(|err| span.error(format!("failed to parse `offset` argument: {err}")))?; let sleepable = pop_bool_arg(&mut args, "sleepable"); + let multi = pop_bool_arg(&mut args, "multi"); err_on_unknown_args(&args)?; Ok(Self { kind, @@ -56,6 +58,7 @@ impl UProbe { function, offset, sleepable, + multi, }) } @@ -67,6 +70,7 @@ impl UProbe { offset, item, sleepable, + multi, } = self; let ItemFn { attrs: _, @@ -75,6 +79,9 @@ impl UProbe { block: _, } = item; let mut prefix = kind.to_string(); + if *multi { + prefix.push_str(".multi"); + } if *sleepable { prefix.push_str(".s"); } diff --git a/aya-obj/src/obj.rs b/aya-obj/src/obj.rs index c3d0d107..4edf7640 100644 --- a/aya-obj/src/obj.rs +++ b/aya-obj/src/obj.rs @@ -227,9 +227,11 @@ pub enum ProgramSection { KProbe, UProbe { sleepable: bool, + multi: bool, }, URetProbe { sleepable: bool, + multi: bool, }, TracePoint, SocketFilter, @@ -297,10 +299,38 @@ impl FromStr for ProgramSection { Ok(match kind { "kprobe" => Self::KProbe, "kretprobe" => Self::KRetProbe, - "uprobe" => Self::UProbe { sleepable: false }, - "uprobe.s" => Self::UProbe { sleepable: true }, - "uretprobe" => Self::URetProbe { sleepable: false }, - "uretprobe.s" => Self::URetProbe { sleepable: true }, + "uprobe" => Self::UProbe { + sleepable: false, + multi: false, + }, + "uprobe.s" => Self::UProbe { + sleepable: true, + multi: false, + }, + "uprobe.multi" => Self::UProbe { + sleepable: false, + multi: true, + }, + "uprobe.multi.s" => Self::UProbe { + sleepable: true, + multi: true, + }, + "uretprobe" => Self::URetProbe { + sleepable: false, + multi: false, + }, + "uretprobe.s" => Self::URetProbe { + sleepable: true, + multi: false, + }, + "uretprobe.multi" => Self::URetProbe { + sleepable: false, + multi: true, + }, + "uretprobe.multi.s" => Self::URetProbe { + sleepable: true, + multi: true, + }, "xdp" | "xdp.frags" => Self::Xdp { frags: kind == "xdp.frags", attach_type: match pieces.next() { diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 3e1d4404..16faa002 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -11,7 +11,7 @@ use aya_obj::{ EbpfSectionKind, Features, Object, ParseError, ProgramSection, btf::{Btf, BtfError, BtfFeatures, BtfRelocationError}, generated::{ - BPF_F_SLEEPABLE, BPF_F_XDP_HAS_FRAGS, + BPF_F_SLEEPABLE, BPF_F_XDP_HAS_FRAGS, bpf_attach_type, bpf_map_type::{self, *}, }, relocation::EbpfRelocationError, @@ -26,7 +26,7 @@ use crate::{ CgroupSockopt, CgroupSysctl, Extension, FEntry, FExit, FlowDissector, Iter, KProbe, LircMode2, Lsm, LsmCgroup, PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, SkLookup, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, - TracePoint, UProbe, Xdp, + TracePoint, UProbe, Xdp, uprobe::MultiCompileState, }, sys::{ bpf_load_btf, is_bpf_cookie_supported, is_bpf_global_data_supported, @@ -463,8 +463,8 @@ impl<'a> EbpfLoader<'a> { } ProgramSection::KRetProbe | ProgramSection::KProbe - | ProgramSection::UProbe { sleepable: _ } - | ProgramSection::URetProbe { sleepable: _ } + | ProgramSection::UProbe { .. } + | ProgramSection::URetProbe { .. } | ProgramSection::TracePoint | ProgramSection::SocketFilter | ProgramSection::Xdp { @@ -604,26 +604,44 @@ impl<'a> EbpfLoader<'a> { data: ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level), kind: ProbeKind::Return, }), - ProgramSection::UProbe { sleepable } => { + ProgramSection::UProbe { sleepable, multi } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } + if *multi { + data.expected_attach_type = + Some(bpf_attach_type::BPF_TRACE_UPROBE_MULTI); + } Program::UProbe(UProbe { data, kind: ProbeKind::Entry, + compiled_for_multi: if *multi { + MultiCompileState::Multi + } else { + MultiCompileState::Single + }, }) } - ProgramSection::URetProbe { sleepable } => { + ProgramSection::URetProbe { sleepable, multi } => { let mut data = ProgramData::new(prog_name, obj, btf_fd, *verifier_log_level); if *sleepable { data.flags = BPF_F_SLEEPABLE; } + if *multi { + data.expected_attach_type = + Some(bpf_attach_type::BPF_TRACE_UPROBE_MULTI); + } Program::UProbe(UProbe { data, kind: ProbeKind::Return, + compiled_for_multi: if *multi { + MultiCompileState::Multi + } else { + MultiCompileState::Single + }, }) } ProgramSection::TracePoint => Program::TracePoint(TracePoint { diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 9e151ad8..4f0597e1 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -1101,7 +1101,6 @@ macro_rules! impl_from_prog_info { impl_from_prog_info!( unsafe KProbe kind : ProbeKind, - unsafe UProbe kind : ProbeKind, TracePoint, Xdp attach_type : XdpAttachType, SkMsg, diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 0e15fc39..0429631f 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -1,29 +1,35 @@ //! User space probes. use std::{ + borrow::Cow, + collections::HashMap, error::Error, - ffi::{CStr, OsStr, OsString}, + ffi::{CStr, CString, OsStr, OsString}, fmt::{self, Write}, fs, io::{self, BufRead as _, Cursor, Read as _}, mem, - os::{fd::AsFd as _, unix::ffi::OsStrExt as _}, + os::{ + fd::{AsFd as _, BorrowedFd}, + unix::ffi::OsStrExt as _, + }, path::{Path, PathBuf}, sync::LazyLock, }; -use aya_obj::generated::{bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_KPROBE}; +use aya_obj::generated::{bpf_attach_type, bpf_link_type, bpf_prog_type::BPF_PROG_TYPE_KPROBE}; +use libc::{ENOTSUP, EOPNOTSUPP}; use object::{Object as _, ObjectSection as _, ObjectSymbol as _, Symbol}; use thiserror::Error; use crate::{ VerifierLogLevel, programs::{ - FdLink, LinkError, ProgramData, ProgramError, ProgramType, define_link_wrapper, - impl_try_into_fdlink, load_program, + FdLink, LinkError, ProgramData, ProgramError, ProgramFd, ProgramInfo, ProgramType, + define_link_wrapper, impl_try_into_fdlink, load_program, perf_attach::{PerfLinkIdInner, PerfLinkInner}, probe::{OsStringExt as _, Probe, ProbeKind, attach}, }, - sys::bpf_link_get_info_by_fd, + sys::{BpfLinkCreateArgs, LinkTarget, SyscallError, bpf_link_create, bpf_link_get_info_by_fd}, util::MMap, }; @@ -46,10 +52,19 @@ const LD_SO_CACHE_HEADER_NEW: &str = "glibc-ld.so.cache1.1"; pub struct UProbe { pub(crate) data: ProgramData, pub(crate) kind: ProbeKind, + pub(crate) compiled_for_multi: MultiCompileState, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum MultiCompileState { + Multi, + Single, + Unknown, } /// The location in the target object file to which the uprobe is to be /// attached. +#[derive(Debug, Clone)] pub enum UProbeAttachLocation<'a> { /// The location of the target function in the target object file. Symbol(&'a str), @@ -72,6 +87,28 @@ impl From for UProbeAttachLocation<'static> { } } +/// Options used when attaching a [`UProbe`] to multiple locations. +#[derive(Debug)] +pub struct UProbeMultiAttachOptions<'a> { + /// The target binary or shared object. + pub target: Cow<'a, Path>, + /// Restrict the attachment to a single process. + pub pid: Option, + /// Attach as [`ProbeKind::Entry`] or [`ProbeKind::Return`]. + pub kind: ProbeKind, + /// Locations to attach to. + pub locations: Cow<'a, [UProbeMultiLocation<'a>]>, +} + +/// Describes a single uprobe attachment within [`UProbeMultiAttachOptions`]. +#[derive(Debug, Clone)] +pub struct UProbeMultiLocation<'a> { + /// The target location. + pub location: UProbeAttachLocation<'a>, + /// Optional per-location attach cookie. + pub cookie: Option, +} + impl UProbe { /// The type of the program according to the kernel. pub const PROGRAM_TYPE: ProgramType = ProgramType::KProbe; @@ -130,11 +167,84 @@ impl UProbe { offset }; - let Self { data, kind } = self; + let Self { data, kind, .. } = self; let path = path.as_os_str(); attach::(data, *kind, path, offset, pid, cookie) } + /// Attaches the program to multiple locations in a single call. + /// + /// This batches multiple uprobes for the same target binary or shared object into one + /// `bpf_link_create` invocation so a single eBPF program can watch many instruction offsets + /// without issuing one syscall per attach. The kernel must support `BPF_TRACE_UPROBE_MULTI` + /// links (introduced in Linux 6.6); otherwise this returns + /// [`UProbeError::UProbeMultiNotSupported`]. + /// + /// The eBPF program must be compiled with the `uprobe.multi/...` section (for example by using + /// `#[uprobe(multi)]` in Aya’s macros) so that `BPF_PROG_LOAD` sets + /// `expected_attach_type = BPF_TRACE_UPROBE_MULTI`. Programs compiled without the multi section + /// prefix are rejected with [`UProbeError::ProgramNotMulti`]. + /// + /// # Parameters + /// + /// The `opts` argument configures the attachment: + /// + /// - [`UProbeMultiAttachOptions::target`] selects the binary or shared object path. + /// - [`UProbeMultiAttachOptions::pid`] optionally restricts events to a single process. + /// - [`UProbeMultiAttachOptions::kind`] chooses entry vs. return probes. + /// - [`UProbeMultiAttachOptions::locations`] describes every symbol/offset plus per-location + /// cookies; the list must be non-empty or the call fails with + /// [`UProbeError::InvalidLocations`]. + pub fn attach_multi( + &mut self, + opts: UProbeMultiAttachOptions<'_>, + ) -> Result { + match self.compiled_for_multi { + MultiCompileState::Multi => {} + MultiCompileState::Single => { + return Err(UProbeError::ProgramNotMulti.into()); + } + MultiCompileState::Unknown => { + log::warn!("attaching multi-uprobe to a program without multi metadata"); + } + } + + let UProbeMultiAttachOptions { + target, + pid, + kind, + locations, + } = opts; + + let proc_map = pid.map(ProcMap::new).transpose()?; + let resolved_path = resolve_attach_path(target.as_ref(), proc_map.as_ref())?; + if locations.is_empty() { + return Err(UProbeError::InvalidLocations { + path: resolved_path.to_path_buf(), + reason: String::from("no locations were provided"), + } + .into()); + } + let path_buf = resolved_path.to_path_buf(); + let (offsets, cookies) = resolve_multi_locations(&path_buf, locations)?; + + let prog_fd = self.data.fd()?; + let prog_fd = prog_fd.as_fd(); + let path_cstr = CString::new(path_buf.as_os_str().as_bytes()).map_err(|error| { + ProgramError::IOError(io::Error::new(io::ErrorKind::InvalidInput, error)) + })?; + + let link = try_attach_uprobe_multi_link( + prog_fd, + &path_cstr, + &offsets, + pid, + kind, + cookies.as_deref(), + )?; + self.data.links.insert(UProbeLink::from(link)) + } + /// Creates a program from a pinned entry on a bpffs. /// /// Existing links will not be populated. To work with existing links you should use [`crate::programs::links::PinnedLink`]. @@ -143,7 +253,50 @@ impl UProbe { /// the program being unloaded from the kernel if it is still pinned. pub fn from_pin>(path: P, kind: ProbeKind) -> Result { let data = ProgramData::from_pinned_path(path, VerifierLogLevel::default())?; - Ok(Self { data, kind }) + Ok(Self { + data, + kind, + compiled_for_multi: MultiCompileState::Unknown, + }) + } + + /// Constructs an instance of a [`Self`] from a [`ProgramInfo`]. + /// + /// This allows the caller to get a handle to an already loaded program from + /// the kernel without having to load it again. + /// + /// # Errors + /// + /// - If the program type reported by the kernel does not match + /// [`Self::PROGRAM_TYPE`]. + /// - If the file descriptor of the program cannot be cloned. + /// + /// # Safety + /// + /// Caller must ensure the info actually describes a uprobe/uretprobe. + pub unsafe fn from_program_info( + info: ProgramInfo, + name: Cow<'static, str>, + kind: ProbeKind, + ) -> Result { + if info.program_type() != Self::PROGRAM_TYPE.into() { + return Err(ProgramError::UnexpectedProgramType {}); + } + // The generic `impl_from_prog_info!` macro can't populate `compiled_for_multi`, + // so we provide this bespoke implementation to keep the new state in sync. + let ProgramFd(fd) = info.fd()?; + let ProgramInfo(bpf_program_info) = info; + Ok(Self { + data: ProgramData::from_bpf_prog_info( + Some(name), + fd, + Path::new(""), + bpf_program_info, + VerifierLogLevel::default(), + )?, + kind, + compiled_for_multi: MultiCompileState::Unknown, + }) } } @@ -196,6 +349,208 @@ where }) } +fn resolve_multi_locations<'a>( + path: &Path, + locations: Cow<'a, [UProbeMultiLocation<'a>]>, +) -> Result<(Vec, Option>), UProbeError> { + let mut offsets = vec![0; locations.len()]; + let mut cookies = Vec::with_capacity(locations.len()); + let mut has_cookie = false; + let mut requests = Vec::new(); + + for (index, UProbeMultiLocation { location, cookie }) in locations.iter().enumerate() { + match location { + UProbeAttachLocation::Symbol(symbol) => requests.push(SymbolRequest { + index, + symbol, + additional: 0, + }), + UProbeAttachLocation::SymbolOffset(symbol, additional) => { + requests.push(SymbolRequest { + index, + symbol, + additional: *additional, + }) + } + UProbeAttachLocation::AbsoluteOffset(offset) => { + offsets[index] = *offset; + } + } + has_cookie |= cookie.is_some(); + cookies.push(cookie.unwrap_or(0)); + } + + if !requests.is_empty() { + // Every requested symbol must successfully resolve to a file offset, `resolve_symbols` + // propagates an error if any entry is missing so we never attach a partially resolved list. + let symbol_names: Vec<&str> = requests.iter().map(|req| req.symbol).collect(); + let resolved = resolve_symbols(path, &symbol_names).map_err(|error| { + let symbol = match &error { + ResolveSymbolError::Unknown(symbol) + | ResolveSymbolError::NotInSection(symbol) + | ResolveSymbolError::SectionFileRangeNone(symbol, _) + | ResolveSymbolError::BuildIdMismatch(symbol) => symbol.clone(), + _ => symbol_names + .first() + .map(|symbol| (*symbol).to_string()) + .unwrap_or_default(), + }; + UProbeError::SymbolError { + symbol, + error: Box::new(error), + } + })?; + + for (request, offset) in requests.iter().zip(resolved.into_iter()) { + offsets[request.index] = offset + request.additional; + } + } + + let cookies = has_cookie.then_some(cookies); + Ok((offsets, cookies)) +} + +struct SymbolRequest<'a> { + index: usize, + symbol: &'a str, + additional: u64, +} + +fn resolve_symbols(path: &Path, symbols: &[&str]) -> Result, ResolveSymbolError> { + if symbols.is_empty() { + return Ok(Vec::new()); + } + + let requests = build_symbol_requests(symbols); + let mut offsets = vec![None; symbols.len()]; + let data = MMap::map_copy_read_only(path)?; + let obj = object::read::File::parse(data.as_ref())?; + resolve_symbols_in_object(&obj, &requests, &mut offsets)?; + + if offsets.iter().all(Option::is_some) { + return finalize_symbol_offsets(offsets, symbols); + } + + if let Some(symbol) = first_unresolved_symbol(symbols, &offsets) { + let debug_path = find_debug_path_in_object(&obj, path, symbol)?; + let data = MMap::map_copy_read_only(&debug_path) + .map_err(|e| ResolveSymbolError::DebuglinkAccessError(debug_path.clone(), e))?; + let debug_obj = object::read::File::parse(data.as_ref())?; + verify_build_ids(&obj, &debug_obj, symbol)?; + resolve_symbols_in_object(&debug_obj, &requests, &mut offsets)?; + } + + finalize_symbol_offsets(offsets, symbols) +} + +fn build_symbol_requests<'a>(symbols: &[&'a str]) -> HashMap<&'a str, Vec> { + let mut requests: HashMap<&'a str, Vec> = HashMap::new(); + for (index, symbol) in symbols.iter().enumerate() { + requests.entry(*symbol).or_default().push(index); + } + requests +} + +fn resolve_symbols_in_object( + obj: &object::File<'_>, + requests: &HashMap<&str, Vec>, + offsets: &mut [Option], +) -> Result<(), ResolveSymbolError> { + if requests.is_empty() { + return Ok(()); + } + + let mut remaining = requests + .values() + .flat_map(|indices| indices.iter()) + .filter(|&&index| offsets[index].is_none()) + .count(); + + for sym in obj.dynamic_symbols().chain(obj.symbols()) { + let symbol_name = match sym.name() { + Ok(name) => name, + Err(_) => continue, + }; + + let Some(indices) = requests.get(symbol_name) else { + continue; + }; + + let offset = symbol_translated_address(obj, sym, symbol_name)?; + for &index in indices { + if offsets[index].is_none() { + offsets[index] = Some(offset); + remaining -= 1; + if remaining == 0 { + return Ok(()); + } + } + } + } + + Ok(()) +} + +fn finalize_symbol_offsets( + offsets: Vec>, + symbols: &[&str], +) -> Result, ResolveSymbolError> { + offsets + .into_iter() + .zip(symbols) + .map(|(offset, symbol)| { + offset.ok_or_else(|| ResolveSymbolError::Unknown((*symbol).to_string())) + }) + .collect() +} + +fn first_unresolved_symbol<'a>(symbols: &[&'a str], offsets: &[Option]) -> Option<&'a str> { + symbols + .iter() + .zip(offsets) + .find_map(|(symbol, offset)| offset.is_none().then_some(*symbol)) +} + +fn try_attach_uprobe_multi_link( + prog_fd: BorrowedFd<'_>, + path: &CString, + offsets: &[u64], + pid: Option, + kind: ProbeKind, + cookies: Option<&[u64]>, +) -> Result { + let flags = match kind { + ProbeKind::Entry => 0, + ProbeKind::Return => aya_obj::generated::BPF_F_UPROBE_MULTI_RETURN, + }; + let args = BpfLinkCreateArgs::UProbeMulti { + path, + offsets, + ref_ctr_offsets: None, + cookies, + pid, + flags, + }; + + let link_fd = bpf_link_create( + prog_fd, + LinkTarget::None, + bpf_attach_type::BPF_TRACE_UPROBE_MULTI, + 0, + Some(args), + ) + .map_err(|io_error| match io_error.raw_os_error() { + Some(code) if code == ENOTSUP || code == EOPNOTSUPP => { + ProgramError::UProbeError(UProbeError::UProbeMultiNotSupported) + } + _ => ProgramError::SyscallError(SyscallError { + call: "bpf_link_create", + io_error, + }), + })?; + Ok(PerfLinkInner::Fd(FdLink::new(link_fd))) +} + // Only run this test on linux with glibc because only in that configuration do we know that we'll // be dynamically linked to libc and can exercise resolving the path to libc via the current // process's memory map. @@ -254,7 +609,9 @@ impl TryFrom for UProbeLink { fn try_from(fd_link: FdLink) -> Result { let info = bpf_link_get_info_by_fd(fd_link.fd.as_fd())?; - if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) { + if info.type_ == (bpf_link_type::BPF_LINK_TYPE_TRACING as u32) + || info.type_ == (bpf_link_type::BPF_LINK_TYPE_UPROBE_MULTI as u32) + { return Ok(Self::new(PerfLinkInner::Fd(fd_link))); } Err(LinkError::InvalidLink) @@ -308,6 +665,23 @@ pub enum UProbeError { #[source] source: ProcMapError, }, + + /// The kernel does not support multi-uprobe links. + #[error("uprobe_multi links are not supported by the running kernel")] + UProbeMultiNotSupported, + + /// The provided locations were invalid. + #[error("invalid uprobe locations for `{path}`: {reason}")] + InvalidLocations { + /// path to target. + path: PathBuf, + /// human-readable reason. + reason: String, + }, + + /// The program was not compiled for multi-attach. + #[error("attach_multi requires a program compiled with #[uprobe(multi)]")] + ProgramNotMulti, } /// Error reading from /proc/pid/maps. @@ -690,32 +1064,11 @@ fn find_debug_path_in_object<'a>( } } -fn find_symbol_in_object<'a>(obj: &'a object::File<'a>, symbol: &str) -> Option> { - obj.dynamic_symbols() - .chain(obj.symbols()) - .find(|sym| sym.name().map(|name| name == symbol).unwrap_or(false)) -} - fn resolve_symbol(path: &Path, symbol: &str) -> Result { - let data = MMap::map_copy_read_only(path)?; - let obj = object::read::File::parse(data.as_ref())?; - - if let Some(sym) = find_symbol_in_object(&obj, symbol) { - symbol_translated_address(&obj, sym, symbol) - } else { - // Only search in the debug object if the symbol was not found in the main object - let debug_path = find_debug_path_in_object(&obj, path, symbol)?; - let debug_data = MMap::map_copy_read_only(&debug_path) - .map_err(|e| ResolveSymbolError::DebuglinkAccessError(debug_path, e))?; - let debug_obj = object::read::File::parse(debug_data.as_ref())?; - - verify_build_ids(&obj, &debug_obj, symbol)?; - - let sym = find_symbol_in_object(&debug_obj, symbol) - .ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string()))?; - - symbol_translated_address(&debug_obj, sym, symbol) - } + let mut offsets = resolve_symbols(path, std::slice::from_ref(&symbol))?; + offsets + .pop() + .ok_or_else(|| ResolveSymbolError::Unknown(symbol.to_string())) } fn symbol_translated_address( diff --git a/aya/src/sys/bpf.rs b/aya/src/sys/bpf.rs index 7a6a0ef2..a0593d05 100644 --- a/aya/src/sys/bpf.rs +++ b/aya/src/sys/bpf.rs @@ -384,15 +384,26 @@ pub(crate) enum LinkTarget<'f> { Fd(BorrowedFd<'f>), IfIndex(u32), Iter, + None, } // Models https://github.com/torvalds/linux/blob/2144da25/include/uapi/linux/bpf.h#L1724-L1782. pub(crate) enum BpfLinkCreateArgs<'a> { TargetBtfId(u32), // since kernel 5.15 - PerfEvent { bpf_cookie: u64 }, + PerfEvent { + bpf_cookie: u64, + }, // since kernel 6.6 Tcx(&'a LinkRef), + UProbeMulti { + path: &'a CStr, + offsets: &'a [u64], + ref_ctr_offsets: Option<&'a [u64]>, + cookies: Option<&'a [u64]>, + pid: Option, + flags: u32, + }, } // since kernel 5.7 @@ -418,7 +429,7 @@ pub(crate) fn bpf_link_create( // fact, the kernel explicitly rejects non-zero target FDs for // iterators: // https://github.com/torvalds/linux/blob/v6.12/kernel/bpf/bpf_iter.c#L517-L518 - LinkTarget::Iter => {} + LinkTarget::Iter | LinkTarget::None => {} }; attr.link_create.attach_type = attach_type as u32; attr.link_create.flags = flags; @@ -447,6 +458,27 @@ pub(crate) fn bpf_link_create( .relative_id = id.to_owned(); } }, + BpfLinkCreateArgs::UProbeMulti { + path, + offsets, + ref_ctr_offsets, + cookies, + pid, + flags, + } => { + let multi = unsafe { &mut attr.link_create.__bindgen_anon_3.uprobe_multi }; + multi.path = path.as_ptr() as u64; + multi.offsets = offsets.as_ptr() as u64; + multi.cnt = offsets.len() as u32; + multi.flags = flags; + multi.pid = pid.unwrap_or(0); + multi.ref_ctr_offsets = ref_ctr_offsets + .map(|slice| slice.as_ptr() as u64) + .unwrap_or_default(); + multi.cookies = cookies + .map(|slice| slice.as_ptr() as u64) + .unwrap_or_default(); + } } } diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index 33715cfb..e232f00d 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -104,6 +104,10 @@ path = "src/xdp_sec.rs" name = "uprobe_cookie" path = "src/uprobe_cookie.rs" +[[bin]] +name = "uprobe_multi" +path = "src/uprobe_multi.rs" + [[bin]] name = "perf_event_bp" path = "src/perf_event_bp.rs" diff --git a/test/integration-ebpf/src/uprobe_multi.rs b/test/integration-ebpf/src/uprobe_multi.rs new file mode 100644 index 00000000..1f974679 --- /dev/null +++ b/test/integration-ebpf/src/uprobe_multi.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] +#![expect(unused_crate_dependencies, reason = "used in other bins")] + +use aya_ebpf::{ + EbpfContext as _, helpers, + macros::{map, uprobe}, + maps::RingBuf, + programs::ProbeContext, +}; +#[cfg(not(test))] +extern crate ebpf_panic; + +#[map] +static RING_BUF: RingBuf = RingBuf::with_byte_size(0, 0); + +#[uprobe(multi)] +fn uprobe_multi(ctx: ProbeContext) { + let cookie = unsafe { helpers::bpf_get_attach_cookie(ctx.as_ptr()) }; + let cookie_bytes = cookie.to_le_bytes(); + let _ = RING_BUF.output::<[u8]>(cookie_bytes, 0); +} diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 01e26f03..d0e219d9 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -59,6 +59,7 @@ bpf_file!( TWO_PROGS => "two_progs", XDP_SEC => "xdp_sec", UPROBE_COOKIE => "uprobe_cookie", + UPROBE_MULTI => "uprobe_multi", ); #[cfg(test)] diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index 4de831aa..42c95861 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -21,4 +21,5 @@ mod smoke; mod strncmp; mod tcx; mod uprobe_cookie; +mod uprobe_multi; mod xdp; diff --git a/test/integration-test/src/tests/uprobe_multi.rs b/test/integration-test/src/tests/uprobe_multi.rs new file mode 100644 index 00000000..d196df9a --- /dev/null +++ b/test/integration-test/src/tests/uprobe_multi.rs @@ -0,0 +1,166 @@ +use std::{borrow::Cow, path::Path}; + +use aya::{ + EbpfLoader, + maps::ring_buf::RingBuf, + programs::{ + ProbeKind, ProgramError, UProbe, + uprobe::{UProbeAttachLocation, UProbeMultiAttachOptions, UProbeMultiLocation}, + }, + util::KernelVersion, +}; + +const PROG_A: &str = "uprobe_multi_trigger_program_a"; +const PROG_B: &str = "uprobe_multi_trigger_program_b"; +const PROG_NO_COOKIE: &str = "uprobe_multi_trigger_program_no_cookie"; + +#[test_log::test] +fn test_uprobe_attach_multi() { + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(6, 6, 0) { + eprintln!( + "skipping test on kernel {kernel_version:?}, uprobe_multi links were added in 6.6" + ); + return; + } + const RING_BUF_BYTE_SIZE: u32 = 512; + let mut bpf = EbpfLoader::new() + .map_max_entries("RING_BUF", RING_BUF_BYTE_SIZE) + .load(crate::UPROBE_MULTI) + .unwrap(); + let ring_buf = match bpf.take_map("RING_BUF") { + Some(map) => map, + None => { + eprintln!("skipping test because RING_BUF map is unavailable"); + return; + } + }; + let mut ring_buf = RingBuf::try_from(ring_buf).unwrap(); + let prog: &mut UProbe = bpf.program_mut("uprobe_multi").unwrap().try_into().unwrap(); + prog.load().unwrap(); + + const COOKIE_A: u64 = 0x11; + const COOKIE_B: u64 = 0x22; + let locations = vec![ + UProbeMultiLocation { + location: UProbeAttachLocation::Symbol(PROG_A), + cookie: Some(COOKIE_A), + }, + UProbeMultiLocation { + location: UProbeAttachLocation::Symbol(PROG_B), + cookie: Some(COOKIE_B), + }, + UProbeMultiLocation { + location: UProbeAttachLocation::Symbol(PROG_NO_COOKIE), + cookie: None, + }, + ]; + let opts = UProbeMultiAttachOptions { + target: Cow::Borrowed(Path::new("/proc/self/exe")), + pid: None, + kind: ProbeKind::Entry, + locations: Cow::Owned(locations), + }; + let link_id = match prog.attach_multi(opts) { + Ok(link) => link, + Err(aya::programs::ProgramError::UProbeError( + aya::programs::uprobe::UProbeError::UProbeMultiNotSupported, + )) => { + eprintln!("skipping test on kernel {kernel_version:?}, uprobe_multi not supported"); + return; + } + Err(err) => panic!("attach_multi failed: {err:?}"), + }; + + // Drain any stale events emitted by other tests before we generate fresh ones. + while ring_buf.next().is_some() {} + + uprobe_multi_trigger_program_a(1); + uprobe_multi_trigger_program_b(2); + uprobe_multi_trigger_program_no_cookie(); + uprobe_multi_trigger_program_a(3); + + prog.detach(link_id).unwrap(); + // Fire another probe to ensure detach removed all active links. + uprobe_multi_trigger_program_a(3); + + const EXP: &[u64] = &[COOKIE_A, COOKIE_B, 0, COOKIE_A]; + let mut seen = Vec::new(); + while let Some(record) = ring_buf.next() { + let data = record.as_ref(); + match data.try_into() { + Ok(bytes) => seen.push(u64::from_le_bytes(bytes)), + Err(std::array::TryFromSliceError { .. }) => { + panic!("invalid ring buffer data: {data:x?}") + } + } + } + assert_eq!(seen, EXP); +} + +#[test_log::test] +fn test_uprobe_attach_multi_rejects_non_multi_program() { + let kernel_version = KernelVersion::current().unwrap(); + if kernel_version < KernelVersion::new(6, 6, 0) { + eprintln!( + "skipping test on kernel {kernel_version:?}, uprobe_multi links were added in 6.6" + ); + return; + } + + // Load the single-attach `uprobe_cookie` program to ensure `attach_multi` + // rejects plain uprobes. + let mut bpf = EbpfLoader::new() + .map_max_entries("RING_BUF", 64) + .load(crate::UPROBE_COOKIE) + .unwrap(); + let prog: &mut UProbe = bpf + .program_mut("uprobe_cookie") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + + let locations = vec![UProbeMultiLocation { + location: UProbeAttachLocation::Symbol(PROG_A), + cookie: None, + }]; + let opts = UProbeMultiAttachOptions { + target: Cow::Borrowed(Path::new("/proc/self/exe")), + pid: None, + kind: ProbeKind::Entry, + locations: Cow::Owned(locations), + }; + + match prog.attach_multi(opts) { + Err(ProgramError::UProbeError(aya::programs::uprobe::UProbeError::ProgramNotMulti)) => {} + Err(ProgramError::UProbeError( + aya::programs::uprobe::UProbeError::UProbeMultiNotSupported, + )) => { + eprintln!("skipping test on kernel {kernel_version:?}, uprobe_multi not supported"); + return; + } + Err(err) => panic!("attach_multi returned unexpected error: {err:?}"), + Ok(_) => panic!("attach_multi succeeded for non-multi program"), + } +} + +#[unsafe(no_mangle)] +#[inline(never)] +extern "C" fn uprobe_multi_trigger_program_a(arg: u64) { + std::hint::black_box(arg); +} + +#[unsafe(no_mangle)] +#[inline(never)] +extern "C" fn uprobe_multi_trigger_program_b(arg: u64) { + // Deliberately make the body differ from program_a so link-time ICF + // doesn't fold both symbols onto the same address. + std::hint::black_box(arg + 2); +} + +#[unsafe(no_mangle)] +#[inline(never)] +extern "C" fn uprobe_multi_trigger_program_no_cookie() { + std::hint::black_box(()); +} diff --git a/xtask/public-api/aya-obj.txt b/xtask/public-api/aya-obj.txt index 5e59c5ea..110b33c6 100644 --- a/xtask/public-api/aya-obj.txt +++ b/xtask/public-api/aya-obj.txt @@ -8647,8 +8647,10 @@ pub aya_obj::obj::ProgramSection::SockOps pub aya_obj::obj::ProgramSection::SocketFilter pub aya_obj::obj::ProgramSection::TracePoint pub aya_obj::obj::ProgramSection::UProbe +pub aya_obj::obj::ProgramSection::UProbe::multi: bool pub aya_obj::obj::ProgramSection::UProbe::sleepable: bool pub aya_obj::obj::ProgramSection::URetProbe +pub aya_obj::obj::ProgramSection::URetProbe::multi: bool pub aya_obj::obj::ProgramSection::URetProbe::sleepable: bool pub aya_obj::obj::ProgramSection::Xdp pub aya_obj::obj::ProgramSection::Xdp::attach_type: aya_obj::programs::xdp::XdpAttachType @@ -9510,8 +9512,10 @@ pub aya_obj::ProgramSection::SockOps pub aya_obj::ProgramSection::SocketFilter pub aya_obj::ProgramSection::TracePoint pub aya_obj::ProgramSection::UProbe +pub aya_obj::ProgramSection::UProbe::multi: bool pub aya_obj::ProgramSection::UProbe::sleepable: bool pub aya_obj::ProgramSection::URetProbe +pub aya_obj::ProgramSection::URetProbe::multi: bool pub aya_obj::ProgramSection::URetProbe::sleepable: bool pub aya_obj::ProgramSection::Xdp pub aya_obj::ProgramSection::Xdp::attach_type: aya_obj::programs::xdp::XdpAttachType diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 3b6618be..d001b9b6 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -7341,8 +7341,12 @@ pub aya::programs::uprobe::UProbeAttachLocation::Symbol(&'a str) pub aya::programs::uprobe::UProbeAttachLocation::SymbolOffset(&'a str, u64) impl core::convert::From for aya::programs::uprobe::UProbeAttachLocation<'static> pub fn aya::programs::uprobe::UProbeAttachLocation<'static>::from(offset: u64) -> Self +impl<'a> core::clone::Clone for aya::programs::uprobe::UProbeAttachLocation<'a> +pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::clone(&self) -> aya::programs::uprobe::UProbeAttachLocation<'a> impl<'a> core::convert::From<&'a str> for aya::programs::uprobe::UProbeAttachLocation<'a> pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::from(s: &'a str) -> Self +impl<'a> core::fmt::Debug for aya::programs::uprobe::UProbeAttachLocation<'a> +pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result impl<'a> core::marker::Freeze for aya::programs::uprobe::UProbeAttachLocation<'a> impl<'a> core::marker::Send for aya::programs::uprobe::UProbeAttachLocation<'a> impl<'a> core::marker::Sync for aya::programs::uprobe::UProbeAttachLocation<'a> @@ -7357,12 +7361,18 @@ pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::try_from(value: U) -> co impl core::convert::TryInto for aya::programs::uprobe::UProbeAttachLocation<'a> where U: core::convert::TryFrom pub type aya::programs::uprobe::UProbeAttachLocation<'a>::Error = >::Error pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for aya::programs::uprobe::UProbeAttachLocation<'a> where T: core::clone::Clone +pub type aya::programs::uprobe::UProbeAttachLocation<'a>::Owned = T +pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::clone_into(&self, target: &mut T) +pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::to_owned(&self) -> T impl core::any::Any for aya::programs::uprobe::UProbeAttachLocation<'a> where T: 'static + ?core::marker::Sized pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::type_id(&self) -> core::any::TypeId impl core::borrow::Borrow for aya::programs::uprobe::UProbeAttachLocation<'a> where T: ?core::marker::Sized pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::borrow(&self) -> &T impl core::borrow::BorrowMut for aya::programs::uprobe::UProbeAttachLocation<'a> where T: ?core::marker::Sized pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::borrow_mut(&mut self) -> &mut T +impl core::clone::CloneToUninit for aya::programs::uprobe::UProbeAttachLocation<'a> where T: core::clone::Clone +pub unsafe fn aya::programs::uprobe::UProbeAttachLocation<'a>::clone_to_uninit(&self, dest: *mut u8) impl core::convert::From for aya::programs::uprobe::UProbeAttachLocation<'a> pub fn aya::programs::uprobe::UProbeAttachLocation<'a>::from(t: T) -> T pub enum aya::programs::uprobe::UProbeError @@ -7371,14 +7381,19 @@ pub aya::programs::uprobe::UProbeError::FileError::filename: std::path::PathBuf pub aya::programs::uprobe::UProbeError::FileError::io_error: std::io::error::Error pub aya::programs::uprobe::UProbeError::InvalidLdSoCache pub aya::programs::uprobe::UProbeError::InvalidLdSoCache::io_error: &'static std::io::error::Error +pub aya::programs::uprobe::UProbeError::InvalidLocations +pub aya::programs::uprobe::UProbeError::InvalidLocations::path: std::path::PathBuf +pub aya::programs::uprobe::UProbeError::InvalidLocations::reason: alloc::string::String pub aya::programs::uprobe::UProbeError::InvalidTarget pub aya::programs::uprobe::UProbeError::InvalidTarget::path: std::path::PathBuf pub aya::programs::uprobe::UProbeError::ProcMap pub aya::programs::uprobe::UProbeError::ProcMap::pid: u32 pub aya::programs::uprobe::UProbeError::ProcMap::source: aya::programs::uprobe::ProcMapError +pub aya::programs::uprobe::UProbeError::ProgramNotMulti pub aya::programs::uprobe::UProbeError::SymbolError pub aya::programs::uprobe::UProbeError::SymbolError::error: alloc::boxed::Box<(dyn core::error::Error + core::marker::Send + core::marker::Sync)> pub aya::programs::uprobe::UProbeError::SymbolError::symbol: alloc::string::String +pub aya::programs::uprobe::UProbeError::UProbeMultiNotSupported impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(source: aya::programs::uprobe::UProbeError) -> Self impl core::error::Error for aya::programs::uprobe::UProbeError @@ -7415,7 +7430,9 @@ pub struct aya::programs::uprobe::UProbe impl aya::programs::uprobe::UProbe pub const aya::programs::uprobe::UProbe::PROGRAM_TYPE: aya::programs::ProgramType pub fn aya::programs::uprobe::UProbe::attach<'loc, T: core::convert::AsRef, Loc: core::convert::Into>>(&mut self, location: Loc, target: T, pid: core::option::Option, cookie: core::option::Option) -> core::result::Result +pub fn aya::programs::uprobe::UProbe::attach_multi(&mut self, opts: aya::programs::uprobe::UProbeMultiAttachOptions<'_>) -> core::result::Result pub fn aya::programs::uprobe::UProbe::from_pin>(path: P, kind: aya::programs::ProbeKind) -> core::result::Result +pub unsafe fn aya::programs::uprobe::UProbe::from_program_info(info: aya::programs::ProgramInfo, name: alloc::borrow::Cow<'static, str>, kind: aya::programs::ProbeKind) -> core::result::Result pub fn aya::programs::uprobe::UProbe::kind(&self) -> aya::programs::ProbeKind pub fn aya::programs::uprobe::UProbe::load(&mut self) -> core::result::Result<(), aya::programs::ProgramError> impl aya::programs::uprobe::UProbe @@ -7424,8 +7441,6 @@ pub fn aya::programs::uprobe::UProbe::take_link(&mut self, link_id: aya::program impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> impl aya::programs::uprobe::UProbe -pub unsafe fn aya::programs::uprobe::UProbe::from_program_info(info: aya::programs::ProgramInfo, name: alloc::borrow::Cow<'static, str>, kind: aya::programs::ProbeKind) -> core::result::Result -impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::info(&self) -> core::result::Result impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::pin>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError> @@ -7545,6 +7560,70 @@ impl core::borrow::BorrowMut for aya::programs::uprobe::UProbeLinkId where pub fn aya::programs::uprobe::UProbeLinkId::borrow_mut(&mut self) -> &mut T impl core::convert::From for aya::programs::uprobe::UProbeLinkId pub fn aya::programs::uprobe::UProbeLinkId::from(t: T) -> T +pub struct aya::programs::uprobe::UProbeMultiAttachOptions<'a> +pub aya::programs::uprobe::UProbeMultiAttachOptions::kind: aya::programs::ProbeKind +pub aya::programs::uprobe::UProbeMultiAttachOptions::locations: alloc::borrow::Cow<'a, [aya::programs::uprobe::UProbeMultiLocation<'a>]> +pub aya::programs::uprobe::UProbeMultiAttachOptions::pid: core::option::Option +pub aya::programs::uprobe::UProbeMultiAttachOptions::target: alloc::borrow::Cow<'a, std::path::Path> +impl<'a> core::fmt::Debug for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl<'a> core::marker::Freeze for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +impl<'a> core::marker::Send for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +impl<'a> core::marker::Sync for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +impl<'a> core::marker::Unpin for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +impl<'a> core::panic::unwind_safe::RefUnwindSafe for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +impl<'a> core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +impl core::convert::Into for aya::programs::uprobe::UProbeMultiAttachOptions<'a> where U: core::convert::From +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::into(self) -> U +impl core::convert::TryFrom for aya::programs::uprobe::UProbeMultiAttachOptions<'a> where U: core::convert::Into +pub type aya::programs::uprobe::UProbeMultiAttachOptions<'a>::Error = core::convert::Infallible +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::uprobe::UProbeMultiAttachOptions<'a> where U: core::convert::TryFrom +pub type aya::programs::uprobe::UProbeMultiAttachOptions<'a>::Error = >::Error +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::try_into(self) -> core::result::Result>::Error> +impl core::any::Any for aya::programs::uprobe::UProbeMultiAttachOptions<'a> where T: 'static + ?core::marker::Sized +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::uprobe::UProbeMultiAttachOptions<'a> where T: ?core::marker::Sized +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::uprobe::UProbeMultiAttachOptions<'a> where T: ?core::marker::Sized +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::borrow_mut(&mut self) -> &mut T +impl core::convert::From for aya::programs::uprobe::UProbeMultiAttachOptions<'a> +pub fn aya::programs::uprobe::UProbeMultiAttachOptions<'a>::from(t: T) -> T +pub struct aya::programs::uprobe::UProbeMultiLocation<'a> +pub aya::programs::uprobe::UProbeMultiLocation::cookie: core::option::Option +pub aya::programs::uprobe::UProbeMultiLocation::location: aya::programs::uprobe::UProbeAttachLocation<'a> +impl<'a> core::clone::Clone for aya::programs::uprobe::UProbeMultiLocation<'a> +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::clone(&self) -> aya::programs::uprobe::UProbeMultiLocation<'a> +impl<'a> core::fmt::Debug for aya::programs::uprobe::UProbeMultiLocation<'a> +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl<'a> core::marker::Freeze for aya::programs::uprobe::UProbeMultiLocation<'a> +impl<'a> core::marker::Send for aya::programs::uprobe::UProbeMultiLocation<'a> +impl<'a> core::marker::Sync for aya::programs::uprobe::UProbeMultiLocation<'a> +impl<'a> core::marker::Unpin for aya::programs::uprobe::UProbeMultiLocation<'a> +impl<'a> core::panic::unwind_safe::RefUnwindSafe for aya::programs::uprobe::UProbeMultiLocation<'a> +impl<'a> core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::UProbeMultiLocation<'a> +impl core::convert::Into for aya::programs::uprobe::UProbeMultiLocation<'a> where U: core::convert::From +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::into(self) -> U +impl core::convert::TryFrom for aya::programs::uprobe::UProbeMultiLocation<'a> where U: core::convert::Into +pub type aya::programs::uprobe::UProbeMultiLocation<'a>::Error = core::convert::Infallible +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for aya::programs::uprobe::UProbeMultiLocation<'a> where U: core::convert::TryFrom +pub type aya::programs::uprobe::UProbeMultiLocation<'a>::Error = >::Error +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for aya::programs::uprobe::UProbeMultiLocation<'a> where T: core::clone::Clone +pub type aya::programs::uprobe::UProbeMultiLocation<'a>::Owned = T +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::clone_into(&self, target: &mut T) +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::to_owned(&self) -> T +impl core::any::Any for aya::programs::uprobe::UProbeMultiLocation<'a> where T: 'static + ?core::marker::Sized +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for aya::programs::uprobe::UProbeMultiLocation<'a> where T: ?core::marker::Sized +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::borrow(&self) -> &T +impl core::borrow::BorrowMut for aya::programs::uprobe::UProbeMultiLocation<'a> where T: ?core::marker::Sized +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::borrow_mut(&mut self) -> &mut T +impl core::clone::CloneToUninit for aya::programs::uprobe::UProbeMultiLocation<'a> where T: core::clone::Clone +pub unsafe fn aya::programs::uprobe::UProbeMultiLocation<'a>::clone_to_uninit(&self, dest: *mut u8) +impl core::convert::From for aya::programs::uprobe::UProbeMultiLocation<'a> +pub fn aya::programs::uprobe::UProbeMultiLocation<'a>::from(t: T) -> T pub mod aya::programs::xdp pub enum aya::programs::xdp::XdpError pub aya::programs::xdp::XdpError::NetlinkError(aya::sys::netlink::NetlinkError) @@ -8622,14 +8701,19 @@ pub aya::programs::UProbeError::FileError::filename: std::path::PathBuf pub aya::programs::UProbeError::FileError::io_error: std::io::error::Error pub aya::programs::UProbeError::InvalidLdSoCache pub aya::programs::UProbeError::InvalidLdSoCache::io_error: &'static std::io::error::Error +pub aya::programs::UProbeError::InvalidLocations +pub aya::programs::UProbeError::InvalidLocations::path: std::path::PathBuf +pub aya::programs::UProbeError::InvalidLocations::reason: alloc::string::String pub aya::programs::UProbeError::InvalidTarget pub aya::programs::UProbeError::InvalidTarget::path: std::path::PathBuf pub aya::programs::UProbeError::ProcMap pub aya::programs::UProbeError::ProcMap::pid: u32 pub aya::programs::UProbeError::ProcMap::source: aya::programs::uprobe::ProcMapError +pub aya::programs::UProbeError::ProgramNotMulti pub aya::programs::UProbeError::SymbolError pub aya::programs::UProbeError::SymbolError::error: alloc::boxed::Box<(dyn core::error::Error + core::marker::Send + core::marker::Sync)> pub aya::programs::UProbeError::SymbolError::symbol: alloc::string::String +pub aya::programs::UProbeError::UProbeMultiNotSupported impl core::convert::From for aya::programs::ProgramError pub fn aya::programs::ProgramError::from(source: aya::programs::uprobe::UProbeError) -> Self impl core::error::Error for aya::programs::uprobe::UProbeError @@ -10148,7 +10232,9 @@ pub struct aya::programs::UProbe impl aya::programs::uprobe::UProbe pub const aya::programs::uprobe::UProbe::PROGRAM_TYPE: aya::programs::ProgramType pub fn aya::programs::uprobe::UProbe::attach<'loc, T: core::convert::AsRef, Loc: core::convert::Into>>(&mut self, location: Loc, target: T, pid: core::option::Option, cookie: core::option::Option) -> core::result::Result +pub fn aya::programs::uprobe::UProbe::attach_multi(&mut self, opts: aya::programs::uprobe::UProbeMultiAttachOptions<'_>) -> core::result::Result pub fn aya::programs::uprobe::UProbe::from_pin>(path: P, kind: aya::programs::ProbeKind) -> core::result::Result +pub unsafe fn aya::programs::uprobe::UProbe::from_program_info(info: aya::programs::ProgramInfo, name: alloc::borrow::Cow<'static, str>, kind: aya::programs::ProbeKind) -> core::result::Result pub fn aya::programs::uprobe::UProbe::kind(&self) -> aya::programs::ProbeKind pub fn aya::programs::uprobe::UProbe::load(&mut self) -> core::result::Result<(), aya::programs::ProgramError> impl aya::programs::uprobe::UProbe @@ -10157,8 +10243,6 @@ pub fn aya::programs::uprobe::UProbe::take_link(&mut self, link_id: aya::program impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::fd(&self) -> core::result::Result<&aya::programs::ProgramFd, aya::programs::ProgramError> impl aya::programs::uprobe::UProbe -pub unsafe fn aya::programs::uprobe::UProbe::from_program_info(info: aya::programs::ProgramInfo, name: alloc::borrow::Cow<'static, str>, kind: aya::programs::ProbeKind) -> core::result::Result -impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::info(&self) -> core::result::Result impl aya::programs::uprobe::UProbe pub fn aya::programs::uprobe::UProbe::pin>(&mut self, path: P) -> core::result::Result<(), aya::pin::PinError>