1use std::path::Path;
7
8use crate::{
9 DumpMetadata, Error, FormatPlugin, MachineType, PhysicalMemoryProvider, PhysicalRange, Result,
10};
11
12const PAGE_MAGIC: u32 = 0x4547_4150;
14const DU64_SIG: u32 = 0x3436_5544;
16const HEADER_SIZE: usize = 0x2000;
18const PAGE_SIZE: u64 = 4096;
20const DUMP_VALID: u32 = 0x504D_5544;
22
23const OFF_MAGIC: usize = 0x000;
25const OFF_SIG: usize = 0x004;
26const OFF_CR3: usize = 0x010;
27const OFF_PS_LOADED_MODULE_LIST: usize = 0x020;
28const OFF_PS_ACTIVE_PROCESS_HEAD: usize = 0x028;
29const OFF_MACHINE_TYPE: usize = 0x030;
30const OFF_NUM_PROCESSORS: usize = 0x034;
31const OFF_KD_DEBUGGER_DATA_BLOCK: usize = 0x080;
32const OFF_PHYS_MEM_BLOCK: usize = 0x088;
33const OFF_DUMP_TYPE: usize = 0xF98;
34const OFF_SYSTEM_TIME: usize = 0xFA8;
35
36#[derive(Debug, Clone)]
38struct PhysMemRun {
39 base_page: u64,
41 page_count: u64,
43}
44
45#[derive(Debug)]
47enum CrashDumpLayout {
48 RunBased {
50 runs: Vec<PhysMemRun>,
52 run_file_offsets: Vec<u64>,
54 },
55 Bitmap {
57 bitmap: Vec<u8>,
59 data_start: u64,
61 },
62}
63
64#[derive(Debug)]
66pub struct CrashDumpProvider {
67 data: Vec<u8>,
68 metadata: DumpMetadata,
69 layout: CrashDumpLayout,
70 ranges: Vec<PhysicalRange>,
71}
72
73fn read_u32(data: &[u8], offset: usize) -> crate::Result<u32> {
75 data.get(offset..offset + 4)
76 .and_then(|b| b.try_into().ok())
77 .map(u32::from_le_bytes)
78 .ok_or_else(|| {
79 crate::Error::Corrupt(format!("truncated header: need 4 bytes at offset {offset}"))
80 })
81}
82
83fn read_u64(data: &[u8], offset: usize) -> crate::Result<u64> {
85 data.get(offset..offset + 8)
86 .and_then(|b| b.try_into().ok())
87 .map(u64::from_le_bytes)
88 .ok_or_else(|| {
89 crate::Error::Corrupt(format!("truncated header: need 8 bytes at offset {offset}"))
90 })
91}
92
93fn parse_machine_type(val: u32) -> Option<MachineType> {
95 match val {
96 0x8664 => Some(MachineType::Amd64),
97 0x014C => Some(MachineType::I386),
98 0xAA64 => Some(MachineType::Aarch64),
99 _ => None,
100 }
101}
102
103fn dump_type_label(val: u32) -> &'static str {
105 match val {
106 0x01 => "Full",
107 0x02 => "Kernel",
108 0x05 => "Bitmap",
109 _ => "Unknown",
110 }
111}
112
113impl CrashDumpProvider {
114 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
116 if bytes.len() < HEADER_SIZE {
117 return Err(Error::Corrupt(format!(
118 "crash dump too small: {} bytes, need at least {HEADER_SIZE}",
119 bytes.len()
120 )));
121 }
122
123 let magic = read_u32(bytes, OFF_MAGIC)?;
125 let sig = read_u32(bytes, OFF_SIG)?;
126 if magic != PAGE_MAGIC || sig != DU64_SIG {
127 return Err(Error::Corrupt(format!(
128 "invalid crash dump magic: expected PAGE+DU64, got 0x{magic:08X}+0x{sig:08X}"
129 )));
130 }
131
132 let cr3 = read_u64(bytes, OFF_CR3)?;
134 let ps_loaded_module_list = read_u64(bytes, OFF_PS_LOADED_MODULE_LIST)?;
135 let ps_active_process_head = read_u64(bytes, OFF_PS_ACTIVE_PROCESS_HEAD)?;
136 let machine_img_type = read_u32(bytes, OFF_MACHINE_TYPE)?;
137 let num_processors = read_u32(bytes, OFF_NUM_PROCESSORS)?;
138 let kd_debugger_data_block = read_u64(bytes, OFF_KD_DEBUGGER_DATA_BLOCK)?;
139 let dump_type_val = read_u32(bytes, OFF_DUMP_TYPE)?;
140 let system_time = read_u64(bytes, OFF_SYSTEM_TIME)?;
141
142 let metadata = DumpMetadata {
143 cr3: Some(cr3),
144 machine_type: parse_machine_type(machine_img_type),
145 os_version: None,
146 num_processors: Some(num_processors),
147 ps_active_process_head: Some(ps_active_process_head),
148 ps_loaded_module_list: Some(ps_loaded_module_list),
149 kd_debugger_data_block: Some(kd_debugger_data_block),
150 system_time: Some(system_time),
151 dump_type: Some(dump_type_label(dump_type_val).to_string()),
152 };
153
154 let num_runs = read_u32(bytes, OFF_PHYS_MEM_BLOCK)? as usize;
156 let mut runs = Vec::with_capacity(num_runs);
158 for i in 0..num_runs {
159 let off = 0x098 + i * 16;
160 let base_page = read_u64(bytes, off)?;
161 let page_count = read_u64(bytes, off + 8)?;
162 runs.push(PhysMemRun {
163 base_page,
164 page_count,
165 });
166 }
167
168 let ranges: Vec<PhysicalRange> = runs
170 .iter()
171 .map(|r| PhysicalRange {
172 start: r.base_page * PAGE_SIZE,
173 end: (r.base_page + r.page_count) * PAGE_SIZE,
174 })
175 .collect();
176
177 let is_bitmap = dump_type_val == 0x02 || dump_type_val == 0x05;
178 let layout = if is_bitmap {
179 Self::parse_bitmap_layout(bytes, HEADER_SIZE)?
180 } else {
181 Self::parse_run_layout(&runs)
182 };
183
184 Ok(Self {
185 data: bytes.to_vec(),
186 metadata,
187 layout,
188 ranges,
189 })
190 }
191
192 pub fn from_path(path: &Path) -> Result<Self> {
194 let data = std::fs::read(path)?;
195 Self::from_bytes(&data)
196 }
197
198 fn parse_run_layout(runs: &[PhysMemRun]) -> CrashDumpLayout {
200 let mut run_file_offsets = Vec::with_capacity(runs.len());
201 let mut offset = HEADER_SIZE as u64;
202 for run in runs {
203 run_file_offsets.push(offset);
204 offset += run.page_count * PAGE_SIZE;
205 }
206 CrashDumpLayout::RunBased {
207 runs: runs.to_vec(),
208 run_file_offsets,
209 }
210 }
211
212 fn parse_bitmap_layout(data: &[u8], summary_offset: usize) -> Result<CrashDumpLayout> {
214 if data.len() < summary_offset + 16 {
215 return Err(Error::Corrupt(
216 "crash dump too small for bitmap summary header".into(),
217 ));
218 }
219
220 let valid_dump = read_u32(data, summary_offset)?;
221 if valid_dump != DUMP_VALID {
222 return Err(Error::Corrupt(format!(
223 "invalid bitmap summary ValidDump: expected 0x{DUMP_VALID:08X}, got 0x{valid_dump:08X}"
224 )));
225 }
226
227 let header_size = read_u32(data, summary_offset + 4)? as usize;
228 let bitmap_size = read_u32(data, summary_offset + 8)? as usize;
229
230 let bitmap_start = summary_offset + 16;
232 if data.len() < bitmap_start + bitmap_size {
233 return Err(Error::Corrupt("crash dump bitmap truncated".into()));
234 }
235
236 let bitmap = data[bitmap_start..bitmap_start + bitmap_size].to_vec();
237 let data_start = (summary_offset + header_size) as u64;
238
239 Ok(CrashDumpLayout::Bitmap { bitmap, data_start })
240 }
241
242 fn read_run_based(
244 &self,
245 addr: u64,
246 buf: &mut [u8],
247 runs: &[PhysMemRun],
248 run_file_offsets: &[u64],
249 ) -> Result<usize> {
250 let pfn = addr / PAGE_SIZE;
251 let page_offset = (addr % PAGE_SIZE) as usize;
252
253 for (i, run) in runs.iter().enumerate() {
254 if pfn >= run.base_page && pfn < run.base_page + run.page_count {
255 let pages_into_run = pfn - run.base_page;
256 let file_offset =
257 run_file_offsets[i] + pages_into_run * PAGE_SIZE + page_offset as u64;
258 let remaining_in_run =
259 ((run.page_count - pages_into_run) * PAGE_SIZE - page_offset as u64) as usize;
260 let to_read = buf.len().min(remaining_in_run);
261 let src = file_offset as usize;
262 if src + to_read > self.data.len() {
263 return Err(Error::Corrupt("run data extends beyond file".into()));
264 }
265 buf[..to_read].copy_from_slice(&self.data[src..src + to_read]);
266 return Ok(to_read);
267 }
268 }
269
270 Ok(0)
272 }
273
274 fn read_bitmap(
276 &self,
277 addr: u64,
278 buf: &mut [u8],
279 bitmap: &[u8],
280 data_start: u64,
281 ) -> Result<usize> {
282 let pfn = addr / PAGE_SIZE;
283 let page_offset = (addr % PAGE_SIZE) as usize;
284
285 let byte_idx = pfn as usize / 8;
287 let bit_idx = pfn as usize % 8;
288 if byte_idx >= bitmap.len() || (bitmap[byte_idx] & (1 << bit_idx)) == 0 {
289 return Ok(0); }
291
292 let page_index = popcount_before(bitmap, pfn as usize);
294 let file_offset = data_start + page_index as u64 * PAGE_SIZE + page_offset as u64;
295 let remaining_in_page = (PAGE_SIZE as usize) - page_offset;
296 let to_read = buf.len().min(remaining_in_page);
297 let src = file_offset as usize;
298 if src + to_read > self.data.len() {
299 return Err(Error::Corrupt(
300 "bitmap page data extends beyond file".into(),
301 ));
302 }
303 buf[..to_read].copy_from_slice(&self.data[src..src + to_read]);
304 Ok(to_read)
305 }
306}
307
308fn popcount_before(bitmap: &[u8], bit_pos: usize) -> usize {
310 let full_bytes = bit_pos / 8;
311 let remaining_bits = bit_pos % 8;
312
313 let mut count: usize = 0;
314 for &byte in &bitmap[..full_bytes] {
315 count += byte.count_ones() as usize;
316 }
317 if remaining_bits > 0 && full_bytes < bitmap.len() {
318 let mask = (1u8 << remaining_bits) - 1;
320 count += (bitmap[full_bytes] & mask).count_ones() as usize;
321 }
322 count
323}
324
325impl PhysicalMemoryProvider for CrashDumpProvider {
326 fn read_phys(&self, addr: u64, buf: &mut [u8]) -> Result<usize> {
327 if buf.is_empty() {
328 return Ok(0);
329 }
330
331 match &self.layout {
332 CrashDumpLayout::RunBased {
333 runs,
334 run_file_offsets,
335 } => self.read_run_based(addr, buf, runs, run_file_offsets),
336 CrashDumpLayout::Bitmap { bitmap, data_start } => {
337 self.read_bitmap(addr, buf, bitmap, *data_start)
338 }
339 }
340 }
341
342 fn ranges(&self) -> &[PhysicalRange] {
343 &self.ranges
344 }
345
346 fn format_name(&self) -> &str {
347 "Windows Crash Dump"
348 }
349
350 fn metadata(&self) -> Option<DumpMetadata> {
351 Some(self.metadata.clone())
352 }
353}
354
355pub struct CrashDumpPlugin;
357
358impl FormatPlugin for CrashDumpPlugin {
359 fn name(&self) -> &str {
360 "Windows Crash Dump"
361 }
362
363 fn probe(&self, header: &[u8]) -> u8 {
364 if header.len() < 8 {
365 return 0;
366 }
367 let Ok(magic) = read_u32(header, 0) else {
368 return 0;
369 };
370 let Ok(sig) = read_u32(header, 4) else {
371 return 0;
372 };
373 if magic == PAGE_MAGIC && sig == DU64_SIG {
374 95
375 } else {
376 0
377 }
378 }
379
380 fn open(&self, path: &Path) -> Result<Box<dyn PhysicalMemoryProvider>> {
381 Ok(Box::new(CrashDumpProvider::from_path(path)?))
382 }
383}
384
385inventory::submit!(&CrashDumpPlugin as &dyn FormatPlugin);
386
387#[cfg(test)]
388mod tests {
389 use crate::test_builders::CrashDumpBuilder;
390 use crate::{Error, MachineType, PhysicalMemoryProvider};
391
392 use super::{CrashDumpPlugin, CrashDumpProvider};
393 use crate::FormatPlugin;
394
395 const PAGE: usize = 4096;
396
397 #[test]
398 fn probe_crashdump_magic() {
399 let dump = CrashDumpBuilder::new().add_run(0, &[0xAA; PAGE]).build();
400 let plugin = CrashDumpPlugin;
401 assert_eq!(plugin.probe(&dump), 95);
402 }
403
404 #[test]
405 fn probe_non_crashdump() {
406 let zeros = vec![0u8; 64];
407 let plugin = CrashDumpPlugin;
408 assert_eq!(plugin.probe(&zeros), 0);
409 }
410
411 #[test]
412 fn probe_short_header_returns_zero() {
413 let plugin = CrashDumpPlugin;
414 assert_eq!(plugin.probe(&[0x50, 0x41, 0x47, 0x45, 0x44, 0x55, 0x36]), 0); assert_eq!(plugin.probe(&[]), 0);
416 }
417
418 #[test]
419 fn single_run_read() {
420 let mut page_data = vec![0u8; PAGE];
421 page_data[0] = 0xDE;
422 page_data[1] = 0xAD;
423 page_data[2] = 0xBE;
424 page_data[3] = 0xEF;
425 let dump = CrashDumpBuilder::new().add_run(0, &page_data).build();
426 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
427 let mut buf = [0u8; 4];
428 let n = provider.read_phys(0, &mut buf).unwrap();
429 assert_eq!(n, 4);
430 assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
431 }
432
433 #[test]
434 fn multi_run_read() {
435 let page_a = vec![0xAAu8; PAGE];
437 let page_b = vec![0xBBu8; PAGE];
438 let dump = CrashDumpBuilder::new()
439 .add_run(0, &page_a)
440 .add_run(4, &page_b)
441 .build();
442 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
443
444 let mut buf = [0u8; 2];
445 let n = provider.read_phys(0, &mut buf).unwrap();
446 assert_eq!(n, 2);
447 assert_eq!(buf, [0xAA, 0xAA]);
448
449 let n = provider.read_phys(4 * PAGE as u64, &mut buf).unwrap();
450 assert_eq!(n, 2);
451 assert_eq!(buf, [0xBB, 0xBB]);
452 }
453
454 #[test]
455 fn read_gap_returns_zero() {
456 let page_data = vec![0xCCu8; PAGE];
457 let dump = CrashDumpBuilder::new().add_run(2, &page_data).build();
458 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
459
460 let mut buf = [0xFFu8; 4];
462 let n = provider.read_phys(0, &mut buf).unwrap();
463 assert_eq!(n, 0);
464 }
465
466 #[test]
467 fn read_empty_buffer() {
468 let page_data = vec![0xAAu8; PAGE];
469 let dump = CrashDumpBuilder::new().add_run(0, &page_data).build();
470 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
471 let mut buf = [];
472 let n = provider.read_phys(0, &mut buf).unwrap();
473 assert_eq!(n, 0);
474 }
475
476 #[test]
477 fn metadata_extraction() {
478 let dump = CrashDumpBuilder::new()
479 .cr3(0x0018_7000)
480 .machine_type(0x8664)
481 .num_processors(4)
482 .dump_type(0x01)
483 .ps_active_process_head(0xFFFFF802_1A2B3C40)
484 .ps_loaded_module_list(0xFFFFF802_1A2B3D60)
485 .kd_debugger_data_block(0xFFFFF802_1A000000)
486 .system_time(0x01DA_5678_9ABC_DEF0)
487 .add_run(0, &[0u8; PAGE])
488 .build();
489 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
490 let meta = provider.metadata().expect("metadata should be Some");
491 assert_eq!(meta.cr3, Some(0x0018_7000));
492 assert_eq!(meta.machine_type, Some(MachineType::Amd64));
493 assert_eq!(meta.num_processors, Some(4));
494 assert_eq!(meta.dump_type.as_deref(), Some("Full"));
495 assert_eq!(meta.ps_active_process_head, Some(0xFFFFF802_1A2B3C40));
496 assert_eq!(meta.ps_loaded_module_list, Some(0xFFFFF802_1A2B3D60));
497 assert_eq!(meta.kd_debugger_data_block, Some(0xFFFFF802_1A000000));
498 assert_eq!(meta.system_time, Some(0x01DA_5678_9ABC_DEF0));
499 }
500
501 #[test]
502 fn plugin_name() {
503 let plugin = CrashDumpPlugin;
504 assert_eq!(plugin.name(), "Windows Crash Dump");
505 }
506
507 #[test]
508 fn builder_produces_valid_header() {
509 let dump = CrashDumpBuilder::new().add_run(0, &[0u8; PAGE]).build();
510 let magic = u32::from_le_bytes(dump[0..4].try_into().unwrap());
512 assert_eq!(magic, 0x4547_4150);
513 let sig = u32::from_le_bytes(dump[4..8].try_into().unwrap());
515 assert_eq!(sig, 0x3436_5544);
516 assert!(dump.len() >= 0x2000 + PAGE);
518 }
519
520 #[test]
521 fn bitmap_single_page_read() {
522 let mut page_data = vec![0u8; PAGE];
523 page_data[0] = 0x42;
524 page_data[1] = 0x4D;
525 let dump = CrashDumpBuilder::new()
526 .dump_type(0x05)
527 .add_run(0, &page_data)
528 .build();
529 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
530 let mut buf = [0u8; 2];
531 let n = provider.read_phys(0, &mut buf).unwrap();
532 assert_eq!(n, 2);
533 assert_eq!(buf, [0x42, 0x4D]);
534 }
535
536 #[test]
537 fn bitmap_multi_run_with_gap() {
538 let page_a = vec![0xAAu8; PAGE];
539 let page_b = vec![0xBBu8; PAGE];
540 let dump = CrashDumpBuilder::new()
541 .dump_type(0x05)
542 .add_run(0, &page_a)
543 .add_run(4, &page_b)
544 .build();
545 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
546
547 let mut buf = [0u8; 2];
548 let n = provider.read_phys(0, &mut buf).unwrap();
549 assert_eq!(n, 2);
550 assert_eq!(buf, [0xAA, 0xAA]);
551
552 let n = provider.read_phys(4 * PAGE as u64, &mut buf).unwrap();
553 assert_eq!(n, 2);
554 assert_eq!(buf, [0xBB, 0xBB]);
555
556 let n = provider.read_phys(PAGE as u64, &mut buf).unwrap();
558 assert_eq!(n, 0);
559 }
560
561 #[test]
562 fn bitmap_popcount_correctness() {
563 let mut data = vec![0u8; PAGE * 3];
565 data[0..PAGE].fill(0x11);
567 data[PAGE..PAGE * 2].fill(0x22);
569 data[PAGE * 2..PAGE * 3].fill(0x33);
571 let dump = CrashDumpBuilder::new()
572 .dump_type(0x02)
573 .add_run(2, &data)
574 .build();
575 let provider = CrashDumpProvider::from_bytes(&dump).unwrap();
576
577 let mut buf = [0u8; 1];
578 let n = provider.read_phys(2 * PAGE as u64, &mut buf).unwrap();
580 assert_eq!(n, 1);
581 assert_eq!(buf[0], 0x11);
582 let n = provider.read_phys(3 * PAGE as u64, &mut buf).unwrap();
584 assert_eq!(n, 1);
585 assert_eq!(buf[0], 0x22);
586 let n = provider.read_phys(4 * PAGE as u64, &mut buf).unwrap();
588 assert_eq!(n, 1);
589 assert_eq!(buf[0], 0x33);
590 }
591
592 #[test]
593 fn from_path_roundtrip() {
594 let mut page_data = vec![0u8; PAGE];
595 page_data[0..4].copy_from_slice(&[0xCA, 0xFE, 0xBA, 0xBE]);
596 let dump = CrashDumpBuilder::new().add_run(0, &page_data).build();
597 let path = std::env::temp_dir().join("memf_test_crashdump_roundtrip.dmp");
598 std::fs::write(&path, &dump).unwrap();
599 let provider = CrashDumpProvider::from_path(&path).unwrap();
600 let mut buf = [0u8; 4];
601 let n = provider.read_phys(0, &mut buf).unwrap();
602 assert_eq!(n, 4);
603 assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE]);
604 std::fs::remove_file(&path).ok();
605 }
606
607 #[test]
608 fn corrupt_magic_errors() {
609 let mut dump = CrashDumpBuilder::new().add_run(0, &[0u8; PAGE]).build();
610 dump[0] = 0xFF;
612 let err = CrashDumpProvider::from_bytes(&dump).unwrap_err();
613 assert!(
614 matches!(err, Error::Corrupt(_)),
615 "expected Corrupt, got {err:?}"
616 );
617 }
618
619 #[test]
620 fn too_small_header_errors() {
621 let data = vec![0u8; 100];
622 let err = CrashDumpProvider::from_bytes(&data).unwrap_err();
623 assert!(
624 matches!(err, Error::Corrupt(_)),
625 "expected Corrupt, got {err:?}"
626 );
627 }
628
629 #[test]
630 fn from_bytes_empty_returns_error_not_panic() {
631 let result = CrashDumpProvider::from_bytes(&[]);
632 assert!(result.is_err(), "empty input must return Err");
633 }
634
635 #[test]
636 fn from_bytes_3_bytes_returns_error_not_panic() {
637 let result = CrashDumpProvider::from_bytes(&[0u8; 3]);
638 assert!(result.is_err(), "3 bytes is too short for any valid header");
639 }
640}