1use memf_core::object_reader::ObjectReader;
8use memf_format::PhysicalMemoryProvider;
9
10use crate::Result;
11
12#[derive(Debug, Clone, serde::Serialize)]
14pub struct PerfEventInfo {
15 pub pid: u32,
17 pub comm: String,
19 pub event_type: u32,
21 pub event_type_name: String,
23 pub config: u64,
25 pub sample_period: u64,
27 pub is_suspicious: bool,
29}
30
31pub fn perf_type_name(t: u32) -> &'static str {
33 match t {
34 0 => "HARDWARE",
35 1 => "SOFTWARE",
36 2 => "TRACEPOINT",
37 3 => "HW_CACHE",
38 4 => "RAW",
39 5 => "BREAKPOINT",
40 _ => "UNKNOWN",
41 }
42}
43
44pub use crate::heuristics::classify_perf_event;
51
52pub fn walk_perf_events<P: PhysicalMemoryProvider>(
57 reader: &ObjectReader<P>,
58) -> Result<Vec<PerfEventInfo>> {
59 let init_task_addr = match reader.symbols().symbol_address("init_task") {
61 Some(addr) => addr,
62 None => return Ok(Vec::new()),
63 };
64
65 let tasks_offset = match reader.symbols().field_offset("task_struct", "tasks") {
67 Some(off) => off,
68 None => return Ok(Vec::new()),
69 };
70
71 let ctxp_offset = match reader
73 .symbols()
74 .field_offset("task_struct", "perf_event_ctxp")
75 {
76 Some(off) => off,
77 None => return Ok(Vec::new()),
78 };
79
80 let mut results = Vec::new();
81
82 let mut task_addrs: Vec<u64> = Vec::new();
84 {
85 let first_next: u64 = match reader.read_field(init_task_addr, "task_struct", "tasks") {
86 Ok(v) => v,
87 Err(_) => return Ok(Vec::new()),
88 };
89 let mut cursor = first_next;
90 let mut guard = 0usize;
91 loop {
92 if cursor == 0 || guard > 65536 {
93 break;
94 }
95 let task_addr = cursor.saturating_sub(tasks_offset);
96 if task_addr == init_task_addr {
97 break;
98 }
99 task_addrs.push(task_addr);
100 cursor = match reader.read_field(cursor, "list_head", "next") {
101 Ok(v) => v,
102 Err(_) => break,
103 };
104 guard += 1;
105 }
106 }
107
108 let all_tasks = std::iter::once(init_task_addr).chain(task_addrs);
110
111 for task_addr in all_tasks {
112 let pid: u32 = reader
113 .read_field::<u32>(task_addr, "task_struct", "pid")
114 .unwrap_or(0);
115 let comm_bytes: [u8; 16] = reader
116 .read_field(task_addr, "task_struct", "comm")
117 .unwrap_or([0u8; 16]);
118 let comm = std::str::from_utf8(&comm_bytes)
119 .unwrap_or("")
120 .trim_end_matches('\0')
121 .to_string();
122
123 let ctx_ptr_addr = task_addr + ctxp_offset;
125 let ctx_ptr: u64 = match reader.read_bytes(ctx_ptr_addr, 8) {
126 Ok(bytes) => u64::from_le_bytes(bytes.try_into().unwrap_or([0u8; 8])),
127 Err(_) => continue,
128 };
129 if ctx_ptr == 0 {
130 continue;
131 }
132
133 for group_field in &["pinned_groups", "flexible_groups"] {
135 let head_addr = match reader
136 .symbols()
137 .field_offset("perf_event_context", group_field)
138 {
139 Some(off) => ctx_ptr + off,
140 None => continue,
141 };
142
143 let first_event_list: u64 = match reader.read_bytes(head_addr, 8) {
144 Ok(b) => u64::from_le_bytes(b.try_into().unwrap_or([0u8; 8])),
145 Err(_) => continue,
146 };
147
148 let event_group_node_offset =
149 match reader.symbols().field_offset("perf_event", "group_entry") {
150 Some(off) => off,
151 None => continue,
152 };
153
154 let mut cursor = first_event_list;
155 let mut guard = 0usize;
156 loop {
157 if cursor == 0 || cursor == head_addr || guard > 4096 {
158 break;
159 }
160 let event_addr = cursor.saturating_sub(event_group_node_offset);
161
162 let attr_offset: u64 = reader
164 .symbols()
165 .field_offset("perf_event", "attr")
166 .map_or(0x20, |o| o);
167
168 let event_type: u32 = if let Ok(b) = reader.read_bytes(event_addr + attr_offset, 4)
169 {
170 u32::from_le_bytes(b.try_into().unwrap_or([0u8; 4]))
171 } else {
172 cursor = match reader.read_bytes(cursor, 8) {
173 Ok(b) => u64::from_le_bytes(b.try_into().unwrap_or([0u8; 8])),
174 Err(_) => break,
175 };
176 guard += 1;
177 continue;
178 };
179
180 let config: u64 = reader
181 .read_bytes(event_addr + attr_offset + 8, 8)
182 .ok()
183 .and_then(|b| b.try_into().ok())
184 .map_or(0, u64::from_le_bytes);
185
186 let sample_period: u64 = reader
187 .read_bytes(event_addr + attr_offset + 16, 8)
188 .ok()
189 .and_then(|b| b.try_into().ok())
190 .map_or(0, u64::from_le_bytes);
191
192 let is_suspicious = classify_perf_event(event_type, config);
193 results.push(PerfEventInfo {
194 pid,
195 comm: comm.clone(),
196 event_type,
197 event_type_name: perf_type_name(event_type).to_string(),
198 config,
199 sample_period,
200 is_suspicious,
201 });
202
203 cursor = match reader.read_bytes(cursor, 8) {
204 Ok(b) => u64::from_le_bytes(b.try_into().unwrap_or([0u8; 8])),
205 Err(_) => break,
206 };
207 guard += 1;
208 }
209 }
210 }
211
212 Ok(results)
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use memf_core::object_reader::ObjectReader;
219 use memf_core::test_builders::{PageTableBuilder, SyntheticPhysMem};
220 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
221 use memf_symbols::isf::IsfResolver;
222 use memf_symbols::test_builders::IsfBuilder;
223
224 fn make_no_symbol_reader() -> ObjectReader<SyntheticPhysMem> {
225 let isf = IsfBuilder::new().build_json();
226 let resolver = IsfResolver::from_value(&isf).unwrap();
227 let (cr3, mem) = PageTableBuilder::new().build();
228 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
229 ObjectReader::new(vas, Box::new(resolver))
230 }
231
232 #[test]
233 fn perf_type_name_hardware() {
234 assert_eq!(perf_type_name(0), "HARDWARE");
235 }
236
237 #[test]
238 fn perf_type_name_unknown() {
239 assert_eq!(perf_type_name(99), "UNKNOWN");
240 }
241
242 #[test]
243 fn classify_ll_cache_event_suspicious() {
244 assert!(
246 classify_perf_event(3, 2),
247 "LL cache event must be suspicious"
248 );
249 }
250
251 #[test]
252 fn classify_l1d_cache_event_suspicious() {
253 assert!(
255 classify_perf_event(3, 0),
256 "L1D cache event must be suspicious"
257 );
258 }
259
260 #[test]
261 fn classify_software_event_not_suspicious() {
262 assert!(
263 !classify_perf_event(1, 0),
264 "SOFTWARE event must not be suspicious"
265 );
266 }
267
268 #[test]
269 fn classify_raw_pmu_event_suspicious() {
270 assert!(
271 classify_perf_event(4, 0xDEAD),
272 "RAW PMU event must be suspicious"
273 );
274 }
275
276 #[test]
277 fn classify_hardware_event_not_suspicious() {
278 assert!(
279 !classify_perf_event(0, 1),
280 "plain HARDWARE event must not be suspicious"
281 );
282 }
283
284 #[test]
285 fn walk_perf_events_no_symbol_returns_empty() {
286 let reader = make_no_symbol_reader();
287 let result = walk_perf_events(&reader).unwrap();
288 assert!(
289 result.is_empty(),
290 "no init_task symbol → empty vec expected"
291 );
292 }
293
294 #[test]
297 fn perf_type_name_software() {
298 assert_eq!(perf_type_name(1), "SOFTWARE");
299 }
300
301 #[test]
302 fn perf_type_name_tracepoint() {
303 assert_eq!(perf_type_name(2), "TRACEPOINT");
304 }
305
306 #[test]
307 fn perf_type_name_hw_cache() {
308 assert_eq!(perf_type_name(3), "HW_CACHE");
309 }
310
311 #[test]
312 fn perf_type_name_raw() {
313 assert_eq!(perf_type_name(4), "RAW");
314 }
315
316 #[test]
317 fn perf_type_name_breakpoint() {
318 assert_eq!(perf_type_name(5), "BREAKPOINT");
319 }
320
321 #[test]
324 fn classify_hw_cache_config_byte_1_suspicious() {
325 assert!(
327 classify_perf_event(3, 1),
328 "HW_CACHE with config=1 must be suspicious"
329 );
330 }
331
332 #[test]
333 fn classify_hw_cache_config_byte_3_not_suspicious() {
334 assert!(
336 !classify_perf_event(3, 3),
337 "HW_CACHE with config byte = 3 must not be suspicious"
338 );
339 }
340
341 #[test]
342 fn classify_hw_cache_config_high_byte_not_suspicious() {
343 assert!(
346 !classify_perf_event(3, 0xFF03),
347 "HW_CACHE with low byte > 2 must not be suspicious"
348 );
349 }
350
351 #[test]
352 fn classify_tracepoint_not_suspicious() {
353 assert!(
354 !classify_perf_event(2, 0),
355 "TRACEPOINT event must not be suspicious"
356 );
357 }
358
359 #[test]
360 fn classify_breakpoint_not_suspicious() {
361 assert!(
362 !classify_perf_event(5, 0),
363 "BREAKPOINT event must not be suspicious"
364 );
365 }
366
367 #[test]
368 fn classify_unknown_type_not_suspicious() {
369 assert!(
370 !classify_perf_event(99, 0),
371 "unknown event type must not be suspicious"
372 );
373 }
374
375 #[test]
378 fn walk_perf_events_missing_tasks_offset_returns_empty() {
379 let isf = IsfBuilder::new()
380 .add_symbol("init_task", 0xFFFF_8888_0000_0000)
381 .build_json();
382 let resolver = IsfResolver::from_value(&isf).unwrap();
383 let (cr3, mem) = PageTableBuilder::new().build();
384 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
385 let reader = ObjectReader::new(vas, Box::new(resolver));
386
387 let result = walk_perf_events(&reader).unwrap();
388 assert!(
389 result.is_empty(),
390 "missing task_struct.tasks offset → empty vec expected"
391 );
392 }
393
394 #[test]
397 fn walk_perf_events_missing_ctxp_offset_returns_empty() {
398 let isf = IsfBuilder::new()
399 .add_symbol("init_task", 0xFFFF_8888_0000_0000)
400 .add_struct("task_struct", 512)
401 .add_field("task_struct", "tasks", 8, "pointer")
402 .build_json();
403 let resolver = IsfResolver::from_value(&isf).unwrap();
404 let (cr3, mem) = PageTableBuilder::new().build();
405 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
406 let reader = ObjectReader::new(vas, Box::new(resolver));
407
408 let result = walk_perf_events(&reader).unwrap();
409 assert!(
410 result.is_empty(),
411 "missing perf_event_ctxp offset → empty vec expected"
412 );
413 }
414
415 #[test]
418 fn walk_perf_events_symbol_present_self_pointing_list_returns_empty() {
419 use memf_core::test_builders::{flags as ptf, SyntheticPhysMem};
420
421 let tasks_offset: u64 = 0x10;
425 let ctxp_offset: u64 = 0x20;
426
427 let sym_vaddr: u64 = 0xFFFF_8800_0020_0000;
428 let sym_paddr: u64 = 0x0040_0000; let isf = IsfBuilder::new()
431 .add_symbol("init_task", sym_vaddr)
432 .add_struct("task_struct", 0x400)
433 .add_field("task_struct", "tasks", tasks_offset, "pointer")
434 .add_field("task_struct", "perf_event_ctxp", ctxp_offset, "pointer")
435 .add_field("task_struct", "pid", 0x30, "unsigned int")
436 .add_field("task_struct", "comm", 0x38, "char")
437 .build_json();
438 let resolver = IsfResolver::from_value(&isf).unwrap();
439
440 let mut page = [0u8; 4096];
444 let self_ptr = sym_vaddr + tasks_offset;
445 page[tasks_offset as usize..tasks_offset as usize + 8]
446 .copy_from_slice(&self_ptr.to_le_bytes());
447 let (cr3, mem) = PageTableBuilder::new()
450 .map_4k(sym_vaddr, sym_paddr, ptf::WRITABLE)
451 .write_phys(sym_paddr, &page)
452 .build();
453
454 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
455 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
456
457 let result = walk_perf_events(&reader).unwrap();
458 assert!(
459 result.is_empty(),
460 "self-pointing tasks list with null ctx_ptr → no perf events"
461 );
462 }
463
464 #[test]
468 fn walk_perf_events_missing_group_field_offsets_returns_empty() {
469 use memf_core::test_builders::{flags as ptf, SyntheticPhysMem};
470
471 let task_vaddr: u64 = 0xFFFF_8800_0030_0000;
472 let ctx_vaddr: u64 = 0xFFFF_8800_0031_0000;
473
474 let task_paddr: u64 = 0x041_000;
475 let ctx_paddr: u64 = 0x042_000;
476
477 let tasks_offset: u64 = 0x10;
478 let ctxp_offset: u64 = 0x20;
479 let pid_offset: u64 = 0x30;
480 let comm_offset: u64 = 0x38;
481
482 let isf = IsfBuilder::new()
483 .add_symbol("init_task", task_vaddr)
484 .add_struct("task_struct", 0x400)
485 .add_field("task_struct", "tasks", tasks_offset, "pointer")
486 .add_field("task_struct", "perf_event_ctxp", ctxp_offset, "pointer")
487 .add_field("task_struct", "pid", pid_offset, "unsigned int")
488 .add_field("task_struct", "comm", comm_offset, "char")
489 .add_struct("perf_event_context", 0x200)
492 .build_json();
493 let resolver = IsfResolver::from_value(&isf).unwrap();
494
495 let mut task_page = [0u8; 4096];
497 let self_ptr = task_vaddr + tasks_offset;
498 task_page[tasks_offset as usize..tasks_offset as usize + 8]
499 .copy_from_slice(&self_ptr.to_le_bytes());
500 task_page[ctxp_offset as usize..ctxp_offset as usize + 8]
501 .copy_from_slice(&ctx_vaddr.to_le_bytes());
502 task_page[pid_offset as usize..pid_offset as usize + 4]
503 .copy_from_slice(&777u32.to_le_bytes());
504
505 let ctx_page = [0u8; 4096];
507
508 let (cr3, mem) = PageTableBuilder::new()
509 .map_4k(task_vaddr, task_paddr, ptf::WRITABLE)
510 .write_phys(task_paddr, &task_page)
511 .map_4k(ctx_vaddr, ctx_paddr, ptf::WRITABLE)
512 .write_phys(ctx_paddr, &ctx_page)
513 .build();
514
515 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
516 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
517
518 let result = walk_perf_events(&reader).unwrap();
519 assert!(
520 result.is_empty(),
521 "missing group field offsets → inner loop continues → no events"
522 );
523 }
524
525 #[test]
529 fn walk_perf_events_empty_group_list_returns_empty() {
530 use memf_core::test_builders::{flags as ptf, SyntheticPhysMem};
531
532 let task_vaddr: u64 = 0xFFFF_8800_0032_0000;
533 let ctx_vaddr: u64 = 0xFFFF_8800_0033_0000;
534
535 let task_paddr: u64 = 0x043_000;
536 let ctx_paddr: u64 = 0x044_000;
537
538 let tasks_offset: u64 = 0x10;
539 let ctxp_offset: u64 = 0x20;
540 let pid_offset: u64 = 0x30;
541 let comm_offset: u64 = 0x38;
542
543 let pinned_offset: u64 = 0x10;
545 let flexible_offset: u64 = 0x18;
546
547 let isf = IsfBuilder::new()
548 .add_symbol("init_task", task_vaddr)
549 .add_struct("task_struct", 0x400)
550 .add_field("task_struct", "tasks", tasks_offset, "pointer")
551 .add_field("task_struct", "perf_event_ctxp", ctxp_offset, "pointer")
552 .add_field("task_struct", "pid", pid_offset, "unsigned int")
553 .add_field("task_struct", "comm", comm_offset, "char")
554 .add_struct("perf_event_context", 0x200)
555 .add_field(
556 "perf_event_context",
557 "pinned_groups",
558 pinned_offset,
559 "list_head",
560 )
561 .add_field(
562 "perf_event_context",
563 "flexible_groups",
564 flexible_offset,
565 "list_head",
566 )
567 .add_struct("perf_event", 0x200)
568 .add_field("perf_event", "group_entry", 0u64, "list_head")
569 .build_json();
570 let resolver = IsfResolver::from_value(&isf).unwrap();
571
572 let mut task_page = [0u8; 4096];
573 let self_ptr = task_vaddr + tasks_offset;
574 task_page[tasks_offset as usize..tasks_offset as usize + 8]
575 .copy_from_slice(&self_ptr.to_le_bytes());
576 task_page[ctxp_offset as usize..ctxp_offset as usize + 8]
577 .copy_from_slice(&ctx_vaddr.to_le_bytes());
578 task_page[pid_offset as usize..pid_offset as usize + 4]
579 .copy_from_slice(&888u32.to_le_bytes());
580
581 let pinned_head = ctx_vaddr + pinned_offset;
583 let flexible_head = ctx_vaddr + flexible_offset;
584 let mut ctx_page = [0u8; 4096];
585 ctx_page[pinned_offset as usize..pinned_offset as usize + 8]
586 .copy_from_slice(&pinned_head.to_le_bytes());
587 ctx_page[flexible_offset as usize..flexible_offset as usize + 8]
588 .copy_from_slice(&flexible_head.to_le_bytes());
589
590 let (cr3, mem) = PageTableBuilder::new()
591 .map_4k(task_vaddr, task_paddr, ptf::WRITABLE)
592 .write_phys(task_paddr, &task_page)
593 .map_4k(ctx_vaddr, ctx_paddr, ptf::WRITABLE)
594 .write_phys(ctx_paddr, &ctx_page)
595 .build();
596
597 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
598 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
599
600 let result = walk_perf_events(&reader).unwrap();
601 assert!(
602 result.is_empty(),
603 "self-pointing group list (empty) → no perf events enumerated"
604 );
605 }
606
607 #[test]
610 fn walk_perf_events_tasks_read_fails_returns_empty() {
611 let isf = IsfBuilder::new()
614 .add_symbol("init_task", 0xFFFF_DEAD_CAFE_0000) .add_struct("task_struct", 0x400)
616 .add_field("task_struct", "tasks", 0x10, "pointer")
617 .add_field("task_struct", "perf_event_ctxp", 0x20, "pointer")
618 .add_field("task_struct", "pid", 0x30, "unsigned int")
619 .add_field("task_struct", "comm", 0x38, "char")
620 .build_json();
621 let resolver = IsfResolver::from_value(&isf).unwrap();
622 let (cr3, mem) = PageTableBuilder::new().build();
623 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
624 let reader = ObjectReader::new(vas, Box::new(resolver));
625
626 let result = walk_perf_events(&reader).unwrap();
627 assert!(
628 result.is_empty(),
629 "unreadable init_task → early return empty"
630 );
631 }
632
633 #[test]
638 fn walk_perf_events_one_task_with_one_event_in_pinned_groups() {
639 use memf_core::test_builders::{flags as ptf, SyntheticPhysMem};
640
641 let tasks_offset: u64 = 0x10;
663 let ctxp_offset: u64 = 0x20;
664 let pid_offset: u64 = 0x30;
665 let comm_offset: u64 = 0x38;
666
667 let pinned_offset: u64 = 0x00;
668 let flexible_offset: u64 = 0x08;
669
670 let group_entry_offset: u64 = 0x00;
671 let attr_offset: u64 = 0x20;
672
673 let init_vaddr: u64 = 0xFFFF_8800_0080_0000;
674 let init_paddr: u64 = 0x0080_0000;
675 let t2_vaddr: u64 = 0xFFFF_8800_0081_0000;
676 let t2_paddr: u64 = 0x0081_0000;
677 let ctx_vaddr: u64 = 0xFFFF_8800_0082_0000;
678 let ctx_paddr: u64 = 0x0082_0000;
679 let ev_vaddr: u64 = 0xFFFF_8800_0083_0000;
680 let ev_paddr: u64 = 0x0083_0000;
681
682 let mut init_page = [0u8; 4096];
687 let t2_list_node = t2_vaddr + tasks_offset;
688 init_page[tasks_offset as usize..tasks_offset as usize + 8]
689 .copy_from_slice(&t2_list_node.to_le_bytes());
690 let mut t2_page = [0u8; 4096];
698 let init_list_node = init_vaddr + tasks_offset;
699 t2_page[tasks_offset as usize..tasks_offset as usize + 8]
700 .copy_from_slice(&init_list_node.to_le_bytes());
701 t2_page[ctxp_offset as usize..ctxp_offset as usize + 8]
702 .copy_from_slice(&ctx_vaddr.to_le_bytes());
703 t2_page[pid_offset as usize..pid_offset as usize + 4].copy_from_slice(&42u32.to_le_bytes());
704 t2_page[comm_offset as usize..comm_offset as usize + 3].copy_from_slice(b"spy");
705
706 let mut ctx_page = [0u8; 4096];
712 let ev_list_node = ev_vaddr + group_entry_offset;
713 ctx_page[pinned_offset as usize..pinned_offset as usize + 8]
714 .copy_from_slice(&ev_list_node.to_le_bytes());
715 let pinned_head = ctx_vaddr + pinned_offset;
720 let flex_head = ctx_vaddr + flexible_offset;
721 ctx_page[flexible_offset as usize..flexible_offset as usize + 8]
722 .copy_from_slice(&flex_head.to_le_bytes());
723
724 let mut ev_page = [0u8; 4096];
732 ev_page[group_entry_offset as usize..group_entry_offset as usize + 8]
733 .copy_from_slice(&pinned_head.to_le_bytes());
734 ev_page[attr_offset as usize..attr_offset as usize + 4]
735 .copy_from_slice(&4u32.to_le_bytes()); ev_page[attr_offset as usize + 8..attr_offset as usize + 16]
737 .copy_from_slice(&0xDEADu64.to_le_bytes());
738 ev_page[attr_offset as usize + 16..attr_offset as usize + 24]
739 .copy_from_slice(&1000u64.to_le_bytes());
740
741 let isf = IsfBuilder::new()
742 .add_symbol("init_task", init_vaddr)
743 .add_struct("list_head", 0x10)
745 .add_field("list_head", "next", 0x00u64, "pointer")
746 .add_struct("task_struct", 0x400)
747 .add_field("task_struct", "tasks", tasks_offset, "pointer")
748 .add_field("task_struct", "perf_event_ctxp", ctxp_offset, "pointer")
749 .add_field("task_struct", "pid", pid_offset, "unsigned int")
750 .add_field("task_struct", "comm", comm_offset, "char")
751 .add_struct("perf_event_context", 0x200)
752 .add_field(
753 "perf_event_context",
754 "pinned_groups",
755 pinned_offset,
756 "list_head",
757 )
758 .add_field(
759 "perf_event_context",
760 "flexible_groups",
761 flexible_offset,
762 "list_head",
763 )
764 .add_struct("perf_event", 0x200)
765 .add_field("perf_event", "group_entry", group_entry_offset, "list_head")
766 .add_field("perf_event", "attr", attr_offset, "pointer")
767 .build_json();
768 let resolver = IsfResolver::from_value(&isf).unwrap();
769
770 let (cr3, mem) = PageTableBuilder::new()
771 .map_4k(init_vaddr, init_paddr, ptf::WRITABLE)
772 .write_phys(init_paddr, &init_page)
773 .map_4k(t2_vaddr, t2_paddr, ptf::WRITABLE)
774 .write_phys(t2_paddr, &t2_page)
775 .map_4k(ctx_vaddr, ctx_paddr, ptf::WRITABLE)
776 .write_phys(ctx_paddr, &ctx_page)
777 .map_4k(ev_vaddr, ev_paddr, ptf::WRITABLE)
778 .write_phys(ev_paddr, &ev_page)
779 .build();
780
781 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
782 let reader: ObjectReader<SyntheticPhysMem> = ObjectReader::new(vas, Box::new(resolver));
783
784 let result = walk_perf_events(&reader).unwrap();
785 assert_eq!(
787 result.len(),
788 1,
789 "expected one perf_event from task2's pinned_groups"
790 );
791 assert_eq!(result[0].pid, 42);
792 assert_eq!(result[0].comm, "spy");
793 assert_eq!(result[0].event_type, 4); assert_eq!(result[0].config, 0xDEAD);
795 assert_eq!(result[0].sample_period, 1000);
796 assert!(
797 result[0].is_suspicious,
798 "RAW PMU event must be flagged suspicious"
799 );
800 }
801
802 #[test]
804 fn perf_event_info_serializes() {
805 let info = PerfEventInfo {
806 pid: 12,
807 comm: "spy".to_string(),
808 event_type: 4,
809 event_type_name: "RAW".to_string(),
810 config: 0xDEAD,
811 sample_period: 1000,
812 is_suspicious: true,
813 };
814 let json = serde_json::to_string(&info).unwrap();
815 assert!(json.contains("\"pid\":12"));
816 assert!(json.contains("RAW"));
817 assert!(json.contains("\"is_suspicious\":true"));
818 }
819}