From 6cba3c51209d3e8d8afced7e3ee4a9f8d8aba155 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 14 May 2023 18:54:13 +0200 Subject: [PATCH] aya+ebpf: Implement read+write methods for PerfEventArray This allow to read _and_ write a PerfEventArray, from userspace _and_ kernel. --- aya/src/maps/perf/perf_event_array.rs | 27 +++++++- bpf/aya-bpf/src/maps/perf/perf_event_array.rs | 61 ++++++++++++++++--- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/aya/src/maps/perf/perf_event_array.rs b/aya/src/maps/perf/perf_event_array.rs index c1df535ae..223725b79 100644 --- a/aya/src/maps/perf/perf_event_array.rs +++ b/aya/src/maps/perf/perf_event_array.rs @@ -8,14 +8,16 @@ use std::{ sync::Arc, }; +use aya_obj::generated::BPF_ANY; use bytes::BytesMut; use crate::{ maps::{ + check_bounds, perf::{Events, PerfBuffer, PerfBufferError}, MapData, MapError, }, - sys::bpf_map_update_elem, + sys::{bpf_map_update_elem, SyscallError}, util::page_size, }; @@ -197,4 +199,27 @@ impl> PerfEventArray { _map: self.map.clone(), }) } + + /// Inserts a perf_event file descriptor at the given index. + /// + /// ## Errors + /// + /// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`] + /// if `bpf_map_update_elem` fails. + pub fn set(&mut self, index: u32, value: i32) -> Result<(), MapError> { + let data: &MapData = self.map.deref().borrow(); + check_bounds(data, index)?; + let fd = data.fd().as_fd(); + + // only BPF_ANY or BPF_EXIST are allowed, and for arrays they do the same thing (the elements always exist) + let flags = BPF_ANY as u64; + + bpf_map_update_elem(fd, Some(&index), &value, flags).map_err(|(_, io_error)| { + SyscallError { + call: "bpf_map_update_elem", + io_error, + } + })?; + Ok(()) + } } diff --git a/bpf/aya-bpf/src/maps/perf/perf_event_array.rs b/bpf/aya-bpf/src/maps/perf/perf_event_array.rs index f7c874a6c..234943793 100644 --- a/bpf/aya-bpf/src/maps/perf/perf_event_array.rs +++ b/bpf/aya-bpf/src/maps/perf/perf_event_array.rs @@ -1,12 +1,25 @@ -use core::{cell::UnsafeCell, marker::PhantomData, mem}; +use core::{ + cell::UnsafeCell, + marker::PhantomData, + mem::{self, MaybeUninit}, +}; + +use aya_bpf_bindings::{bindings::bpf_perf_event_value, helpers::bpf_perf_event_read_value}; use crate::{ - bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_F_CURRENT_CPU}, + bindings::{bpf_map_def, bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY, BPF_F_CURRENT_CPU, BPF_F_INDEX_MASK}, helpers::bpf_perf_event_output, maps::PinningType, BpfContext, }; +/// A map of type `BPF_MAP_TYPE_PERF_EVENT_ARRAY`. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to read perf_event values using [PerfEventArray] is 4.15. +/// This concerns the functions [`read_current_cpu()`], [`read_at_index()`] and [`read()`]. +/// #[repr(transparent)] pub struct PerfEventArray { def: UnsafeCell, @@ -50,20 +63,54 @@ impl PerfEventArray { } } - pub fn output(&self, ctx: &C, data: &T, flags: u32) { - self.output_at_index(ctx, BPF_F_CURRENT_CPU as u32, data, flags) + pub fn output_current_cpu(&self, ctx: &C, data: &T) -> Result<(), i64> { + self.output(ctx, data, BPF_F_CURRENT_CPU) + } + + pub fn output_at_index(&self, ctx: &C, data: &T, index: u32) -> Result<(), i64> { + self.output(ctx, data, (index as u64) & BPF_F_INDEX_MASK) } - pub fn output_at_index(&self, ctx: &C, index: u32, data: &T, flags: u32) { - let flags = u64::from(flags) << 32 | u64::from(index); + fn output(&self, ctx: &C, data: &T, flags: u64) -> Result<(), i64> { unsafe { - bpf_perf_event_output( + let ret = bpf_perf_event_output( ctx.as_ptr(), self.def.get() as *mut _, flags, data as *const _ as *mut _, mem::size_of::() as u64, ); + if ret == 0 { + Ok(()) + } else { + Err(ret) + } + } + } + + pub fn read_current_cpu(&self) -> Result { + self.read(BPF_F_CURRENT_CPU) + } + + pub fn read_at_index(&self, index: u32) -> Result { + self.read(u64::from(index) & BPF_F_INDEX_MASK) + } + + fn read(&self, flags: u64) -> Result { + let mut buf = MaybeUninit::::uninit(); + unsafe { + // According to the Linux manual, `bpf_perf_event_read_value` is preferred over `bpf_perf_event_read`. + let ret = bpf_perf_event_read_value( + self.def.get() as *mut _, + flags, + buf.as_mut_ptr(), + mem::size_of::() as u32, + ); + if ret == 0 { + Ok(buf.assume_init()) + } else { + Err(ret) + } } } }