Skip to main content

profile_bee_aya/sys/
mod.rs

1//! A collection of system calls for performing eBPF related operations.
2
3mod bpf;
4pub(crate) mod feature_probe;
5mod netlink;
6mod perf_event;
7
8#[cfg(test)]
9mod fake;
10
11use std::{
12    ffi::{c_int, c_void},
13    io,
14    os::fd::{BorrowedFd, OwnedFd},
15};
16
17use aya_obj::generated::{bpf_attr, bpf_cmd, bpf_stats_type, perf_event_attr};
18pub(crate) use bpf::*;
19#[cfg(test)]
20pub(crate) use fake::*;
21pub use feature_probe::{is_map_supported, is_program_supported};
22#[doc(hidden)]
23pub use netlink::netlink_set_link_up;
24pub(crate) use netlink::*;
25pub(crate) use perf_event::*;
26use thiserror::Error;
27
28pub(crate) type SysResult = Result<i64, (i64, io::Error)>;
29
30#[cfg_attr(test, expect(dead_code, reason = "test stubs cut above this"))]
31#[derive(Debug)]
32pub(crate) enum PerfEventIoctlRequest<'a> {
33    Enable,
34    Disable,
35    SetBpf(BorrowedFd<'a>),
36}
37
38#[cfg_attr(test, expect(dead_code, reason = "test stubs cut above this"))]
39pub(crate) enum Syscall<'a> {
40    Ebpf {
41        cmd: bpf_cmd,
42        attr: &'a mut bpf_attr,
43    },
44    PerfEventOpen {
45        attr: perf_event_attr,
46        pid: libc::pid_t,
47        cpu: i32,
48        group: i32,
49        flags: u32,
50    },
51    PerfEventIoctl {
52        fd: BorrowedFd<'a>,
53        request: PerfEventIoctlRequest<'a>,
54    },
55}
56
57/// A system call error.
58#[derive(Debug, Error)]
59#[error("`{call}` failed")]
60pub struct SyscallError {
61    /// The name of the syscall which failed.
62    pub call: &'static str,
63    /// The [`io::Error`] returned by the syscall.
64    #[source]
65    pub io_error: io::Error,
66}
67
68impl std::fmt::Debug for Syscall<'_> {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            Self::Ebpf { cmd, attr: _ } => f
72                .debug_struct("Syscall::Ebpf")
73                .field("cmd", cmd)
74                .field("attr", &format_args!("_"))
75                .finish(),
76            Self::PerfEventOpen {
77                attr: _,
78                pid,
79                cpu,
80                group,
81                flags,
82            } => f
83                .debug_struct("Syscall::PerfEventOpen")
84                .field("attr", &format_args!("_"))
85                .field("pid", pid)
86                .field("cpu", cpu)
87                .field("group", group)
88                .field("flags", flags)
89                .finish(),
90            Self::PerfEventIoctl { fd, request } => f
91                .debug_struct("Syscall::PerfEventIoctl")
92                .field("fd", fd)
93                .field("request", request)
94                .finish(),
95        }
96    }
97}
98
99fn syscall(call: Syscall<'_>) -> SysResult {
100    #[cfg(test)]
101    {
102        TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) })
103    }
104
105    #[cfg(not(test))]
106    {
107        let ret = unsafe {
108            match call {
109                Syscall::Ebpf { cmd, attr } => {
110                    libc::syscall(libc::SYS_bpf, cmd, attr, size_of::<bpf_attr>())
111                }
112                Syscall::PerfEventOpen {
113                    attr,
114                    pid,
115                    cpu,
116                    group,
117                    flags,
118                } => libc::syscall(libc::SYS_perf_event_open, &attr, pid, cpu, group, flags),
119                Syscall::PerfEventIoctl { fd, request } => {
120                    use std::os::fd::AsRawFd as _;
121
122                    let fd = fd.as_raw_fd();
123                    match request {
124                        PerfEventIoctlRequest::Enable => libc::syscall(
125                            libc::SYS_ioctl,
126                            fd,
127                            aya_obj::generated::PERF_EVENT_IOC_ENABLE,
128                        ),
129                        PerfEventIoctlRequest::Disable => libc::syscall(
130                            libc::SYS_ioctl,
131                            fd,
132                            aya_obj::generated::PERF_EVENT_IOC_DISABLE,
133                        ),
134                        PerfEventIoctlRequest::SetBpf(bpf_fd) => libc::syscall(
135                            libc::SYS_ioctl,
136                            fd,
137                            aya_obj::generated::PERF_EVENT_IOC_SET_BPF,
138                            bpf_fd.as_raw_fd(),
139                        ),
140                    }
141                }
142            }
143        };
144        // c_long is i32 on armv7.
145        #[expect(clippy::allow_attributes, reason = "architecture specific")]
146        #[allow(clippy::useless_conversion, reason = "architecture specific")]
147        let ret: i64 = ret.into();
148
149        match ret {
150            0.. => Ok(ret),
151            ret => Err((ret, io::Error::last_os_error())),
152        }
153    }
154}
155
156#[cfg_attr(
157    test,
158    expect(unused_variables, reason = "TODO: we should validate all arguments")
159)]
160pub(crate) unsafe fn mmap(
161    addr: *mut c_void,
162    len: usize,
163    prot: c_int,
164    flags: c_int,
165    fd: BorrowedFd<'_>,
166    offset: libc::off_t,
167) -> *mut c_void {
168    #[cfg(test)]
169    {
170        TEST_MMAP_RET.with(|ret| *ret.borrow())
171    }
172
173    #[cfg(not(test))]
174    {
175        use std::os::fd::AsRawFd as _;
176
177        unsafe { libc::mmap(addr, len, prot, flags, fd.as_raw_fd(), offset) }
178    }
179}
180
181#[cfg_attr(
182    test,
183    expect(clippy::missing_const_for_fn, reason = "only const in cfg(test)"),
184    expect(unused_variables, reason = "TODO: we should validate all arguments")
185)]
186pub(crate) unsafe fn munmap(addr: *mut c_void, len: usize) -> c_int {
187    #[cfg(test)]
188    {
189        0
190    }
191
192    #[cfg(not(test))]
193    {
194        unsafe { libc::munmap(addr, len) }
195    }
196}
197
198/// The type of eBPF statistic to enable.
199#[non_exhaustive]
200#[doc(alias = "bpf_stats_type")]
201#[derive(Copy, Clone, Debug)]
202pub enum Stats {
203    /// Tracks [`run_time`](crate::programs::ProgramInfo::run_time) and
204    /// [`run_count`](crate::programs::ProgramInfo::run_count) fields.
205    #[doc(alias = "BPF_STATS_RUN_TIME")]
206    RunTime,
207}
208
209impl From<Stats> for bpf_stats_type {
210    fn from(value: Stats) -> Self {
211        match value {
212            Stats::RunTime => Self::BPF_STATS_RUN_TIME,
213        }
214    }
215}
216
217/// Enable global statistics tracking for eBPF programs and returns a
218/// [file descriptor](`OwnedFd`) handler.
219///
220/// Statistics tracking is disabled when the [file descriptor](`OwnedFd`) is
221/// dropped (either automatically when the variable goes out of scope or
222/// manually through [`Drop`]).
223///
224/// Usage:
225/// 1. Obtain fd from [`enable_stats`] and bind it to a variable.
226/// 2. Record the statistic of interest.
227/// 3. Wait for a recorded period of time.
228/// 4. Record the statistic of interest again, and calculate the difference.
229/// 5. Close/release fd automatically or manually.
230///
231/// Introduced in kernel v5.8.
232///
233/// # Examples
234///
235/// ```no_run
236/// # use aya::sys::{SyscallError};
237/// use aya::sys::{enable_stats, Stats};
238///
239/// let _fd = enable_stats(Stats::RunTime)?;
240/// # Ok::<(), SyscallError>(())
241/// ```
242#[doc(alias = "BPF_ENABLE_STATS")]
243pub fn enable_stats(stats_type: Stats) -> Result<OwnedFd, SyscallError> {
244    bpf_enable_stats(stats_type.into()).map(super::MockableFd::into_inner)
245}