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}