From 7e5c6d737efa811a5867f0608f30dd4fa5e84e5a Mon Sep 17 00:00:00 2001 From: swananan Date: Thu, 23 Apr 2026 19:19:03 +0800 Subject: [PATCH 1/3] aya: use Pid for perf event scopes Introduce a Pid newtype backed by NonZeroU32 so public APIs communicate PID semantics while preventing pid 0 from being represented as OneProcess. PerfEventScope::OneProcess now takes Pid. pid 0 remains the perf_event_open sentinel for the calling process, which is represented by CallingProcess instead. Route Some(0) to CallingProcess in perf_event_open_trace_point so the pid=0 sentinel is preserved through the typed scope. None continues to map to all-processes CPU 0. Add syscall-level coverage for the pid/cpu mappings and update the breakpoint integration test. --- aya/src/programs/mod.rs | 27 ++++++++ aya/src/programs/perf_event.rs | 4 +- aya/src/sys/fake.rs | 10 +-- aya/src/sys/mod.rs | 3 +- aya/src/sys/perf_event.rs | 63 +++++++++++++++++-- .../src/tests/perf_event_bp.rs | 5 +- xtask/public-api/aya.txt | 19 +++++- 7 files changed, 114 insertions(+), 17 deletions(-) diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 40965840e..7f39b41e3 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -77,6 +77,7 @@ use std::{ borrow::Cow, ffi::CString, io, + num::NonZeroU32, os::fd::{AsFd, BorrowedFd}, path::{Path, PathBuf}, sync::Arc, @@ -270,6 +271,32 @@ impl AsFd for ProgramFd { } } +/// A non-zero process identifier. +/// +/// This type represents a real process ID. Kernel sentinel values such as `0` +/// are modeled separately by the APIs that accept them. +#[derive(Debug, Clone, Copy)] +pub struct Pid(NonZeroU32); + +impl Pid { + /// Creates a process identifier from a raw PID. + /// + /// Returns `None` if `pid` is zero. + pub fn new(pid: u32) -> Option { + NonZeroU32::new(pid).map(Self) + } + + /// Returns the identifier of the current process. + pub fn current() -> Self { + Self(NonZeroU32::new(std::process::id()).unwrap()) + } + + /// Returns the raw PID value. + pub const fn get(self) -> u32 { + self.0.get() + } +} + /// A [`Program`] identifier. pub struct ProgramId(u32); diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs index 0a8eb2728..e978e3cd6 100644 --- a/aya/src/programs/perf_event.rs +++ b/aya/src/programs/perf_event.rs @@ -11,7 +11,7 @@ use aya_obj::generated::{ use crate::{ programs::{ - ProgramData, ProgramError, ProgramType, impl_try_from_fdlink, impl_try_into_fdlink, + Pid, ProgramData, ProgramError, ProgramType, impl_try_from_fdlink, impl_try_into_fdlink, links::define_link_wrapper, load_program_without_attach_type, perf_attach::{PerfLinkIdInner, PerfLinkInner, perf_attach}, @@ -372,7 +372,7 @@ pub enum PerfEventScope { /// one process OneProcess { /// process id - pid: u32, + pid: Pid, /// cpu id or any cpu if None cpu: Option, }, diff --git a/aya/src/sys/fake.rs b/aya/src/sys/fake.rs index a994cbfaa..83c62630b 100644 --- a/aya/src/sys/fake.rs +++ b/aya/src/sys/fake.rs @@ -2,20 +2,20 @@ use std::{cell::RefCell, ffi::c_void, io, ptr}; use super::{SysResult, Syscall}; -type SyscallFn = unsafe fn(Syscall<'_>) -> SysResult; +type SyscallFn = dyn for<'a> Fn(Syscall<'a>) -> SysResult; #[cfg(test)] thread_local! { - pub(crate) static TEST_SYSCALL: RefCell = RefCell::new(test_syscall); + pub(crate) static TEST_SYSCALL: RefCell> = RefCell::new(Box::new(test_syscall)); pub(crate) static TEST_MMAP_RET: RefCell<*mut c_void> = const { RefCell::new(ptr::null_mut()) }; } #[cfg(test)] -unsafe fn test_syscall(_call: Syscall<'_>) -> SysResult { +fn test_syscall(_call: Syscall<'_>) -> SysResult { Err((-1, io::Error::from_raw_os_error(libc::EINVAL))) } #[cfg(test)] -pub(crate) fn override_syscall(call: unsafe fn(Syscall<'_>) -> SysResult) { - TEST_SYSCALL.with(|test_impl| *test_impl.borrow_mut() = call); +pub(crate) fn override_syscall(call: impl for<'a> Fn(Syscall<'a>) -> SysResult + 'static) { + TEST_SYSCALL.with(|test_impl| *test_impl.borrow_mut() = Box::new(call)); } diff --git a/aya/src/sys/mod.rs b/aya/src/sys/mod.rs index d17abd4cd..2598851b8 100644 --- a/aya/src/sys/mod.rs +++ b/aya/src/sys/mod.rs @@ -35,7 +35,6 @@ pub(crate) enum PerfEventIoctlRequest<'a> { SetBpf(BorrowedFd<'a>), } -#[cfg_attr(test, expect(dead_code, reason = "test stubs cut above this"))] pub(crate) enum Syscall<'a> { Ebpf { cmd: bpf_cmd, @@ -99,7 +98,7 @@ impl std::fmt::Debug for Syscall<'_> { fn syscall(call: Syscall<'_>) -> SysResult { #[cfg(test)] { - TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) }) + TEST_SYSCALL.with(|test_impl| test_impl.borrow()(call)) } #[cfg(not(test))] diff --git a/aya/src/sys/perf_event.rs b/aya/src/sys/perf_event.rs index 9a0fd3fbf..17b009add 100644 --- a/aya/src/sys/perf_event.rs +++ b/aya/src/sys/perf_event.rs @@ -16,9 +16,12 @@ use aya_obj::generated::{ use libc::pid_t; use super::{PerfEventIoctlRequest, Syscall, syscall}; -use crate::programs::perf_event::{ - BreakpointConfig, PerfEventConfig, PerfEventScope, SamplePolicy, WakeupPolicy, - perf_type_id_to_u32, +use crate::programs::{ + Pid, + perf_event::{ + BreakpointConfig, PerfEventConfig, PerfEventScope, SamplePolicy, WakeupPolicy, + perf_type_id_to_u32, + }, }; pub(crate) fn perf_event_open( @@ -125,7 +128,9 @@ pub(crate) fn perf_event_open( let (pid, cpu) = match scope { PerfEventScope::CallingProcess { cpu } => (0, cpu.map_or(-1, |cpu| cpu as i32)), - PerfEventScope::OneProcess { pid, cpu } => (pid as i32, cpu.map_or(-1, |cpu| cpu as i32)), + PerfEventScope::OneProcess { pid, cpu } => { + (pid.get() as i32, cpu.map_or(-1, |cpu| cpu as i32)) + } PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32), }; @@ -167,8 +172,12 @@ pub(crate) fn perf_event_open_trace_point( pid: Option, ) -> io::Result { let scope = match pid { - Some(pid) => PerfEventScope::OneProcess { pid, cpu: None }, None => PerfEventScope::AllProcessesOneCpu { cpu: 0 }, + Some(0) => PerfEventScope::CallingProcess { cpu: None }, + Some(pid) => PerfEventScope::OneProcess { + pid: Pid::new(pid).unwrap(), + cpu: None, + }, }; perf_event_open( PerfEventConfig::TracePoint { event_id }, @@ -253,3 +262,47 @@ impl TryFrom for perf_event_type { } } */ + +#[cfg(test)] +mod tests { + use std::os::fd::AsRawFd as _; + + use libc::pid_t; + use test_case::test_case; + + use super::{PERF_FLAG_FD_CLOEXEC, perf_event_open_trace_point}; + use crate::sys::{Syscall, override_syscall}; + + const EVENT_ID: u64 = 123; + + #[test_case(None, -1, 0; "all_processes")] + #[test_case(Some(0), 0, -1; "calling_process")] + #[test_case(Some(42), 42, -1; "one_process")] + fn perf_event_open_trace_point_maps_pid_scope( + pid: Option, + expected_pid: pid_t, + expected_cpu: i32, + ) { + override_syscall(move |call| match call { + Syscall::PerfEventOpen { + attr, + pid: actual_pid, + cpu: actual_cpu, + group, + flags, + } => { + assert_eq!(attr.config, EVENT_ID); + assert_eq!(unsafe { attr.__bindgen_anon_1.sample_period }, 0); + assert_eq!(actual_pid, expected_pid); + assert_eq!(actual_cpu, expected_cpu); + assert_eq!(group, -1); + assert_eq!(flags, PERF_FLAG_FD_CLOEXEC); + Ok(crate::MockableFd::mock_signed_fd().into()) + } + call => panic!("unexpected syscall: {call:?}"), + }); + + let fd = perf_event_open_trace_point(EVENT_ID, pid).unwrap(); + assert_eq!(fd.as_raw_fd(), crate::MockableFd::mock_signed_fd()); + } +} diff --git a/test/integration-test/src/tests/perf_event_bp.rs b/test/integration-test/src/tests/perf_event_bp.rs index 01dae4d58..29d5cf02e 100644 --- a/test/integration-test/src/tests/perf_event_bp.rs +++ b/test/integration-test/src/tests/perf_event_bp.rs @@ -4,7 +4,7 @@ use assert_matches::assert_matches; use aya::{ Ebpf, maps, programs::{ - ProgramError, + Pid, ProgramError, perf_event::{ BreakpointConfig, PerfBreakpointLength, PerfBreakpointType, PerfEventConfig, PerfEventScope, SamplePolicy, @@ -114,7 +114,7 @@ where let mut one_process_scopes = Vec::new(); let mut all_processes_one_cpu_scopes = Vec::new(); - let pid = std::process::id(); + let pid = Pid::current(); for cpu in online_cpus().unwrap() { calling_process_scopes.push(PerfEventScope::CallingProcess { cpu: Some(cpu) }); one_process_scopes.push(PerfEventScope::OneProcess { @@ -171,6 +171,7 @@ where trigger(); + let pid = pid.get(); let lookup = map.get(&pid, 0); if attached { let recorded = diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index 2a3b2e8ae..e42d12d86 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -3631,7 +3631,7 @@ pub aya::programs::perf_event::PerfEventScope::CallingProcess pub aya::programs::perf_event::PerfEventScope::CallingProcess::cpu: core::option::Option pub aya::programs::perf_event::PerfEventScope::OneProcess pub aya::programs::perf_event::PerfEventScope::OneProcess::cpu: core::option::Option -pub aya::programs::perf_event::PerfEventScope::OneProcess::pid: u32 +pub aya::programs::perf_event::PerfEventScope::OneProcess::pid: aya::programs::Pid impl core::clone::Clone for aya::programs::perf_event::PerfEventScope pub fn aya::programs::perf_event::PerfEventScope::clone(&self) -> aya::programs::perf_event::PerfEventScope impl core::fmt::Debug for aya::programs::perf_event::PerfEventScope @@ -6287,6 +6287,23 @@ impl core::marker::Unpin for aya::programs::perf_event::PerfEvent impl core::marker::UnsafeUnpin for aya::programs::perf_event::PerfEvent impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::perf_event::PerfEvent impl core::panic::unwind_safe::UnwindSafe for aya::programs::perf_event::PerfEvent +pub struct aya::programs::Pid(_) +impl aya::programs::Pid +pub fn aya::programs::Pid::current() -> Self +pub const fn aya::programs::Pid::get(self) -> u32 +pub fn aya::programs::Pid::new(pid: u32) -> core::option::Option +impl core::clone::Clone for aya::programs::Pid +pub fn aya::programs::Pid::clone(&self) -> aya::programs::Pid +impl core::fmt::Debug for aya::programs::Pid +pub fn aya::programs::Pid::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for aya::programs::Pid +impl core::marker::Freeze for aya::programs::Pid +impl core::marker::Send for aya::programs::Pid +impl core::marker::Sync for aya::programs::Pid +impl core::marker::Unpin for aya::programs::Pid +impl core::marker::UnsafeUnpin for aya::programs::Pid +impl core::panic::unwind_safe::RefUnwindSafe for aya::programs::Pid +impl core::panic::unwind_safe::UnwindSafe for aya::programs::Pid pub struct aya::programs::ProgramFd(_) impl aya::programs::ProgramFd pub fn aya::programs::ProgramFd::try_clone(&self) -> std::io::error::Result From 1da66d340c92f8469055335b3e2471a4cbd85988 Mon Sep 17 00:00:00 2001 From: swananan Date: Sat, 2 May 2026 23:24:18 +0800 Subject: [PATCH 2/3] aya: use Pid for uprobe scope Update UProbeScope::OneProcess to take Pid instead of exposing NonZeroU32 directly. This keeps the uprobe process scope API aligned with PerfEventScope while preserving the non-zero PID invariant. --- aya/src/programs/uprobe.rs | 5 ++--- test/integration-test/src/tests/load.rs | 8 +++----- xtask/public-api/aya.txt | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 8dc138de9..f85d6cf70 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -6,7 +6,6 @@ use std::{ fmt::{self, Write}, fs, io::{self, BufRead as _, Cursor, Read as _}, - num::NonZeroU32, os::unix::ffi::{OsStrExt as _, OsStringExt as _}, path::{Path, PathBuf}, sync::LazyLock, @@ -19,7 +18,7 @@ use thiserror::Error; use crate::{ VerifierLogLevel, programs::{ - ProgramData, ProgramError, ProgramType, define_link_wrapper, impl_try_from_fdlink, + Pid, ProgramData, ProgramError, ProgramType, define_link_wrapper, impl_try_from_fdlink, impl_try_into_fdlink, load_program_without_attach_type, perf_attach::{PerfLinkIdInner, PerfLinkInner}, probe::{OsStringExt as _, Probe, ProbeKind, attach}, @@ -97,7 +96,7 @@ pub enum UProbeScope { /// Fire only when the calling process/thread hits the attach point. CallingProcess, /// Fire only when the given process hits the attach point. - OneProcess(NonZeroU32), + OneProcess(Pid), } impl UProbe { diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/src/tests/load.rs index 86fa92047..4a035a0b9 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/src/tests/load.rs @@ -1,6 +1,4 @@ -use std::{ - convert::TryInto as _, fs::remove_file, num::NonZeroU32, path::Path, thread, time::Duration, -}; +use std::{convert::TryInto as _, fs::remove_file, path::Path, thread, time::Duration}; use assert_matches::assert_matches; use aya::{ @@ -8,7 +6,7 @@ use aya::{ maps::{Array, RingBuf}, pin::PinError, programs::{ - FlowDissector, KProbe, LinkOrder, ProbeKind, Program, ProgramError, SchedClassifier, + FlowDissector, KProbe, LinkOrder, Pid, ProbeKind, Program, ProgramError, SchedClassifier, TcAttachType, TracePoint, UProbe, Xdp, XdpMode, flow_dissector::{FlowDissectorLink, FlowDissectorLinkId}, kprobe::{KProbeLink, KProbeLinkId}, @@ -370,7 +368,7 @@ fn basic_tracepoint() { #[test_case(UProbeScope::AllProcesses; "all_processes")] #[test_case(UProbeScope::CallingProcess; "calling_process")] #[test_case( - UProbeScope::OneProcess(NonZeroU32::new(std::process::id()).unwrap()); + UProbeScope::OneProcess(Pid::current()); "one_process" )] #[test_log::test] diff --git a/xtask/public-api/aya.txt b/xtask/public-api/aya.txt index e42d12d86..b4bc4a7fc 100644 --- a/xtask/public-api/aya.txt +++ b/xtask/public-api/aya.txt @@ -4787,7 +4787,7 @@ impl !core::panic::unwind_safe::UnwindSafe for aya::programs::uprobe::UProbeErro pub enum aya::programs::uprobe::UProbeScope pub aya::programs::uprobe::UProbeScope::AllProcesses pub aya::programs::uprobe::UProbeScope::CallingProcess -pub aya::programs::uprobe::UProbeScope::OneProcess(core::num::nonzero::NonZeroU32) +pub aya::programs::uprobe::UProbeScope::OneProcess(aya::programs::Pid) impl core::clone::Clone for aya::programs::uprobe::UProbeScope pub fn aya::programs::uprobe::UProbeScope::clone(&self) -> aya::programs::uprobe::UProbeScope impl core::fmt::Debug for aya::programs::uprobe::UProbeScope From 7595c4efcb14a7a3993d4ffc487d9e88041345af Mon Sep 17 00:00:00 2001 From: swananan Date: Mon, 27 Apr 2026 00:22:16 +0800 Subject: [PATCH 3/3] aya: plumb PerfEventScope through probe attach Remove the intermediate Option pid plumbing from probe attachment paths and pass PerfEventScope through to the perf_event helpers directly. This keeps the UProbeScope to PerfEventScope conversion in UProbe::attach, where both the process scope and /proc//maps lookup semantics are visible. These kprobe, uprobe, and tracepoint attach paths are backed by perf_event. For all-processes attachment, perf_event_open requires an explicit CPU, so use CPU 0 only to open the backing perf event. Process-scoped uprobes continue to use cpu=-1 through PerfEventScope. --- aya/src/programs/kprobe.rs | 5 ++- aya/src/programs/probe.rs | 17 +++---- aya/src/programs/trace_point.rs | 9 +++- aya/src/programs/uprobe.rs | 23 ++++++---- aya/src/sys/perf_event.rs | 79 +++++++++++++++++++-------------- 5 files changed, 81 insertions(+), 52 deletions(-) diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index b21d6fa51..3b89fc343 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -15,6 +15,7 @@ use crate::{ ProgramData, ProgramError, ProgramType, define_link_wrapper, impl_try_from_fdlink, impl_try_into_fdlink, load_program_without_attach_type, perf_attach::{PerfLinkIdInner, PerfLinkInner}, + perf_event::PerfEventScope, probe::{Probe, ProbeKind, attach}, }, }; @@ -87,7 +88,9 @@ impl KProbe { *kind, fn_name.as_ref(), offset, - None, // pid + // For all-processes attachment, perf_event_open requires an explicit + // CPU. Use CPU 0 only to open the backing perf event. + PerfEventScope::AllProcessesOneCpu { cpu: 0 }, None, // cookie ) } diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index e6de2e382..2009f5b3a 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -12,7 +12,8 @@ use std::{ use crate::{ programs::{ Link, ProgramData, ProgramError, perf_attach, perf_attach::PerfLinkInner, - perf_attach_debugfs, trace_point::read_sys_fs_trace_point_id, utils::find_tracefs_path, + perf_attach_debugfs, perf_event::PerfEventScope, trace_point::read_sys_fs_trace_point_id, + utils::find_tracefs_path, }, sys::{SyscallError, perf_event_open_probe, perf_event_open_trace_point}, util::KernelVersion, @@ -145,7 +146,7 @@ pub(crate) fn attach>( // separate argument. fn_name: &OsStr, offset: u64, - pid: Option, + scope: PerfEventScope, cookie: Option, ) -> Result { // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 @@ -153,13 +154,13 @@ pub(crate) fn attach>( let prog_fd = program_data.fd()?; let prog_fd = prog_fd.as_fd(); let link = if KernelVersion::at_least(4, 17, 0) { - let perf_fd = create_as_probe::

