kdmp_parser/
virt.rs

1// Axel '0vercl0k' Souchet - November 9 2025
2//! Everything related to virtual memory.
3use core::slice;
4use std::cmp::min;
5use std::mem::MaybeUninit;
6
7use crate::error::{Error, PageReadError, PxeKind, Result};
8use crate::gxa::{Gpa, Gva, Gxa};
9use crate::parse::KernelDumpParser;
10use crate::phys;
11use crate::pxe::{Pfn, Pxe};
12use crate::structs::{PageKind, Pod};
13
14/// The details related to a virtual to physical address translation.
15///
16/// If you are wondering why there is no 'readable' field, it is because
17/// [`Reader::translate`] returns an error if one of the PXE is
18/// marked as not present. In other words, if the translation succeeds, the page
19/// is at least readable.
20#[derive(Debug)]
21pub struct Translation {
22    /// The physical address backing the virtual address that was requested.
23    pub pfn: Pfn,
24    /// The byte offset in that physical page.
25    pub offset: u64,
26    /// The kind of physical page.
27    pub page_kind: PageKind,
28    /// Is the page writable?
29    pub writable: bool,
30    /// Is the page executable?
31    pub executable: bool,
32    /// Is the page user accessible?
33    pub user_accessible: bool,
34}
35
36impl Translation {
37    #[must_use]
38    pub fn huge_page(pxes: &[Pxe; 2], gva: Gva) -> Self {
39        Self::inner_new(pxes, gva)
40    }
41
42    #[must_use]
43    pub fn large_page(pxes: &[Pxe; 3], gva: Gva) -> Self {
44        Self::inner_new(pxes, gva)
45    }
46
47    #[must_use]
48    pub fn new(pxes: &[Pxe; 4], gva: Gva) -> Self {
49        Self::inner_new(pxes, gva)
50    }
51
52    fn inner_new(pxes: &[Pxe], gva: Gva) -> Self {
53        let writable = pxes.iter().all(Pxe::writable);
54        let executable = pxes.iter().all(Pxe::executable);
55        let user_accessible = pxes.iter().all(Pxe::user_accessible);
56        let pfn = pxes.last().map(|p| p.pfn).expect("at least one pxe");
57        let page_kind = match pxes.len() {
58            4 => PageKind::Normal,
59            3 => PageKind::Large,
60            2 => PageKind::Huge,
61            _ => unreachable!("pxes len should be between 2 and 4"),
62        };
63        let offset = page_kind.page_offset(gva.u64());
64
65        Self {
66            pfn,
67            offset,
68            page_kind,
69            writable,
70            executable,
71            user_accessible,
72        }
73    }
74
75    #[must_use]
76    pub fn gpa(&self) -> Gpa {
77        self.pfn.gpa_with_offset(self.offset)
78    }
79}
80
81pub(crate) fn ignore_non_fatal<T>(r: Result<T>) -> Result<Option<T>> {
82    match r {
83        Ok(o) => Ok(Some(o)),
84        Err(Error::PageRead(_) | Error::PartialRead { .. }) => Ok(None),
85        Err(e) => Err(e),
86    }
87}
88
89/// A reader lets you translate & read virtual memory from a dump file.
90pub struct Reader<'parser> {
91    parser: &'parser KernelDumpParser,
92    dtb: Gpa,
93}
94
95impl<'parser> Reader<'parser> {
96    pub fn new(parser: &'parser KernelDumpParser) -> Self {
97        Self::with_dtb(parser, Gpa::new(parser.headers().directory_table_base))
98    }
99
100    pub fn with_dtb(parser: &'parser KernelDumpParser, dtb: Gpa) -> Self {
101        Self { parser, dtb }
102    }
103
104    /// Translate a [`Gva`] into a [`Gpa`].
105    #[expect(clippy::similar_names)]
106    pub fn translate(&self, gva: Gva) -> Result<Translation> {
107        let read_pxe = |gpa: Gpa, pxe: PxeKind| -> Result<Pxe> {
108            let r = phys::Reader::new(self.parser);
109            let Ok(pxe) = r.read_struct::<u64>(gpa).map(Pxe::from) else {
110                // If the physical page isn't in the dump, enrich the error by adding the gva
111                // that was getting translated as well as the pxe level we were at.
112                return Err(PageReadError::NotInDump {
113                    gva: Some((gva, Some(pxe))),
114                    gpa,
115                }
116                .into());
117            };
118
119            Ok(pxe)
120        };
121
122        // Aligning in case PCID bits are set (bits 11:0)
123        let pml4_base = self.dtb.page_align();
124        let pml4e_gpa = Gpa::new(pml4_base.u64() + (gva.pml4e_idx() * 8));
125        let pml4e = read_pxe(pml4e_gpa, PxeKind::Pml4e)?;
126        if !pml4e.present() {
127            return Err(PageReadError::NotPresent {
128                gva,
129                which_pxe: PxeKind::Pml4e,
130            }
131            .into());
132        }
133
134        let pdpt_base = pml4e.pfn.gpa();
135        let pdpte_gpa = Gpa::new(pdpt_base.u64() + (gva.pdpe_idx() * 8));
136        let pdpte = read_pxe(pdpte_gpa, PxeKind::Pdpte)?;
137        if !pdpte.present() {
138            return Err(PageReadError::NotPresent {
139                gva,
140                which_pxe: PxeKind::Pdpte,
141            }
142            .into());
143        }
144
145        // huge pages:
146        // 7 (PS) - Page size; must be 1 (otherwise, this entry references a page
147        // directory; see Table 4-1.
148        let pd_base = pdpte.pfn.gpa();
149        if pdpte.large_page() {
150            return Ok(Translation::huge_page(&[pml4e, pdpte], gva));
151        }
152
153        let pde_gpa = Gpa::new(pd_base.u64() + (gva.pde_idx() * 8));
154        let pde = read_pxe(pde_gpa, PxeKind::Pde)?;
155        if !pde.present() {
156            return Err(PageReadError::NotPresent {
157                gva,
158                which_pxe: PxeKind::Pde,
159            }
160            .into());
161        }
162
163        // large pages:
164        // 7 (PS) - Page size; must be 1 (otherwise, this entry references a page
165        // table; see Table 4-18.
166        let pt_base = pde.pfn.gpa();
167        if pde.large_page() {
168            return Ok(Translation::large_page(&[pml4e, pdpte, pde], gva));
169        }
170
171        let pte_gpa = Gpa::new(pt_base.u64() + (gva.pte_idx() * 8));
172        let pte = read_pxe(pte_gpa, PxeKind::Pte)?;
173        if !pte.present() {
174            // We'll allow reading from a transition PTE, so return an error only if it's
175            // not one, otherwise we'll carry on.
176            if !pte.transition() {
177                return Err(PageReadError::NotPresent {
178                    gva,
179                    which_pxe: PxeKind::Pte,
180                }
181                .into());
182            }
183        }
184
185        Ok(Translation::new(&[pml4e, pdpte, pde, pte], gva))
186    }
187
188    /// Read the exact amount of bytes asked by the user & return a
189    /// [`Error::PartialRead`] error if it couldn't read as much as wanted.
190    pub fn read_exact(&self, gva: Gva, buf: &mut [u8]) -> Result<()> {
191        // Amount of bytes left to read.
192        let mut amount_left = buf.len();
193        // Total amount of bytes that we have successfully read.
194        let mut total_read = 0;
195        // The current gva we are reading from.
196        let mut addr = gva;
197        // Let's try to read as much as the user wants.
198        while amount_left > 0 {
199            // Translate the gva into a gpa.
200            let translation = match self.translate(addr) {
201                Ok(t) => t,
202                Err(Error::PageRead(reason)) => {
203                    return Err(Error::PartialRead {
204                        expected_amount: buf.len(),
205                        actual_amount: total_read,
206                        reason,
207                    });
208                }
209                // ..otherwise this is an error.
210                Err(e) => return Err(e),
211            };
212
213            // We need to take care of reads that straddle different virtual memory pages.
214            // First, figure out the maximum amount of bytes we can read off this page.
215            let left_in_page =
216                usize::try_from(translation.page_kind.size() - translation.offset).unwrap();
217            // Then, either we read it until its end, or we stop before if we can get by
218            // with less.
219            let amount_wanted = min(amount_left, left_in_page);
220            // Figure out where we should read into.
221            let slice = &mut buf[total_read..total_read + amount_wanted];
222
223            // Read the physical memory!
224            let gpa = translation.gpa();
225            match phys::Reader::new(self.parser).read_exact(gpa, slice) {
226                Ok(()) => {}
227                Err(Error::PartialRead {
228                    actual_amount,
229                    reason: PageReadError::NotInDump { gva: None, gpa },
230                    ..
231                }) => {
232                    // Augment `NotInDump` with the `gva` as `phys::Reader::read_exact` doesn't know
233                    // anything about it.
234                    let reason = PageReadError::NotInDump {
235                        gva: Some((addr, None)),
236                        gpa,
237                    };
238
239                    return Err(Error::PartialRead {
240                        expected_amount: buf.len(),
241                        actual_amount: total_read + actual_amount,
242                        reason,
243                    });
244                }
245                Err(Error::PartialRead { .. }) => {
246                    // We should never get there; `phys::Reader::read_exact` can only return a
247                    // [`PageReadError::NotInDump`] error if it cannot read the gpa because it
248                    // isn't in the dump.
249                    unreachable!();
250                }
251                Err(e) => return Err(e),
252            }
253
254            // Update the total amount of read bytes and how much work we have left.
255            total_read += amount_wanted;
256            amount_left -= amount_wanted;
257            // We have more work to do, so let's move to the next page.
258            addr = addr.next_aligned_page();
259        }
260
261        // Yay, we read as much bytes as the user wanted!
262        Ok(())
263    }
264
265    /// Read the virtual memory starting at `gva` into `buf`. If it cannot read
266    /// as much as asked by the user because the dump file is missing a physical
267    /// memory page or because one of the PXE is non present, the function
268    /// doesn't error out and return the amount of bytes that was
269    /// successfully read.
270    pub fn read(&self, gva: Gva, buf: &mut [u8]) -> Result<usize> {
271        match self.read_exact(gva, buf) {
272            Ok(()) => Ok(buf.len()),
273            Err(Error::PartialRead { actual_amount, .. }) => Ok(actual_amount),
274            Err(e) => Err(e),
275        }
276    }
277
278    /// Read a `T` from virtual memory.
279    pub fn read_struct<T: Pod>(&self, gva: Gva) -> Result<T> {
280        let mut t: MaybeUninit<T> = MaybeUninit::uninit();
281        let size_of_t = size_of_val(&t);
282        let slice_over_t =
283            unsafe { slice::from_raw_parts_mut(t.as_mut_ptr().cast::<u8>(), size_of_t) };
284
285        self.read_exact(gva, slice_over_t)?;
286
287        Ok(unsafe { t.assume_init() })
288    }
289
290    /// Try to translate `gva` into [`Gpa`].
291    pub fn try_translate(&self, gva: Gva) -> Result<Option<Translation>> {
292        ignore_non_fatal(self.translate(gva))
293    }
294
295    /// Try to read the exact amount of bytes asked by the user.
296    pub fn try_read_exact(&self, gva: Gva, buf: &mut [u8]) -> Result<Option<()>> {
297        ignore_non_fatal(self.read_exact(gva, buf))
298    }
299
300    /// Try to read a `T` from virtual memory.
301    pub fn try_read_struct<T: Pod>(&self, gva: Gva) -> Result<Option<T>> {
302        ignore_non_fatal(self.read_struct(gva))
303    }
304}