From c39dff602530b757367f93099a867a4307a7d519 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Fri, 13 Aug 2021 17:20:03 -0400 Subject: [PATCH] Add support for PerfEvent programs. --- aya/src/bpf.rs | 7 +- aya/src/maps/perf/perf_buffer.rs | 4 +- aya/src/obj/mod.rs | 4 + aya/src/programs/mod.rs | 12 ++- aya/src/programs/perf_event.rs | 115 +++++++++++++++++++++++++ aya/src/sys/perf_event.rs | 44 ++++++++-- bpf/aya-bpf-macros/src/expand.rs | 29 +++++++ bpf/aya-bpf-macros/src/lib.rs | 15 +++- bpf/aya-bpf/src/programs/mod.rs | 2 + bpf/aya-bpf/src/programs/perf_event.rs | 18 ++++ 10 files changed, 235 insertions(+), 15 deletions(-) create mode 100644 aya/src/programs/perf_event.rs create mode 100644 bpf/aya-bpf/src/programs/perf_event.rs diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index bc1f8f1a..5ed1beba 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -19,9 +19,9 @@ use crate::{ Object, ParseError, ProgramSection, }, programs::{ - CgroupSkb, CgroupSkbAttachType, KProbe, LircMode2, ProbeKind, Program, ProgramData, - ProgramError, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, - UProbe, Xdp, + CgroupSkb, CgroupSkbAttachType, KProbe, LircMode2, PerfEvent, ProbeKind, Program, + ProgramData, ProgramError, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, + TracePoint, UProbe, Xdp, }, sys::bpf_map_update_elem_ptr, util::{possible_cpus, POSSIBLE_CPUS}, @@ -202,6 +202,7 @@ impl Bpf { expected_attach_type: Some(CgroupSkbAttachType::Egress), }), ProgramSection::LircMode2 { .. } => Program::LircMode2(LircMode2 { data }), + ProgramSection::PerfEvent { .. } => Program::PerfEvent(PerfEvent { data }), }; (name, program) diff --git a/aya/src/maps/perf/perf_buffer.rs b/aya/src/maps/perf/perf_buffer.rs index c288a2f3..3706a781 100644 --- a/aya/src/maps/perf/perf_buffer.rs +++ b/aya/src/maps/perf/perf_buffer.rs @@ -15,7 +15,7 @@ use crate::{ perf_event_header, perf_event_mmap_page, perf_event_type::{PERF_RECORD_LOST, PERF_RECORD_SAMPLE}, }, - sys::{perf_event_ioctl, perf_event_open}, + sys::{perf_event_ioctl, perf_event_open_bpf}, PERF_EVENT_IOC_DISABLE, PERF_EVENT_IOC_ENABLE, }; @@ -87,7 +87,7 @@ impl PerfBuffer { return Err(PerfBufferError::InvalidPageCount { page_count }); } - let fd = perf_event_open(cpu_id as i32) + let fd = perf_event_open_bpf(cpu_id as i32) .map_err(|(_, io_error)| PerfBufferError::OpenError { io_error })? as RawFd; let size = page_size * page_count; diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index 8e2efecb..1849abcf 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -84,6 +84,7 @@ pub enum ProgramSection { CgroupSkbIngress { name: String }, CgroupSkbEgress { name: String }, LircMode2 { name: String }, + PerfEvent { name: String }, } impl ProgramSection { @@ -104,6 +105,7 @@ impl ProgramSection { ProgramSection::CgroupSkbIngress { name } => name, ProgramSection::CgroupSkbEgress { name } => name, ProgramSection::LircMode2 { name } => name, + ProgramSection::PerfEvent { name } => name, } } } @@ -144,6 +146,7 @@ impl FromStr for ProgramSection { "cgroup_skb/ingress" => CgroupSkbIngress { name }, "cgroup_skb/egress" => CgroupSkbEgress { name }, "lirc_mode2" => LircMode2 { name }, + "perf_event" => PerfEvent { name }, _ => { return Err(ParseError::InvalidProgramSection { section: section.to_owned(), @@ -556,6 +559,7 @@ fn is_program_section(name: &str) -> bool { "kprobe", "kretprobe", "lirc_mode2", + "perf_event", "sk_msg", "sk_skb/stream_parser", "sk_skb/stream_verdict", diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 6fabfd95..fa5893ec 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -40,6 +40,7 @@ mod cgroup_skb; mod kprobe; mod lirc_mode2; mod perf_attach; +mod perf_event; mod probe; mod sk_msg; mod sk_skb; @@ -66,6 +67,7 @@ pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; pub use kprobe::{KProbe, KProbeError}; pub use lirc_mode2::LircMode2; use perf_attach::*; +pub use perf_event::{PerfEvent, PerfEventScope, SamplePolicy}; pub use probe::ProbeKind; pub use sk_msg::SkMsg; pub use sk_skb::{SkSkb, SkSkbKind}; @@ -181,6 +183,7 @@ pub enum Program { SchedClassifier(SchedClassifier), CgroupSkb(CgroupSkb), LircMode2(LircMode2), + PerfEvent(PerfEvent), } impl Program { @@ -213,6 +216,7 @@ impl Program { Program::SchedClassifier(_) => BPF_PROG_TYPE_SCHED_CLS, Program::CgroupSkb(_) => BPF_PROG_TYPE_CGROUP_SKB, Program::LircMode2(_) => BPF_PROG_TYPE_LIRC_MODE2, + Program::PerfEvent(_) => BPF_PROG_TYPE_PERF_EVENT, } } @@ -234,6 +238,7 @@ impl Program { Program::SchedClassifier(p) => &p.data, Program::CgroupSkb(p) => &p.data, Program::LircMode2(p) => &p.data, + Program::PerfEvent(p) => &p.data, } } @@ -250,6 +255,7 @@ impl Program { Program::SchedClassifier(p) => &mut p.data, Program::CgroupSkb(p) => &mut p.data, Program::LircMode2(p) => &mut p.data, + Program::PerfEvent(p) => &mut p.data, } } } @@ -535,7 +541,8 @@ impl_program_fd!( SkSkb, SchedClassifier, CgroupSkb, - LircMode2 + LircMode2, + PerfEvent ); macro_rules! impl_try_from_program { @@ -577,7 +584,8 @@ impl_try_from_program!( SockOps, SchedClassifier, CgroupSkb, - LircMode2 + LircMode2, + PerfEvent ); /// Provides information about a loaded program, like name, id and statistics diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs new file mode 100644 index 00000000..7722c1c5 --- /dev/null +++ b/aya/src/programs/perf_event.rs @@ -0,0 +1,115 @@ +use crate::{generated::bpf_prog_type::BPF_PROG_TYPE_PERF_EVENT, sys::perf_event_open}; + +pub use crate::generated::perf_type_id; + +use super::{load_program, perf_attach, LinkRef, ProgramData, ProgramError}; + +#[derive(Debug, Clone)] +pub enum SamplePolicy { + Period(u64), + Frequency(u64), +} + +#[derive(Debug, Clone)] +#[allow(clippy::enum_variant_names)] +pub enum PerfEventScope { + CallingProcessAnyCpu, + CallingProcessOneCpu { cpu: u32 }, + OneProcessAnyCpu { pid: u32 }, + OneProcessOneCpu { cpu: u32, pid: u32 }, + AllProcessesOneCpu { cpu: u32 }, +} + +/// A program that can be attached at a perf event. +/// +/// TODO: Explain the different types of perf events and how to get a list. +/// Maybe just link to the man page of `perf list`. +/// (But it's not clear to me how to translate those strings into numbers.) +/// +/// # Minimum kernel version +/// +/// TODO: minimum kernel version? +/// +/// # Examples +/// +/// ```no_run +/// # #[derive(Debug, thiserror::Error)] +/// # enum Error { +/// # #[error(transparent)] +/// # IO(#[from] std::io::Error), +/// # #[error(transparent)] +/// # Map(#[from] aya::maps::MapError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError) +/// # } +/// # let mut bpf = aya::Bpf::load(&[], None)?; +/// use std::convert::TryInto; +/// use aya::programs::{PerfEvent, PerfEventScope, SamplePolicy }; +/// use aya::util::online_cpus; +/// +/// let prog: &mut PerfEvent = bpf.program_mut("observe_cpu_clock")?.try_into()?; +/// prog.load()?; +/// +/// for cpu in online_cpus()? { +/// prog.attach( +/// 1, /* PERF_TYPE_SOFTWARE */ +/// 0, /* PERF_COUNT_SW_CPU_CLOCK */ +/// PerfEventScope::AllProcessesOneCpu { cpu }, +/// SamplePolicy::Period(1000000), +/// )?; +/// } +/// # Ok::<(), Error>(()) +/// ``` +#[derive(Debug)] +#[doc(alias = "BPF_PROG_TYPE_PERF_EVENT")] +pub struct PerfEvent { + pub(crate) data: ProgramData, +} + +impl PerfEvent { + /// Loads the program inside the kernel. + /// + /// See also [`Program::load`](crate::programs::Program::load). + pub fn load(&mut self) -> Result<(), ProgramError> { + load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data) + } + + /// Attaches to a given perf event. + pub fn attach( + &mut self, + perf_type: u32, // perf_type_id + config: u64, + scope: PerfEventScope, + sample_policy: SamplePolicy, + ) -> Result { + let (sample_period, sample_frequency) = match sample_policy { + SamplePolicy::Period(period) => (period, None), + SamplePolicy::Frequency(frequency) => (0, Some(frequency)), + }; + let (pid, cpu) = match scope { + PerfEventScope::CallingProcessAnyCpu => (0, -1), + PerfEventScope::CallingProcessOneCpu { cpu } => (0, cpu as i32), + PerfEventScope::OneProcessAnyCpu { pid } => (pid as i32, -1), + PerfEventScope::OneProcessOneCpu { cpu, pid } => (pid as i32, cpu as i32), + PerfEventScope::AllProcessesOneCpu { cpu } => (-1, cpu as i32), + }; + let fd = perf_event_open( + perf_type, + config, + pid, + cpu, + sample_period, + sample_frequency, + false, + 0, + ) + .map_err(|(_code, io_error)| ProgramError::SyscallError { + call: "perf_event_open".to_owned(), + io_error, + })? as i32; + + perf_attach(&mut self.data, fd) + } +} diff --git a/aya/src/sys/perf_event.rs b/aya/src/sys/perf_event.rs index 54078f03..7f065036 100644 --- a/aya/src/sys/perf_event.rs +++ b/aya/src/sys/perf_event.rs @@ -12,25 +12,55 @@ use crate::generated::{ use super::{syscall, SysResult, Syscall}; -pub(crate) fn perf_event_open(cpu: c_int) -> SysResult { +#[allow(clippy::too_many_arguments)] +pub(crate) fn perf_event_open( + perf_type: u32, + config: u64, + pid: pid_t, + cpu: c_int, + sample_period: u64, + sample_frequency: Option, + wakeup: bool, + flags: u32, +) -> SysResult { let mut attr = unsafe { mem::zeroed::() }; - attr.config = PERF_COUNT_SW_BPF_OUTPUT as u64; + attr.config = config; attr.size = mem::size_of::() as u32; - attr.type_ = PERF_TYPE_SOFTWARE as u32; + attr.type_ = perf_type; attr.sample_type = PERF_SAMPLE_RAW as u64; - attr.__bindgen_anon_1.sample_period = 1; - attr.__bindgen_anon_2.wakeup_events = 1; + // attr.inherits = if pid > 0 { 1 } else { 0 }; + attr.__bindgen_anon_2.wakeup_events = if wakeup { 1 } else { 0 }; + + if let Some(frequency) = sample_frequency { + attr.set_freq(1); + attr.__bindgen_anon_1.sample_freq = frequency; + } else { + attr.__bindgen_anon_1.sample_period = sample_period; + } syscall(Syscall::PerfEventOpen { attr, - pid: -1, + pid, cpu, group: -1, - flags: PERF_FLAG_FD_CLOEXEC, + flags, }) } +pub(crate) fn perf_event_open_bpf(cpu: c_int) -> SysResult { + perf_event_open( + PERF_TYPE_SOFTWARE as u32, + PERF_COUNT_SW_BPF_OUTPUT as u64, + -1, + cpu, + 1, + None, + true, + PERF_FLAG_FD_CLOEXEC, + ) +} + pub(crate) fn perf_event_open_probe( ty: u32, ret_bit: Option, diff --git a/bpf/aya-bpf-macros/src/expand.rs b/bpf/aya-bpf-macros/src/expand.rs index 40313845..279e9458 100644 --- a/bpf/aya-bpf-macros/src/expand.rs +++ b/bpf/aya-bpf-macros/src/expand.rs @@ -323,3 +323,32 @@ impl TracePoint { }) } } + +pub struct PerfEvent { + item: ItemFn, + name: String, +} + +impl PerfEvent { + pub fn from_syn(mut args: Args, item: ItemFn) -> Result { + let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string()); + + Ok(PerfEvent { item, name }) + } + + pub fn expand(&self) -> Result { + let section_name = format!("perf_event/{}", self.name); + let fn_name = &self.item.sig.ident; + let item = &self.item; + Ok(quote! { + #[no_mangle] + #[link_section = #section_name] + fn #fn_name(ctx: *mut ::core::ffi::c_void) -> u32 { + let _ = #fn_name(::aya_bpf::programs::PerfEventContext::new(ctx)); + return 0; + + #item + } + }) + } +} diff --git a/bpf/aya-bpf-macros/src/lib.rs b/bpf/aya-bpf-macros/src/lib.rs index 9ed55673..1ccfe362 100644 --- a/bpf/aya-bpf-macros/src/lib.rs +++ b/bpf/aya-bpf-macros/src/lib.rs @@ -1,6 +1,8 @@ mod expand; -use expand::{Args, Map, Probe, ProbeKind, SchedClassifier, SkMsg, SockOps, TracePoint, Xdp}; +use expand::{ + Args, Map, PerfEvent, Probe, ProbeKind, SchedClassifier, SkMsg, SockOps, TracePoint, Xdp, +}; use proc_macro::TokenStream; use syn::{parse_macro_input, ItemFn, ItemStatic}; @@ -112,3 +114,14 @@ pub fn tracepoint(attrs: TokenStream, item: TokenStream) -> TokenStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } + +#[proc_macro_attribute] +pub fn perf_event(attrs: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attrs as Args); + let item = parse_macro_input!(item as ItemFn); + + PerfEvent::from_syn(args, item) + .and_then(|u| u.expand()) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/bpf/aya-bpf/src/programs/mod.rs b/bpf/aya-bpf/src/programs/mod.rs index 979fe11a..2e55a4b1 100644 --- a/bpf/aya-bpf/src/programs/mod.rs +++ b/bpf/aya-bpf/src/programs/mod.rs @@ -1,3 +1,4 @@ +pub mod perf_event; pub mod probe; pub mod sk_msg; pub mod sk_skb; @@ -5,6 +6,7 @@ pub mod sock_ops; pub mod tracepoint; pub mod xdp; +pub use perf_event::PerfEventContext; pub use probe::ProbeContext; pub use sk_msg::SkMsgContext; pub use sk_skb::SkSkbContext; diff --git a/bpf/aya-bpf/src/programs/perf_event.rs b/bpf/aya-bpf/src/programs/perf_event.rs new file mode 100644 index 00000000..d24ed737 --- /dev/null +++ b/bpf/aya-bpf/src/programs/perf_event.rs @@ -0,0 +1,18 @@ +use crate::BpfContext; +use core::ffi::c_void; + +pub struct PerfEventContext { + ctx: *mut c_void, +} + +impl PerfEventContext { + pub fn new(ctx: *mut c_void) -> PerfEventContext { + PerfEventContext { ctx } + } +} + +impl BpfContext for PerfEventContext { + fn as_ptr(&self) -> *mut c_void { + self.ctx + } +}