1use serde::{Deserialize, Serialize};
37
38use super::btf_offsets::{TaskEnrichmentOffsets, pid_type};
39use super::guest::GuestKernel;
40use super::idr::translate_any_kva;
41
42const TASK_COMM_LEN: usize = 16;
48
49#[derive(Debug, Clone, Default)]
63pub struct SchedClassRegistry {
64 pub fair: Option<u64>,
65 pub rt: Option<u64>,
66 pub dl: Option<u64>,
67 pub idle: Option<u64>,
68 pub stop: Option<u64>,
69 pub ext: Option<u64>,
70}
71
72#[allow(dead_code)] impl SchedClassRegistry {
77 pub fn from_guest_kernel(kernel: &GuestKernel) -> Self {
81 Self {
82 fair: kernel.symbol_kva("fair_sched_class"),
83 rt: kernel.symbol_kva("rt_sched_class"),
84 dl: kernel.symbol_kva("dl_sched_class"),
85 idle: kernel.symbol_kva("idle_sched_class"),
86 stop: kernel.symbol_kva("stop_sched_class"),
87 ext: kernel.symbol_kva("ext_sched_class"),
88 }
89 }
90
91 pub fn decode(&self, sched_class_kva: u64) -> Option<&'static str> {
96 if sched_class_kva == 0 {
97 return None;
98 }
99 if Some(sched_class_kva) == self.fair {
100 return Some("fair");
101 }
102 if Some(sched_class_kva) == self.rt {
103 return Some("rt");
104 }
105 if Some(sched_class_kva) == self.dl {
106 return Some("dl");
107 }
108 if Some(sched_class_kva) == self.idle {
109 return Some("idle");
110 }
111 if Some(sched_class_kva) == self.stop {
112 return Some("stop");
113 }
114 if Some(sched_class_kva) == self.ext {
115 return Some("ext");
116 }
117 None
118 }
119}
120
121#[derive(Debug, Clone, Default)]
133pub struct LockSlowpathRegistry {
134 pub queued_spin_lock_slowpath: Option<u64>,
135 pub mutex_lock_slowpath: Option<u64>,
136 pub rwsem_down_read_slowpath: Option<u64>,
137 pub rwsem_down_write_slowpath: Option<u64>,
138}
139
140const LOCK_SLOWPATH_FN_MAX_SIZE: u64 = 4096;
143
144#[allow(dead_code)] impl LockSlowpathRegistry {
148 pub fn from_guest_kernel(kernel: &GuestKernel) -> Self {
153 Self {
154 queued_spin_lock_slowpath: kernel.symbol_kva("queued_spin_lock_slowpath"),
155 mutex_lock_slowpath: kernel
162 .symbol_kva("__mutex_lock_slowpath")
163 .or_else(|| kernel.symbol_kva("__mutex_lock")),
164 rwsem_down_read_slowpath: kernel.symbol_kva("rwsem_down_read_slowpath"),
165 rwsem_down_write_slowpath: kernel.symbol_kva("rwsem_down_write_slowpath"),
166 }
167 }
168
169 pub fn match_pc(&self, pc: u64) -> Option<&'static str> {
173 let probe = |start: Option<u64>, name: &'static str| -> Option<&'static str> {
174 let s = start?;
175 let end = s.checked_add(LOCK_SLOWPATH_FN_MAX_SIZE)?;
182 if pc >= s && pc < end {
183 Some(name)
184 } else {
185 None
186 }
187 };
188 probe(self.queued_spin_lock_slowpath, "queued_spin_lock_slowpath")
189 .or_else(|| probe(self.mutex_lock_slowpath, "mutex_lock_slowpath"))
190 .or_else(|| probe(self.rwsem_down_read_slowpath, "rwsem_down_read_slowpath"))
191 .or_else(|| probe(self.rwsem_down_write_slowpath, "rwsem_down_write_slowpath"))
192 }
193}
194
195#[derive(Debug, Clone, Default, Serialize, Deserialize)]
205#[non_exhaustive]
206pub struct TaskEnrichment {
207 pub pid: i32,
209 pub tgid: i32,
211 pub comm: String,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub group_leader_pid: Option<i32>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub real_parent_pid: Option<i32>,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub real_parent_comm: Option<String>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub pgid: Option<i32>,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
231 pub sid: Option<i32>,
232 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub nr_threads: Option<i32>,
236 pub weight: u32,
239 pub prio: i32,
242 pub static_prio: i32,
244 pub normal_prio: i32,
246 pub rt_priority: u32,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub sched_class: Option<String>,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub core_cookie: Option<u64>,
257 pub pi_boosted_out_of_scx: bool,
265 pub nvcsw: u64,
268 pub nivcsw: u64,
271 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub signal_nvcsw: Option<u64>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
277 pub signal_nivcsw: Option<u64>,
278 pub utime: u64,
285 pub stime: u64,
290 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub signal_utime: Option<u64>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub signal_stime: Option<u64>,
302 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub lock_slowpath_match: Option<String>,
317}
318
319#[allow(dead_code)]
331pub fn walk_task_enrichment(
332 kernel: &GuestKernel,
333 task_kva: u64,
334 offsets: &TaskEnrichmentOffsets,
335 classes: &SchedClassRegistry,
336 locks: &LockSlowpathRegistry,
337 is_runnable_in_scx: bool,
338 pc: Option<u64>,
339) -> Option<TaskEnrichment> {
340 let mem = kernel.mem();
341 let walk = kernel.walk_context();
342
343 let task_pa = translate_any_kva(
344 mem,
345 walk.cr3_pa,
346 walk.page_offset,
347 task_kva,
348 walk.l5,
349 walk.tcr_el1,
350 )?;
351
352 let pid = mem.read_u32(task_pa, offsets.task_struct_pid) as i32;
354 let tgid = mem.read_u32(task_pa, offsets.task_struct_tgid) as i32;
355 let comm = read_comm(mem, task_pa, offsets.task_struct_comm);
356
357 let prio = mem.read_u32(task_pa, offsets.task_struct_prio) as i32;
359 let static_prio = mem.read_u32(task_pa, offsets.task_struct_static_prio) as i32;
360 let normal_prio = mem.read_u32(task_pa, offsets.task_struct_normal_prio) as i32;
361 let rt_priority = mem.read_u32(task_pa, offsets.task_struct_rt_priority);
362 let sched_class_kva = mem.read_u64(task_pa, offsets.task_struct_sched_class);
363 let sched_class = classes.decode(sched_class_kva).map(str::to_string);
364 let weight = mem.read_u32(task_pa, offsets.task_struct_scx + offsets.see_weight);
365 let core_cookie = offsets
366 .task_struct_core_cookie
367 .map(|off| mem.read_u64(task_pa, off));
368
369 let pi_boosted_out_of_scx =
375 is_runnable_in_scx && classes.ext.is_some() && Some(sched_class_kva) != classes.ext;
376
377 let nvcsw = mem.read_u64(task_pa, offsets.task_struct_nvcsw);
379 let nivcsw = mem.read_u64(task_pa, offsets.task_struct_nivcsw);
380 let utime = mem.read_u64(task_pa, offsets.task_struct_utime);
385 let stime = mem.read_u64(task_pa, offsets.task_struct_stime);
386
387 let group_leader_kva = mem.read_u64(task_pa, offsets.task_struct_group_leader);
389 let group_leader_pid =
390 follow_task_for_pid(mem, walk, group_leader_kva, offsets.task_struct_pid);
391
392 let real_parent_kva = mem.read_u64(task_pa, offsets.task_struct_real_parent);
393 let (real_parent_pid, real_parent_comm) = follow_task_for_pid_and_comm(
394 mem,
395 walk,
396 real_parent_kva,
397 offsets.task_struct_pid,
398 offsets.task_struct_comm,
399 );
400
401 let signal_kva = mem.read_u64(task_pa, offsets.task_struct_signal);
402 let (nr_threads, signal_nvcsw, signal_nivcsw, signal_utime, signal_stime, pgid, sid) =
403 if signal_kva == 0 {
404 (None, None, None, None, None, None, None)
405 } else {
406 match translate_any_kva(
407 mem,
408 walk.cr3_pa,
409 walk.page_offset,
410 signal_kva,
411 walk.l5,
412 walk.tcr_el1,
413 ) {
414 None => (None, None, None, None, None, None, None),
415 Some(signal_pa) => {
416 let nr_threads_v =
417 mem.read_u32(signal_pa, offsets.signal_struct_nr_threads) as i32;
418 let signal_nvcsw_v = mem.read_u64(signal_pa, offsets.signal_struct_nvcsw);
419 let signal_nivcsw_v = mem.read_u64(signal_pa, offsets.signal_struct_nivcsw);
420 let signal_utime_v = mem.read_u64(signal_pa, offsets.signal_struct_utime);
424 let signal_stime_v = mem.read_u64(signal_pa, offsets.signal_struct_stime);
425 let pgid_v = read_pid_nr_at_index(
430 mem,
431 walk,
432 signal_pa,
433 offsets.signal_struct_pids,
434 pid_type::PGID,
435 offsets.pid_numbers,
436 offsets.upid_size,
437 offsets.upid_nr,
438 );
439 let sid_v = read_pid_nr_at_index(
440 mem,
441 walk,
442 signal_pa,
443 offsets.signal_struct_pids,
444 pid_type::SID,
445 offsets.pid_numbers,
446 offsets.upid_size,
447 offsets.upid_nr,
448 );
449 (
450 Some(nr_threads_v),
451 Some(signal_nvcsw_v),
452 Some(signal_nivcsw_v),
453 Some(signal_utime_v),
454 Some(signal_stime_v),
455 pgid_v,
456 sid_v,
457 )
458 }
459 }
460 };
461
462 let lock_slowpath_match = pc.and_then(|p| locks.match_pc(p)).map(str::to_string);
464
465 Some(TaskEnrichment {
466 pid,
467 tgid,
468 comm,
469 group_leader_pid,
470 real_parent_pid,
471 real_parent_comm,
472 pgid,
473 sid,
474 nr_threads,
475 weight,
476 prio,
477 static_prio,
478 normal_prio,
479 rt_priority,
480 sched_class,
481 core_cookie,
482 pi_boosted_out_of_scx,
483 nvcsw,
484 nivcsw,
485 signal_nvcsw,
486 signal_nivcsw,
487 utime,
488 stime,
489 signal_utime,
490 signal_stime,
491 lock_slowpath_match,
492 })
493}
494
495fn read_comm(mem: &super::reader::GuestMem, task_pa: u64, comm_off: usize) -> String {
497 let mut buf = [0u8; TASK_COMM_LEN];
498 mem.read_bytes(task_pa + comm_off as u64, &mut buf);
499 let n = buf.iter().position(|&b| b == 0).unwrap_or(TASK_COMM_LEN);
500 String::from_utf8_lossy(&buf[..n]).to_string()
501}
502
503fn follow_task_for_pid_and_comm(
506 mem: &super::reader::GuestMem,
507 walk: super::reader::WalkContext,
508 task_kva: u64,
509 pid_off: usize,
510 comm_off: usize,
511) -> (Option<i32>, Option<String>) {
512 if task_kva == 0 {
513 return (None, None);
514 }
515 let Some(task_pa) = translate_any_kva(
516 mem,
517 walk.cr3_pa,
518 walk.page_offset,
519 task_kva,
520 walk.l5,
521 walk.tcr_el1,
522 ) else {
523 return (None, None);
524 };
525 let pid = mem.read_u32(task_pa, pid_off) as i32;
526 let comm = read_comm(mem, task_pa, comm_off);
527 (Some(pid), Some(comm))
528}
529
530fn follow_task_for_pid(
532 mem: &super::reader::GuestMem,
533 walk: super::reader::WalkContext,
534 task_kva: u64,
535 pid_off: usize,
536) -> Option<i32> {
537 if task_kva == 0 {
538 return None;
539 }
540 let task_pa = translate_any_kva(
541 mem,
542 walk.cr3_pa,
543 walk.page_offset,
544 task_kva,
545 walk.l5,
546 walk.tcr_el1,
547 )?;
548 Some(mem.read_u32(task_pa, pid_off) as i32)
549}
550
551#[allow(clippy::too_many_arguments)]
563fn read_pid_nr_at_index(
564 mem: &super::reader::GuestMem,
565 walk: super::reader::WalkContext,
566 signal_pa: u64,
567 pids_off: usize,
568 idx: usize,
569 numbers_off: usize,
570 upid_size: usize,
571 nr_off: usize,
572) -> Option<i32> {
573 let pid_kva = mem.read_u64(signal_pa, pids_off + idx * 8);
574 if pid_kva == 0 {
575 return None;
576 }
577 let pid_pa = translate_any_kva(
578 mem,
579 walk.cr3_pa,
580 walk.page_offset,
581 pid_kva,
582 walk.l5,
583 walk.tcr_el1,
584 )?;
585 let _ = upid_size; Some(mem.read_u32(pid_pa, numbers_off + nr_off) as i32)
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595
596 #[test]
597 fn sched_class_registry_decode_known_class() {
598 let r = SchedClassRegistry {
599 fair: Some(0xffff_ffff_8000_1000),
600 rt: Some(0xffff_ffff_8000_1100),
601 dl: None,
602 idle: None,
603 stop: None,
604 ext: Some(0xffff_ffff_8000_1300),
605 };
606 assert_eq!(r.decode(0xffff_ffff_8000_1000), Some("fair"));
607 assert_eq!(r.decode(0xffff_ffff_8000_1100), Some("rt"));
608 assert_eq!(r.decode(0xffff_ffff_8000_1300), Some("ext"));
609 }
610
611 #[test]
612 fn sched_class_registry_decode_unknown_returns_none() {
613 let r = SchedClassRegistry {
614 fair: Some(0xffff_ffff_8000_1000),
615 rt: None,
616 dl: None,
617 idle: None,
618 stop: None,
619 ext: None,
620 };
621 assert_eq!(r.decode(0xffff_ffff_8000_2000), None);
622 assert_eq!(r.decode(0), None);
624 }
625
626 #[test]
627 fn lock_slowpath_match_within_window() {
628 let r = LockSlowpathRegistry {
629 queued_spin_lock_slowpath: Some(0xffff_ffff_8001_0000),
630 mutex_lock_slowpath: Some(0xffff_ffff_8002_0000),
631 rwsem_down_read_slowpath: None,
632 rwsem_down_write_slowpath: None,
633 };
634 assert_eq!(
636 r.match_pc(0xffff_ffff_8001_0010),
637 Some("queued_spin_lock_slowpath")
638 );
639 assert_eq!(
641 r.match_pc(0xffff_ffff_8002_0fff),
642 Some("mutex_lock_slowpath")
643 );
644 assert!(r.match_pc(0xffff_ffff_8001_2000).is_none());
646 assert!(r.match_pc(0xffff_ffff_8000_ffff).is_none());
648 }
649
650 #[test]
651 fn lock_slowpath_no_match_when_all_none() {
652 let r = LockSlowpathRegistry::default();
653 assert_eq!(r.match_pc(0xdeadbeef), None);
654 }
655
656 #[test]
660 fn task_enrichment_serde_skip_none_fields() {
661 let e = TaskEnrichment {
662 pid: 42,
663 tgid: 42,
664 comm: "ktstr_worker".to_string(),
665 group_leader_pid: None,
666 real_parent_pid: None,
667 real_parent_comm: None,
668 pgid: None,
669 sid: None,
670 nr_threads: None,
671 weight: 100,
672 prio: 120,
673 static_prio: 120,
674 normal_prio: 120,
675 rt_priority: 0,
676 sched_class: Some("fair".to_string()),
677 core_cookie: None,
678 pi_boosted_out_of_scx: false,
679 nvcsw: 0,
680 nivcsw: 0,
681 signal_nvcsw: None,
682 signal_nivcsw: None,
683 utime: 0,
684 stime: 0,
685 signal_utime: None,
686 signal_stime: None,
687 lock_slowpath_match: None,
688 };
689 let json = serde_json::to_string(&e).unwrap();
690 assert!(!json.contains("group_leader_pid"));
692 assert!(!json.contains("real_parent_pid"));
693 assert!(!json.contains("pgid"));
694 assert!(!json.contains("nr_threads"));
695 assert!(!json.contains("core_cookie"));
696 assert!(!json.contains("signal_nvcsw"));
697 assert!(!json.contains("signal_utime"));
700 assert!(!json.contains("signal_stime"));
701 assert!(!json.contains("lock_slowpath_match"));
702 assert!(json.contains("\"pid\":42"));
704 assert!(json.contains("\"comm\":\"ktstr_worker\""));
705 assert!(json.contains("\"weight\":100"));
706 assert!(json.contains("\"sched_class\":\"fair\""));
707 assert!(json.contains("\"utime\":0"));
709 assert!(json.contains("\"stime\":0"));
710 }
711
712 #[test]
713 fn task_enrichment_serde_roundtrip_populated() {
714 let e = TaskEnrichment {
715 pid: 1234,
716 tgid: 1230,
717 comm: "stress-ng".to_string(),
718 group_leader_pid: Some(1230),
719 real_parent_pid: Some(1),
720 real_parent_comm: Some("systemd".to_string()),
721 pgid: Some(1230),
722 sid: Some(1),
723 nr_threads: Some(8),
724 weight: 200,
725 prio: 100,
726 static_prio: 120,
727 normal_prio: 100,
728 rt_priority: 50,
729 sched_class: Some("rt".to_string()),
730 core_cookie: Some(0xc0c01e),
731 pi_boosted_out_of_scx: true,
732 nvcsw: 12345,
733 nivcsw: 678,
734 signal_nvcsw: Some(50_000),
735 signal_nivcsw: Some(1_234),
736 utime: 99_000,
737 stime: 88_000,
738 signal_utime: Some(7_000),
739 signal_stime: Some(6_000),
740 lock_slowpath_match: Some("queued_spin_lock_slowpath".to_string()),
741 };
742 let json = serde_json::to_string(&e).unwrap();
743 let parsed: TaskEnrichment = serde_json::from_str(&json).unwrap();
744 assert_eq!(parsed.pid, 1234);
745 assert_eq!(parsed.comm, "stress-ng");
746 assert_eq!(parsed.real_parent_comm.as_deref(), Some("systemd"));
747 assert_eq!(parsed.nr_threads, Some(8));
748 assert_eq!(parsed.core_cookie, Some(0xc0c01e));
749 assert!(parsed.pi_boosted_out_of_scx);
750 assert_eq!(parsed.utime, 99_000);
751 assert_eq!(parsed.stime, 88_000);
752 assert_eq!(parsed.signal_utime, Some(7_000));
753 assert_eq!(parsed.signal_stime, Some(6_000));
754 assert_eq!(
755 parsed.lock_slowpath_match.as_deref(),
756 Some("queued_spin_lock_slowpath"),
757 );
758 }
759
760 fn fixture_offsets() -> TaskEnrichmentOffsets {
776 TaskEnrichmentOffsets {
777 task_struct_pid: 0x00,
779 task_struct_tgid: 0x04,
780 task_struct_prio: 0x08,
781 task_struct_static_prio: 0x0c,
782 task_struct_normal_prio: 0x10,
783 task_struct_rt_priority: 0x14,
784 task_struct_comm: 0x18,
785 task_struct_sched_class: 0x28,
786 task_struct_scx: 0x30,
787 task_struct_core_cookie: Some(0x38),
788 task_struct_nvcsw: 0x40,
789 task_struct_nivcsw: 0x48,
790 task_struct_utime: 0x50,
791 task_struct_stime: 0x58,
792 task_struct_group_leader: 0x60,
793 task_struct_real_parent: 0x68,
794 task_struct_signal: 0x70,
795 task_struct_stack: 0x78,
796 see_weight: 0x04,
798 signal_struct_nr_threads: 0x00,
800 signal_struct_nvcsw: 0x08,
801 signal_struct_nivcsw: 0x10,
802 signal_struct_utime: 0x18,
803 signal_struct_stime: 0x20,
804 signal_struct_pids: 0x30,
805 pid_numbers: 0x00,
807 pid_size: 0x00,
808 upid_nr: 0x00,
809 upid_size: 16,
810 }
811 }
812
813 const TASK_ADDR: u64 = 0x100;
815 const SIGNAL_ADDR: u64 = 0x400;
816 const PGID_PID_ADDR: u64 = 0x600;
817 const SID_PID_ADDR: u64 = 0x680;
818 const PARENT_TASK_ADDR: u64 = 0x800;
819 const EXT_CLASS_KVA: u64 = 0x9000;
822 const FAIR_CLASS_KVA: u64 = 0x9100;
823
824 fn put_u32(buf: &mut [u8], at: u64, off: usize, val: u32) {
825 let a = at as usize + off;
826 buf[a..a + 4].copy_from_slice(&val.to_le_bytes());
827 }
828
829 fn put_u64(buf: &mut [u8], at: u64, off: usize, val: u64) {
830 let a = at as usize + off;
831 buf[a..a + 8].copy_from_slice(&val.to_le_bytes());
832 }
833
834 fn put_comm(buf: &mut [u8], at: u64, off: usize, bytes: &[u8]) {
835 let a = at as usize + off;
836 buf[a..a + bytes.len()].copy_from_slice(bytes);
837 }
838
839 fn plant_happy_task(buf: &mut [u8]) {
845 let o = fixture_offsets();
846 put_u32(buf, TASK_ADDR, o.task_struct_pid, 4321);
848 put_u32(buf, TASK_ADDR, o.task_struct_tgid, 4300);
849 put_comm(buf, TASK_ADDR, o.task_struct_comm, b"worker\0");
850 put_u32(buf, TASK_ADDR, o.task_struct_prio, 100);
852 put_u32(buf, TASK_ADDR, o.task_struct_static_prio, 120);
853 put_u32(buf, TASK_ADDR, o.task_struct_normal_prio, 100);
854 put_u32(buf, TASK_ADDR, o.task_struct_rt_priority, 50);
855 put_u64(buf, TASK_ADDR, o.task_struct_sched_class, EXT_CLASS_KVA);
856 put_u32(buf, TASK_ADDR, o.task_struct_scx + o.see_weight, 200);
858 put_u64(buf, TASK_ADDR, o.task_struct_core_cookie.unwrap(), 0xABCD);
860 put_u64(buf, TASK_ADDR, o.task_struct_nvcsw, 11);
862 put_u64(buf, TASK_ADDR, o.task_struct_nivcsw, 22);
863 put_u64(buf, TASK_ADDR, o.task_struct_utime, 999_000);
864 put_u64(buf, TASK_ADDR, o.task_struct_stime, 888_000);
865 put_u64(buf, TASK_ADDR, o.task_struct_group_leader, PARENT_TASK_ADDR);
868 put_u64(buf, TASK_ADDR, o.task_struct_real_parent, PARENT_TASK_ADDR);
869 put_u64(buf, TASK_ADDR, o.task_struct_signal, SIGNAL_ADDR);
870
871 put_u32(buf, PARENT_TASK_ADDR, o.task_struct_pid, 1);
876 put_comm(buf, PARENT_TASK_ADDR, o.task_struct_comm, b"init\0");
877
878 put_u32(buf, SIGNAL_ADDR, o.signal_struct_nr_threads, 8);
880 put_u64(buf, SIGNAL_ADDR, o.signal_struct_nvcsw, 70_000);
881 put_u64(buf, SIGNAL_ADDR, o.signal_struct_nivcsw, 3);
882 put_u64(buf, SIGNAL_ADDR, o.signal_struct_utime, 7_000);
883 put_u64(buf, SIGNAL_ADDR, o.signal_struct_stime, 6_000);
884 put_u64(
886 buf,
887 SIGNAL_ADDR,
888 o.signal_struct_pids + pid_type::PGID * 8,
889 PGID_PID_ADDR,
890 );
891 put_u64(
892 buf,
893 SIGNAL_ADDR,
894 o.signal_struct_pids + pid_type::SID * 8,
895 SID_PID_ADDR,
896 );
897 put_u32(buf, PGID_PID_ADDR, o.pid_numbers + o.upid_nr, 4300);
899 put_u32(buf, SID_PID_ADDR, o.pid_numbers + o.upid_nr, 1);
900 }
901
902 fn build_kernel(buf: &mut [u8]) -> GuestKernel {
906 let mem =
909 unsafe { crate::monitor::reader::GuestMem::new(buf.as_mut_ptr(), buf.len() as u64) };
910 GuestKernel::new_for_test(
911 std::sync::Arc::new(mem),
912 std::collections::HashMap::new(),
913 0,
914 0,
915 false,
916 )
917 }
918
919 fn ext_registry() -> SchedClassRegistry {
920 SchedClassRegistry {
921 fair: Some(FAIR_CLASS_KVA),
922 ext: Some(EXT_CLASS_KVA),
923 ..Default::default()
924 }
925 }
926
927 #[test]
928 fn walk_task_enrichment_root_translate_fail_returns_none() {
929 let mut buf = vec![0u8; 0x2000];
933 let kernel = build_kernel(&mut buf);
934 let offsets = fixture_offsets();
935 let oob_task_kva = kernel.mem().size(); let result = walk_task_enrichment(
937 &kernel,
938 oob_task_kva,
939 &offsets,
940 &ext_registry(),
941 &LockSlowpathRegistry::default(),
942 false,
943 None,
944 );
945 assert!(
946 result.is_none(),
947 "unreadable task_struct must abort the whole enrichment"
948 );
949 }
950
951 #[test]
952 fn walk_task_enrichment_full_happy_path_exact_fields() {
953 let mut buf = vec![0u8; 0x2000];
954 plant_happy_task(&mut buf);
955 let kernel = build_kernel(&mut buf);
956 let offsets = fixture_offsets();
957 let e = walk_task_enrichment(
958 &kernel,
959 TASK_ADDR,
960 &offsets,
961 &ext_registry(),
962 &LockSlowpathRegistry::default(),
963 false,
964 None,
965 )
966 .expect("task built");
967
968 assert_eq!(e.pid, 4321);
969 assert_eq!(e.tgid, 4300);
970 assert_eq!(e.comm, "worker");
971 assert_eq!(e.prio, 100);
972 assert_eq!(e.static_prio, 120);
973 assert_eq!(e.normal_prio, 100);
974 assert_eq!(e.rt_priority, 50);
975 assert_eq!(e.sched_class.as_deref(), Some("ext"));
976 assert_eq!(e.weight, 200);
977 assert_eq!(e.core_cookie, Some(0xABCD));
978 assert_eq!(e.nvcsw, 11);
979 assert_eq!(e.nivcsw, 22);
980 assert_eq!(e.utime, 999_000);
981 assert_eq!(e.stime, 888_000);
982 assert_eq!(e.nr_threads, Some(8));
983 assert_eq!(e.signal_nvcsw, Some(70_000));
984 assert_eq!(e.signal_nivcsw, Some(3));
985 assert_eq!(e.signal_utime, Some(7_000));
986 assert_eq!(e.signal_stime, Some(6_000));
987 assert_eq!(e.pgid, Some(4300));
988 assert_eq!(e.sid, Some(1));
989 assert_eq!(e.group_leader_pid, Some(1));
990 assert_eq!(e.real_parent_pid, Some(1));
991 assert_eq!(e.real_parent_comm.as_deref(), Some("init"));
992 assert!(!e.pi_boosted_out_of_scx);
995 assert_eq!(e.lock_slowpath_match, None);
997 }
998
999 fn pi_boosted(sched_class_kva: u64, ext: Option<u64>, is_runnable_in_scx: bool) -> bool {
1003 let mut buf = vec![0u8; 0x2000];
1004 plant_happy_task(&mut buf);
1005 let offsets = fixture_offsets();
1006 put_u64(
1007 &mut buf,
1008 TASK_ADDR,
1009 offsets.task_struct_sched_class,
1010 sched_class_kva,
1011 );
1012 let kernel = build_kernel(&mut buf);
1013 let classes = SchedClassRegistry {
1014 fair: Some(FAIR_CLASS_KVA),
1015 ext,
1016 ..Default::default()
1017 };
1018 walk_task_enrichment(
1019 &kernel,
1020 TASK_ADDR,
1021 &offsets,
1022 &classes,
1023 &LockSlowpathRegistry::default(),
1024 is_runnable_in_scx,
1025 None,
1026 )
1027 .expect("task built")
1028 .pi_boosted_out_of_scx
1029 }
1030
1031 #[test]
1032 fn walk_task_enrichment_pi_boosted_out_of_scx_truth_table() {
1033 assert!(!pi_boosted(EXT_CLASS_KVA, Some(EXT_CLASS_KVA), true));
1035 assert!(pi_boosted(FAIR_CLASS_KVA, Some(EXT_CLASS_KVA), true));
1037 assert!(!pi_boosted(FAIR_CLASS_KVA, None, true));
1039 assert!(!pi_boosted(FAIR_CLASS_KVA, Some(EXT_CLASS_KVA), false));
1041 }
1042
1043 #[test]
1044 fn walk_task_enrichment_core_cookie_absent_offset_yields_none() {
1045 let mut buf = vec![0u8; 0x2000];
1048 plant_happy_task(&mut buf);
1049 let kernel = build_kernel(&mut buf);
1050 let mut offsets = fixture_offsets();
1051 offsets.task_struct_core_cookie = None;
1052 let e = walk_task_enrichment(
1053 &kernel,
1054 TASK_ADDR,
1055 &offsets,
1056 &ext_registry(),
1057 &LockSlowpathRegistry::default(),
1058 false,
1059 None,
1060 )
1061 .expect("task built");
1062 assert_eq!(e.core_cookie, None);
1063 }
1064
1065 #[test]
1066 fn walk_task_enrichment_signal_null_pointer_all_signal_fields_none() {
1067 let mut buf = vec![0u8; 0x2000];
1070 plant_happy_task(&mut buf);
1071 let offsets = fixture_offsets();
1072 put_u64(&mut buf, TASK_ADDR, offsets.task_struct_signal, 0);
1073 let kernel = build_kernel(&mut buf);
1074 let e = walk_task_enrichment(
1075 &kernel,
1076 TASK_ADDR,
1077 &offsets,
1078 &ext_registry(),
1079 &LockSlowpathRegistry::default(),
1080 false,
1081 None,
1082 )
1083 .expect("task built");
1084 assert_eq!(e.nr_threads, None);
1085 assert_eq!(e.signal_nvcsw, None);
1086 assert_eq!(e.signal_nivcsw, None);
1087 assert_eq!(e.signal_utime, None);
1088 assert_eq!(e.signal_stime, None);
1089 assert_eq!(e.pgid, None);
1090 assert_eq!(e.sid, None);
1091 assert_eq!(e.pid, 4321);
1093 }
1094
1095 #[test]
1096 fn walk_task_enrichment_signal_translate_fail_all_signal_fields_none() {
1097 let mut buf = vec![0u8; 0x2000];
1100 plant_happy_task(&mut buf);
1101 let offsets = fixture_offsets();
1102 put_u64(&mut buf, TASK_ADDR, offsets.task_struct_signal, 0xF000_0000);
1104 let kernel = build_kernel(&mut buf);
1105 let e = walk_task_enrichment(
1106 &kernel,
1107 TASK_ADDR,
1108 &offsets,
1109 &ext_registry(),
1110 &LockSlowpathRegistry::default(),
1111 false,
1112 None,
1113 )
1114 .expect("task built");
1115 assert_eq!(
1116 (
1117 e.nr_threads,
1118 e.signal_nvcsw,
1119 e.signal_nivcsw,
1120 e.signal_utime,
1121 e.signal_stime,
1122 e.pgid,
1123 e.sid
1124 ),
1125 (None, None, None, None, None, None, None)
1126 );
1127 assert_eq!(e.tgid, 4300);
1128 }
1129
1130 #[test]
1131 fn read_pid_nr_at_index_null_slot_returns_none_via_pgid() {
1132 let mut buf = vec![0u8; 0x2000];
1136 plant_happy_task(&mut buf);
1137 let offsets = fixture_offsets();
1138 put_u64(
1139 &mut buf,
1140 SIGNAL_ADDR,
1141 offsets.signal_struct_pids + pid_type::PGID * 8,
1142 0,
1143 );
1144 let kernel = build_kernel(&mut buf);
1145 let e = walk_task_enrichment(
1146 &kernel,
1147 TASK_ADDR,
1148 &offsets,
1149 &ext_registry(),
1150 &LockSlowpathRegistry::default(),
1151 false,
1152 None,
1153 )
1154 .expect("task built");
1155 assert_eq!(e.pgid, None);
1156 assert_eq!(e.sid, Some(1));
1157 }
1158
1159 #[test]
1160 fn follow_task_for_pid_and_comm_null_and_translate_fail() {
1161 let offsets = fixture_offsets();
1162
1163 let mut buf_a = vec![0u8; 0x2000];
1165 plant_happy_task(&mut buf_a);
1166 put_u64(&mut buf_a, TASK_ADDR, offsets.task_struct_group_leader, 0);
1167 put_u64(&mut buf_a, TASK_ADDR, offsets.task_struct_real_parent, 0);
1168 let kernel_a = build_kernel(&mut buf_a);
1169 let ea = walk_task_enrichment(
1170 &kernel_a,
1171 TASK_ADDR,
1172 &offsets,
1173 &ext_registry(),
1174 &LockSlowpathRegistry::default(),
1175 false,
1176 None,
1177 )
1178 .expect("task built");
1179 assert_eq!(ea.group_leader_pid, None);
1180 assert_eq!(ea.real_parent_pid, None);
1181 assert_eq!(ea.real_parent_comm, None);
1182
1183 let mut buf_b = vec![0u8; 0x2000];
1185 plant_happy_task(&mut buf_b);
1186 put_u64(
1187 &mut buf_b,
1188 TASK_ADDR,
1189 offsets.task_struct_group_leader,
1190 0xF000_0000,
1191 );
1192 put_u64(
1193 &mut buf_b,
1194 TASK_ADDR,
1195 offsets.task_struct_real_parent,
1196 0xF000_0000,
1197 );
1198 let kernel_b = build_kernel(&mut buf_b);
1199 let eb = walk_task_enrichment(
1200 &kernel_b,
1201 TASK_ADDR,
1202 &offsets,
1203 &ext_registry(),
1204 &LockSlowpathRegistry::default(),
1205 false,
1206 None,
1207 )
1208 .expect("task built");
1209 assert_eq!(eb.group_leader_pid, None);
1210 assert_eq!(eb.real_parent_pid, None);
1211 assert_eq!(eb.real_parent_comm, None);
1212
1213 let mut buf_c = vec![0u8; 0x2000];
1216 plant_happy_task(&mut buf_c);
1217 let kernel_c = build_kernel(&mut buf_c);
1218 let ec = walk_task_enrichment(
1219 &kernel_c,
1220 TASK_ADDR,
1221 &offsets,
1222 &ext_registry(),
1223 &LockSlowpathRegistry::default(),
1224 false,
1225 None,
1226 )
1227 .expect("task built");
1228 assert_eq!(ec.group_leader_pid, Some(1));
1229 assert_eq!(ec.real_parent_pid, Some(1));
1230 assert_eq!(ec.real_parent_comm.as_deref(), Some("init"));
1231 }
1232
1233 #[test]
1234 fn read_comm_no_nul_reads_full_16_bytes_and_nul_truncates() {
1235 let offsets = fixture_offsets();
1236
1237 let mut buf_t = vec![0u8; 0x2000];
1239 plant_happy_task(&mut buf_t);
1240 put_comm(&mut buf_t, TASK_ADDR, offsets.task_struct_comm, b"short\0");
1241 let kernel_t = build_kernel(&mut buf_t);
1242 let et = walk_task_enrichment(
1243 &kernel_t,
1244 TASK_ADDR,
1245 &offsets,
1246 &ext_registry(),
1247 &LockSlowpathRegistry::default(),
1248 false,
1249 None,
1250 )
1251 .expect("task built");
1252 assert_eq!(et.comm, "short");
1253
1254 let mut buf_n = vec![0u8; 0x2000];
1256 plant_happy_task(&mut buf_n);
1257 put_comm(
1258 &mut buf_n,
1259 TASK_ADDR,
1260 offsets.task_struct_comm,
1261 b"sixteencharcomm!",
1262 );
1263 let kernel_n = build_kernel(&mut buf_n);
1264 let en = walk_task_enrichment(
1265 &kernel_n,
1266 TASK_ADDR,
1267 &offsets,
1268 &ext_registry(),
1269 &LockSlowpathRegistry::default(),
1270 false,
1271 None,
1272 )
1273 .expect("task built");
1274 assert_eq!(en.comm, "sixteencharcomm!");
1275 assert_eq!(en.comm.len(), 16);
1276
1277 let mut buf_l = vec![0u8; 0x2000];
1280 plant_happy_task(&mut buf_l);
1281 put_comm(
1282 &mut buf_l,
1283 TASK_ADDR,
1284 offsets.task_struct_comm,
1285 &[0xFF, 0x00],
1286 );
1287 let kernel_l = build_kernel(&mut buf_l);
1288 let el = walk_task_enrichment(
1289 &kernel_l,
1290 TASK_ADDR,
1291 &offsets,
1292 &ext_registry(),
1293 &LockSlowpathRegistry::default(),
1294 false,
1295 None,
1296 )
1297 .expect("task built");
1298 assert_eq!(el.comm, "\u{FFFD}");
1299 }
1300
1301 #[test]
1302 fn lock_slowpath_match_pc_supplied_sets_field_and_nonmatch_clears() {
1303 let offsets = fixture_offsets();
1304 let locks = LockSlowpathRegistry {
1305 queued_spin_lock_slowpath: Some(0x5_0000),
1306 ..Default::default()
1307 };
1308
1309 let mut buf_m = vec![0u8; 0x2000];
1311 plant_happy_task(&mut buf_m);
1312 let kernel_m = build_kernel(&mut buf_m);
1313 let em = walk_task_enrichment(
1314 &kernel_m,
1315 TASK_ADDR,
1316 &offsets,
1317 &ext_registry(),
1318 &locks,
1319 false,
1320 Some(0x5_0010),
1321 )
1322 .expect("task built");
1323 assert_eq!(
1324 em.lock_slowpath_match.as_deref(),
1325 Some("queued_spin_lock_slowpath")
1326 );
1327
1328 let mut buf_x = vec![0u8; 0x2000];
1330 plant_happy_task(&mut buf_x);
1331 let kernel_x = build_kernel(&mut buf_x);
1332 let ex = walk_task_enrichment(
1333 &kernel_x,
1334 TASK_ADDR,
1335 &offsets,
1336 &ext_registry(),
1337 &locks,
1338 false,
1339 Some(0x9_9999),
1340 )
1341 .expect("task built");
1342 assert_eq!(ex.lock_slowpath_match, None);
1343 }
1344
1345 #[test]
1346 fn lock_slowpath_match_pc_window_overflow_returns_none() {
1347 let r = LockSlowpathRegistry {
1351 queued_spin_lock_slowpath: Some(u64::MAX - 100),
1352 ..Default::default()
1353 };
1354 assert_eq!(r.match_pc(u64::MAX - 50), None);
1355 assert_eq!(r.match_pc(u64::MAX), None);
1356 }
1357}