(kind, fn_name, offset, pid)?; + let perf_fd = create_as_probe::

(kind, fn_name, offset, scope)?; perf_attach(prog_fd, perf_fd, cookie) } else { if cookie.is_some() { return Err(ProgramError::AttachCookieNotSupported); } - let (perf_fd, event) = create_as_trace_point::

(kind, fn_name, offset, pid)?; + let (perf_fd, event) = create_as_trace_point::

(kind, fn_name, offset, scope)?; perf_attach_debugfs(prog_fd, perf_fd, event) }?; program_data.links.insert(T::from(link)) @@ -176,7 +177,7 @@ fn create_as_probe( kind: ProbeKind, fn_name: &OsStr, offset: u64, - pid: Option, + scope: PerfEventScope, ) -> Result { let perf_ty = read_sys_fs_perf_type(P::PMU) .map_err(|(filename, io_error)| P::file_error(filename, io_error).into())?; @@ -189,7 +190,7 @@ fn create_as_probe( ProbeKind::Entry => None, }; - perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid) + perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, scope) .map_err(|io_error| SyscallError { call: "perf_event_open", io_error, @@ -201,7 +202,7 @@ fn create_as_trace_point( kind: ProbeKind, name: &OsStr, offset: u64, - pid: Option, + scope: PerfEventScope, ) -> Result<(crate::MockableFd, ProbeEvent), ProgramError> { let tracefs = find_tracefs_path()?; @@ -214,7 +215,7 @@ fn create_as_trace_point( } = &event; let category = format!("{}s", P::PMU); let tpid = read_sys_fs_trace_point_id(tracefs, &category, event_alias.as_ref())?; - let perf_fd = perf_event_open_trace_point(tpid, pid).map_err(|io_error| SyscallError { + let perf_fd = perf_event_open_trace_point(tpid, scope).map_err(|io_error| SyscallError { call: "perf_event_open", io_error, })?; diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs index 0dad511a2..30e67292e 100644 --- a/aya/src/programs/trace_point.rs +++ b/aya/src/programs/trace_point.rs @@ -13,6 +13,7 @@ use crate::{ ProgramData, ProgramError, ProgramType, define_link_wrapper, impl_try_from_fdlink, impl_try_into_fdlink, load_program_without_attach_type, perf_attach::{PerfLinkIdInner, PerfLinkInner, perf_attach}, + perf_event::PerfEventScope, utils::find_tracefs_path, }, sys::{SyscallError, perf_event_open_trace_point}, @@ -80,7 +81,13 @@ impl TracePoint { let prog_fd = prog_fd.as_fd(); let tracefs = find_tracefs_path()?; let id = read_sys_fs_trace_point_id(tracefs, category, name.as_ref())?; - let perf_fd = perf_event_open_trace_point(id, None).map_err(|io_error| SyscallError { + let perf_fd = perf_event_open_trace_point( + id, + // For all-processes attachment, perf_event_open requires an explicit + // CPU. Use CPU 0 only to open the backing perf event. + PerfEventScope::AllProcessesOneCpu { cpu: 0 }, + ) + .map_err(|io_error| SyscallError { call: "perf_event_open_trace_point", io_error, })?; diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index f85d6cf70..b6025a79a 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -21,6 +21,7 @@ use crate::{ Pid, ProgramData, ProgramError, ProgramType, define_link_wrapper, impl_try_from_fdlink, impl_try_into_fdlink, load_program_without_attach_type, perf_attach::{PerfLinkIdInner, PerfLinkInner}, + perf_event::PerfEventScope, probe::{OsStringExt as _, Probe, ProbeKind, attach}, }, util::MMap, @@ -143,15 +144,21 @@ impl UProbe { ) -> Result { let UProbeAttachPoint { location, cookie } = point.into(); let target = target.as_ref(); - let (proc_map_pid, perf_event_pid) = match scope { - UProbeScope::AllProcesses => (None, None), + let (proc_map_pid, perf_event_scope) = match scope { + // For an all-processes uprobe, perf_event_open uses pid=-1 and + // requires an explicit CPU. Use CPU 0 only to open the backing + // perf event. + UProbeScope::AllProcesses => (None, PerfEventScope::AllProcessesOneCpu { cpu: 0 }), // /proc/0/maps does not exist, so use the real pid for ProcMap // resolution while keeping the kernel's pid=0 sentinel for attach. - UProbeScope::CallingProcess => (Some(std::process::id()), Some(0)), - UProbeScope::OneProcess(pid) => { - let pid = pid.get(); - (Some(pid), Some(pid)) - } + UProbeScope::CallingProcess => ( + Some(std::process::id()), + PerfEventScope::CallingProcess { cpu: None }, + ), + UProbeScope::OneProcess(pid) => ( + Some(pid.get()), + PerfEventScope::OneProcess { pid, cpu: None }, + ), }; // Keep ProcMap in this scope so resolve_attach_target_basename can return @@ -186,7 +193,7 @@ impl UProbe { let Self { data, kind } = self; let path = path.as_os_str(); - attach::(data, *kind, path, offset, perf_event_pid, cookie) + attach::(data, *kind, path, offset, perf_event_scope, cookie) } /// Creates a program from a pinned entry on a bpffs. diff --git a/aya/src/sys/perf_event.rs b/aya/src/sys/perf_event.rs index 17b009add..d87fdca54 100644 --- a/aya/src/sys/perf_event.rs +++ b/aya/src/sys/perf_event.rs @@ -16,12 +16,9 @@ use aya_obj::generated::{ use libc::pid_t; use super::{PerfEventIoctlRequest, Syscall, syscall}; -use crate::programs::{ - Pid, - perf_event::{ - BreakpointConfig, PerfEventConfig, PerfEventScope, SamplePolicy, WakeupPolicy, - perf_type_id_to_u32, - }, +use crate::programs::perf_event::{ + BreakpointConfig, PerfEventConfig, PerfEventScope, SamplePolicy, WakeupPolicy, + perf_type_id_to_u32, }; pub(crate) fn perf_event_open( @@ -126,13 +123,7 @@ pub(crate) fn perf_event_open( } } - let (pid, cpu) = match scope { - PerfEventScope::CallingProcess { cpu } => (0, cpu.map_or(-1, |cpu| cpu as i32)), - PerfEventScope::OneProcess { pid, cpu } => { - (pid.get() as i32, cpu.map_or(-1, |cpu| cpu as i32)) - } - PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32), - }; + let (pid, cpu) = perf_event_scope_pid_cpu(scope); perf_event_sys(attr, pid, cpu, flags) } @@ -142,7 +133,7 @@ pub(crate) fn perf_event_open_probe( ret_bit: Option, name: &OsStr, offset: u64, - pid: Option, + scope: PerfEventScope, ) -> io::Result { use std::os::unix::ffi::OsStrExt as _; @@ -159,26 +150,15 @@ pub(crate) fn perf_event_open_probe( attr.__bindgen_anon_3.config1 = c_name.as_ptr() as u64; attr.__bindgen_anon_4.config2 = offset; - let (pid, cpu) = match pid { - Some(pid) => (pid as i32, -1), - None => (-1, 0), - }; + let (pid, cpu) = perf_event_scope_pid_cpu(scope); perf_event_sys(attr, pid, cpu, PERF_FLAG_FD_CLOEXEC) } pub(crate) fn perf_event_open_trace_point( event_id: u64, - pid: Option, + scope: PerfEventScope, ) -> io::Result { - let scope = match pid { - None => PerfEventScope::AllProcessesOneCpu { cpu: 0 }, - Some(0) => PerfEventScope::CallingProcess { cpu: None }, - Some(pid) => PerfEventScope::OneProcess { - pid: Pid::new(pid).unwrap(), - cpu: None, - }, - }; perf_event_open( PerfEventConfig::TracePoint { event_id }, scope, @@ -189,6 +169,16 @@ pub(crate) fn perf_event_open_trace_point( ) } +fn perf_event_scope_pid_cpu(scope: PerfEventScope) -> (pid_t, i32) { + match scope { + PerfEventScope::CallingProcess { cpu } => (0, cpu.map_or(-1, |cpu| cpu as i32)), + PerfEventScope::OneProcess { pid, cpu } => { + (pid.get() as i32, cpu.map_or(-1, |cpu| cpu as i32)) + } + PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32), + } +} + pub(crate) fn perf_event_ioctl( fd: BorrowedFd<'_>, request: PerfEventIoctlRequest<'_>, @@ -271,15 +261,36 @@ mod tests { use test_case::test_case; use super::{PERF_FLAG_FD_CLOEXEC, perf_event_open_trace_point}; - use crate::sys::{Syscall, override_syscall}; + use crate::{ + programs::{Pid, perf_event::PerfEventScope}, + sys::{Syscall, override_syscall}, + }; const EVENT_ID: u64 = 123; - #[test_case(None, -1, 0; "all_processes")] - #[test_case(Some(0), 0, -1; "calling_process")] - #[test_case(Some(42), 42, -1; "one_process")] - fn perf_event_open_trace_point_maps_pid_scope( - pid: Option, + #[test_case( + PerfEventScope::AllProcessesOneCpu { cpu: 0 }, + -1, + 0; + "all_processes" + )] + #[test_case( + PerfEventScope::CallingProcess { cpu: None }, + 0, + -1; + "calling_process" + )] + #[test_case( + PerfEventScope::OneProcess { + pid: Pid::new(42).unwrap(), + cpu: None, + }, + 42, + -1; + "one_process" + )] + fn perf_event_open_trace_point_maps_scope( + scope: PerfEventScope, expected_pid: pid_t, expected_cpu: i32, ) { @@ -302,7 +313,7 @@ mod tests { call => panic!("unexpected syscall: {call:?}"), }); - let fd = perf_event_open_trace_point(EVENT_ID, pid).unwrap(); + let fd = perf_event_open_trace_point(EVENT_ID, scope).unwrap(); assert_eq!(fd.as_raw_fd(), crate::MockableFd::mock_signed_fd()); } }