1use bytemuck::Pod;
4use memf_format::PhysicalMemoryProvider;
5use memf_symbols::SymbolResolver;
6
7use crate::vas::VirtualAddressSpace;
8use crate::{Error, Result};
9
10const MAX_LIST_ITERATIONS: usize = 100_000;
12
13pub struct ObjectReader<P: PhysicalMemoryProvider> {
18 vas: VirtualAddressSpace<P>,
19 symbols: Box<dyn SymbolResolver>,
20 kernel_base: u64,
25}
26
27impl<P: PhysicalMemoryProvider> ObjectReader<P> {
28 pub fn new(vas: VirtualAddressSpace<P>, symbols: Box<dyn SymbolResolver>) -> Self {
30 Self {
31 vas,
32 symbols,
33 kernel_base: 0,
34 }
35 }
36
37 #[must_use]
40 pub fn with_kernel_base(mut self, kernel_base: u64) -> Self {
41 self.kernel_base = kernel_base;
42 self
43 }
44
45 pub fn symbols(&self) -> &dyn SymbolResolver {
47 self.symbols.as_ref()
48 }
49
50 pub fn vas(&self) -> &VirtualAddressSpace<P> {
52 &self.vas
53 }
54
55 pub fn with_cr3(&self, cr3: u64) -> Self
59 where
60 P: Clone,
61 {
62 let vas = VirtualAddressSpace::new(self.vas.physical().clone(), cr3, self.vas.mode());
63 Self {
64 vas,
65 symbols: self.symbols.clone_boxed(),
66 kernel_base: self.kernel_base,
67 }
68 }
69
70 #[must_use]
77 pub fn map_symbols(
78 self,
79 f: impl FnOnce(Box<dyn SymbolResolver>) -> Box<dyn SymbolResolver>,
80 ) -> Self {
81 let Self {
82 vas,
83 symbols,
84 kernel_base,
85 } = self;
86 Self {
87 vas,
88 symbols: f(symbols),
89 kernel_base,
90 }
91 }
92
93 pub fn read_field<T: Pod + Default>(
98 &self,
99 base_vaddr: u64,
100 struct_name: &str,
101 field_name: &str,
102 ) -> Result<T> {
103 let offset = self
104 .symbols
105 .field_offset(struct_name, field_name)
106 .ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))?;
107
108 let size = std::mem::size_of::<T>();
109 let mut buf = vec![0u8; size];
110 self.vas
111 .read_virt(base_vaddr.wrapping_add(offset), &mut buf)?;
112
113 if buf.len() != size {
114 return Err(Error::SizeMismatch {
115 expected: size,
116 got: buf.len(),
117 });
118 }
119
120 Ok(*bytemuck::from_bytes::<T>(&buf))
121 }
122
123 pub fn read_pointer(
125 &self,
126 base_vaddr: u64,
127 struct_name: &str,
128 field_name: &str,
129 ) -> Result<u64> {
130 self.read_field::<u64>(base_vaddr, struct_name, field_name)
131 }
132
133 pub fn read_string(&self, vaddr: u64, max_len: usize) -> Result<String> {
135 let mut buf = vec![0u8; max_len];
136 self.vas.read_virt(vaddr, &mut buf)?;
137
138 let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
139 Ok(String::from_utf8_lossy(&buf[..end]).into_owned())
140 }
141
142 pub fn read_field_string(
144 &self,
145 base_vaddr: u64,
146 struct_name: &str,
147 field_name: &str,
148 max_len: usize,
149 ) -> Result<String> {
150 let offset = self
151 .symbols
152 .field_offset(struct_name, field_name)
153 .ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))?;
154
155 self.read_string(base_vaddr.wrapping_add(offset), max_len)
156 }
157
158 pub fn walk_list(
166 &self,
167 head_vaddr: u64,
168 struct_name: &str,
169 list_field: &str,
170 ) -> Result<Vec<u64>> {
171 self.walk_list_with(head_vaddr, "list_head", "next", struct_name, list_field)
172 }
173
174 pub fn walk_list_with(
189 &self,
190 head_vaddr: u64,
191 list_struct: &str,
192 next_field: &str,
193 container_struct: &str,
194 list_field: &str,
195 ) -> Result<Vec<u64>> {
196 let list_offset = self
197 .symbols
198 .field_offset(container_struct, list_field)
199 .ok_or_else(|| Error::MissingSymbol(format!("{container_struct}.{list_field}")))?;
200
201 let next_offset = self
202 .symbols
203 .field_offset(list_struct, next_field)
204 .ok_or_else(|| Error::MissingSymbol(format!("{list_struct}.{next_field}")))?;
205
206 let mut current = self.read_u64_at(head_vaddr.wrapping_add(next_offset))?;
208
209 let mut result = Vec::new();
210
211 for _ in 0..MAX_LIST_ITERATIONS {
212 if current == head_vaddr {
214 return Ok(result);
215 }
216
217 if current == 0 {
221 return Ok(result);
222 }
223
224 let Ok(next) = self.read_u64_at(current.wrapping_add(next_offset)) else {
233 return Ok(result);
234 };
235
236 result.push(current.wrapping_sub(list_offset));
238 current = next;
239 }
240
241 Err(Error::ListCycle(MAX_LIST_ITERATIONS))
242 }
243
244 pub fn walk_list_bidirectional(
254 &self,
255 head_vaddr: u64,
256 list_struct: &str,
257 next_field: &str,
258 prev_field: &str,
259 container_struct: &str,
260 list_field: &str,
261 ) -> Result<Vec<u64>> {
262 let mut forward = self.walk_list_with(
263 head_vaddr,
264 list_struct,
265 next_field,
266 container_struct,
267 list_field,
268 )?;
269 let backward = self.walk_list_with(
270 head_vaddr,
271 list_struct,
272 prev_field,
273 container_struct,
274 list_field,
275 )?;
276 let mut seen: std::collections::HashSet<u64> = forward.iter().copied().collect();
277 for container in backward {
278 if seen.insert(container) {
279 forward.push(container);
280 }
281 }
282 Ok(forward)
283 }
284
285 pub fn read_bytes(&self, vaddr: u64, len: usize) -> Result<Vec<u8>> {
287 let mut buf = vec![0u8; len];
288 self.vas.read_virt(vaddr, &mut buf)?;
289 Ok(buf)
290 }
291
292 pub fn required_symbol(&self, name: &str) -> Result<u64> {
296 self.symbols()
297 .symbol_address(name)
298 .map(|rva| self.kernel_base.wrapping_add(rva))
299 .ok_or_else(|| Error::MissingSymbol(name.to_owned()))
300 }
301
302 pub fn required_field_offset(&self, struct_name: &str, field_name: &str) -> Result<usize> {
304 self.symbols()
305 .field_offset(struct_name, field_name)
306 .map(|v| v as usize)
307 .ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))
308 }
309
310 fn read_u64_at(&self, vaddr: u64) -> Result<u64> {
311 let mut buf = [0u8; 8];
312 self.vas.read_virt(vaddr, &mut buf)?;
313 Ok(u64::from_le_bytes(buf))
314 }
315
316 pub fn iter_list<'a>(
329 &'a self,
330 head_vaddr: u64,
331 container_struct: &'a str,
332 list_field: &'a str,
333 ) -> ListIter<'a, P> {
334 let list_offset = self
335 .symbols
336 .field_offset(container_struct, list_field)
337 .unwrap_or(0);
338 let next_offset = self.symbols.field_offset("list_head", "next").unwrap_or(0);
339
340 let current = match self.read_u64_at(head_vaddr.wrapping_add(next_offset)) {
341 Ok(v) => v,
342 Err(_) => head_vaddr, };
344
345 ListIter {
346 reader: self,
347 head_vaddr,
348 current,
349 list_offset,
350 next_offset,
351 seen: std::collections::HashSet::new(),
352 done: false,
353 }
354 }
355}
356
357pub struct ListIter<'a, P: PhysicalMemoryProvider> {
362 reader: &'a ObjectReader<P>,
363 head_vaddr: u64,
364 current: u64,
365 list_offset: u64,
366 next_offset: u64,
367 seen: std::collections::HashSet<u64>,
368 done: bool,
369}
370
371impl<P: PhysicalMemoryProvider> Iterator for ListIter<'_, P> {
372 type Item = crate::Result<u64>;
373
374 fn next(&mut self) -> Option<Self::Item> {
375 if self.done {
376 return None;
377 }
378 if self.current == self.head_vaddr {
380 return None;
381 }
382 if !self.seen.insert(self.current) {
384 self.done = true;
385 return None; }
387 if self.seen.len() > MAX_LIST_ITERATIONS {
388 self.done = true;
389 return Some(Err(crate::Error::ListCycle(MAX_LIST_ITERATIONS)));
390 }
391
392 let container = self.current.wrapping_sub(self.list_offset);
394
395 match self
397 .reader
398 .read_u64_at(self.current.wrapping_add(self.next_offset))
399 {
400 Ok(next) => self.current = next,
401 Err(e) => {
402 self.done = true;
403 return Some(Err(e));
404 }
405 }
406
407 Some(Ok(container))
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::test_builders::{flags, PageTableBuilder};
415 use crate::vas::TranslationMode;
416 use memf_symbols::isf::IsfResolver;
417 use memf_symbols::test_builders::IsfBuilder;
418
419 fn make_reader(
420 isf: &IsfBuilder,
421 builder: PageTableBuilder,
422 ) -> ObjectReader<crate::test_builders::SyntheticPhysMem> {
423 let json = isf.build_json();
424 let resolver = IsfResolver::from_value(&json).unwrap();
425 let (cr3, mem) = builder.build();
426 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
427 ObjectReader::new(vas, Box::new(resolver))
428 }
429
430 #[test]
431 fn read_field_u32() {
432 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
433 "task_struct",
434 "pid",
435 0,
436 "int",
437 );
438
439 let vaddr: u64 = 0xFFFF_8000_0010_0000;
440 let paddr: u64 = 0x0080_0000;
441
442 let ptb = PageTableBuilder::new()
443 .map_4k(vaddr, paddr, flags::WRITABLE)
444 .write_phys_u64(paddr, u64::from(42u32)); let reader = make_reader(&isf, ptb);
447 let pid: u32 = reader.read_field(vaddr, "task_struct", "pid").unwrap();
448 assert_eq!(pid, 42);
449 }
450
451 #[test]
452 fn read_field_u64() {
453 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
454 "task_struct",
455 "mm",
456 8,
457 "pointer",
458 );
459
460 let vaddr: u64 = 0xFFFF_8000_0010_0000;
461 let paddr: u64 = 0x0080_0000;
462 let mm_value: u64 = 0xFFFF_8000_DEAD_BEEF;
463
464 let ptb = PageTableBuilder::new()
465 .map_4k(vaddr, paddr, flags::WRITABLE)
466 .write_phys_u64(paddr + 8, mm_value);
467
468 let reader = make_reader(&isf, ptb);
469 let mm: u64 = reader.read_field(vaddr, "task_struct", "mm").unwrap();
470 assert_eq!(mm, mm_value);
471 }
472
473 #[test]
474 fn read_field_missing_symbol() {
475 let isf = IsfBuilder::new().add_struct("task_struct", 128);
476
477 let vaddr: u64 = 0xFFFF_8000_0010_0000;
478 let paddr: u64 = 0x0080_0000;
479
480 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
481
482 let reader = make_reader(&isf, ptb);
483 let result = reader.read_field::<u32>(vaddr, "task_struct", "nonexistent");
484 assert!(result.is_err());
485 match result.unwrap_err() {
486 Error::MissingSymbol(s) => assert_eq!(s, "task_struct.nonexistent"),
487 other => panic!("unexpected error: {other}"),
488 }
489 }
490
491 #[test]
492 fn map_symbols_applies_the_transform() {
493 let isf_old = IsfBuilder::new().add_symbol("old_sym", 0x1000);
495 let reader = make_reader(&isf_old, PageTableBuilder::new());
496 assert_eq!(reader.symbols().symbol_address("old_sym"), Some(0x1000));
497 assert_eq!(reader.symbols().symbol_address("new_sym"), None);
498
499 let new_json = IsfBuilder::new().add_symbol("new_sym", 0x2000).build_json();
502 let reader =
503 reader.map_symbols(|_old| Box::new(IsfResolver::from_value(&new_json).unwrap()));
504
505 assert_eq!(
506 reader.symbols().symbol_address("new_sym"),
507 Some(0x2000),
508 "map_symbols must apply f to the resolver"
509 );
510 assert_eq!(
511 reader.symbols().symbol_address("old_sym"),
512 None,
513 "the old resolver must be replaced by f's result"
514 );
515 }
516
517 #[test]
518 fn read_field_string_test() {
519 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
520 "task_struct",
521 "comm",
522 16,
523 "char",
524 );
525
526 let vaddr: u64 = 0xFFFF_8000_0010_0000;
527 let paddr: u64 = 0x0080_0000;
528
529 let ptb = PageTableBuilder::new()
530 .map_4k(vaddr, paddr, flags::WRITABLE)
531 .write_phys(paddr + 16, b"systemd\0");
532
533 let reader = make_reader(&isf, ptb);
534 let comm = reader
535 .read_field_string(vaddr, "task_struct", "comm", 16)
536 .unwrap();
537 assert_eq!(comm, "systemd");
538 }
539
540 #[test]
541 fn read_string_with_null() {
542 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
543 "task_struct",
544 "comm",
545 16,
546 "char",
547 );
548
549 let vaddr: u64 = 0xFFFF_8000_0010_0000;
550 let paddr: u64 = 0x0080_0000;
551
552 let ptb = PageTableBuilder::new()
553 .map_4k(vaddr, paddr, flags::WRITABLE)
554 .write_phys(paddr + 16, b"init\0\0\0\0\0\0\0\0\0\0\0\0");
555
556 let reader = make_reader(&isf, ptb);
557 let s = reader.read_string(vaddr + 16, 16).unwrap();
558 assert_eq!(s, "init");
559 }
560
561 #[test]
562 fn walk_list_simple() {
563 let isf = IsfBuilder::new()
569 .add_struct("task_struct", 128)
570 .add_field("task_struct", "pid", 0, "int")
571 .add_field("task_struct", "tasks", 8, "list_head")
572 .add_field("task_struct", "comm", 16, "char")
573 .add_struct("list_head", 16)
574 .add_field("list_head", "next", 0, "pointer")
575 .add_field("list_head", "prev", 8, "pointer");
576
577 let head_paddr: u64 = 0x0080_0000;
585 let a_paddr: u64 = 0x0080_1000;
586 let b_paddr: u64 = 0x0080_2000;
587
588 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
589 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
590 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
591
592 let list_offset: u64 = 8; let ptb = PageTableBuilder::new()
598 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
599 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
600 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
601 .write_phys_u64(head_paddr, 0) .write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset) .write_phys_u64(a_paddr, 100) .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset) .write_phys_u64(b_paddr, 200) .write_phys_u64(b_paddr + list_offset, head_vaddr + list_offset); let reader = make_reader(&isf, ptb);
612
613 let containers = reader
614 .walk_list(head_vaddr + list_offset, "task_struct", "tasks")
615 .unwrap();
616 assert_eq!(containers.len(), 2);
617 assert_eq!(containers[0], a_vaddr);
618 assert_eq!(containers[1], b_vaddr);
619 }
620
621 #[test]
622 fn read_pointer_test() {
623 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
624 "task_struct",
625 "mm",
626 8,
627 "pointer",
628 );
629
630 let vaddr: u64 = 0xFFFF_8000_0010_0000;
631 let paddr: u64 = 0x0080_0000;
632 let mm_value: u64 = 0xFFFF_8000_CAFE_BABE;
633
634 let ptb = PageTableBuilder::new()
635 .map_4k(vaddr, paddr, flags::WRITABLE)
636 .write_phys_u64(paddr + 8, mm_value);
637
638 let reader = make_reader(&isf, ptb);
639 let ptr = reader.read_pointer(vaddr, "task_struct", "mm").unwrap();
640 assert_eq!(ptr, mm_value);
641 }
642
643 #[test]
644 fn read_field_invalid_struct_name() {
645 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
646 "task_struct",
647 "pid",
648 0,
649 "int",
650 );
651
652 let vaddr: u64 = 0xFFFF_8000_0010_0000;
653 let paddr: u64 = 0x0080_0000;
654
655 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
656
657 let reader = make_reader(&isf, ptb);
658 let result = reader.read_field::<u32>(vaddr, "nonexistent_struct", "pid");
659 assert!(result.is_err());
660 match result.unwrap_err() {
661 Error::MissingSymbol(s) => assert_eq!(s, "nonexistent_struct.pid"),
662 other => panic!("unexpected error: {other}"),
663 }
664 }
665
666 #[test]
667 fn walk_list_empty_list() {
668 let isf = IsfBuilder::new()
670 .add_struct("task_struct", 128)
671 .add_field("task_struct", "tasks", 8, "list_head")
672 .add_struct("list_head", 16)
673 .add_field("list_head", "next", 0, "pointer")
674 .add_field("list_head", "prev", 8, "pointer");
675
676 let head_paddr: u64 = 0x0080_0000;
677 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
678 let list_offset: u64 = 8;
679
680 let ptb = PageTableBuilder::new()
682 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
683 .write_phys_u64(head_paddr + list_offset, head_vaddr + list_offset);
684
685 let reader = make_reader(&isf, ptb);
686 let containers = reader
687 .walk_list(head_vaddr + list_offset, "task_struct", "tasks")
688 .unwrap();
689 assert!(containers.is_empty());
690 }
691
692 #[test]
693 fn walk_list_with_windows_list_entry() {
694 let isf = IsfBuilder::new()
698 .add_struct("_EPROCESS", 256)
699 .add_field("_EPROCESS", "UniqueProcessId", 0, "pointer")
700 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
701 .add_struct("_LIST_ENTRY", 16)
702 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
703 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
704
705 let head_paddr: u64 = 0x0080_0000;
710 let a_paddr: u64 = 0x0080_1000;
711 let b_paddr: u64 = 0x0080_2000;
712
713 let head_vaddr: u64 = 0xFFFF_8000_0010_0000; let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
715 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
716
717 let list_offset: u64 = 0x10; let ptb = PageTableBuilder::new()
721 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
722 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
723 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
724 .write_phys_u64(head_paddr, a_vaddr + list_offset) .write_phys_u64(a_paddr, 4) .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset) .write_phys_u64(b_paddr, 100) .write_phys_u64(b_paddr + list_offset, head_vaddr); let reader = make_reader(&isf, ptb);
734
735 let containers = reader
736 .walk_list_with(
737 head_vaddr,
738 "_LIST_ENTRY",
739 "Flink",
740 "_EPROCESS",
741 "ActiveProcessLinks",
742 )
743 .unwrap();
744
745 assert_eq!(containers.len(), 2);
746 assert_eq!(containers[0], a_vaddr);
747 assert_eq!(containers[1], b_vaddr);
748 }
749
750 #[test]
751 fn walk_list_with_tolerates_smeared_null_link() {
752 let isf = IsfBuilder::new()
759 .add_struct("_EPROCESS", 256)
760 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
761 .add_struct("_LIST_ENTRY", 16)
762 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
763 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
764
765 let head_paddr: u64 = 0x0080_0000;
766 let a_paddr: u64 = 0x0080_1000;
767 let b_paddr: u64 = 0x0080_2000;
768 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
769 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
770 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
771 let list_offset: u64 = 0x10;
772
773 let ptb = PageTableBuilder::new()
775 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
776 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
777 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
778 .write_phys_u64(head_paddr, a_vaddr + list_offset)
779 .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
780 .write_phys_u64(b_paddr + list_offset, 0); let reader = make_reader(&isf, ptb);
783
784 let containers = reader
785 .walk_list_with(
786 head_vaddr,
787 "_LIST_ENTRY",
788 "Flink",
789 "_EPROCESS",
790 "ActiveProcessLinks",
791 )
792 .expect("a smeared null link must terminate the walk gracefully, not error");
793
794 assert_eq!(containers, vec![a_vaddr, b_vaddr]);
796 }
797
798 #[test]
799 fn walk_list_with_stops_on_non_canonical_kernel_pointer() {
800 let isf = IsfBuilder::new()
805 .add_struct("_EPROCESS", 256)
806 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
807 .add_struct("_LIST_ENTRY", 16)
808 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
809 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
810
811 let lo: u64 = 0x10;
812 let head_p = 0x0080_0000u64;
813 let a_p = 0x0080_1000u64;
814 let head_v = 0xFFFF_8000_0010_0000u64;
815 let a_v = 0xFFFF_8000_0010_1000u64;
816
817 let ptb = PageTableBuilder::new()
819 .map_4k(head_v, head_p, flags::WRITABLE)
820 .map_4k(a_v, a_p, flags::WRITABLE)
821 .write_phys_u64(head_p, a_v + lo)
822 .write_phys_u64(a_p + lo, 0x0000_0000_5A28_9000); let reader = make_reader(&isf, ptb);
825 let containers = reader
826 .walk_list_with(
827 head_v,
828 "_LIST_ENTRY",
829 "Flink",
830 "_EPROCESS",
831 "ActiveProcessLinks",
832 )
833 .expect("non-canonical link terminates the walk, not errors");
834
835 assert_eq!(
836 containers,
837 vec![a_v],
838 "only the real node A; no bogus container"
839 );
840 }
841
842 #[test]
843 fn walk_list_bidirectional_recovers_forward_orphans() {
844 let isf = IsfBuilder::new()
851 .add_struct("_EPROCESS", 256)
852 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
853 .add_struct("_LIST_ENTRY", 16)
854 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
855 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
856
857 let lo: u64 = 0x10;
858 let head_p = 0x0080_0000u64;
859 let a_p = 0x0080_1000u64;
860 let b_p = 0x0080_2000u64;
861 let c_p = 0x0080_3000u64;
862 let head_v = 0xFFFF_8000_0010_0000u64;
863 let a_v = 0xFFFF_8000_0010_1000u64;
864 let b_v = 0xFFFF_8000_0010_2000u64;
865 let c_v = 0xFFFF_8000_0010_3000u64;
866
867 let ptb = PageTableBuilder::new()
868 .map_4k(head_v, head_p, flags::WRITABLE)
869 .map_4k(a_v, a_p, flags::WRITABLE)
870 .map_4k(b_v, b_p, flags::WRITABLE)
871 .map_4k(c_v, c_p, flags::WRITABLE)
872 .write_phys_u64(head_p, a_v + lo)
874 .write_phys_u64(head_p + 8, c_v + lo)
875 .write_phys_u64(a_p + lo, b_v + lo)
877 .write_phys_u64(a_p + lo + 8, head_v)
878 .write_phys_u64(b_p + lo, 0)
880 .write_phys_u64(b_p + lo + 8, a_v + lo)
881 .write_phys_u64(c_p + lo, head_v)
883 .write_phys_u64(c_p + lo + 8, b_v + lo);
884
885 let reader = make_reader(&isf, ptb);
886
887 let containers = reader
888 .walk_list_bidirectional(
889 head_v,
890 "_LIST_ENTRY",
891 "Flink",
892 "Blink",
893 "_EPROCESS",
894 "ActiveProcessLinks",
895 )
896 .unwrap();
897
898 assert_eq!(
901 containers.len(),
902 3,
903 "all three nodes recovered: {containers:x?}"
904 );
905 assert!(containers.contains(&a_v));
906 assert!(containers.contains(&b_v));
907 assert!(
908 containers.contains(&c_v),
909 "forward-orphaned C recovered via Blink"
910 );
911 }
912
913 #[test]
914 fn walk_list_with_empty() {
915 let isf = IsfBuilder::new()
917 .add_struct("_EPROCESS", 256)
918 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
919 .add_struct("_LIST_ENTRY", 16)
920 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
921 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
922
923 let head_paddr: u64 = 0x0080_0000;
924 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
925
926 let ptb = PageTableBuilder::new()
928 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
929 .write_phys_u64(head_paddr, head_vaddr); let reader = make_reader(&isf, ptb);
932
933 let containers = reader
934 .walk_list_with(
935 head_vaddr,
936 "_LIST_ENTRY",
937 "Flink",
938 "_EPROCESS",
939 "ActiveProcessLinks",
940 )
941 .unwrap();
942
943 assert!(containers.is_empty());
944 }
945
946 #[test]
947 fn walk_list_still_works_after_refactor() {
948 let isf = IsfBuilder::new()
951 .add_struct("task_struct", 128)
952 .add_field("task_struct", "pid", 0, "int")
953 .add_field("task_struct", "tasks", 8, "list_head")
954 .add_struct("list_head", 16)
955 .add_field("list_head", "next", 0, "pointer")
956 .add_field("list_head", "prev", 8, "pointer");
957
958 let head_paddr: u64 = 0x0080_0000;
959 let a_paddr: u64 = 0x0080_1000;
960
961 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
962 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
963
964 let list_offset: u64 = 8;
965
966 let ptb = PageTableBuilder::new()
968 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
969 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
970 .write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
971 .write_phys_u64(a_paddr, 42) .write_phys_u64(a_paddr + list_offset, head_vaddr + list_offset);
973
974 let reader = make_reader(&isf, ptb);
975
976 let containers = reader
977 .walk_list(head_vaddr + list_offset, "task_struct", "tasks")
978 .unwrap();
979 assert_eq!(containers.len(), 1);
980 assert_eq!(containers[0], a_vaddr);
981 }
982
983 #[test]
984 fn symbols_accessor() {
985 let isf = IsfBuilder::new()
986 .add_struct("task_struct", 128)
987 .add_field("task_struct", "pid", 0, "int")
988 .add_symbol("init_task", 0xFFFF_0000);
989
990 let vaddr: u64 = 0xFFFF_8000_0010_0000;
991 let paddr: u64 = 0x0080_0000;
992 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
993
994 let reader = make_reader(&isf, ptb);
995 assert_eq!(reader.symbols().backend_name(), "ISF JSON");
996 assert_eq!(reader.symbols().field_offset("task_struct", "pid"), Some(0));
997 }
998
999 #[test]
1000 fn required_symbol_ok() {
1001 let isf = IsfBuilder::new()
1002 .add_struct("task_struct", 128)
1003 .add_symbol("init_task", 0xFFFF_8000_CAFE_0000);
1004
1005 let vaddr: u64 = 0xFFFF_8000_0010_0000;
1006 let paddr: u64 = 0x0080_0000;
1007 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
1008
1009 let reader = make_reader(&isf, ptb);
1010 assert_eq!(
1011 reader.required_symbol("init_task").unwrap(),
1012 0xFFFF_8000_CAFE_0000
1013 );
1014 }
1015
1016 #[test]
1017 fn required_symbol_rebases_by_kernel_base() {
1018 let isf = IsfBuilder::new()
1019 .add_struct("x", 1)
1020 .add_symbol("PsActiveProcessHead", 0x002b_00a0);
1021 let reader =
1022 make_reader(&isf, PageTableBuilder::new()).with_kernel_base(0xFFFF_F800_CBE0_0000);
1023 assert_eq!(
1024 reader.required_symbol("PsActiveProcessHead").unwrap(),
1025 0xFFFF_F800_CC0B_00A0
1026 );
1027 }
1028
1029 #[test]
1030 fn required_symbol_missing_returns_error() {
1031 let isf = IsfBuilder::new().add_struct("task_struct", 128);
1032
1033 let vaddr: u64 = 0xFFFF_8000_0010_0000;
1034 let paddr: u64 = 0x0080_0000;
1035 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
1036
1037 let reader = make_reader(&isf, ptb);
1038 assert!(reader.required_symbol("nonexistent").is_err());
1039 }
1040
1041 #[test]
1042 fn required_field_offset_ok() {
1043 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
1044 "task_struct",
1045 "pid",
1046 4,
1047 "int",
1048 );
1049
1050 let vaddr: u64 = 0xFFFF_8000_0010_0000;
1051 let paddr: u64 = 0x0080_0000;
1052 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
1053
1054 let reader = make_reader(&isf, ptb);
1055 assert_eq!(
1056 reader.required_field_offset("task_struct", "pid").unwrap(),
1057 4
1058 );
1059 }
1060
1061 #[test]
1062 fn required_field_offset_missing_returns_error() {
1063 let isf = IsfBuilder::new().add_struct("task_struct", 128);
1064
1065 let vaddr: u64 = 0xFFFF_8000_0010_0000;
1066 let paddr: u64 = 0x0080_0000;
1067 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
1068
1069 let reader = make_reader(&isf, ptb);
1070 assert!(reader
1071 .required_field_offset("task_struct", "nonexistent")
1072 .is_err());
1073 }
1074
1075 #[test]
1076 fn walk_list_cycle_detection() {
1077 let isf = IsfBuilder::new()
1080 .add_struct("_EPROCESS", 256)
1081 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
1082 .add_struct("_LIST_ENTRY", 16)
1083 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
1084 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
1085
1086 let head_paddr: u64 = 0x0080_0000;
1088 let a_paddr: u64 = 0x0080_1000;
1089 let b_paddr: u64 = 0x0080_2000;
1090
1091 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
1092 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
1093 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
1094
1095 let list_offset: u64 = 0x10; let ptb = PageTableBuilder::new()
1101 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
1102 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
1103 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
1104 .write_phys_u64(head_paddr, a_vaddr + list_offset)
1106 .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
1108 .write_phys_u64(b_paddr + list_offset, a_vaddr + list_offset);
1110
1111 let reader = make_reader(&isf, ptb);
1112 let result = reader.walk_list_with(
1113 head_vaddr,
1114 "_LIST_ENTRY",
1115 "Flink",
1116 "_EPROCESS",
1117 "ActiveProcessLinks",
1118 );
1119
1120 assert!(
1121 matches!(result, Err(Error::ListCycle(_))),
1122 "expected ListCycle error, got: {result:?}"
1123 );
1124 }
1125
1126 #[test]
1127 fn iter_list_yields_same_as_walk_list() {
1128 let isf = IsfBuilder::new()
1129 .add_struct("task_struct", 128)
1130 .add_field("task_struct", "pid", 0, "int")
1131 .add_field("task_struct", "tasks", 8, "list_head")
1132 .add_field("task_struct", "comm", 16, "char")
1133 .add_struct("list_head", 16)
1134 .add_field("list_head", "next", 0, "pointer")
1135 .add_field("list_head", "prev", 8, "pointer");
1136
1137 let head_paddr: u64 = 0x0080_0000;
1138 let a_paddr: u64 = 0x0080_1000;
1139 let b_paddr: u64 = 0x0080_2000;
1140 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
1141 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
1142 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
1143 let list_offset: u64 = 8;
1144
1145 let ptb = PageTableBuilder::new()
1146 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
1147 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
1148 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
1149 .write_phys_u64(head_paddr, 0)
1150 .write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
1151 .write_phys_u64(a_paddr, 100)
1152 .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
1153 .write_phys_u64(b_paddr, 200)
1154 .write_phys_u64(b_paddr + list_offset, head_vaddr + list_offset);
1155
1156 let reader = make_reader(&isf, ptb);
1157 let head = head_vaddr + list_offset;
1158
1159 let walk_result = reader.walk_list(head, "task_struct", "tasks").unwrap();
1160 let iter_result: Vec<u64> = reader
1161 .iter_list(head, "task_struct", "tasks")
1162 .collect::<crate::Result<Vec<_>>>()
1163 .unwrap();
1164 assert_eq!(iter_result, walk_result);
1165 }
1166
1167 #[test]
1168 fn iter_list_take_stops_early() {
1169 let isf = IsfBuilder::new()
1170 .add_struct("task_struct", 128)
1171 .add_field("task_struct", "pid", 0, "int")
1172 .add_field("task_struct", "tasks", 8, "list_head")
1173 .add_field("task_struct", "comm", 16, "char")
1174 .add_struct("list_head", 16)
1175 .add_field("list_head", "next", 0, "pointer")
1176 .add_field("list_head", "prev", 8, "pointer");
1177
1178 let head_paddr: u64 = 0x0080_0000;
1179 let a_paddr: u64 = 0x0080_1000;
1180 let b_paddr: u64 = 0x0080_2000;
1181 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
1182 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
1183 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
1184 let list_offset: u64 = 8;
1185
1186 let ptb = PageTableBuilder::new()
1187 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
1188 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
1189 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
1190 .write_phys_u64(head_paddr, 0)
1191 .write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
1192 .write_phys_u64(a_paddr, 100)
1193 .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
1194 .write_phys_u64(b_paddr, 200)
1195 .write_phys_u64(b_paddr + list_offset, head_vaddr + list_offset);
1196
1197 let reader = make_reader(&isf, ptb);
1198 let head = head_vaddr + list_offset;
1199
1200 let first_two: Vec<u64> = reader
1201 .iter_list(head, "task_struct", "tasks")
1202 .take(2)
1203 .collect::<crate::Result<Vec<_>>>()
1204 .unwrap();
1205 assert_eq!(first_two.len(), 2);
1206 }
1207}