From 34aa790a917512783fa50c60527b2e694fb93ce3 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Wed, 17 Nov 2021 07:56:19 +1000 Subject: [PATCH] Support k/uprobes on older kernels. Prior to kernel 4.17 probes were attached via debugfs and this patch attempts to make that work. Tested on kernel 4.14. --- aya/src/programs/kprobe.rs | 6 +- aya/src/programs/probe.rs | 207 +++++++++++++++++++++++++++++++- aya/src/programs/trace_point.rs | 2 +- aya/src/programs/uprobe.rs | 6 +- 4 files changed, 212 insertions(+), 9 deletions(-) diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs index 9dddf6f8..5bea9c36 100644 --- a/aya/src/programs/kprobe.rs +++ b/aya/src/programs/kprobe.rs @@ -6,7 +6,7 @@ use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE, programs::{ load_program, - probe::{attach, ProbeKind}, + probe::{attach, detach, ProbeKind}, LinkRef, ProgramData, ProgramError, }, }; @@ -73,6 +73,10 @@ impl KProbe { pub fn attach(&mut self, fn_name: &str, offset: u64) -> Result { attach(&mut self.data, self.kind, fn_name, offset, None) } + + pub fn detach(&mut self, fn_name: &str) -> Result<(), ProgramError> { + detach(&mut self.data, self.kind, fn_name) + } } /// The type returned when attaching a [`KProbe`] fails. diff --git a/aya/src/programs/probe.rs b/aya/src/programs/probe.rs index 92d84ae6..0d11eacb 100644 --- a/aya/src/programs/probe.rs +++ b/aya/src/programs/probe.rs @@ -1,11 +1,16 @@ use libc::pid_t; -use std::{fs, io}; +use std::{ + fs, + io::{self, Write}, + process, +}; use crate::{ programs::{ - kprobe::KProbeError, perf_attach, uprobe::UProbeError, LinkRef, ProgramData, ProgramError, + kprobe::KProbeError, perf_attach, trace_point::read_sys_fs_trace_point_id, + uprobe::UProbeError, LinkRef, ProgramData, ProgramError, }, - sys::perf_event_open_probe, + sys::{kernel_version, perf_event_open_probe, perf_event_open_trace_point}, }; #[derive(Debug, Copy, Clone)] @@ -23,10 +28,89 @@ pub enum ProbeKind { pub(crate) fn attach( program_data: &mut ProgramData, kind: ProbeKind, - name: &str, + fn_name: &str, offset: u64, pid: Option, ) -> Result { + // https://github.com/torvalds/linux/commit/e12f03d7031a977356e3d7b75a68c2185ff8d155 + let k_ver = kernel_version().unwrap(); + let fd = if k_ver >= (4, 17, 0) { + create_as_probe(kind, fn_name, offset, pid)? + } else { + create_as_trace_point(kind, fn_name, offset, pid)? + }; + + perf_attach(program_data, fd) +} + +pub(crate) fn detach( + program_data: &mut ProgramData, + kind: ProbeKind, + fn_name: &str, +) -> Result<(), ProgramError> { + use ProbeKind::*; + + /* + * Taken from https://github.com/iovisor/bcc/blob/67f59ee80fcf5deedaacba1436d9fa09d32a16a0/src/cc/libbpf.c#L1173 + * + * For [k,u]probe created with perf_event_open (on newer kernel), it is + * not necessary to clean it up in [k,u]probe_events. We first look up + * the %s_bcc_%d line in [k,u]probe_events. If the event is not found, + * it is safe to skip the cleaning up process (write -:... to the file). + */ + + let event_type = match kind { + KProbe | KRetProbe => "kprobe", + UProbe | URetProbe => "uprobe", + }; + + let probe_type_prefix = match kind { + KProbe | UProbe => 'p', + KRetProbe | URetProbe => 'r', + }; + + let events_file_name = format!("/sys/kernel/debug/tracing/{}_events", event_type); + let event_name = format!( + "{}s/{}_{}_aya_{}", + probe_type_prefix, + event_type, + program_data.name, + process::id() + ); + + let found = match kind { + KProbe | KRetProbe => { + find_in_sys_kernel_debug_tracing_events(&events_file_name, event_name.clone()) + .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })? + } + UProbe | URetProbe => { + find_in_sys_kernel_debug_tracing_events(&events_file_name, event_name.clone()) + .map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })? + } + }; + + if found { + match kind { + KProbe | KRetProbe => { + delete_in_sys_kernel_debug_tracing_events(&events_file_name, &event_name) + .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })? + } + UProbe | URetProbe => { + delete_in_sys_kernel_debug_tracing_events(&events_file_name, &event_name) + .map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })? + } + }; + } + + Ok(()) +} + +fn create_as_probe( + kind: ProbeKind, + fn_name: &str, + offset: u64, + pid: Option, +) -> Result { use ProbeKind::*; let perf_ty = match kind { @@ -48,14 +132,125 @@ pub(crate) fn attach( _ => None, }; - let fd = perf_event_open_probe(perf_ty, ret_bit, name, offset, pid).map_err( + let fd = perf_event_open_probe(perf_ty, ret_bit, fn_name, offset, pid).map_err( |(_code, io_error)| ProgramError::SyscallError { call: "perf_event_open".to_owned(), io_error, }, )? as i32; - perf_attach(program_data, fd) + Ok(fd) +} + +fn create_as_trace_point( + kind: ProbeKind, + name: &str, + offset: u64, + pid: Option, +) -> Result { + use ProbeKind::*; + + let (event_type, event_alias) = match kind { + KProbe | KRetProbe => ( + "kprobes", + create_probe_event(kind, "kprobe", name, offset) + .map_err(|(filename, io_error)| KProbeError::FileError { filename, io_error })?, + ), + UProbe | URetProbe => ( + "uprobes", + create_probe_event(kind, "uprobe", name, offset) + .map_err(|(filename, io_error)| UProbeError::FileError { filename, io_error })?, + ), + }; + + // TODO: pid and cpu handling + let tpid = read_sys_fs_trace_point_id(event_type, &event_alias)?; + let fd = perf_event_open_trace_point(tpid).map_err(|(_code, io_error)| { + ProgramError::SyscallError { + call: "perf_event_open".to_owned(), + io_error, + } + })? as i32; + + Ok(fd) +} + +fn create_probe_event( + kind: ProbeKind, + event_type: &str, + fn_name: &str, + offset: u64, +) -> Result { + use ProbeKind::*; + + let events_file_name = format!("/sys/kernel/debug/tracing/{}_events", event_type); + let probe_type_prefix = match kind { + KProbe | UProbe => 'p', + KRetProbe | URetProbe => 'r', + }; + let event_alias = format!("{}_{}_aya_{}", probe_type_prefix, fn_name, process::id()); + + let mut events_file = fs::OpenOptions::new() + .append(true) + .open(&events_file_name) + .map_err(|e| (events_file_name.clone(), e))?; + + // FIXME: add offset + let p = match kind { + KProbe => format!( + "{}:{}s/{} {}", + probe_type_prefix, event_type, event_alias, fn_name + ), + KRetProbe => format!( + "{}:{}s/{} {}", + probe_type_prefix, event_type, event_alias, fn_name + ), + UProbe => format!( + "{}:{}s/{} {}", + probe_type_prefix, event_type, event_alias, fn_name + ), + URetProbe => format!( + "{}:{}s/{} {}", + probe_type_prefix, event_type, event_alias, fn_name + ), + }; + + events_file + .write_all(p.as_bytes()) + .map_err(|e| (events_file_name.clone(), e))?; + + Ok(event_alias) +} + +fn find_in_sys_kernel_debug_tracing_events( + events_file_name: &String, + event_name: String, +) -> Result { + use std::io::BufRead; + + let events_file = + fs::File::open(events_file_name).map_err(|e| (events_file_name.clone(), e))?; + + Ok(io::BufReader::new(events_file) + .lines() + .map(|line| line.unwrap()) + .any(|line| line == event_name)) +} + +fn delete_in_sys_kernel_debug_tracing_events( + events_file_name: &String, + event_name: &String, +) -> Result<(), (String, io::Error)> { + let mut events_file = fs::OpenOptions::new() + .append(true) + .open(events_file_name) + .map_err(|e| (events_file_name.clone(), e))?; + + events_file + .write_fmt(format_args!("-:{}", event_name)) + .map_err(|e| (events_file_name.clone(), e))?; + + Ok(()) } fn read_sys_fs_perf_type(pmu: &str) -> Result { diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs index 331be6ca..5d128ac4 100644 --- a/aya/src/programs/trace_point.rs +++ b/aya/src/programs/trace_point.rs @@ -80,7 +80,7 @@ impl TracePoint { } } -fn read_sys_fs_trace_point_id(category: &str, name: &str) -> Result { +pub fn read_sys_fs_trace_point_id(category: &str, name: &str) -> Result { let file = format!("/sys/kernel/debug/tracing/events/{}/{}/id", category, name); let id = fs::read_to_string(&file).map_err(|io_error| TracePointError::FileError { diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs index 2444be86..65963c17 100644 --- a/aya/src/programs/uprobe.rs +++ b/aya/src/programs/uprobe.rs @@ -17,7 +17,7 @@ use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_KPROBE, programs::{ load_program, - probe::{attach, ProbeKind}, + probe::{attach, detach, ProbeKind}, LinkRef, ProgramData, ProgramError, }, }; @@ -126,6 +126,10 @@ impl UProbe { attach(&mut self.data, self.kind, &path, sym_offset + offset, pid) } + + pub fn detach(&mut self, fn_name: &str) -> Result<(), ProgramError> { + detach(&mut self.data, self.kind, fn_name) + } } /// The type returned when attaching an [`UProbe`] fails.