1use 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#[derive(Debug)]
21pub struct Translation {
22 pub pfn: Pfn,
24 pub offset: u64,
26 pub page_kind: PageKind,
28 pub writable: bool,
30 pub executable: bool,
32 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
89pub 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 #[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 return Err(PageReadError::NotInDump {
113 gva: Some((gva, Some(pxe))),
114 gpa,
115 }
116 .into());
117 };
118
119 Ok(pxe)
120 };
121
122 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 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 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 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 pub fn read_exact(&self, gva: Gva, buf: &mut [u8]) -> Result<()> {
191 let mut amount_left = buf.len();
193 let mut total_read = 0;
195 let mut addr = gva;
197 while amount_left > 0 {
199 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 Err(e) => return Err(e),
211 };
212
213 let left_in_page =
216 usize::try_from(translation.page_kind.size() - translation.offset).unwrap();
217 let amount_wanted = min(amount_left, left_in_page);
220 let slice = &mut buf[total_read..total_read + amount_wanted];
222
223 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 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 unreachable!();
250 }
251 Err(e) => return Err(e),
252 }
253
254 total_read += amount_wanted;
256 amount_left -= amount_wanted;
257 addr = addr.next_aligned_page();
259 }
260
261 Ok(())
263 }
264
265 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 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 pub fn try_translate(&self, gva: Gva) -> Result<Option<Translation>> {
292 ignore_non_fatal(self.translate(gva))
293 }
294
295 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 pub fn try_read_struct<T: Pod>(&self, gva: Gva) -> Result<Option<T>> {
302 ignore_non_fatal(self.read_struct(gva))
303 }
304}