kdmp_parser/phys.rs
1// Axel '0vercl0k' Souchet - November 9 2025
2//! Everything related to physical memory.
3use core::slice;
4use std::cmp::min;
5use std::io::SeekFrom;
6use std::mem::MaybeUninit;
7
8use crate::error::{Error, PageReadError, Result};
9use crate::gxa::{Gpa, Gxa};
10use crate::parse::KernelDumpParser;
11use crate::structs::PageKind;
12
13/// A reader lets you translate & read physical memory from a dump file.
14pub struct Reader<'parser> {
15 parser: &'parser KernelDumpParser,
16}
17
18impl<'parser> Reader<'parser> {
19 pub fn new(parser: &'parser KernelDumpParser) -> Self {
20 Self { parser }
21 }
22
23 /// Translate a [`Gpa`] into a file offset. At that file offset is where the
24 /// content of the page resides in.
25 pub fn translate(&self, gpa: Gpa) -> Result<SeekFrom> {
26 let Some(base_offset) = self.parser.physmem.get(&gpa.page_align()) else {
27 return Err(PageReadError::NotInDump { gva: None, gpa }.into());
28 };
29
30 base_offset
31 .checked_add(gpa.offset())
32 .map(SeekFrom::Start)
33 .ok_or(Error::Overflow("w/ gpa offset"))
34 }
35
36 /// Read the exact amount of bytes asked by the user & return a
37 /// [`Error::PartialRead`] error if it couldn't read as much as
38 /// wanted.
39 pub fn read_exact(&self, gpa: Gpa, buf: &mut [u8]) -> Result<()> {
40 // Amount of bytes left to read.
41 let mut amount_left = buf.len();
42 // Total amount of bytes that we have successfully read.
43 let mut total_read = 0;
44 // The current gpa we are reading from.
45 let mut addr = gpa;
46 // Let's try to read as much as the user wants.
47 while amount_left > 0 {
48 // Translate the gpa into a file offset..
49 let offset = match self.translate(addr) {
50 Ok(o) => o,
51 Err(Error::PageRead(PageReadError::NotInDump { gva: None, gpa })) => {
52 return Err(Error::PartialRead {
53 expected_amount: buf.len(),
54 actual_amount: total_read,
55 reason: PageReadError::NotInDump { gva: None, gpa },
56 });
57 }
58 Err(Error::PageRead(_)) => {
59 // We should never get there; `translate` can only fail with a
60 // [`PageReadError::NotInDump`] if and only if the gpa
61 // doesn't exist in the dump.
62 unreachable!();
63 }
64 Err(e) => return Err(e),
65 };
66 // ..and seek the reader there.
67 self.parser.seek(offset)?;
68 // We need to take care of reads that straddle different physical memory pages.
69 // So let's figure out the maximum amount of bytes we can read off this page.
70 // Either, we read it until its end, or we stop if the user wants us to read
71 // less.
72 let left_in_page = usize::try_from(PageKind::Normal.size() - gpa.offset()).unwrap();
73 let amount_wanted = min(amount_left, left_in_page);
74 // Figure out where we should read into.
75 let slice = &mut buf[total_read..total_read + amount_wanted];
76 // Read the physical memory!
77 self.parser.read_exact(slice)?;
78 // Update the total amount of read bytes and how much work we have left.
79 total_read += amount_wanted;
80 amount_left -= amount_wanted;
81 // We have more work to do, so let's move to the next page.
82 addr = addr.next_aligned_page();
83 }
84
85 // Yay, we read as much bytes as the user wanted!
86 Ok(())
87 }
88
89 /// Read the physical memory starting at `gpa` into `buf`. If it cannot read
90 /// as much as asked by the user because the dump file is missing a physical
91 /// memory page, the function doesn't error out and return the amount of
92 /// bytes that was successfully read.
93 pub fn read(&self, gpa: Gpa, buf: &mut [u8]) -> Result<usize> {
94 match self.read_exact(gpa, buf) {
95 Ok(()) => Ok(buf.len()),
96 Err(Error::PartialRead { actual_amount, .. }) => Ok(actual_amount),
97 Err(e) => Err(e),
98 }
99 }
100
101 /// Read a `T` from physical memory.
102 pub fn read_struct<T>(&self, gpa: Gpa) -> Result<T> {
103 let mut t: MaybeUninit<T> = MaybeUninit::uninit();
104 let size_of_t = size_of_val(&t);
105 let slice_over_t =
106 unsafe { slice::from_raw_parts_mut(t.as_mut_ptr().cast::<u8>(), size_of_t) };
107
108 self.read_exact(gpa, slice_over_t)?;
109
110 Ok(unsafe { t.assume_init() })
111 }
112}