From 8b58fc13fce1cb5f7f943864307c8a5ef4b77c9b Mon Sep 17 00:00:00 2001 From: Friday Ortiz Date: Mon, 13 Oct 2025 15:25:46 -0400 Subject: [PATCH] test: add a perf_event breakpoint test Test perf_event breakpoints by attaching a RW breakpoint to modprobe_path and triggering a read from procfs, asserting that the tgid of the program triggering the breakpoint matches the test program. --- test/integration-ebpf/Cargo.toml | 4 + test/integration-ebpf/src/perf_event_bp.rs | 24 ++++++ test/integration-test/src/lib.rs | 1 + test/integration-test/src/tests.rs | 1 + .../src/tests/perf_event_bp.rs | 81 +++++++++++++++++++ 5 files changed, 111 insertions(+) create mode 100644 test/integration-ebpf/src/perf_event_bp.rs create mode 100644 test/integration-test/src/tests/perf_event_bp.rs diff --git a/test/integration-ebpf/Cargo.toml b/test/integration-ebpf/Cargo.toml index cb52e728..33715cfb 100644 --- a/test/integration-ebpf/Cargo.toml +++ b/test/integration-ebpf/Cargo.toml @@ -103,3 +103,7 @@ path = "src/xdp_sec.rs" [[bin]] name = "uprobe_cookie" path = "src/uprobe_cookie.rs" + +[[bin]] +name = "perf_event_bp" +path = "src/perf_event_bp.rs" diff --git a/test/integration-ebpf/src/perf_event_bp.rs b/test/integration-ebpf/src/perf_event_bp.rs new file mode 100644 index 00000000..f6928c01 --- /dev/null +++ b/test/integration-ebpf/src/perf_event_bp.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] +#![expect(unused_crate_dependencies, reason = "used in other bins")] + +use aya_ebpf::{ + EbpfContext as _, + macros::{map, perf_event}, + maps::HashMap, + programs::PerfEventContext, +}; + +#[cfg(not(test))] +extern crate ebpf_panic; + +#[map] +static READERS: HashMap = HashMap::with_max_entries(1, 0); + +#[perf_event] +fn perf_event_bp(ctx: PerfEventContext) -> u32 { + let tgid = ctx.tgid(); + let addr = unsafe { (*ctx.ctx).addr }; + let _ = READERS.insert(tgid, addr, 0); + 0 +} diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs index 64f37d18..01e26f03 100644 --- a/test/integration-test/src/lib.rs +++ b/test/integration-test/src/lib.rs @@ -46,6 +46,7 @@ bpf_file!( MEMMOVE_TEST => "memmove_test", NAME_TEST => "name_test", PASS => "pass", + PERF_EVENT_BP => "perf_event_bp", RAW_TRACEPOINT => "raw_tracepoint", REDIRECT => "redirect", RELOCATIONS => "relocations", diff --git a/test/integration-test/src/tests.rs b/test/integration-test/src/tests.rs index b7d4d492..79122ac2 100644 --- a/test/integration-test/src/tests.rs +++ b/test/integration-test/src/tests.rs @@ -10,6 +10,7 @@ mod load; mod log; mod lsm; mod map_pin; +mod perf_event_bp; mod raw_tracepoint; mod rbpf; mod relocations; diff --git a/test/integration-test/src/tests/perf_event_bp.rs b/test/integration-test/src/tests/perf_event_bp.rs new file mode 100644 index 00000000..56d0179c --- /dev/null +++ b/test/integration-test/src/tests/perf_event_bp.rs @@ -0,0 +1,81 @@ +use std::{ + fs::{self, File}, + io::{BufRead as _, BufReader}, +}; + +use aya::{ + Ebpf, + programs::{ + PerfEventScope, PerfTypeId, SamplePolicy, + perf_event::{ + PerfBreakpoint, PerfBreakpointSize::HwBreakpointLen1, + PerfBreakpointType::HwBreakpointRW, + }, + }, + util::online_cpus, +}; +use log::info; + +// Parse /proc/kallsyms and return the address for the given symbol name, if +// found. +fn find_kallsyms_symbol(sym: &str) -> Option { + let file = File::open("/proc/kallsyms").ok()?; + let reader = BufReader::new(file); + + for line in reader.lines().map_while(Result::ok) { + // Format: " []" + let mut parts = line.split_whitespace(); + let addr_str = parts.next()?; + let _type = parts.next()?; + let name = parts.next()?; + if name == sym + && let Ok(addr) = u64::from_str_radix(addr_str, 16) + { + return Some(addr); + } + } + + None +} + +#[test_log::test] +fn perf_event_bp() { + let mut bpf = Ebpf::load(crate::PERF_EVENT_BP).unwrap(); + + let attach_addr = find_kallsyms_symbol("modprobe_path").unwrap(); + + let prog: &mut aya::programs::PerfEvent = bpf + .program_mut("perf_event_bp") + .unwrap() + .try_into() + .unwrap(); + prog.load().unwrap(); + + // attach hardware breakpoint to modprobe_path global + for cpu in online_cpus().unwrap() { + info!("attaching to cpu {cpu}"); + prog.attach( + PerfTypeId::Breakpoint, + 0u64, + PerfEventScope::AllProcessesOneCpu { cpu }, + SamplePolicy::Period(1), + true, + Some(PerfBreakpoint { + address: attach_addr, + length: HwBreakpointLen1, + type_: HwBreakpointRW, + }), + ) + .unwrap(); + } + + // trigger hardware breakpoint by reading modprobe_path via procfs + let _ = fs::read_to_string("/proc/sys/kernel/modprobe"); + + // assert that the map contains an entry for this process + let map: aya::maps::HashMap<_, u32, u64> = + aya::maps::HashMap::try_from(bpf.map_mut("READERS").unwrap()).unwrap(); + let tgid = std::process::id(); + let read_addr = map.get(&tgid, 0).unwrap(); + assert_eq!(read_addr, attach_addr); +}