1use aya::maps::MapData;
2use aya_obj::maps::bpf_map_def;
3use aya_obj::{
4 generated::bpf_map_type::BPF_MAP_TYPE_HASH, maps::LegacyMap, EbpfSectionKind, Map as ObjMap,
5};
6use libc as c;
7use std::io;
8use std::os::fd::{AsFd, AsRawFd};
9use std::path::{Path, PathBuf};
10use tracing::{info, warn};
11
12pub fn proc_offsets_pin_path() -> PathBuf {
15 let pid = std::process::id();
16 PathBuf::from(format!("/sys/fs/bpf/ghostscope/{pid}/proc_module_offsets"))
17}
18
19pub fn proc_offsets_pin_dir() -> PathBuf {
21 proc_offsets_pin_path()
22 .parent()
23 .map(|p| p.to_path_buf())
24 .unwrap_or_else(|| PathBuf::from("/sys/fs/bpf/ghostscope"))
25}
26
27pub const PROC_OFFSETS_MAP_NAME: &str = "proc_module_offsets";
29pub const ALLOWED_PIDS_MAP_NAME: &str = "allowed_pids";
30
31#[repr(C)]
33#[derive(Debug, Clone, Copy)]
34pub struct ProcModuleKey {
35 pub pid: u32,
36 pub pad: u32,
37 pub cookie_lo: u32,
38 pub cookie_hi: u32,
39}
40
41#[repr(C)]
43#[derive(Debug, Clone, Copy)]
44pub struct ProcModuleOffsetsValue {
45 pub text: u64,
46 pub rodata: u64,
47 pub data: u64,
48 pub bss: u64,
49}
50
51unsafe impl aya::Pod for ProcModuleKey {}
52unsafe impl aya::Pod for ProcModuleOffsetsValue {}
53
54impl ProcModuleOffsetsValue {
55 pub fn new(text: u64, rodata: u64, data: u64, bss: u64) -> Self {
56 Self {
57 text,
58 rodata,
59 data,
60 bss,
61 }
62 }
63}
64
65fn ensure_pin_dir(path: &Path) -> std::io::Result<()> {
66 if let Some(dir) = path.parent() {
67 std::fs::create_dir_all(dir)
68 } else {
69 Ok(())
70 }
71}
72
73pub fn ensure_pinned_proc_offsets_exists(max_entries: u32) -> anyhow::Result<()> {
76 let pin_path = proc_offsets_pin_path();
77 ensure_pin_dir(&pin_path)?;
79
80 if pin_path.exists() {
82 if MapData::from_pin(&pin_path).is_ok() {
83 info!(
84 "Reusing existing pinned map at {} (no recreate)",
85 pin_path.display()
86 );
87 return Ok(());
88 } else {
89 let _ = std::fs::remove_file(&pin_path);
91 }
92 }
93
94 let obj_map = ObjMap::Legacy(LegacyMap {
96 section_index: 0,
97 section_kind: EbpfSectionKind::Maps,
98 symbol_index: None,
99 def: bpf_map_def {
100 map_type: BPF_MAP_TYPE_HASH as u32,
101 key_size: 16, value_size: 32, max_entries,
104 map_flags: 0,
105 id: 0,
106 pinning: aya_obj::maps::PinningType::None,
107 },
108 data: Vec::new(),
109 });
110
111 let map = MapData::create(obj_map, PROC_OFFSETS_MAP_NAME, None)?;
113 info!(
114 "Created {} map with capacity {} entries",
115 PROC_OFFSETS_MAP_NAME, max_entries
116 );
117
118 match map.pin(&pin_path) {
120 Ok(()) => {
121 info!("Pinned {} at {}", PROC_OFFSETS_MAP_NAME, pin_path.display());
122 Ok(())
123 }
124 Err(e) => {
125 match MapData::from_pin(&pin_path) {
127 Ok(_) => {
128 info!(
129 "Pin path {} already exists; reusing existing map ({}).",
130 pin_path.display(),
131 e
132 );
133 Ok(())
134 }
135 Err(_) => {
136 let _ = std::fs::remove_file(&pin_path);
138 Err(anyhow::anyhow!(
139 "Failed to pin {} at {}: {}",
140 PROC_OFFSETS_MAP_NAME,
141 pin_path.display(),
142 e
143 ))
144 }
145 }
146 }
147 }
148}
149
150#[repr(C)]
152struct BpfMapUpdateAttr {
153 map_fd: u32,
154 _pad: u32, key: u64,
156 value: u64,
157 flags: u64,
158}
159
160const BPF_MAP_UPDATE_ELEM: c::c_long = 2; const BPF_MAP_DELETE_ELEM: c::c_long = 1; const BPF_MAP_GET_NEXT_KEY: c::c_long = 4; fn bpf_map_update_elem(
165 fd: i32,
166 key: *const c::c_void,
167 value: *const c::c_void,
168 flags: u64,
169) -> io::Result<()> {
170 let attr = BpfMapUpdateAttr {
171 map_fd: fd as u32,
172 _pad: 0,
173 key: key as usize as u64,
174 value: value as usize as u64,
175 flags,
176 };
177 let ret = unsafe {
178 c::syscall(
179 c::SYS_bpf,
180 BPF_MAP_UPDATE_ELEM,
181 &attr,
182 std::mem::size_of::<BpfMapUpdateAttr>(),
183 )
184 };
185 if ret < 0 {
186 Err(io::Error::last_os_error())
187 } else {
188 Ok(())
189 }
190}
191
192#[repr(C)]
193struct BpfMapKeyAttr {
194 map_fd: u32,
195 _pad: u32, key: u64,
197 next_key: u64,
198}
199
200fn bpf_map_get_next_key(
201 fd: i32,
202 key: *const c::c_void,
203 next_key: *mut c::c_void,
204) -> io::Result<()> {
205 let attr = BpfMapKeyAttr {
206 map_fd: fd as u32,
207 _pad: 0,
208 key: key as usize as u64,
209 next_key: next_key as usize as u64,
210 };
211 let ret = unsafe {
212 c::syscall(
213 c::SYS_bpf,
214 BPF_MAP_GET_NEXT_KEY,
215 &attr,
216 std::mem::size_of::<BpfMapKeyAttr>(),
217 )
218 };
219 if ret < 0 {
220 Err(io::Error::last_os_error())
221 } else {
222 Ok(())
223 }
224}
225
226pub fn allowed_pids_pin_path() -> PathBuf {
228 let pid = std::process::id();
229 PathBuf::from(format!("/sys/fs/bpf/ghostscope/{pid}/allowed_pids"))
230}
231
232pub fn ensure_pinned_allowed_pids_exists(max_entries: u32) -> anyhow::Result<()> {
234 let pin_path = allowed_pids_pin_path();
235 ensure_pin_dir(&pin_path)?;
236
237 if pin_path.exists() {
238 if MapData::from_pin(&pin_path).is_ok() {
239 info!("Reusing existing pinned map at {}", pin_path.display());
240 return Ok(());
241 } else {
242 let _ = std::fs::remove_file(&pin_path);
243 }
244 }
245
246 let obj_map = ObjMap::Legacy(LegacyMap {
247 section_index: 0,
248 section_kind: EbpfSectionKind::Maps,
249 symbol_index: None,
250 def: bpf_map_def {
251 map_type: BPF_MAP_TYPE_HASH as u32,
252 key_size: 4,
253 value_size: 1,
254 max_entries,
255 map_flags: 0,
256 id: 0,
257 pinning: aya_obj::maps::PinningType::None,
258 },
259 data: Vec::new(),
260 });
261
262 let map = MapData::create(obj_map, ALLOWED_PIDS_MAP_NAME, None)?;
263 info!(
264 "Created {} map with capacity {} entries",
265 ALLOWED_PIDS_MAP_NAME, max_entries
266 );
267
268 match map.pin(&pin_path) {
269 Ok(()) => {
270 info!("Pinned {} at {}", ALLOWED_PIDS_MAP_NAME, pin_path.display());
271 Ok(())
272 }
273 Err(e) => match MapData::from_pin(&pin_path) {
274 Ok(_) => {
275 info!(
276 "Pin path {} already exists; reusing existing map ({}).",
277 pin_path.display(),
278 e
279 );
280 Ok(())
281 }
282 Err(_) => {
283 let _ = std::fs::remove_file(&pin_path);
284 Err(anyhow::anyhow!(
285 "Failed to pin {} at {}: {}",
286 ALLOWED_PIDS_MAP_NAME,
287 pin_path.display(),
288 e
289 ))
290 }
291 },
292 }
293}
294
295pub fn insert_allowed_pid(pid: u32) -> anyhow::Result<()> {
297 let map_data = MapData::from_pin(allowed_pids_pin_path())?;
298 let fd = map_data.fd().as_fd().as_raw_fd();
299 let key = pid;
300 let val: u8 = 1;
301 bpf_map_update_elem(
302 fd,
303 &key as *const _ as *const _,
304 &val as *const _ as *const _,
305 0,
306 )
307 .map_err(|e| anyhow::anyhow!("allowed_pids update failed for {}: {}", pid, e))
308}
309
310pub fn remove_allowed_pid(pid: u32) -> anyhow::Result<()> {
312 let map_data = MapData::from_pin(allowed_pids_pin_path())?;
313 let fd = map_data.fd().as_fd().as_raw_fd();
314 bpf_map_delete_elem(fd, &pid as *const _ as *const _)
315 .map_err(|e| anyhow::anyhow!("allowed_pids delete failed for {}: {}", pid, e))
316}
317
318fn bpf_map_delete_elem(fd: i32, key: *const c::c_void) -> io::Result<()> {
319 let attr = BpfMapUpdateAttr {
320 map_fd: fd as u32,
321 _pad: 0,
322 key: key as usize as u64,
323 value: 0,
324 flags: 0,
325 };
326 let ret = unsafe {
327 c::syscall(
328 c::SYS_bpf,
329 BPF_MAP_DELETE_ELEM,
330 &attr,
331 std::mem::size_of::<BpfMapUpdateAttr>(),
332 )
333 };
334 if ret < 0 {
335 Err(io::Error::last_os_error())
336 } else {
337 Ok(())
338 }
339}
340
341pub fn purge_offsets_for_pid(pid: u32) -> anyhow::Result<usize> {
343 let map_data = MapData::from_pin(proc_offsets_pin_path())?;
344 let fd = map_data.fd().as_fd().as_raw_fd();
345 let mut deleted = 0usize;
346
347 let mut prev: Option<ProcModuleKey> = None;
349 loop {
350 let mut next: ProcModuleKey = ProcModuleKey {
351 pid: 0,
352 pad: 0,
353 cookie_lo: 0,
354 cookie_hi: 0,
355 };
356 let key_ptr = prev
357 .as_ref()
358 .map(|k| k as *const _ as *const c::c_void)
359 .unwrap_or(std::ptr::null());
360 let res = bpf_map_get_next_key(fd, key_ptr, &mut next as *mut _ as *mut _);
361 match res {
362 Ok(()) => {
363 if next.pid == pid {
365 let _ = bpf_map_delete_elem(fd, &next as *const _ as *const _);
367 deleted += 1;
368 continue;
370 } else {
371 prev = Some(next);
372 }
373 }
374 Err(e) => {
375 if e.raw_os_error() == Some(libc::ENOENT) {
377 break;
378 } else {
379 return Err(anyhow::anyhow!("bpf_map_get_next_key failed: {}", e));
380 }
381 }
382 }
383 }
384 Ok(deleted)
385}
386
387pub fn insert_offsets_for_pid(
389 pid: u32,
390 items: &[(u64, ProcModuleOffsetsValue)],
391) -> anyhow::Result<usize> {
392 let map_data = MapData::from_pin(proc_offsets_pin_path())?;
393 let fd = map_data.fd().as_fd().as_raw_fd();
394 let mut inserted = 0usize;
395 for (cookie, off) in items {
396 let key = ProcModuleKey {
397 pid,
398 pad: 0,
399 cookie_lo: (*cookie & 0xffff_ffff) as u32,
400 cookie_hi: (*cookie >> 32) as u32,
401 };
402 match bpf_map_update_elem(
403 fd,
404 &key as *const _ as *const _,
405 off as *const _ as *const _,
406 0,
407 ) {
408 Ok(()) => {
409 tracing::debug!(
410 "proc_module_offsets insert ok: pid={} cookie=0x{:08x}{:08x} text=0x{:x} rodata=0x{:x} data=0x{:x} bss=0x{:x}",
411 pid, key.cookie_hi, key.cookie_lo, off.text, off.rodata, off.data, off.bss
412 );
413 inserted += 1
414 }
415 Err(e) => warn!(
416 "bpf_map_update_elem failed for pid={} cookie=0x{:08x}{:08x}: {}",
417 pid, key.cookie_hi, key.cookie_lo, e
418 ),
419 }
420 }
421 Ok(inserted)
422}
423
424pub fn cleanup_pinned_proc_offsets() -> anyhow::Result<()> {
427 let path = proc_offsets_pin_path();
428 if path.exists() {
429 let _ = std::fs::remove_file(&path);
430 }
431 if let Some(dir) = path.parent() {
432 if let Ok(mut rd) = std::fs::read_dir(dir) {
433 if rd.next().is_none() {
434 let _ = std::fs::remove_dir(dir);
435 }
436 }
437 }
438 Ok(())
439}
440