1use core::slice;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::fmt::{self, Debug};
7use std::fs::File;
8use std::mem::MaybeUninit;
9use std::ops::Range;
10use std::path::Path;
11use std::{io, mem};
12
13use crate::bits::Bits;
14use crate::error::{Error, Result};
15use crate::gxa::{Gpa, Gva};
16use crate::map::{MappedFileReader, Reader};
17use crate::pxe::Pfn;
18use crate::structs::{
19 BmpHeader64, Context, DUMP_HEADER64_EXPECTED_SIGNATURE, DUMP_HEADER64_EXPECTED_VALID_DUMP,
20 DumpType, ExceptionRecord64, FullRdmpHeader64, Header64, KdDebuggerData64, KernelRdmpHeader64,
21 PageKind, PfnRange, PhysmemDesc, PhysmemMap, PhysmemRun, Pod,
22};
23use crate::virt;
24use crate::virt_utils::{
25 ModuleMap, try_extract_kernel_modules, try_extract_user_modules, try_find_prcb,
26};
27
28fn gpa_from_bitmap(bitmap_idx: u64, bit_idx: usize) -> Option<Gpa> {
29 let pfn = Pfn::new(
30 bitmap_idx
31 .checked_mul(8)?
32 .checked_add(bit_idx.try_into().ok()?)?,
33 );
34
35 Some(pfn.gpa())
36}
37
38fn gpa_from_pfn_range(pfn_range: &PfnRange, page_idx: u64) -> Option<Gpa> {
39 let offset = page_idx.checked_mul(PageKind::Normal.size())?;
40
41 Some(Pfn::new(pfn_range.page_file_number).gpa_with_offset(offset))
42}
43
44fn read_struct<T: Pod>(reader: &mut impl Reader) -> Result<T> {
46 let mut s: MaybeUninit<T> = MaybeUninit::uninit();
47 let size_of_s = size_of_val(&s);
48 let slice_over_s = unsafe { slice::from_raw_parts_mut(s.as_mut_ptr().cast::<u8>(), size_of_s) };
49 reader.read_exact(slice_over_s)?;
50
51 Ok(unsafe { s.assume_init() })
52}
53
54pub struct KernelDumpParser {
58 dump_type: DumpType,
60 context: Box<Context>,
62 headers: Box<Header64>,
64 pub(crate) physmem: PhysmemMap,
67 reader: RefCell<Box<dyn Reader>>,
70 kernel_modules: ModuleMap,
73 user_modules: ModuleMap,
76}
77
78impl Debug for KernelDumpParser {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 f.debug_struct("KernelDumpParser")
81 .field("dump_type", &self.dump_type)
82 .finish_non_exhaustive()
83 }
84}
85
86impl KernelDumpParser {
87 pub fn with_reader(mut reader: impl Reader + 'static) -> Result<Self> {
89 let headers = Box::new(read_struct::<Header64>(&mut reader)?);
91 if headers.signature != DUMP_HEADER64_EXPECTED_SIGNATURE {
92 return Err(Error::InvalidSignature(headers.signature));
93 }
94
95 if headers.valid_dump != DUMP_HEADER64_EXPECTED_VALID_DUMP {
96 return Err(Error::InvalidValidDump(headers.valid_dump));
97 }
98
99 let dump_type = DumpType::try_from(headers.dump_type)?;
101
102 let physmem = Self::build_physmem(dump_type, &headers, &mut reader)?;
104
105 let context = Box::new(read_struct(&mut io::Cursor::new(
107 headers.context_record_buffer.as_slice(),
108 ))?);
109
110 let reader: RefCell<Box<dyn Reader>> = RefCell::new(Box::new(reader));
111 let mut parser = Self {
112 dump_type,
113 context,
114 headers,
115 physmem,
116 reader,
117 kernel_modules: HashMap::default(),
118 user_modules: HashMap::default(),
119 };
120
121 if let Some(kernel_modules) = try_extract_kernel_modules(&parser)? {
124 parser.kernel_modules.extend(kernel_modules);
125 }
126
127 let virt_reader = virt::Reader::new(&parser);
132 let Some(kd_debugger_data_block) = virt_reader
133 .try_read_struct::<KdDebuggerData64>(parser.headers().kd_debugger_data_block.into())?
134 else {
135 return Ok(parser);
136 };
137 let kd_debugger_data_block = Box::new(kd_debugger_data_block);
138
139 let Some(prcb_addr) = try_find_prcb(&parser, &kd_debugger_data_block)? else {
141 return Ok(parser);
142 };
143
144 let Some(user_modules) =
146 try_extract_user_modules(&virt_reader, &kd_debugger_data_block, prcb_addr)?
147 else {
148 return Ok(parser);
149 };
150
151 parser.user_modules.extend(user_modules);
152
153 Ok(parser)
154 }
155
156 pub fn new(dump_path: impl AsRef<Path>) -> Result<Self> {
159 const FOUR_GIGS: u64 = 1_024 * 1_024 * 1_024 * 4;
160 let size = dump_path.as_ref().metadata()?.len();
163
164 if let 0..=FOUR_GIGS = size {
165 let mapped_file = MappedFileReader::new(dump_path.as_ref())?;
166
167 Self::with_reader(mapped_file)
168 } else {
169 let file = File::open(dump_path)?;
170
171 Self::with_reader(file)
172 }
173 }
174
175 pub fn physmem(&self) -> impl ExactSizeIterator<Item = (Gpa, u64)> + '_ {
179 self.physmem.iter().map(|(&k, &v)| (k, v))
180 }
181
182 pub fn kernel_modules(&self) -> impl ExactSizeIterator<Item = (&Range<Gva>, &str)> + '_ {
184 self.kernel_modules.iter().map(|(k, v)| (k, v.as_str()))
185 }
186
187 pub fn user_modules(&self) -> impl ExactSizeIterator<Item = (&Range<Gva>, &str)> + '_ {
189 self.user_modules.iter().map(|(k, v)| (k, v.as_str()))
190 }
191
192 pub fn dump_type(&self) -> DumpType {
194 self.dump_type
195 }
196
197 pub fn headers(&self) -> &Header64 {
199 &self.headers
200 }
201
202 pub fn exception_record(&self) -> &ExceptionRecord64 {
204 &self.headers.exception
205 }
206
207 pub fn context_record(&self) -> &Context {
209 &self.context
210 }
211
212 pub(crate) fn seek(&self, pos: io::SeekFrom) -> Result<u64> {
214 Ok(self.reader.borrow_mut().seek(pos)?)
215 }
216
217 pub(crate) fn read_exact(&self, buf: &mut [u8]) -> Result<()> {
219 Ok(self.reader.borrow_mut().read_exact(buf)?)
220 }
221
222 fn full_physmem(headers: &Header64, reader: &mut impl Reader) -> Result<PhysmemMap> {
240 let mut page_offset = reader.stream_position()?;
241 let mut run_cursor = io::Cursor::new(headers.physical_memory_block_buffer);
242 let physmem_desc = read_struct::<PhysmemDesc>(&mut run_cursor)?;
243 let mut physmem = PhysmemMap::new();
244
245 for run_idx in 0..physmem_desc.number_of_runs {
246 let run = read_struct::<PhysmemRun>(&mut run_cursor)?;
247 for page_idx in 0..run.page_count {
248 let phys_addr = run
250 .phys_addr(page_idx)
251 .ok_or(Error::PhysAddrOverflow(run_idx, page_idx))?;
252
253 if physmem.insert(phys_addr, page_offset).is_some() {
255 return Err(Error::DuplicateGpa(phys_addr));
256 }
257
258 page_offset = page_offset
260 .checked_add(PageKind::Normal.size())
261 .ok_or(Error::PageOffsetOverflow(run_idx, page_idx))?;
262 }
263 }
264
265 Ok(physmem)
266 }
267
268 fn bmp_physmem(reader: &mut impl Reader) -> Result<PhysmemMap> {
270 let bmp_header = read_struct::<BmpHeader64>(reader)?;
271 if !bmp_header.looks_good() {
272 return Err(Error::InvalidData("bmp header doesn't look right"));
273 }
274
275 let remaining_bits = bmp_header.pages % 8;
276 let bitmap_size = bmp_header.pages.next_multiple_of(8) / 8;
277 let mut page_offset = bmp_header.first_page;
278 let mut physmem = PhysmemMap::new();
279
280 for bitmap_idx in 0..bitmap_size {
282 let mut byte = [0u8];
283 reader.read_exact(&mut byte)?;
284 let last_byte = bitmap_idx == bitmap_size - 1;
286 if last_byte && remaining_bits != 0 {
287 let mask = (1u8 << remaining_bits).wrapping_sub(1);
289 byte[0] &= mask;
290 }
291
292 let byte = byte[0];
293 for bit_idx in 0..8 {
295 if byte.bit(bit_idx) == 0 {
297 continue;
298 }
299
300 let pa =
302 gpa_from_bitmap(bitmap_idx, bit_idx).ok_or(Error::Overflow("pfn in bitmap"))?;
303
304 let insert = physmem.insert(pa, page_offset);
305 debug_assert!(insert.is_none());
306 page_offset = page_offset
307 .checked_add(PageKind::Normal.size())
308 .ok_or(Error::BitmapPageOffsetOverflow(bitmap_idx, bit_idx))?;
309 }
310 }
311
312 Ok(physmem)
313 }
314
315 fn kernel_physmem(dump_type: DumpType, reader: &mut impl Reader) -> Result<PhysmemMap> {
318 use DumpType as D;
319 let mut page_count = 0u64;
320 let (mut page_offset, metadata_size, total_number_of_pages) = match dump_type {
321 D::KernelMemory | D::KernelAndUserMemory => {
322 let kernel_hdr = read_struct::<KernelRdmpHeader64>(reader)?;
323 if !kernel_hdr.hdr.looks_good() {
324 return Err(Error::InvalidData("RdmpHeader64 doesn't look right"));
325 }
326
327 (
328 kernel_hdr.hdr.first_page_offset,
329 kernel_hdr.hdr.metadata_size,
330 0,
331 )
332 }
333 D::CompleteMemory => {
334 let full_hdr = read_struct::<FullRdmpHeader64>(reader)?;
335 if !full_hdr.hdr.looks_good() {
336 return Err(Error::InvalidData("FullRdmpHeader64 doesn't look right"));
337 }
338
339 (
340 full_hdr.hdr.first_page_offset,
341 full_hdr.hdr.metadata_size,
342 full_hdr.total_number_of_pages,
343 )
344 }
345 _ => unreachable!(),
346 };
347
348 if page_offset == 0 || metadata_size == 0 {
349 return Err(Error::InvalidData("no first page or metadata size"));
350 }
351
352 let pfn_range_size = mem::size_of::<PfnRange>();
353 if (metadata_size % pfn_range_size as u64) != 0 {
354 return Err(Error::InvalidData("metadata size is not a multiple of 8"));
355 }
356
357 let number_pfns = metadata_size / pfn_range_size as u64;
358 let mut physmem = PhysmemMap::new();
359
360 for _ in 0..number_pfns {
361 if dump_type == D::CompleteMemory {
362 if page_count == total_number_of_pages {
365 break;
366 }
367
368 if page_count > total_number_of_pages {
369 return Err(Error::InvalidData("page_count > total_number_of_pages"));
370 }
371 }
372
373 let pfn_range = read_struct::<PfnRange>(reader)?;
374 if pfn_range.page_file_number == 0 {
375 break;
376 }
377
378 for page_idx in 0..pfn_range.number_of_pages {
379 let gpa = gpa_from_pfn_range(&pfn_range, page_idx)
380 .ok_or(Error::Overflow("w/ pfn_range"))?;
381 let insert = physmem.insert(gpa, page_offset);
382 debug_assert!(insert.is_none());
383 page_offset = page_offset
384 .checked_add(PageKind::Normal.size())
385 .ok_or(Error::Overflow("w/ page_offset"))?;
386 }
387
388 page_count = page_count
389 .checked_add(pfn_range.number_of_pages)
390 .ok_or(Error::Overflow("w/ page_count"))?;
391 }
392
393 Ok(physmem)
394 }
395
396 fn build_physmem(
397 dump_type: DumpType,
398 headers: &Header64,
399 reader: &mut impl Reader,
400 ) -> Result<PhysmemMap> {
401 use DumpType as D;
402 match dump_type {
403 D::Full => Self::full_physmem(headers, reader),
404 D::Bmp | D::LiveKernelMemory => Self::bmp_physmem(reader),
405 D::KernelMemory | D::KernelAndUserMemory | D::CompleteMemory => {
406 Self::kernel_physmem(dump_type, reader)
407 }
408 }
409 }
410}