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}