perf_event/events/
probe.rs

1use std::ffi::CString;
2use std::os::unix::ffi::OsStrExt;
3use std::path::Path;
4use std::sync::Arc;
5use std::{fmt, io};
6
7use perf_event_open_sys::bindings::perf_event_attr;
8
9use crate::events::{CachedPmuType, Event, EventData};
10
11static KPROBE_TYPE: CachedPmuType = CachedPmuType::new("kprobe");
12static UPROBE_TYPE: CachedPmuType = CachedPmuType::new("uprobe");
13
14#[derive(Clone, Debug)]
15enum ProbeTarget {
16    Func { name: CString, offset: u64 },
17    Addr(u64),
18}
19
20#[derive(Clone, Debug)]
21struct Probe {
22    ty: u32,
23    retprobe: bool,
24    target: ProbeTarget,
25}
26
27impl Event for Probe {
28    fn update_attrs(self, _: &mut perf_event_attr) {
29        unimplemented!("probes require storing data within the Builder")
30    }
31
32    fn update_attrs_with_data(self, attr: &mut perf_event_attr) -> Option<Arc<dyn EventData>> {
33        attr.type_ = self.ty;
34        attr.config = self.retprobe.into();
35        match self.target {
36            ProbeTarget::Addr(addr) => {
37                attr.kprobe_func = 0;
38                attr.kprobe_addr = addr;
39                None
40            }
41            ProbeTarget::Func { name, offset } => {
42                attr.kprobe_func = name.as_ptr() as usize as u64;
43                attr.probe_offset = offset;
44                Some(Arc::new(name))
45            }
46        }
47    }
48}
49
50/// Kernel-space probe event.
51///
52/// Kprobes allow you to dynamically insert breakpoints into kernel functions.
53/// This can be used to count function executions or to attach eBPF programs
54/// that run during those breakpoints.
55///
56/// There are two types of kprobes:
57/// - [`kprobe`](KProbe::probe)s trigger when the relevant function is called
58///   (or, potentially, executed at an offset within that function).
59/// - [`kretprobe`](KProbe::retprobe)s trigger just before the relevant function
60///   returns.
61///
62/// Kprobes can be create either for a named function or at a raw address in
63/// kernel space.
64///
65/// The internal documentation on how kprobes work is available [here][kdoc].
66///
67/// [kdoc]: https://www.kernel.org/doc/Documentation/kprobes.txt
68#[derive(Clone)]
69pub struct KProbe(Probe);
70
71impl KProbe {
72    /// Create a kprobe or kretprobe for a named kernel function.
73    ///
74    /// # Errors
75    /// This will attempt to read the kprobe PMU type from
76    /// `/sys/bus/event_source`. It will return an error if the kprobe PMU is
77    /// not available or the filesystem exposed by the kernel there is otherwise
78    /// unparseable.
79    pub fn for_function(retprobe: bool, func: CString, offset: u64) -> io::Result<Self> {
80        Ok(Self(Probe {
81            ty: KPROBE_TYPE.get()?,
82            retprobe,
83            target: ProbeTarget::Func { name: func, offset },
84        }))
85    }
86
87    /// Create a kprobe or kretprobe for a kernel address.
88    ///
89    /// # Errors
90    /// This will attempt to read the kprobe PMU type from
91    /// `/sys/bus/event_source`. It will return an error if the kprobe PMU is
92    /// not available or the filesystem exposed by the kernel there is otherwise
93    /// unparseable.
94    pub fn for_addr(retprobe: bool, addr: u64) -> io::Result<Self> {
95        Ok(Self(Probe {
96            ty: UPROBE_TYPE.get()?,
97            retprobe,
98            target: ProbeTarget::Addr(addr),
99        }))
100    }
101
102    fn new_generic(retprobe: bool, func: impl AsRef<[u8]>, offset: u64) -> io::Result<Self> {
103        let func = CString::new(func.as_ref())
104            .expect("kprobe function target contained an internal nul byte");
105        Self::for_function(retprobe, func, offset)
106    }
107
108    /// Create a kprobe on the given function at `offset`.
109    ///
110    /// # Errors
111    /// This will attempt to read the kprobe PMU type from
112    /// `/sys/bus/event_source`. It will return an error if the kprobe PMU is
113    /// not available or the filesystem exposed by the kernel there is otherwise
114    /// unparseable.
115    ///
116    /// # Panics
117    /// Panics if `func` contains a nul byte other than at the very end.
118    pub fn probe(func: impl AsRef<[u8]>, offset: u64) -> io::Result<Self> {
119        Self::new_generic(false, func, offset)
120    }
121
122    /// Create a kretprobe on the given function at `offset`.
123    ///
124    /// # Errors
125    /// This will attempt to read the kprobe PMU type from
126    /// `/sys/bus/event_source`. It will return an error if the kprobe PMU is
127    /// not available or the filesystem exposed by the kernel there is otherwise
128    /// unparseable.
129    ///
130    /// # Panics
131    /// Panics if `func` contains a nul byte other than at the very end.
132    pub fn retprobe(func: impl AsRef<[u8]>, offset: u64) -> io::Result<Self> {
133        Self::new_generic(true, func, offset)
134    }
135}
136
137impl Event for KProbe {
138    fn update_attrs(self, attr: &mut perf_event_attr) {
139        self.0.update_attrs(attr);
140    }
141
142    fn update_attrs_with_data(self, attr: &mut perf_event_attr) -> Option<Arc<dyn EventData>> {
143        self.0.update_attrs_with_data(attr)
144    }
145}
146
147impl fmt::Debug for KProbe {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        let mut dbg = f.debug_struct("KProbe");
150        dbg.field("type", &self.0.ty);
151        dbg.field("retprobe", &self.0.retprobe);
152
153        match &self.0.target {
154            ProbeTarget::Addr(addr) => dbg.field("addr", addr),
155            ProbeTarget::Func { name, offset } => dbg.field("func", name).field("offset", offset),
156        };
157
158        dbg.finish()
159    }
160}
161
162/// User-space probe event.
163///
164/// Uprobes allow you to dynamically insert tracepoints within user-space
165/// processes. This allows you to gather metrics on how many times a function
166/// is called (e.g. malloc) or attach eBPF programs to run when the tracepoint
167/// is triggered.
168///
169/// There are two types of kprobes:
170/// - [`uprobe`](UProbe::probe)s trigger when the relevant function is called
171///   (or, potentially, executed at an offset within that function).
172/// - [`uretprobe`](UProbe::retprobe)s trigger just before the relevant function
173///   returns.
174///
175/// To create a uprobe you will need to provide both a path to a binary and
176/// the offset within that binary at which you want to insert the probe.
177/// Discovering the offset that corresponds to a given function is up to you.
178#[derive(Clone)]
179pub struct UProbe(Probe);
180
181impl UProbe {
182    /// Create a new uprobe from a path string and offset.
183    ///
184    /// # Errors
185    /// This will attempt to read the kprobe PMU type from
186    /// `/sys/bus/event_source`. It will return an error if the uprobe PMU is
187    /// not available or the filesystem exposed by the kernel there is otherwise
188    /// unparseable.
189    pub fn new(retprobe: bool, path: CString, offset: u64) -> io::Result<Self> {
190        Ok(Self(Probe {
191            ty: UPROBE_TYPE.get()?,
192            retprobe,
193            target: ProbeTarget::Func { name: path, offset },
194        }))
195    }
196
197    fn new_generic(retprobe: bool, path: impl AsRef<Path>, offset: u64) -> io::Result<Self> {
198        let path = CString::new(path.as_ref().as_os_str().as_bytes())
199            .expect("uprobe path contained an internal nul byte");
200        Self::new(retprobe, path, offset)
201    }
202
203    /// Create a new uprobe from a path and an offset within that file.
204    ///
205    /// # Errors
206    /// This will attempt to read the kprobe PMU type from
207    /// `/sys/bus/event_source`. It will return an error if the uprobe PMU is
208    /// not available or the filesystem exposed by the kernel there is otherwise
209    /// unparseable.
210    pub fn probe(path: impl AsRef<Path>, offset: u64) -> io::Result<Self> {
211        Self::new_generic(false, path, offset)
212    }
213
214    /// Create a new uretprobe from a path and an offset within that file.
215    ///
216    /// # Errors
217    /// This will attempt to read the kprobe PMU type from
218    /// `/sys/bus/event_source`. It will return an error if the uprobe PMU is
219    /// not available or the filesystem exposed by the kernel there is otherwise
220    /// unparseable.
221    pub fn retprobe(path: impl AsRef<Path>, offset: u64) -> io::Result<Self> {
222        Self::new_generic(true, path, offset)
223    }
224}
225
226impl fmt::Debug for UProbe {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        let mut dbg = f.debug_struct("UProbe");
229        dbg.field("type", &self.0.ty);
230        dbg.field("retprobe", &self.0.retprobe);
231
232        match &self.0.target {
233            ProbeTarget::Addr(addr) => dbg.field("addr", addr),
234            ProbeTarget::Func { name, offset } => dbg.field("path", name).field("offset", offset),
235        };
236
237        dbg.finish()
238    }
239}