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}
21
22impl<P: PhysicalMemoryProvider> ObjectReader<P> {
23 pub fn new(vas: VirtualAddressSpace<P>, symbols: Box<dyn SymbolResolver>) -> Self {
25 Self { vas, symbols }
26 }
27
28 pub fn symbols(&self) -> &dyn SymbolResolver {
30 self.symbols.as_ref()
31 }
32
33 pub fn vas(&self) -> &VirtualAddressSpace<P> {
35 &self.vas
36 }
37
38 pub fn with_cr3(&self, cr3: u64) -> Self
42 where
43 P: Clone,
44 {
45 let vas = VirtualAddressSpace::new(self.vas.physical().clone(), cr3, self.vas.mode());
46 Self {
47 vas,
48 symbols: self.symbols.clone_boxed(),
49 }
50 }
51
52 pub fn read_field<T: Pod + Default>(
57 &self,
58 base_vaddr: u64,
59 struct_name: &str,
60 field_name: &str,
61 ) -> Result<T> {
62 let offset = self
63 .symbols
64 .field_offset(struct_name, field_name)
65 .ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))?;
66
67 let size = std::mem::size_of::<T>();
68 let mut buf = vec![0u8; size];
69 self.vas
70 .read_virt(base_vaddr.wrapping_add(offset), &mut buf)?;
71
72 if buf.len() != size {
73 return Err(Error::SizeMismatch {
74 expected: size,
75 got: buf.len(),
76 });
77 }
78
79 Ok(*bytemuck::from_bytes::<T>(&buf))
80 }
81
82 pub fn read_pointer(
84 &self,
85 base_vaddr: u64,
86 struct_name: &str,
87 field_name: &str,
88 ) -> Result<u64> {
89 self.read_field::<u64>(base_vaddr, struct_name, field_name)
90 }
91
92 pub fn read_string(&self, vaddr: u64, max_len: usize) -> Result<String> {
94 let mut buf = vec![0u8; max_len];
95 self.vas.read_virt(vaddr, &mut buf)?;
96
97 let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
98 Ok(String::from_utf8_lossy(&buf[..end]).into_owned())
99 }
100
101 pub fn read_field_string(
103 &self,
104 base_vaddr: u64,
105 struct_name: &str,
106 field_name: &str,
107 max_len: usize,
108 ) -> Result<String> {
109 let offset = self
110 .symbols
111 .field_offset(struct_name, field_name)
112 .ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))?;
113
114 self.read_string(base_vaddr.wrapping_add(offset), max_len)
115 }
116
117 pub fn walk_list(
125 &self,
126 head_vaddr: u64,
127 struct_name: &str,
128 list_field: &str,
129 ) -> Result<Vec<u64>> {
130 self.walk_list_with(head_vaddr, "list_head", "next", struct_name, list_field)
131 }
132
133 pub fn walk_list_with(
148 &self,
149 head_vaddr: u64,
150 list_struct: &str,
151 next_field: &str,
152 container_struct: &str,
153 list_field: &str,
154 ) -> Result<Vec<u64>> {
155 let list_offset = self
156 .symbols
157 .field_offset(container_struct, list_field)
158 .ok_or_else(|| Error::MissingSymbol(format!("{container_struct}.{list_field}")))?;
159
160 let next_offset = self
161 .symbols
162 .field_offset(list_struct, next_field)
163 .ok_or_else(|| Error::MissingSymbol(format!("{list_struct}.{next_field}")))?;
164
165 let mut current = self.read_u64_at(head_vaddr.wrapping_add(next_offset))?;
167
168 let mut result = Vec::new();
169
170 for _ in 0..MAX_LIST_ITERATIONS {
171 if current == head_vaddr {
173 return Ok(result);
174 }
175
176 let container = current.wrapping_sub(list_offset);
178 result.push(container);
179
180 current = self.read_u64_at(current.wrapping_add(next_offset))?;
182 }
183
184 Err(Error::ListCycle(MAX_LIST_ITERATIONS))
185 }
186
187 pub fn read_bytes(&self, vaddr: u64, len: usize) -> Result<Vec<u8>> {
189 let mut buf = vec![0u8; len];
190 self.vas.read_virt(vaddr, &mut buf)?;
191 Ok(buf)
192 }
193
194 pub fn required_symbol(&self, name: &str) -> Result<u64> {
196 self.symbols()
197 .symbol_address(name)
198 .ok_or_else(|| Error::MissingSymbol(name.to_owned()))
199 }
200
201 pub fn required_field_offset(&self, struct_name: &str, field_name: &str) -> Result<usize> {
203 self.symbols()
204 .field_offset(struct_name, field_name)
205 .map(|v| v as usize)
206 .ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))
207 }
208
209 fn read_u64_at(&self, vaddr: u64) -> Result<u64> {
210 let mut buf = [0u8; 8];
211 self.vas.read_virt(vaddr, &mut buf)?;
212 Ok(u64::from_le_bytes(buf))
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::test_builders::{flags, PageTableBuilder};
220 use crate::vas::TranslationMode;
221 use memf_symbols::isf::IsfResolver;
222 use memf_symbols::test_builders::IsfBuilder;
223
224 fn make_reader(
225 isf: &IsfBuilder,
226 builder: PageTableBuilder,
227 ) -> ObjectReader<crate::test_builders::SyntheticPhysMem> {
228 let json = isf.build_json();
229 let resolver = IsfResolver::from_value(&json).unwrap();
230 let (cr3, mem) = builder.build();
231 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
232 ObjectReader::new(vas, Box::new(resolver))
233 }
234
235 #[test]
236 fn read_field_u32() {
237 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
238 "task_struct",
239 "pid",
240 0,
241 "int",
242 );
243
244 let vaddr: u64 = 0xFFFF_8000_0010_0000;
245 let paddr: u64 = 0x0080_0000;
246
247 let ptb = PageTableBuilder::new()
248 .map_4k(vaddr, paddr, flags::WRITABLE)
249 .write_phys_u64(paddr, 42u32 as u64); let reader = make_reader(&isf, ptb);
252 let pid: u32 = reader.read_field(vaddr, "task_struct", "pid").unwrap();
253 assert_eq!(pid, 42);
254 }
255
256 #[test]
257 fn read_field_u64() {
258 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
259 "task_struct",
260 "mm",
261 8,
262 "pointer",
263 );
264
265 let vaddr: u64 = 0xFFFF_8000_0010_0000;
266 let paddr: u64 = 0x0080_0000;
267 let mm_value: u64 = 0xFFFF_8000_DEAD_BEEF;
268
269 let ptb = PageTableBuilder::new()
270 .map_4k(vaddr, paddr, flags::WRITABLE)
271 .write_phys_u64(paddr + 8, mm_value);
272
273 let reader = make_reader(&isf, ptb);
274 let mm: u64 = reader.read_field(vaddr, "task_struct", "mm").unwrap();
275 assert_eq!(mm, mm_value);
276 }
277
278 #[test]
279 fn read_field_missing_symbol() {
280 let isf = IsfBuilder::new().add_struct("task_struct", 128);
281
282 let vaddr: u64 = 0xFFFF_8000_0010_0000;
283 let paddr: u64 = 0x0080_0000;
284
285 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
286
287 let reader = make_reader(&isf, ptb);
288 let result = reader.read_field::<u32>(vaddr, "task_struct", "nonexistent");
289 assert!(result.is_err());
290 match result.unwrap_err() {
291 Error::MissingSymbol(s) => assert_eq!(s, "task_struct.nonexistent"),
292 other => panic!("unexpected error: {other}"),
293 }
294 }
295
296 #[test]
297 fn read_field_string_test() {
298 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
299 "task_struct",
300 "comm",
301 16,
302 "char",
303 );
304
305 let vaddr: u64 = 0xFFFF_8000_0010_0000;
306 let paddr: u64 = 0x0080_0000;
307
308 let ptb = PageTableBuilder::new()
309 .map_4k(vaddr, paddr, flags::WRITABLE)
310 .write_phys(paddr + 16, b"systemd\0");
311
312 let reader = make_reader(&isf, ptb);
313 let comm = reader
314 .read_field_string(vaddr, "task_struct", "comm", 16)
315 .unwrap();
316 assert_eq!(comm, "systemd");
317 }
318
319 #[test]
320 fn read_string_with_null() {
321 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
322 "task_struct",
323 "comm",
324 16,
325 "char",
326 );
327
328 let vaddr: u64 = 0xFFFF_8000_0010_0000;
329 let paddr: u64 = 0x0080_0000;
330
331 let ptb = PageTableBuilder::new()
332 .map_4k(vaddr, paddr, flags::WRITABLE)
333 .write_phys(paddr + 16, b"init\0\0\0\0\0\0\0\0\0\0\0\0");
334
335 let reader = make_reader(&isf, ptb);
336 let s = reader.read_string(vaddr + 16, 16).unwrap();
337 assert_eq!(s, "init");
338 }
339
340 #[test]
341 fn walk_list_simple() {
342 let isf = IsfBuilder::new()
348 .add_struct("task_struct", 128)
349 .add_field("task_struct", "pid", 0, "int")
350 .add_field("task_struct", "tasks", 8, "list_head")
351 .add_field("task_struct", "comm", 16, "char")
352 .add_struct("list_head", 16)
353 .add_field("list_head", "next", 0, "pointer")
354 .add_field("list_head", "prev", 8, "pointer");
355
356 let head_paddr: u64 = 0x0080_0000;
364 let a_paddr: u64 = 0x0080_1000;
365 let b_paddr: u64 = 0x0080_2000;
366
367 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
368 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
369 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
370
371 let list_offset: u64 = 8; let ptb = PageTableBuilder::new()
377 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
378 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
379 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
380 .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);
391
392 let containers = reader
393 .walk_list(head_vaddr + list_offset, "task_struct", "tasks")
394 .unwrap();
395 assert_eq!(containers.len(), 2);
396 assert_eq!(containers[0], a_vaddr);
397 assert_eq!(containers[1], b_vaddr);
398 }
399
400 #[test]
401 fn read_pointer_test() {
402 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
403 "task_struct",
404 "mm",
405 8,
406 "pointer",
407 );
408
409 let vaddr: u64 = 0xFFFF_8000_0010_0000;
410 let paddr: u64 = 0x0080_0000;
411 let mm_value: u64 = 0xFFFF_8000_CAFE_BABE;
412
413 let ptb = PageTableBuilder::new()
414 .map_4k(vaddr, paddr, flags::WRITABLE)
415 .write_phys_u64(paddr + 8, mm_value);
416
417 let reader = make_reader(&isf, ptb);
418 let ptr = reader.read_pointer(vaddr, "task_struct", "mm").unwrap();
419 assert_eq!(ptr, mm_value);
420 }
421
422 #[test]
423 fn read_field_invalid_struct_name() {
424 let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
425 "task_struct",
426 "pid",
427 0,
428 "int",
429 );
430
431 let vaddr: u64 = 0xFFFF_8000_0010_0000;
432 let paddr: u64 = 0x0080_0000;
433
434 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
435
436 let reader = make_reader(&isf, ptb);
437 let result = reader.read_field::<u32>(vaddr, "nonexistent_struct", "pid");
438 assert!(result.is_err());
439 match result.unwrap_err() {
440 Error::MissingSymbol(s) => assert_eq!(s, "nonexistent_struct.pid"),
441 other => panic!("unexpected error: {other}"),
442 }
443 }
444
445 #[test]
446 fn walk_list_empty_list() {
447 let isf = IsfBuilder::new()
449 .add_struct("task_struct", 128)
450 .add_field("task_struct", "tasks", 8, "list_head")
451 .add_struct("list_head", 16)
452 .add_field("list_head", "next", 0, "pointer")
453 .add_field("list_head", "prev", 8, "pointer");
454
455 let head_paddr: u64 = 0x0080_0000;
456 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
457 let list_offset: u64 = 8;
458
459 let ptb = PageTableBuilder::new()
461 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
462 .write_phys_u64(head_paddr + list_offset, head_vaddr + list_offset);
463
464 let reader = make_reader(&isf, ptb);
465 let containers = reader
466 .walk_list(head_vaddr + list_offset, "task_struct", "tasks")
467 .unwrap();
468 assert!(containers.is_empty());
469 }
470
471 #[test]
472 fn walk_list_with_windows_list_entry() {
473 let isf = IsfBuilder::new()
477 .add_struct("_EPROCESS", 256)
478 .add_field("_EPROCESS", "UniqueProcessId", 0, "pointer")
479 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
480 .add_struct("_LIST_ENTRY", 16)
481 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
482 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
483
484 let head_paddr: u64 = 0x0080_0000;
489 let a_paddr: u64 = 0x0080_1000;
490 let b_paddr: u64 = 0x0080_2000;
491
492 let head_vaddr: u64 = 0xFFFF_8000_0010_0000; let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
494 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
495
496 let list_offset: u64 = 0x10; let ptb = PageTableBuilder::new()
500 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
501 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
502 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
503 .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);
513
514 let containers = reader
515 .walk_list_with(
516 head_vaddr,
517 "_LIST_ENTRY",
518 "Flink",
519 "_EPROCESS",
520 "ActiveProcessLinks",
521 )
522 .unwrap();
523
524 assert_eq!(containers.len(), 2);
525 assert_eq!(containers[0], a_vaddr);
526 assert_eq!(containers[1], b_vaddr);
527 }
528
529 #[test]
530 fn walk_list_with_empty() {
531 let isf = IsfBuilder::new()
533 .add_struct("_EPROCESS", 256)
534 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
535 .add_struct("_LIST_ENTRY", 16)
536 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
537 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
538
539 let head_paddr: u64 = 0x0080_0000;
540 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
541
542 let ptb = PageTableBuilder::new()
544 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
545 .write_phys_u64(head_paddr, head_vaddr); let reader = make_reader(&isf, ptb);
548
549 let containers = reader
550 .walk_list_with(
551 head_vaddr,
552 "_LIST_ENTRY",
553 "Flink",
554 "_EPROCESS",
555 "ActiveProcessLinks",
556 )
557 .unwrap();
558
559 assert!(containers.is_empty());
560 }
561
562 #[test]
563 fn walk_list_still_works_after_refactor() {
564 let isf = IsfBuilder::new()
567 .add_struct("task_struct", 128)
568 .add_field("task_struct", "pid", 0, "int")
569 .add_field("task_struct", "tasks", 8, "list_head")
570 .add_struct("list_head", 16)
571 .add_field("list_head", "next", 0, "pointer")
572 .add_field("list_head", "prev", 8, "pointer");
573
574 let head_paddr: u64 = 0x0080_0000;
575 let a_paddr: u64 = 0x0080_1000;
576
577 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
578 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
579
580 let list_offset: u64 = 8;
581
582 let ptb = PageTableBuilder::new()
584 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
585 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
586 .write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
587 .write_phys_u64(a_paddr, 42) .write_phys_u64(a_paddr + list_offset, head_vaddr + list_offset);
589
590 let reader = make_reader(&isf, ptb);
591
592 let containers = reader
593 .walk_list(head_vaddr + list_offset, "task_struct", "tasks")
594 .unwrap();
595 assert_eq!(containers.len(), 1);
596 assert_eq!(containers[0], a_vaddr);
597 }
598
599 #[test]
600 fn symbols_accessor() {
601 let isf = IsfBuilder::new()
602 .add_struct("task_struct", 128)
603 .add_field("task_struct", "pid", 0, "int")
604 .add_symbol("init_task", 0xFFFF_0000);
605
606 let vaddr: u64 = 0xFFFF_8000_0010_0000;
607 let paddr: u64 = 0x0080_0000;
608 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
609
610 let reader = make_reader(&isf, ptb);
611 assert_eq!(reader.symbols().backend_name(), "ISF JSON");
612 assert_eq!(reader.symbols().field_offset("task_struct", "pid"), Some(0));
613 }
614
615 #[test]
616 fn required_symbol_ok() {
617 let isf = IsfBuilder::new()
618 .add_struct("task_struct", 128)
619 .add_symbol("init_task", 0xFFFF_8000_CAFE_0000);
620
621 let vaddr: u64 = 0xFFFF_8000_0010_0000;
622 let paddr: u64 = 0x0080_0000;
623 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
624
625 let reader = make_reader(&isf, ptb);
626 assert_eq!(
627 reader.required_symbol("init_task").unwrap(),
628 0xFFFF_8000_CAFE_0000
629 );
630 }
631
632 #[test]
633 fn required_symbol_missing_returns_error() {
634 let isf = IsfBuilder::new().add_struct("task_struct", 128);
635
636 let vaddr: u64 = 0xFFFF_8000_0010_0000;
637 let paddr: u64 = 0x0080_0000;
638 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
639
640 let reader = make_reader(&isf, ptb);
641 assert!(reader.required_symbol("nonexistent").is_err());
642 }
643
644 #[test]
645 fn required_field_offset_ok() {
646 let isf = IsfBuilder::new()
647 .add_struct("task_struct", 128)
648 .add_field("task_struct", "pid", 4, "int");
649
650 let vaddr: u64 = 0xFFFF_8000_0010_0000;
651 let paddr: u64 = 0x0080_0000;
652 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
653
654 let reader = make_reader(&isf, ptb);
655 assert_eq!(
656 reader.required_field_offset("task_struct", "pid").unwrap(),
657 4
658 );
659 }
660
661 #[test]
662 fn required_field_offset_missing_returns_error() {
663 let isf = IsfBuilder::new().add_struct("task_struct", 128);
664
665 let vaddr: u64 = 0xFFFF_8000_0010_0000;
666 let paddr: u64 = 0x0080_0000;
667 let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
668
669 let reader = make_reader(&isf, ptb);
670 assert!(reader
671 .required_field_offset("task_struct", "nonexistent")
672 .is_err());
673 }
674
675 #[test]
676 fn walk_list_cycle_detection() {
677 let isf = IsfBuilder::new()
680 .add_struct("_EPROCESS", 256)
681 .add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
682 .add_struct("_LIST_ENTRY", 16)
683 .add_field("_LIST_ENTRY", "Flink", 0, "pointer")
684 .add_field("_LIST_ENTRY", "Blink", 8, "pointer");
685
686 let head_paddr: u64 = 0x0080_0000;
688 let a_paddr: u64 = 0x0080_1000;
689 let b_paddr: u64 = 0x0080_2000;
690
691 let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
692 let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
693 let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
694
695 let list_offset: u64 = 0x10; let ptb = PageTableBuilder::new()
701 .map_4k(head_vaddr, head_paddr, flags::WRITABLE)
702 .map_4k(a_vaddr, a_paddr, flags::WRITABLE)
703 .map_4k(b_vaddr, b_paddr, flags::WRITABLE)
704 .write_phys_u64(head_paddr, a_vaddr + list_offset)
706 .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
708 .write_phys_u64(b_paddr + list_offset, a_vaddr + list_offset);
710
711 let reader = make_reader(&isf, ptb);
712 let result = reader.walk_list_with(
713 head_vaddr,
714 "_LIST_ENTRY",
715 "Flink",
716 "_EPROCESS",
717 "ActiveProcessLinks",
718 );
719
720 assert!(
721 matches!(result, Err(Error::ListCycle(_))),
722 "expected ListCycle error, got: {result:?}"
723 );
724 }
725}