diff --git a/Cargo.toml b/Cargo.toml index 9f3e528..029976f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kdmp-parser" -version = "0.7.0" +version = "0.8.0" edition = "2024" authors = ["Axel '0vercl0k' Souchet"] categories = ["parser-implementations"] @@ -11,16 +11,14 @@ license = "MIT" repository = "https://github.com/0vercl0k/kdmp-parser-rs" rust-version = "1.85" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -bitflags = "2.9.2" -thiserror = "2" - [dev-dependencies] anyhow = "1.0" clap = { version = "4.5.45", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +[lints.clippy] +pedantic = "warn" + [[example]] name = "parser" diff --git a/README.md b/README.md index b8e1ede..9575d15 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

kdmp-parser

- A KISS Rust crate to parse Windows kernel crash-dumps created by Windows & its debugger. + A KISS, dependency free Rust crate to parse Windows kernel crash-dumps created by Windows & its debugger.

diff --git a/examples/parser.rs b/examples/parser.rs index 3871ad0..6bbd558 100644 --- a/examples/parser.rs +++ b/examples/parser.rs @@ -183,10 +183,10 @@ fn main() -> Result<()> { .unwrap_or(Gpa::new(parser.headers().directory_table_base)), ) } else { - parser.phys_read(Gpa::new(addr), &mut buffer) - }; + parser.phys_read(Gpa::new(addr), &mut buffer).map(Some) + }?; - if let Ok(amount) = amount { + if let Some(amount) = amount { hexdump(addr, &buffer[..amount], args.len); } else { println!( diff --git a/src/bits.rs b/src/bits.rs index ac8fcc3..f2bf58f 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -16,9 +16,11 @@ use std::ops::RangeInclusive; /// Utility trait to make it easier to extract ranges of bits. pub trait Bits: Sized { /// Get a range of bits. + #[must_use] fn bits(&self, r: RangeInclusive) -> Self; /// Get a bit. + #[must_use] fn bit(&self, n: usize) -> Self { self.bits(n..=n) } diff --git a/src/error.rs b/src/error.rs index 2fdacd6..d26b626 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,71 +1,211 @@ // Axel '0vercl0k' Souchet - March 19 2024 //! This is the error type used across the codebase. -use std::fmt::Display; -use std::{io, string}; - -use thiserror::Error; +use std::error::Error; +use std::fmt::{self, Display}; +use std::io; +use std::string::FromUtf16Error; use crate::structs::{DUMP_HEADER64_EXPECTED_SIGNATURE, DUMP_HEADER64_EXPECTED_VALID_DUMP}; use crate::{Gpa, Gva}; pub type Result = std::result::Result; -#[derive(Debug)] -pub enum PxeNotPresent { +/// Identifies which page table entry level. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PxeKind { Pml4e, Pdpte, Pde, Pte, } -#[derive(Debug, Error)] -pub enum AddrTranslationError { - Virt(Gva, PxeNotPresent), - Phys(Gpa), +/// Represent the fundamental reason a single page read can fail. +#[derive(Debug, Clone)] +pub enum PageReadError { + /// Virtual address translation failed because a page table entry is not + /// present (it exists in the dump but is marked as not present). + NotPresent { gva: Gva, which_pxe: PxeKind }, + /// A physical page is missing from the dump. + NotInDump { + gva: Option<(Gva, Option)>, + gpa: Gpa, + }, +} + +impl Error for PageReadError {} + +/// Recoverable memory errors that can occur during memory reads. +/// +/// There are several failure conditions that can happen while trying to read +/// virtual (or physical) memory out of a crash-dump that might not be obvious. +/// +/// For example, consider reading two 4K pages from the virtual address +/// `0x1337_000`; it can fail because: +/// - The virtual address (the first 4K page) isn't present in the address space +/// at the `Pde` level: `MemoryReadError::PageRead(PageReadError::NotPresent { +/// gva: 0x1337_000, which_pxe: PxeKind::Pde })` +/// - The `Pde` that needs reading as part of the address translation (of the +/// first page) isn't part of the crash-dump: +/// `MemoryReadError::PageRead(PageReadError::NotInDump { gva: +/// Some((0x1337_000, PxeKind::Pde)), gpa: .. })` +/// - The physical page backing that virtual address isn't included in the +/// crash-dump: `MemoryReadError::PageRead(PageReadError::NotInDump { gva: +/// Some((0x1337_000, None)), gpa: .. })` +/// - Reading the second (and only the second) page failed because of any of the +/// previous reasons: `MemoryReadError::PartialRead { expected_amount: 8_192, +/// actual_amount: 4_096, reason: PageReadError::.. }` +/// +/// Similarly, for physical memory reads starting at `0x1337_000`: +/// - A direct physical page isn't in the crash-dump: +/// `MemoryError::PageRead(PageReadError::NotInDump { gpa: 0x1337_000 })` +/// - Reading the second page failed: `MemoryError::PartialRead { +/// expected_amount: 8_192, actual_amount: 4_096, reason: +/// PageReadError::NotInDump { gva: None, gpa: 0x1338_000 } }` +/// +/// We consider any of those errors 'recoverable' which means that we won't even +/// bubble those up to the callers with the regular APIs. Only the `strict` +/// versions will. +#[derive(Debug, Clone)] +pub enum MemoryReadError { + /// A single page/read failed. + PageRead(PageReadError), + /// A read request was only partially fulfilled. + PartialRead { + expected_amount: usize, + actual_amount: usize, + reason: PageReadError, + }, } -impl Display for AddrTranslationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Display for PageReadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - AddrTranslationError::Virt(gva, not_pres) => f.write_fmt(format_args!( - "virt to phys translation of {gva}: {not_pres:?}" - )), - AddrTranslationError::Phys(gpa) => { - f.write_fmt(format_args!("phys to offset translation of {gpa}")) + PageReadError::NotPresent { gva, which_pxe } => { + write!(f, "{gva} isn't present at the {which_pxe:?} level") } + PageReadError::NotInDump { gva, gpa } => match gva { + Some((gva, Some(which_pxe))) => write!( + f, + "{gpa} was needed while translating {gva} at the {which_pxe:?} level but is missing from the dump)" + ), + Some((gva, None)) => write!(f, "{gpa} backs {gva} but is missing from the dump)"), + None => { + write!(f, "{gpa} is missing from the dump)") + } + }, } } } -#[derive(Error, Debug)] +impl Error for MemoryReadError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + MemoryReadError::PageRead(e) => Some(e), + MemoryReadError::PartialRead { reason, .. } => Some(reason), + } + } +} + +impl Display for MemoryReadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MemoryReadError::PageRead(_) => write!(f, "page read"), + MemoryReadError::PartialRead { + expected_amount, + actual_amount, + .. + } => { + write!( + f, + "partially read {actual_amount} off {expected_amount} wanted bytes" + ) + } + } + } +} + +#[derive(Debug)] pub enum KdmpParserError { - #[error("invalid UNICODE_STRING")] InvalidUnicodeString, - #[error("utf16: {0}")] - Utf16(#[from] string::FromUtf16Error), - #[error("overflow: {0}")] + Utf16(FromUtf16Error), Overflow(&'static str), - #[error("io: {0}")] - Io(#[from] io::Error), - #[error("invalid data: {0}")] + Io(io::Error), InvalidData(&'static str), - #[error("unsupported dump type {0:#x}")] UnknownDumpType(u32), - #[error("duplicate gpa found in physmem map for {0}")] DuplicateGpa(Gpa), - #[error("header's signature looks wrong: {0:#x} vs {DUMP_HEADER64_EXPECTED_SIGNATURE:#x}")] InvalidSignature(u32), - #[error("header's valid dump looks wrong: {0:#x} vs {DUMP_HEADER64_EXPECTED_VALID_DUMP:#x}")] InvalidValidDump(u32), - #[error("overflow for phys addr w/ run {0} page {1}")] PhysAddrOverflow(u32, u64), - #[error("overflow for page offset w/ run {0} page {1}")] PageOffsetOverflow(u32, u64), - #[error("overflow for page offset w/ bitmap_idx {0} bit_idx {1}")] BitmapPageOffsetOverflow(u64, usize), - #[error("partial physical memory read")] - PartialPhysRead, - #[error("partial virtual memory read")] - PartialVirtRead, - #[error("memory translation: {0}")] - AddrTranslation(#[from] AddrTranslationError), + MemoryRead(MemoryReadError), +} + +impl From for KdmpParserError { + fn from(value: io::Error) -> Self { + KdmpParserError::Io(value) + } +} + +impl From for KdmpParserError { + fn from(value: FromUtf16Error) -> Self { + KdmpParserError::Utf16(value) + } +} + +impl From for KdmpParserError { + fn from(value: MemoryReadError) -> Self { + KdmpParserError::MemoryRead(value) + } +} + +impl From for KdmpParserError { + fn from(value: PageReadError) -> Self { + Self::MemoryRead(MemoryReadError::PageRead(value)) + } +} + +impl Display for KdmpParserError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + KdmpParserError::InvalidUnicodeString => write!(f, "invalid UNICODE_STRING"), + KdmpParserError::Utf16(_) => write!(f, "utf16"), + KdmpParserError::Overflow(o) => write!(f, "overflow: {o}"), + KdmpParserError::Io(_) => write!(f, "io"), + KdmpParserError::InvalidData(i) => write!(f, "invalid data: {i}"), + KdmpParserError::UnknownDumpType(u) => write!(f, "unsupported dump type {u:#x}"), + KdmpParserError::DuplicateGpa(gpa) => { + write!(f, "duplicate gpa found in physmem map for {gpa}") + } + KdmpParserError::InvalidSignature(sig) => write!( + f, + "header's signature looks wrong: {sig:#x} vs {DUMP_HEADER64_EXPECTED_SIGNATURE:#x}" + ), + KdmpParserError::InvalidValidDump(dump) => write!( + f, + "header's valid dump looks wrong: {dump:#x} vs {DUMP_HEADER64_EXPECTED_VALID_DUMP:#x}" + ), + KdmpParserError::PhysAddrOverflow(run, page) => { + write!(f, "overflow for phys addr w/ run {run} page {page}") + } + KdmpParserError::PageOffsetOverflow(run, page) => { + write!(f, "overflow for page offset w/ run {run} page {page}") + } + KdmpParserError::BitmapPageOffsetOverflow(bitmap_idx, bit_idx) => write!( + f, + "overflow for page offset w/ bitmap_idx {bitmap_idx} bit_idx {bit_idx}" + ), + KdmpParserError::MemoryRead(_) => write!(f, "memory read"), + } + } +} + +impl Error for KdmpParserError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + KdmpParserError::Utf16(u) => Some(u), + KdmpParserError::Io(e) => Some(e), + KdmpParserError::MemoryRead(m) => Some(m), + _ => None, + } + } } diff --git a/src/gxa.rs b/src/gxa.rs index 5bd0797..58a7ccc 100644 --- a/src/gxa.rs +++ b/src/gxa.rs @@ -13,7 +13,7 @@ //! let page_aligned_gva = gva.page_align(); //! let page_offset = gva.offset(); //! ``` -use std::fmt::Display; +use std::fmt::{self, Debug, Display}; use std::num::ParseIntError; use std::ops::AddAssign; use std::str::FromStr; @@ -33,16 +33,19 @@ pub trait Gxa: Sized + Default + Copy + From { } /// Is it page aligned? + #[must_use] fn page_aligned(&self) -> bool { self.offset() == 0 } /// Page-align it. + #[must_use] fn page_align(&self) -> Self { Self::from(self.u64() & !0xf_ff) } /// Get the next aligned page. + #[must_use] fn next_aligned_page(self) -> Self { Self::from( self.page_align() @@ -84,6 +87,7 @@ impl Gpa { /// let gpa = Gpa::new(1337); /// # } /// ``` + #[must_use] pub const fn new(addr: u64) -> Self { Self(addr) } @@ -99,6 +103,7 @@ impl Gpa { /// assert_eq!(gpa.u64(), 0x1337_000); /// # } /// ``` + #[must_use] pub const fn from_pfn(pfn: Pfn) -> Self { Self(pfn.u64() << (4 * 3)) } @@ -115,6 +120,7 @@ impl Gpa { /// assert_eq!(gpa.u64(), 0x1337_011); /// # } /// ``` + #[must_use] pub const fn from_pfn_with_offset(pfn: Pfn, offset: u64) -> Self { let base = pfn.u64() << (4 * 3); @@ -132,6 +138,7 @@ impl Gpa { /// assert_eq!(gpa.pfn(), 0x1337); /// # } /// ``` + #[must_use] pub const fn pfn(&self) -> u64 { self.0 >> (4 * 3) } @@ -140,7 +147,7 @@ impl Gpa { /// Operator += for [`Gpa`]. impl AddAssign for Gpa { fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0 + self.0 += rhs.0; } } @@ -222,7 +229,7 @@ impl From<&Gpa> for u64 { /// Format a [`Gpa`] as a string. impl Display for Gpa { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "GPA:{:#x}", self.0) } } @@ -272,6 +279,7 @@ impl Gva { /// let gva = Gva::new(0xdeadbeef); /// # } /// ``` + #[must_use] pub const fn new(addr: u64) -> Self { Self(addr) } @@ -290,6 +298,7 @@ impl Gva { /// # } /// ``` #[allow(clippy::erasing_op, clippy::identity_op)] + #[must_use] pub const fn pte_idx(&self) -> u64 { (self.0 >> (12 + (9 * 0))) & 0b1_1111_1111 } @@ -308,6 +317,7 @@ impl Gva { /// # } /// ``` #[allow(clippy::identity_op)] + #[must_use] pub const fn pde_idx(&self) -> u64 { (self.0 >> (12 + (9 * 1))) & 0b1_1111_1111 } @@ -325,6 +335,7 @@ impl Gva { /// assert_eq!(second.pdpe_idx(), 0x88); /// # } /// ``` + #[must_use] pub const fn pdpe_idx(&self) -> u64 { (self.0 >> (12 + (9 * 2))) & 0b1_1111_1111 } @@ -342,6 +353,7 @@ impl Gva { /// assert_eq!(second.pml4e_idx(), 0x22); /// # } /// ``` + #[must_use] pub fn pml4e_idx(&self) -> u64 { (self.0 >> (12 + (9 * 3))) & 0b1_1111_1111 } @@ -350,7 +362,7 @@ impl Gva { /// Operator += for [`Gva`]. impl AddAssign for Gva { fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0 + self.0 += rhs.0; } } diff --git a/src/lib.rs b/src/lib.rs index 0c8dd89..f9eaee0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ // Axel '0vercl0k' Souchet - February 25 2024 +#![allow(clippy::doc_markdown)] #![doc = include_str!("../README.md")] mod bits; mod error; @@ -9,7 +10,7 @@ mod pxe; mod structs; pub use bits::Bits; -pub use error::{AddrTranslationError, KdmpParserError, PxeNotPresent, Result}; +pub use error::{KdmpParserError, MemoryReadError, PageReadError, PxeKind, Result}; pub use gxa::{Gpa, Gva, Gxa}; pub use map::{MappedFileReader, Reader}; pub use parse::{KernelDumpParser, VirtTranslationDetails}; diff --git a/src/map.rs b/src/map.rs index f25176c..f14d86a 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,10 +1,10 @@ // Axel '0vercl0k' Souchet - July 18 2023 //! This implements logic that allows to memory map a file on both //! Unix and Windows (cf [`memory_map_file`] / [`unmap_memory_mapped_file`]). -use std::fmt::Debug; -use std::io::{Read, Seek}; +use std::fmt::{self, Debug}; +use std::fs::File; +use std::io::{self, Cursor, Read, Seek}; use std::path::Path; -use std::{fs, io, ptr, slice}; pub trait Reader: Read + Seek {} @@ -14,27 +14,31 @@ impl Reader for T where T: Read + Seek {} /// mapping and a cursor to be able to access the region. pub struct MappedFileReader<'map> { mapped_file: &'map [u8], - cursor: io::Cursor<&'map [u8]>, + cursor: Cursor<&'map [u8]>, } impl Debug for MappedFileReader<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MappedFileReader").finish() } } impl MappedFileReader<'_> { /// Create a new [`MappedFileReader`] from a path using a memory map. + /// + /// # Errors + /// + /// Returns an error if the file cannot be opened or memory mapped. pub fn new(path: impl AsRef) -> io::Result { // Open the file.. - let file = fs::File::open(path)?; + let file = File::open(path)?; // ..and memory map it using the underlying OS-provided APIs. - let mapped_file = memory_map_file(file)?; + let mapped_file = memory_map_file(&file)?; Ok(Self { mapped_file, - cursor: io::Cursor::new(mapped_file), + cursor: Cursor::new(mapped_file), }) } } @@ -55,19 +59,19 @@ impl Seek for MappedFileReader<'_> { /// need to drop the mapping using OS-provided APIs. impl Drop for MappedFileReader<'_> { fn drop(&mut self) { - unmap_memory_mapped_file(self.mapped_file).expect("failed to unmap") + unmap_memory_mapped_file(self.mapped_file).expect("failed to unmap"); } } #[cfg(windows)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)] -/// Module that implements memory mapping on Windows using CreateFileMappingA / -/// MapViewOfFile. +/// Module that implements memory mapping on Windows using `CreateFileMappingA` +/// / `MapViewOfFile`. mod windows { + use std::fs::File; use std::os::windows::prelude::AsRawHandle; use std::os::windows::raw::HANDLE; - - use super::*; + use std::{io, ptr, slice}; const PAGE_READONLY: DWORD = 2; const FILE_MAP_READ: DWORD = 4; @@ -117,7 +121,7 @@ mod windows { } /// Memory map a file into memory. - pub fn memory_map_file<'map>(file: fs::File) -> Result<&'map [u8], io::Error> { + pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> { // Grab the underlying HANDLE. let file_handle = file.as_raw_handle(); @@ -161,9 +165,7 @@ mod windows { } // Make sure the size is not bigger than what [`slice::from_raw_parts`] wants. - if size > isize::MAX.try_into().unwrap() { - panic!("slice is too large"); - } + assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large"); // Create the slice over the mapping. // SAFETY: This is safe because: @@ -191,14 +193,14 @@ mod windows { } #[cfg(windows)] -use windows::*; +use windows::{memory_map_file, unmap_memory_mapped_file}; #[cfg(unix)] /// Module that implements memory mapping on Unix using the mmap syscall. mod unix { + use std::fs::File; use std::os::fd::AsRawFd; - - use super::*; + use std::{io, ptr, slice}; const PROT_READ: i32 = 1; const MAP_SHARED: i32 = 1; @@ -217,7 +219,7 @@ mod unix { fn munmap(addr: *const u8, length: usize) -> i32; } - pub fn memory_map_file<'map>(file: fs::File) -> Result<&'map [u8], io::Error> { + pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> { // Grab the underlying file descriptor. let file_fd = file.as_raw_fd(); @@ -234,9 +236,7 @@ mod unix { } // Make sure the size is not bigger than what [`slice::from_raw_parts`] wants. - if size > isize::MAX.try_into().unwrap() { - panic!("slice is too large"); - } + assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large"); // Create the slice over the mapping. // SAFETY: This is safe because: @@ -264,7 +264,7 @@ mod unix { } #[cfg(unix)] -use unix::*; +use unix::{memory_map_file, unmap_memory_mapped_file}; #[cfg(not(any(windows, unix)))] /// Your system hasn't been implemented; if you do it, send a PR! diff --git a/src/parse.rs b/src/parse.rs index 1f42d06..198eea8 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -4,14 +4,16 @@ use core::slice; use std::cell::RefCell; use std::cmp::min; use std::collections::HashMap; -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::fs::File; +use std::io::SeekFrom; +use std::mem::MaybeUninit; use std::ops::Range; use std::path::Path; use std::{io, mem}; use crate::bits::Bits; -use crate::error::{PxeNotPresent, Result}; +use crate::error::{MemoryReadError, PageReadError, PxeKind, Result}; use crate::gxa::Gxa; use crate::map::{MappedFileReader, Reader}; use crate::structs::{ @@ -20,7 +22,7 @@ use crate::structs::{ LdrDataTableEntry, ListEntry, PageKind, PfnRange, PhysmemDesc, PhysmemMap, PhysmemRun, UnicodeString, read_struct, }; -use crate::{AddrTranslationError, Gpa, Gva, KdmpParserError, Pfn, Pxe}; +use crate::{Gpa, Gva, KdmpParserError, Pfn, Pxe}; /// The details related to a virtual to physical address translation. /// @@ -45,6 +47,11 @@ pub struct VirtTranslationDetails { } impl VirtTranslationDetails { + /// Create a new instance from a slice of PXEs and the original GVA. + /// + /// # Panics + /// + /// Panics if `pxes` is malformed (i.e. not between 2 and 4 entries). pub fn new(pxes: &[Pxe], gva: Gva) -> Self { let writable = pxes.iter().all(Pxe::writable); let executable = pxes.iter().all(Pxe::executable); @@ -68,6 +75,7 @@ impl VirtTranslationDetails { } } + #[must_use] pub fn gpa(&self) -> Gpa { self.pfn.gpa_with_offset(self.offset) } @@ -107,14 +115,14 @@ macro_rules! impl_checked_add { impl_checked_add!(u32, u64); -/// Walk a LIST_ENTRY of LdrDataTableEntry. It is used to dump both the user & -/// driver / module lists. +/// Walk a `LIST_ENTRY` of `LdrDataTableEntry`. It is used to dump both the user +/// & driver / module lists. fn try_read_module_map

(parser: &mut KernelDumpParser, head: Gva) -> Result> where P: PtrSize, { let mut modules = ModuleMap::new(); - let Some(entry) = parser.try_virt_read_struct::>(head)? else { + let Some(entry) = parser.virt_read_struct::>(head)? else { return Ok(None); }; @@ -122,18 +130,18 @@ where // We'll walk it until we hit the starting point (it is circular). while entry_addr != head { // Read the table entry.. - let Some(data) = parser.try_virt_read_struct::>(entry_addr)? else { + let Some(data) = parser.virt_read_struct::>(entry_addr)? else { return Ok(None); }; // ..and read it. We first try to read `full_dll_name` but will try // `base_dll_name` is we couldn't read the former. let Some(dll_name) = parser - .try_virt_read_unicode_string::

(&data.full_dll_name) + .virt_read_unicode_string::

(&data.full_dll_name) .and_then(|s| { if s.is_none() { // If we failed to read the `full_dll_name`, give `base_dll_name` a shot. - parser.try_virt_read_unicode_string::

(&data.base_dll_name) + parser.virt_read_unicode_string::

(&data.base_dll_name) } else { Ok(s) } @@ -173,7 +181,7 @@ fn try_find_prcb( let mut processor_block = kd_debugger_data_block.ki_processor_block; for _ in 0..parser.headers().number_processors { // Read the KPRCB pointer. - let Some(kprcb_addr) = parser.try_virt_read_struct::(processor_block.into())? else { + let Some(kprcb_addr) = parser.virt_read_struct::(processor_block.into())? else { return Ok(None); }; @@ -183,15 +191,13 @@ fn try_find_prcb( .ok_or(KdmpParserError::Overflow("offset_prcb"))?; // ..and read it. - let Some(kprcb_context_addr) = - parser.try_virt_read_struct::(kprcb_context_addr.into())? + let Some(kprcb_context_addr) = parser.virt_read_struct::(kprcb_context_addr.into())? else { return Ok(None); }; // Read the context.. - let Some(kprcb_context) = - parser.try_virt_read_struct::(kprcb_context_addr.into())? + let Some(kprcb_context) = parser.virt_read_struct::(kprcb_context_addr.into())? else { return Ok(None); }; @@ -225,7 +231,7 @@ fn try_extract_user_modules( .u64() .checked_add(kd_debugger_data_block.offset_prcb_current_thread.into()) .ok_or(KdmpParserError::Overflow("offset prcb current thread"))?; - let Some(kthread_addr) = parser.try_virt_read_struct::(kthread_addr.into())? else { + let Some(kthread_addr) = parser.virt_read_struct::(kthread_addr.into())? else { return Ok(None); }; @@ -233,7 +239,7 @@ fn try_extract_user_modules( let teb_addr = kthread_addr .checked_add(kd_debugger_data_block.offset_kthread_teb.into()) .ok_or(KdmpParserError::Overflow("offset kthread teb"))?; - let Some(teb_addr) = parser.try_virt_read_struct::(teb_addr.into())? else { + let Some(teb_addr) = parser.virt_read_struct::(teb_addr.into())? else { return Ok(None); }; @@ -251,7 +257,7 @@ fn try_extract_user_modules( let peb_addr = teb_addr .checked_add(peb_offset) .ok_or(KdmpParserError::Overflow("peb offset"))?; - let Some(peb_addr) = parser.try_virt_read_struct::(peb_addr.into())? else { + let Some(peb_addr) = parser.virt_read_struct::(peb_addr.into())? else { return Ok(None); }; @@ -264,7 +270,7 @@ fn try_extract_user_modules( let peb_ldr_addr = peb_addr .checked_add(ldr_offset) .ok_or(KdmpParserError::Overflow("ldr offset"))?; - let Some(peb_ldr_addr) = parser.try_virt_read_struct::(peb_ldr_addr.into())? else { + let Some(peb_ldr_addr) = parser.virt_read_struct::(peb_ldr_addr.into())? else { return Ok(None); }; @@ -302,7 +308,7 @@ fn try_extract_user_modules( let peb32_addr = teb32_addr .checked_add(peb32_offset) .ok_or(KdmpParserError::Overflow("peb32 offset"))?; - let Some(peb32_addr) = parser.try_virt_read_struct::(peb32_addr.into())? else { + let Some(peb32_addr) = parser.virt_read_struct::(peb32_addr.into())? else { return Ok(Some(modules)); }; @@ -315,8 +321,7 @@ fn try_extract_user_modules( let peb32_ldr_addr = peb32_addr .checked_add(ldr_offset) .ok_or(KdmpParserError::Overflow("ldr32 offset"))?; - let Some(peb32_ldr_addr) = - parser.try_virt_read_struct::(Gva::new(peb32_ldr_addr.into()))? + let Some(peb32_ldr_addr) = parser.virt_read_struct::(Gva::new(peb32_ldr_addr.into()))? else { return Ok(Some(modules)); }; @@ -347,19 +352,6 @@ fn try_extract_user_modules( Ok(Some(modules)) } -/// Filter out [`AddrTranslationError`] errors and turn them into `None`. This -/// makes it easier for caller code to write logic that can recover from a -/// memory read failure by bailing out for example, and not bubbling up an -/// error. -fn filter_addr_translation_err(res: Result) -> Result> { - match res { - Ok(o) => Ok(Some(o)), - // If we encountered a memory reading error, we won't consider this as a failure. - Err(KdmpParserError::AddrTranslation(..)) => Ok(None), - Err(e) => Err(e), - } -} - /// A module map. The key is the range of where the module lives at and the /// value is a path to the module or it's name if no path is available. pub type ModuleMap = HashMap, String>; @@ -389,16 +381,20 @@ pub struct KernelDumpParser { } impl Debug for KernelDumpParser { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("KernelDumpParser") .field("dump_type", &self.dump_type) - .finish() + .finish_non_exhaustive() } } impl KernelDumpParser { - /// Create an instance from a file path. This memory maps the file and - /// parses it. + /// Create an instance from a [`Reader`] & parse the file. + /// + /// # Errors + /// + /// Returns an error if the dump is malformed or if we encounter an I/O + /// error. pub fn with_reader(mut reader: impl Reader + 'static) -> Result { // Parse the dump header and check if things look right. let headers = Box::new(read_struct::(&mut reader)?); @@ -428,8 +424,8 @@ impl KernelDumpParser { headers, physmem, reader, - kernel_modules: Default::default(), - user_modules: Default::default(), + kernel_modules: HashMap::default(), + user_modules: HashMap::default(), }; // Extract the kernel modules if we can. If it fails because of a memory @@ -439,12 +435,11 @@ impl KernelDumpParser { } // Now let's try to find out user-modules. For that we need the - // KDDEBUGGER_DATA_BLOCK structure to know where a bunch of things are. + // `KDDEBUGGER_DATA_BLOCK` structure to know where a bunch of things are. // If we can't read the block, we'll have to stop the adventure here as we won't // be able to read the things we need to keep going. - let Some(kd_debugger_data_block) = parser.try_virt_read_struct::( - parser.headers().kd_debugger_data_block.into(), - )? + let Some(kd_debugger_data_block) = parser + .virt_read_struct::(parser.headers().kd_debugger_data_block.into())? else { return Ok(parser); }; @@ -467,23 +462,26 @@ impl KernelDumpParser { Ok(parser) } + /// Create an instance from a file path; depending on the file size, it'll + /// either memory maps it or open it as a regular file. + /// + /// # Errors + /// + /// Returns an error if the file can't be memory mapped or opened. pub fn new(dump_path: impl AsRef) -> Result { + const FOUR_GIGS: u64 = 1_024 * 1_024 * 1_024 * 4; // We'll assume that if you are opening a dump file larger than 4gb, you don't // want it memory mapped. let size = dump_path.as_ref().metadata()?.len(); - const FOUR_GIGS: u64 = 1_024 * 1_024 * 1_024 * 4; - match size { - 0..=FOUR_GIGS => { - let mapped_file = MappedFileReader::new(dump_path.as_ref())?; + if let 0..=FOUR_GIGS = size { + let mapped_file = MappedFileReader::new(dump_path.as_ref())?; - Self::with_reader(mapped_file) - } - _ => { - let file = File::open(dump_path)?; + Self::with_reader(mapped_file) + } else { + let file = File::open(dump_path)?; - Self::with_reader(file) - } + Self::with_reader(file) } } @@ -526,14 +524,21 @@ impl KernelDumpParser { /// Translate a [`Gpa`] into a file offset of where the content of the page /// resides in. - pub fn phys_translate(&self, gpa: Gpa) -> Result { + /// + /// # Errors + /// + /// Returns an error if the `gpa` has no backing page or if an integer + /// overflow is triggered while calculating where in the input file the + /// backing page is at. + pub fn phys_translate(&self, gpa: Gpa) -> Result { let offset = *self .physmem .get(&gpa.page_align()) - .ok_or(AddrTranslationError::Phys(gpa))?; + .ok_or(PageReadError::NotInDump { gva: None, gpa })?; offset .checked_add(gpa.offset()) + .map(SeekFrom::Start) .ok_or(KdmpParserError::Overflow("w/ gpa offset")) } @@ -550,12 +555,12 @@ impl KernelDumpParser { // Translate the gpa into a file offset.. let phy_offset = self.phys_translate(addr)?; // ..and seek the reader there. - self.seek(io::SeekFrom::Start(phy_offset))?; + self.seek(phy_offset)?; // We need to take care of reads that straddle different physical memory pages. // So let's figure out the maximum amount of bytes we can read off this page. // Either, we read it until its end, or we stop if the user wants us to read // less. - let left_in_page = (PageKind::Normal.size() - gpa.offset()) as usize; + let left_in_page = usize::try_from(PageKind::Normal.size() - gpa.offset()).unwrap(); let amount_wanted = min(amount_left, left_in_page); // Figure out where we should read into. let slice = &mut buf[total_read..total_read + amount_wanted]; @@ -589,16 +594,23 @@ impl KernelDumpParser { } // ..otherwise, we call it quits. else { - Err(KdmpParserError::PartialPhysRead) + let gpa = Gpa::new(gpa.u64() + u64::try_from(len).unwrap()); + + Err(MemoryReadError::PartialRead { + expected_amount: buf.len(), + actual_amount: len, + reason: PageReadError::NotInDump { gva: None, gpa }, + } + .into()) } } /// Read a `T` from physical memory. pub fn phys_read_struct(&self, gpa: Gpa) -> Result { - let mut t = mem::MaybeUninit::uninit(); - let size_of_t = mem::size_of_val(&t); + let mut t: MaybeUninit = MaybeUninit::uninit(); + let size_of_t = size_of_val(&t); let slice_over_t = - unsafe { slice::from_raw_parts_mut(t.as_mut_ptr() as *mut u8, size_of_t) }; + unsafe { slice::from_raw_parts_mut(t.as_mut_ptr().cast::(), size_of_t) }; self.phys_read_exact(gpa, slice_over_t)?; @@ -612,65 +624,122 @@ impl KernelDumpParser { /// Translate a [`Gva`] into a [`Gpa`] using a specific directory table base /// / set of page tables. + #[allow(clippy::similar_names)] pub fn virt_translate_with_dtb(&self, gva: Gva, dtb: Gpa) -> Result { + let read_pxe = |gpa: Gpa, pxe: PxeKind| { + self.phys_read_struct::(gpa) + .map_err(|e| match e { + // If the physical page isn't in the dump, enrich the error by adding the gva + // that was getting translated as well as the pxe level. + KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotInDump { gpa, .. }, + )) => PageReadError::NotInDump { + gva: Some((gva, Some(pxe))), + gpa, + } + .into(), + e => e, + }) + .map(Pxe::from) + }; + // Aligning in case PCID bits are set (bits 11:0) let pml4_base = dtb.page_align(); let pml4e_gpa = Gpa::new(pml4_base.u64() + (gva.pml4e_idx() * 8)); - let pml4e = Pxe::from(self.phys_read_struct::(pml4e_gpa)?); + let pml4e = read_pxe(pml4e_gpa, PxeKind::Pml4e)?; if !pml4e.present() { - return Err(AddrTranslationError::Virt(gva, PxeNotPresent::Pml4e).into()); + return Err(PageReadError::NotPresent { + gva, + which_pxe: PxeKind::Pml4e, + } + .into()); } let pdpt_base = pml4e.pfn.gpa(); let pdpte_gpa = Gpa::new(pdpt_base.u64() + (gva.pdpe_idx() * 8)); - let pdpte = Pxe::from(self.phys_read_struct::(pdpte_gpa)?); + let pdpte = read_pxe(pdpte_gpa, PxeKind::Pdpte)?; if !pdpte.present() { - return Err(AddrTranslationError::Virt(gva, PxeNotPresent::Pdpte).into()); + return Err(PageReadError::NotPresent { + gva, + which_pxe: PxeKind::Pdpte, + } + .into()); } // huge pages: // 7 (PS) - Page size; must be 1 (otherwise, this entry references a page - // directory; see Table 4-1 + // directory; see Table 4-1. let pd_base = pdpte.pfn.gpa(); if pdpte.large_page() { return Ok(VirtTranslationDetails::new(&[pml4e, pdpte], gva)); } let pde_gpa = Gpa::new(pd_base.u64() + (gva.pde_idx() * 8)); - let pde = Pxe::from(self.phys_read_struct::(pde_gpa)?); + let pde = read_pxe(pde_gpa, PxeKind::Pde)?; if !pde.present() { - return Err(AddrTranslationError::Virt(gva, PxeNotPresent::Pde).into()); + return Err(PageReadError::NotPresent { + gva, + which_pxe: PxeKind::Pde, + } + .into()); } // large pages: // 7 (PS) - Page size; must be 1 (otherwise, this entry references a page - // table; see Table 4-18 + // table; see Table 4-18. let pt_base = pde.pfn.gpa(); if pde.large_page() { return Ok(VirtTranslationDetails::new(&[pml4e, pdpte, pde], gva)); } let pte_gpa = Gpa::new(pt_base.u64() + (gva.pte_idx() * 8)); - let pte = Pxe::from(self.phys_read_struct::(pte_gpa)?); + let pte = read_pxe(pte_gpa, PxeKind::Pte)?; if !pte.present() { // We'll allow reading from a transition PTE, so return an error only if it's // not one, otherwise we'll carry on. if !pte.transition() { - return Err(AddrTranslationError::Virt(gva, PxeNotPresent::Pte).into()); + return Err(PageReadError::NotPresent { + gva, + which_pxe: PxeKind::Pte, + } + .into()); } } Ok(VirtTranslationDetails::new(&[pml4e, pdpte, pde, pte], gva)) } - /// Read virtual memory starting at `gva` into a `buffer`. - pub fn virt_read(&self, gva: Gva, buf: &mut [u8]) -> Result { + /// Read virtual memory starting at `gva` into a `buffer`. Returns `None` if + /// a memory error occurs (page not present, page not in dump, etc.). + pub fn virt_read(&self, gva: Gva, buf: &mut [u8]) -> Result> { self.virt_read_with_dtb(gva, buf, Gpa::new(self.headers.directory_table_base)) } /// Read virtual memory starting at `gva` into a `buffer` using a specific - /// directory table base / set of page tables. - pub fn virt_read_with_dtb(&self, gva: Gva, buf: &mut [u8], dtb: Gpa) -> Result { + /// directory table base / set of page tables. Returns `None` if a memory + /// error occurs (page not present, page not in dump, etc.). + pub fn virt_read_with_dtb(&self, gva: Gva, buf: &mut [u8], dtb: Gpa) -> Result> { + match self.virt_read_strict_with_dtb(gva, buf, dtb) { + Ok(n) => Ok(Some(n)), + Err(KdmpParserError::MemoryRead(MemoryReadError::PartialRead { + actual_amount, + .. + })) => Ok(Some(actual_amount)), + Err(KdmpParserError::MemoryRead(_)) => Ok(None), + Err(e) => Err(e), + } + } + + /// Read virtual memory starting at `gva` into a `buffer`, propagating all + /// errors including memory errors. + pub fn virt_read_strict(&self, gva: Gva, buf: &mut [u8]) -> Result { + self.virt_read_strict_with_dtb(gva, buf, Gpa::new(self.headers.directory_table_base)) + } + + /// Read virtual memory starting at `gva` into a `buffer` using a specific + /// directory table base / set of page tables, propagating all errors + /// including memory errors. + pub fn virt_read_strict_with_dtb(&self, gva: Gva, buf: &mut [u8], dtb: Gpa) -> Result { // Amount of bytes left to read. let mut amount_left = buf.len(); // Total amount of bytes that we have successfully read. @@ -683,19 +752,25 @@ impl KernelDumpParser { // occured if we already have read some bytes. let translation = match self.virt_translate_with_dtb(addr, dtb) { Ok(tr) => tr, - Err(e) => { - if total_read > 0 { - // If we already read some bytes, return how many we read. - return Ok(total_read); + // If we already read some bytes, convert the error to a PartialRead.. + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead(reason))) + if total_read > 0 => + { + return Err(MemoryReadError::PartialRead { + expected_amount: buf.len(), + actual_amount: total_read, + reason, } - - return Err(e); + .into()); } + // ..otherwise this is an error. + Err(e) => return Err(e), }; // We need to take care of reads that straddle different virtual memory pages. // First, figure out the maximum amount of bytes we can read off this page. - let left_in_page = (translation.page_kind.size() - translation.offset) as usize; + let left_in_page = + usize::try_from(translation.page_kind.size() - translation.offset).unwrap(); // Then, either we read it until its end, or we stop before if we can get by // with less. let amount_wanted = min(amount_left, left_in_page); @@ -703,7 +778,40 @@ impl KernelDumpParser { let slice = &mut buf[total_read..total_read + amount_wanted]; // Read the physical memory! - let amount_read = self.phys_read(translation.gpa(), slice)?; + let gpa = translation.gpa(); + let amount_read = match self.phys_read(gpa, slice) { + Ok(n) => n, + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead(reason))) + if total_read > 0 => + { + // Convert physical read error to include virtual address context + let reason = match reason { + PageReadError::NotInDump { gpa, .. } => PageReadError::NotInDump { + gva: Some((addr, None)), + gpa, + }, + e @ PageReadError::NotPresent { .. } => e, + }; + + return Err(MemoryReadError::PartialRead { + expected_amount: buf.len(), + actual_amount: total_read, + reason, + } + .into()); + } + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotInDump { gpa, .. }, + ))) => { + // First read failed, convert to BackingPageNotInDump + return Err(PageReadError::NotInDump { + gva: Some((addr, None)), + gpa, + } + .into()); + } + Err(e) => return Err(e), + }; // Update the total amount of read bytes and how much work we have left. total_read += amount_read; amount_left -= amount_read; @@ -720,123 +828,140 @@ impl KernelDumpParser { Ok(total_read) } - /// Try to read virtual memory starting at `gva` into a `buffer`. If a - /// memory translation error occurs, it'll return `None` instead of an - /// error. - pub fn try_virt_read(&self, gva: Gva, buf: &mut [u8]) -> Result> { - filter_addr_translation_err(self.virt_read(gva, buf)) + /// Read an exact amount of virtual memory starting at `gva`. Returns `None` + /// if a memory error occurs (page not present, page not in dump, etc.). + pub fn virt_read_exact(&self, gva: Gva, buf: &mut [u8]) -> Result> { + self.virt_read_exact_with_dtb(gva, buf, Gpa::new(self.headers.directory_table_base)) } - /// Try to read virtual memory starting at `gva` into a `buffer` using a - /// specific directory table base / set of page tables. If a - /// memory translation error occurs, it'll return `None` instead of an - /// error. - pub fn try_virt_read_with_dtb( + /// Read an exact amount of virtual memory starting at `gva` using a + /// specific directory table base / set of page tables. Returns `None` if a + /// memory error occurs (page not present, page not in dump, etc.). + pub fn virt_read_exact_with_dtb( &self, gva: Gva, buf: &mut [u8], dtb: Gpa, - ) -> Result> { - filter_addr_translation_err(self.virt_read_with_dtb(gva, buf, dtb)) + ) -> Result> { + match self.virt_read_exact_strict_with_dtb(gva, buf, dtb) { + Ok(()) => Ok(Some(())), + Err(KdmpParserError::MemoryRead(_)) => Ok(None), + Err(e) => Err(e), + } } - /// Read an exact amount of virtual memory starting at `gva`. - pub fn virt_read_exact(&self, gva: Gva, buf: &mut [u8]) -> Result<()> { - self.virt_read_exact_with_dtb(gva, buf, Gpa::new(self.headers.directory_table_base)) + /// Read an exact amount of virtual memory starting at `gva`, propagating + /// all errors including memory errors. + pub fn virt_read_exact_strict(&self, gva: Gva, buf: &mut [u8]) -> Result<()> { + self.virt_read_exact_strict_with_dtb(gva, buf, Gpa::new(self.headers.directory_table_base)) } /// Read an exact amount of virtual memory starting at `gva` using a - /// specific directory table base / set of page tables. - pub fn virt_read_exact_with_dtb(&self, gva: Gva, buf: &mut [u8], dtb: Gpa) -> Result<()> { + /// specific directory table base / set of page tables, propagating all + /// errors including memory errors. + pub fn virt_read_exact_strict_with_dtb( + &self, + gva: Gva, + buf: &mut [u8], + dtb: Gpa, + ) -> Result<()> { // Read virtual memory. - let len = self.virt_read_with_dtb(gva, buf, dtb)?; + let len = self.virt_read_strict_with_dtb(gva, buf, dtb)?; // If we read as many bytes as we wanted, then it's a win.. if len == buf.len() { Ok(()) } - // ..otherwise, we call it quits. + // ..otherwise, we call it quits. The failure should have been reported + // as a PartialRead by virt_read_strict_with_dtb already, but this handles + // the case where we read some bytes but not all without hitting an error. else { - Err(KdmpParserError::PartialVirtRead) + let failed_gva = gva + .u64() + .checked_add(len.try_into().unwrap()) + .map_or(gva, Gva::new); + // This shouldn't normally happen as virt_read_strict_with_dtb should report + // the specific error, but we provide a generic error just in case. + Err(MemoryReadError::PartialRead { + expected_amount: buf.len(), + actual_amount: len, + reason: PageReadError::NotInDump { + gva: Some((failed_gva, None)), + gpa: Gpa::new(0), // Unknown GPA + }, + } + .into()) } } - /// Try to read an exact amount of virtual memory starting at `gva`. If a - /// memory translation error occurs, it'll return `None` instead of an - /// error. - pub fn try_virt_read_exact(&self, gva: Gva, buf: &mut [u8]) -> Result> { - self.try_virt_read_exact_with_dtb(gva, buf, Gpa::new(self.headers.directory_table_base)) + /// Read a `T` from virtual memory. Returns `None` if a memory error occurs + /// (page not present, page not in dump, etc.). + pub fn virt_read_struct(&self, gva: Gva) -> Result> { + self.virt_read_struct_with_dtb(gva, Gpa::new(self.headers.directory_table_base)) } - /// Try to read an exact amount of virtual memory starting at `gva` using a - /// specific directory table base / set of page tables. If a - /// memory translation error occurs, it'll return `None` instead of an - /// error. - pub fn try_virt_read_exact_with_dtb( - &self, - gva: Gva, - buf: &mut [u8], - dtb: Gpa, - ) -> Result> { - filter_addr_translation_err(self.virt_read_exact_with_dtb(gva, buf, dtb)) + /// Read a `T` from virtual memory using a specific directory table base / + /// set of page tables. Returns `None` if a memory error occurs (page not + /// present, page not in dump, etc.). + pub fn virt_read_struct_with_dtb(&self, gva: Gva, dtb: Gpa) -> Result> { + match self.virt_read_struct_strict_with_dtb::(gva, dtb) { + Ok(t) => Ok(Some(t)), + Err(KdmpParserError::MemoryRead(_)) => Ok(None), + Err(e) => Err(e), + } } - /// Read a `T` from virtual memory. - pub fn virt_read_struct(&self, gva: Gva) -> Result { - self.virt_read_struct_with_dtb(gva, Gpa::new(self.headers.directory_table_base)) + /// Read a `T` from virtual memory, propagating all errors including memory + /// errors. + pub fn virt_read_struct_strict(&self, gva: Gva) -> Result { + self.virt_read_struct_strict_with_dtb(gva, Gpa::new(self.headers.directory_table_base)) } /// Read a `T` from virtual memory using a specific directory table base / - /// set of page tables. - pub fn virt_read_struct_with_dtb(&self, gva: Gva, dtb: Gpa) -> Result { - let mut t = mem::MaybeUninit::uninit(); - let size_of_t = mem::size_of_val(&t); + /// set of page tables, propagating all errors including memory errors. + pub fn virt_read_struct_strict_with_dtb(&self, gva: Gva, dtb: Gpa) -> Result { + let mut t: MaybeUninit = MaybeUninit::uninit(); + let size_of_t = size_of_val(&t); let slice_over_t = - unsafe { slice::from_raw_parts_mut(t.as_mut_ptr() as *mut u8, size_of_t) }; + unsafe { slice::from_raw_parts_mut(t.as_mut_ptr().cast::(), size_of_t) }; - self.virt_read_exact_with_dtb(gva, slice_over_t, dtb)?; + self.virt_read_exact_strict_with_dtb(gva, slice_over_t, dtb)?; Ok(unsafe { t.assume_init() }) } - /// Try to read a `T` from virtual memory . If a memory translation error - /// occurs, it'll return `None` instead of an error. - pub fn try_virt_read_struct(&self, gva: Gva) -> Result> { - self.try_virt_read_struct_with_dtb::(gva, Gpa::new(self.headers.directory_table_base)) - } - - /// Try to read a `T` from virtual memory using a specific directory table - /// base / set of page tables. If a memory translation error occurs, it' - /// ll return `None` instead of an error. - pub fn try_virt_read_struct_with_dtb(&self, gva: Gva, dtb: Gpa) -> Result> { - filter_addr_translation_err(self.virt_read_struct_with_dtb::(gva, dtb)) - } - + /// Seek to `pos`. + /// + /// # Errors + /// + /// Returns an error if the file cannot be seeked to `pos`. pub fn seek(&self, pos: io::SeekFrom) -> Result { Ok(self.reader.borrow_mut().seek(pos)?) } + /// Read however many bytes in `buf` and returns the amount of bytes read. + /// + /// # Errors + /// + /// Returns an error if it encountered any kind of I/O error. pub fn read(&self, buf: &mut [u8]) -> Result { Ok(self.reader.borrow_mut().read(buf)?) } - /// Try to read a `UNICODE_STRING`. - fn try_virt_read_unicode_string

( - &self, - unicode_str: &UnicodeString

, - ) -> Result> + /// Read a `UNICODE_STRING`. Returns `None` if a memory error occurs. + fn virt_read_unicode_string

(&self, unicode_str: &UnicodeString

) -> Result> where P: PtrSize, { - self.try_virt_read_unicode_string_with_dtb( + self.virt_read_unicode_string_with_dtb( unicode_str, Gpa::new(self.headers.directory_table_base), ) } - /// Try to read a `UNICODE_STRING` using a specific directory table base / - /// set of page tables. - fn try_virt_read_unicode_string_with_dtb

( + /// Read a `UNICODE_STRING` using a specific directory table base / set of + /// page tables. Returns `None` if a memory error occurs. + fn virt_read_unicode_string_with_dtb

( &self, unicode_str: &UnicodeString

, dtb: Gpa, @@ -849,11 +974,10 @@ impl KernelDumpParser { } let mut buffer = vec![0; unicode_str.length.into()]; - match self.virt_read_exact_with_dtb(Gva::new(unicode_str.buffer.into()), &mut buffer, dtb) { - Ok(_) => {} - // If we encountered a memory translation error, we don't consider this a failure. - Err(KdmpParserError::AddrTranslation(_)) => return Ok(None), - Err(e) => return Err(e), + let Some(()) = + self.virt_read_exact_with_dtb(Gva::new(unicode_str.buffer.into()), &mut buffer, dtb)? + else { + return Ok(None); }; let n = unicode_str.length / 2; @@ -869,17 +993,17 @@ impl KernelDumpParser { /// physical pages starting at a `PFN`. This means that you can have /// "holes" in the physical address space and you don't need to write any /// data for them. Here is a small example: - /// - Run[0]: BasePage = 1_337, PageCount = 2 - /// - Run[1]: BasePage = 1_400, PageCount = 1 + /// - `Run[0]`: `BasePage = 1_337`, `PageCount = 2` + /// - `Run[1]`: `BasePage = 1_400`, `PageCount = 1` /// - /// In the above, there is a "hole" between the two runs. It has 2+1 memory - /// pages at: Pfn(1_337+0), Pfn(1_337+1) and Pfn(1_400+0) (but nothing - /// at Pfn(1_339)). + /// In the above, there is a "hole" between the two runs. It has `2+1` + /// memory pages at: `Pfn(1_337+0)`, `Pfn(1_337+1)` and `Pfn(1_400+0)` + /// (but nothing at `Pfn(1_339)`). /// /// In terms of the content of those physical memory pages, they are packed /// and stored one after another. If the first page of the first run is - /// at file offset 0x2_000, then the first page of the second run is at - /// file offset 0x2_000+(2*0x1_000). + /// at file offset `0x2_000`, then the first page of the second run is at + /// file offset `0x2_000+(2*0x1_000)`. fn full_physmem(headers: &Header64, reader: &mut impl Reader) -> Result { let mut page_offset = reader.stream_position()?; let mut run_cursor = io::Cursor::new(headers.physical_memory_block_buffer); diff --git a/src/pxe.rs b/src/pxe.rs index fcc83b6..cb21f5d 100644 --- a/src/pxe.rs +++ b/src/pxe.rs @@ -8,29 +8,100 @@ //! # use kdmp_parser::{Pxe, PxeFlags, Pfn}; //! let pxe = Pxe::new( //! Pfn::new(0x6d600), -//! PxeFlags::UserAccessible | PxeFlags::Accessed | PxeFlags::Present +//! PxeFlags::USER_ACCESSIBLE | PxeFlags::ACCESSED | PxeFlags::PRESENT //! ); //! let encoded = u64::from(pxe); //! let decoded = Pxe::from(encoded); //! ``` -use bitflags::bitflags; - -use crate::Gpa; - -bitflags! { - /// The various bits and flags that a [`Pxe`] has. - #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Default, PartialOrd, Ord)] - pub struct PxeFlags : u64 { - const Present = 1 << 0; - const Writable = 1 << 1; - const UserAccessible = 1 << 2; - const WriteThrough = 1 << 3; - const CacheDisabled = 1 << 4; - const Accessed = 1 << 5; - const Dirty = 1 << 6; - const LargePage = 1 << 7; - const Transition = 1 << 11; - const NoExecute = 1 << 63; +use std::ops::{BitOr, Deref}; + +use crate::{Bits, Gpa}; + +/// The various bits and flags that a [`Pxe`] has. +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Default, PartialOrd, Ord)] +pub struct PxeFlags(u64); + +impl PxeFlags { + pub const ACCESSED: Self = Self(1 << 5); + pub const CACHE_DISABLED: Self = Self(1 << 4); + pub const DIRTY: Self = Self(1 << 6); + pub const LARGE_PAGE: Self = Self(1 << 7); + pub const NO_EXECUTE: Self = Self(1 << 63); + pub const PRESENT: Self = Self(1 << 0); + pub const TRANSITION: Self = Self(1 << 11); + pub const USER_ACCESSIBLE: Self = Self(1 << 2); + pub const WRITABLE: Self = Self(1 << 1); + pub const WRITE_THROUGH: Self = Self(1 << 3); + + #[must_use] + pub fn new(bits: u64) -> Self { + Self(bits) + } + + #[must_use] + pub fn present(&self) -> bool { + self.0.bit(0) != 0 + } + + #[must_use] + pub fn writable(&self) -> bool { + self.0.bit(1) != 0 + } + + #[must_use] + pub fn user_accessible(&self) -> bool { + self.0.bit(2) != 0 + } + + #[must_use] + pub fn write_through(&self) -> bool { + self.0.bit(3) != 0 + } + + #[must_use] + pub fn cache_disabled(&self) -> bool { + self.0.bit(4) != 0 + } + + #[must_use] + pub fn accessed(&self) -> bool { + self.0.bit(5) != 0 + } + + #[must_use] + pub fn dirty(&self) -> bool { + self.0.bit(6) != 0 + } + + #[must_use] + pub fn large_page(&self) -> bool { + self.0.bit(7) != 0 + } + + #[must_use] + pub fn transition(&self) -> bool { + self.0.bit(11) != 0 + } + + #[must_use] + pub fn no_execute(&self) -> bool { + self.0.bit(63) != 0 + } +} + +impl BitOr for PxeFlags { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self::new(*self | *rhs) + } +} + +impl Deref for PxeFlags { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 } } @@ -49,18 +120,22 @@ bitflags! { pub struct Pfn(u64); impl Pfn { + #[must_use] pub const fn new(pfn: u64) -> Self { Self(pfn) } + #[must_use] pub const fn u64(&self) -> u64 { self.0 } + #[must_use] pub const fn gpa(&self) -> Gpa { Gpa::from_pfn(*self) } + #[must_use] pub const fn gpa_with_offset(&self, offset: u64) -> Gpa { Gpa::from_pfn_with_offset(*self, offset) } @@ -79,9 +154,6 @@ impl From for u64 { } /// A [`Pxe`] is a set of flags ([`PxeFlags`]) and a Page Frame Number (PFN). -/// This representation takes more space than a regular `PXE` but it is more -/// convenient to split the flags / the pfn as [`bitflags!`] doesn't seem to -/// support bitfields. #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Default, PartialOrd, Ord)] pub struct Pxe { /// The PFN of the next table or the final page. @@ -100,11 +172,12 @@ impl Pxe { /// # fn main() { /// let pxe = Pxe::new( /// Pfn::new(0x6d600), - /// PxeFlags::UserAccessible | PxeFlags::Accessed | PxeFlags::Present + /// PxeFlags::USER_ACCESSIBLE | PxeFlags::ACCESSED | PxeFlags::PRESENT /// ); /// assert_eq!(pxe.pfn.u64(), 0x6d600); /// # } /// ``` + #[must_use] pub fn new(pfn: Pfn, flags: PxeFlags) -> Self { Self { pfn, flags } } @@ -118,18 +191,19 @@ impl Pxe { /// # fn main() { /// let p = Pxe::new( /// Pfn::new(0x6d600), - /// PxeFlags::Present + /// PxeFlags::PRESENT /// ); /// assert!(p.present()); /// let np = Pxe::new( /// Pfn::new(0x1337), - /// PxeFlags::UserAccessible + /// PxeFlags::USER_ACCESSIBLE /// ); /// assert!(!np.present()); /// # } /// ``` + #[must_use] pub fn present(&self) -> bool { - self.flags.contains(PxeFlags::Present) + self.flags.present() } /// Is it a large page? @@ -141,18 +215,19 @@ impl Pxe { /// # fn main() { /// let p = Pxe::new( /// Pfn::new(0x6d600), - /// PxeFlags::LargePage + /// PxeFlags::LARGE_PAGE /// ); /// assert!(p.large_page()); /// let np = Pxe::new( /// Pfn::new(0x1337), - /// PxeFlags::UserAccessible + /// PxeFlags::USER_ACCESSIBLE /// ); /// assert!(!np.large_page()); /// # } /// ``` + #[must_use] pub fn large_page(&self) -> bool { - self.flags.contains(PxeFlags::LargePage) + self.flags.large_page() } /// Is it a transition PTE? @@ -168,8 +243,9 @@ impl Pxe { /// assert!(!np.transition()); /// # } /// ``` + #[must_use] pub fn transition(&self) -> bool { - !self.present() && self.flags.contains(PxeFlags::Transition) + !self.present() && self.flags.transition() } /// Is the memory described by this [`Pxe`] writable? @@ -185,8 +261,9 @@ impl Pxe { /// assert!(!ro.writable()); /// # } /// ``` + #[must_use] pub fn writable(&self) -> bool { - self.flags.contains(PxeFlags::Writable) + self.flags.writable() } /// Is the memory described by this [`Pxe`] executable? @@ -202,8 +279,9 @@ impl Pxe { /// assert!(!nx.executable()); /// # } /// ``` + #[must_use] pub fn executable(&self) -> bool { - !self.flags.contains(PxeFlags::NoExecute) + !self.flags.no_execute() } /// Is the memory described by this [`Pxe`] accessible by user-mode? @@ -219,8 +297,9 @@ impl Pxe { /// assert!(!s.user_accessible()); /// # } /// ``` + #[must_use] pub fn user_accessible(&self) -> bool { - self.flags.contains(PxeFlags::UserAccessible) + self.flags.user_accessible() } } @@ -235,12 +314,14 @@ impl From for Pxe { /// # fn main() { /// let pxe = Pxe::from(0x6D_60_00_25); /// assert_eq!(pxe.pfn.u64(), 0x6d600); - /// assert_eq!(pxe.flags, PxeFlags::UserAccessible | PxeFlags::Accessed | PxeFlags::Present); + /// assert_eq!(pxe.flags, PxeFlags::USER_ACCESSIBLE | PxeFlags::ACCESSED | PxeFlags::PRESENT); /// # } /// ``` fn from(value: u64) -> Self { - let pfn = Pfn::new((value >> 12) & 0xf_ffff_ffff); - let flags = PxeFlags::from_bits(value & PxeFlags::all().bits()).expect("PxeFlags"); + const PFN_MASK: u64 = 0xffff_ffff_f000; + const FLAGS_MASK: u64 = !PFN_MASK; + let pfn = Pfn::new((value & PFN_MASK) >> 12); + let flags = PxeFlags::new(value & FLAGS_MASK); Self::new(pfn, flags) } @@ -257,7 +338,7 @@ impl From for u64 { /// # fn main() { /// let pxe = Pxe::new( /// Pfn::new(0x6d600), - /// PxeFlags::UserAccessible | PxeFlags::Accessed | PxeFlags::Present, + /// PxeFlags::USER_ACCESSIBLE | PxeFlags::ACCESSED | PxeFlags::PRESENT, /// ); /// assert_eq!(u64::from(pxe), 0x6D_60_00_25); /// # } @@ -265,6 +346,6 @@ impl From for u64 { fn from(pxe: Pxe) -> Self { debug_assert!(pxe.pfn.u64() <= 0xf_ffff_ffffu64); - pxe.flags.bits() | (pxe.pfn.u64() << 12u64) + *pxe.flags | (pxe.pfn.u64() << 12u64) } } diff --git a/src/structs.rs b/src/structs.rs index e85084f..a0584a3 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -2,7 +2,9 @@ //! This has all the raw structures that makes up Windows kernel crash-dumps. use std::collections::BTreeMap; use std::fmt::Debug; -use std::{io, mem, slice}; +use std::io::SeekFrom; +use std::mem::MaybeUninit; +use std::slice; use crate::error::Result; use crate::{Gpa, KdmpParserError, Reader}; @@ -20,6 +22,7 @@ pub enum PageKind { impl PageKind { /// Size in bytes of the page. + #[must_use] pub fn size(&self) -> u64 { match self { Self::Normal => 4 * 1_024, @@ -29,6 +32,7 @@ impl PageKind { } /// Extract the page offset of `addr`. + #[must_use] pub fn page_offset(&self, addr: u64) -> u64 { let mask = self.size() - 1; @@ -40,12 +44,12 @@ impl PageKind { #[derive(Debug, Clone, Copy, PartialEq)] #[repr(u32)] pub enum DumpType { - // Old dump types from dbgeng.dll + // Old dump types from `dbgeng.dll`. Full = 0x1, Bmp = 0x5, /// Produced by `.dump /m`. // Mini = 0x4, - /// (22H2+) Produced by TaskMgr > System > Create live kernel Memory Dump. + /// (22H2+) Produced by `TaskMgr > System > Create live kernel Memory Dump`. LiveKernelMemory = 0x6, /// Produced by `.dump /k`. KernelMemory = 0x8, @@ -163,7 +167,7 @@ impl Debug for Header64 { .field("kd_secondary_version", &self.kd_secondary_version) .field("attributes", &self.attributes) .field("boot_id", &self.boot_id) - .finish() + .finish_non_exhaustive() } } @@ -183,13 +187,13 @@ pub struct BmpHeader64 { // )]], // # The offset of the first page in the file. // 'FirstPage': [0x20, ['unsigned long long']], - padding1: [u8; 0x20 - (0x4 + mem::size_of::())], + padding1: [u8; 0x20 - (0x4 + size_of::())], /// The offset of the first page in the file. pub first_page: u64, /// Total number of pages present in the bitmap. pub total_present_pages: u64, /// Total number of pages in image. This dictates the total size of the - /// bitmap.This is not the same as the TotalPresentPages which is only + /// bitmap. This is not the same as the `TotalPresentPages` which is only /// the sum of the bits set to 1. pub pages: u64, // Bitmap follows @@ -237,7 +241,7 @@ impl TryFrom<&[u8]> for PhysmemDesc { type Error = KdmpParserError; fn try_from(slice: &[u8]) -> Result { - let expected_len = mem::size_of::(); + let expected_len = size_of::(); if slice.len() < expected_len { return Err(KdmpParserError::InvalidData("physmem desc is too small")); } @@ -379,19 +383,19 @@ impl Debug for Context { .field("last_branch_from_rip", &self.last_branch_from_rip) .field("last_exception_to_rip", &self.last_exception_to_rip) .field("last_exception_from_rip", &self.last_exception_from_rip) - .finish() + .finish_non_exhaustive() } } /// Peek for a `T` from the cursor. pub fn peek_struct(reader: &mut impl Reader) -> Result { - let mut s = mem::MaybeUninit::uninit(); - let size_of_s = mem::size_of_val(&s); - let slice_over_s = unsafe { slice::from_raw_parts_mut(s.as_mut_ptr() as *mut u8, size_of_s) }; + let mut s: MaybeUninit = MaybeUninit::uninit(); + let size_of_s = size_of_val(&s); + let slice_over_s = unsafe { slice::from_raw_parts_mut(s.as_mut_ptr().cast::(), size_of_s) }; let pos = reader.stream_position()?; reader.read_exact(slice_over_s)?; - reader.seek(io::SeekFrom::Start(pos))?; + reader.seek(SeekFrom::Start(pos))?; Ok(unsafe { s.assume_init() }) } @@ -399,9 +403,9 @@ pub fn peek_struct(reader: &mut impl Reader) -> Result { /// Read a `T` from the cursor. pub fn read_struct(reader: &mut impl Reader) -> Result { let s = peek_struct(reader)?; - let size_of_s = mem::size_of_val(&s); + let size_of_s = size_of_val(&s); - reader.seek(io::SeekFrom::Current(size_of_s.try_into().unwrap()))?; + reader.seek(SeekFrom::Current(size_of_s.try_into().unwrap()))?; Ok(s) } @@ -520,18 +524,18 @@ pub struct KdDebuggerData64 { pub header: DbgKdDebugDataHeader64, /// Base address of kernel image pub kern_base: u64, - /// DbgBreakPointWithStatus is a function which takes an argument - /// and hits a breakpoint. This field contains the address of the - /// breakpoint instruction. When the debugger sees a breakpoint + /// `DbgBreakPointWithStatus` is a function which takes an argument + /// and hits a breakpoint. This field contains the address of the + /// breakpoint instruction. When the debugger sees a breakpoint /// at this address, it may retrieve the argument from the first /// argument register, or on x86 the eax register. pub breakpoint_with_status: u64, /// Address of the saved context record during a bugcheck - /// N.B. This is an automatic in KeBugcheckEx's frame, and + /// N.B. This is an automatic in `KeBugcheckEx`'s frame, and /// is only valid after a bugcheck. pub saved_context: u64, /// The address of the thread structure is provided in the - /// WAIT_STATE_CHANGE packet. This is the offset from the base of + /// `WAIT_STATE_CHANGE` packet. This is the offset from the base of /// the thread structure to the pointer to the kernel stack frame /// for the currently active usermode callback. pub th_callback_stack: u16, diff --git a/tests/regression.rs b/tests/regression.rs index 7b475fa..0e93521 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -6,7 +6,7 @@ use std::ops::Range; use std::path::PathBuf; use kdmp_parser::{ - AddrTranslationError, Gpa, Gva, KdmpParserError, KernelDumpParser, PageKind, PxeNotPresent, + Gpa, Gva, KdmpParserError, KernelDumpParser, MemoryReadError, PageKind, PageReadError, PxeKind, }; use serde::Deserialize; @@ -83,7 +83,7 @@ fn compare_modules(parser: &KernelDumpParser, modules: &[Module]) -> bool { continue; } - eprintln!("{name} {found_mod:?}"); + eprintln!("name: {name} filename: {filename} found_mod: {found_mod:#x?}"); return false; } } @@ -436,8 +436,8 @@ fn regressions() { // PXE at FFFFECF67B3D9018 PPE at FFFFECF67B203480 PDE at FFFFECF640690BA8 PTE at FFFFEC80D2175180 // contains 0A0000000ECC0867 contains 0A00000013341867 contains 0A000000077AF867 contains 00000000166B7880 // pfn ecc0 ---DA--UWEV pfn 13341 ---DA--UWEV pfn 77af ---DA--UWEV not valid - // Transition: 166b7 - // Protect: 4 - ReadWrite + // Transition: 166b7 + // Protect: 4 - ReadWrite // ``` let parser = KernelDumpParser::new(&kernel_user_dump.file).unwrap(); let mut buffer = [0; 16]; @@ -456,8 +456,8 @@ fn regressions() { // PXE at FFFFECF67B3D9018 PPE at FFFFECF67B203480 PDE at FFFFECF640690BA8 PTE at FFFFEC80D2175180 // contains 0A0000000ECC0867 contains 0A00000013341867 contains 0A000000077AF867 contains 00000000166B7880 // pfn ecc0 ---DA--UWEV pfn 13341 ---DA--UWEV pfn 77af ---DA--UWEV not valid - // Transition: 166b7 - // Protect: 4 - ReadWrite + // Transition: 166b7 + // Protect: 4 - ReadWrite // kd> !db 166b7240 // Physical memory read at 166b7240 failed // @@ -471,18 +471,35 @@ fn regressions() { // ``` let parser = KernelDumpParser::new(&kernel_dump.file).unwrap(); let mut buffer = [0]; + assert!(matches!( + parser.virt_read_strict(0x1a42ea30240.into(), &mut buffer), + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotInDump { gva: Some((gva, None)), gpa } + ))) if gpa == 0x166b7240.into() && gva == 0x1a42ea30240.into() + )); + assert!(matches!( parser.virt_read(0x1a42ea30240.into(), &mut buffer), - Err(KdmpParserError::AddrTranslation( - AddrTranslationError::Phys(gpa) - )) if gpa == 0x166b7240.into() + Ok(None) + )); + + assert!(matches!( + parser.phys_read(0x166b7240.into(), &mut buffer), + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotInDump { gva: None, gpa } + ))) if gpa == 0x166b7240.into() + )); + + assert!(matches!( + parser.virt_read_strict(0x16e23fa060.into(), &mut buffer), + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotInDump { gva: Some((gva, None)), gpa } + ))) if gpa == 0x1bc4060.into() && gva == 0x16e23fa060.into() )); assert!(matches!( parser.virt_read(0x16e23fa060.into(), &mut buffer), - Err(KdmpParserError::AddrTranslation( - AddrTranslationError::Phys(gpa) - )) if gpa == 0x1bc4060.into() + Ok(None) )); // BUG: https://github.com/0vercl0k/kdmp-parser-rs/issues/10 @@ -497,12 +514,17 @@ fn regressions() { // ```text // kd> db 00007ff7`ab766ff7 // 00007ff7`ab766ff7 00 00 00 00 00 00 00 00-00 ?? ?? ?? ?? ?? ?? ?? .........??????? - // ... + // ``` + // + // ```text // kdmp-parser-rs>cargo r --example parser -- mem.dmp --mem 00007ff7`ab766ff7 --virt --len 10 // Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s // Running `target\debug\examples\parser.exe mem.dmp --mem 00007ff7`ab766ff7 --virt --len 10` // There is no virtual memory available at 0x7ff7ab766ff7 // ``` + // + // The below address mirrors the same behavior than in the issue's dump: + // // ```text // kd> db fffff803`f3086fef // fffff803`f3086fef 9d f5 de ff 48 85 c0 74-0a 40 8a cf e8 80 ee ba ....H..t.@...... @@ -510,13 +532,21 @@ fn regressions() { // fffff803`f308700f ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? // ``` let mut buffer = [0; 32]; - assert_eq!( - parser - .virt_read(0xfffff803f3086fef.into(), &mut buffer) - .unwrap(), - 17 + assert!( + matches!(parser.virt_read_strict(0xfffff803f3086fef.into(), &mut buffer), + Err(KdmpParserError::MemoryRead(MemoryReadError::PartialRead { + expected_amount: 32, + actual_amount: 17, + reason: PageReadError::NotPresent { gva, which_pxe } + })) if gva == 0xfffff803f3087000.into() && which_pxe == PxeKind::Pte + ) ); + assert!(matches!( + parser.virt_read(0xfffff803f3086fef.into(), &mut buffer), + Ok(Some(17)) + )); + // ```text // kd> !process 0 0 // PROCESS ffffc00c5120d580 @@ -566,8 +596,10 @@ fn regressions() { assert!( parser .virt_read_exact(Gva::new(0xfffff80122800000 + 0x100000 - 8), &mut buffer) - .is_ok() + .unwrap() + .is_some() ); + assert_eq!(buffer, [ 0x70, 0x72, 0x05, 0x00, 0x04, 0x3a, 0x65, 0x00, 0x54, 0x3a, 0x65, 0x00, 0xbc, 0x82, 0x0c, 0x00 @@ -593,8 +625,10 @@ fn regressions() { assert!( parser .virt_read_exact(Gva::new(0xfffff80122800000 + 0x200000 - 0x8), &mut buffer) - .is_ok() + .unwrap() + .is_some() ); + assert_eq!(buffer, [ 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc @@ -678,16 +712,16 @@ fn regressions() { let gva = 0.into(); assert!(matches!( parser.virt_translate(gva), - Err(KdmpParserError::AddrTranslation( - AddrTranslationError::Virt(fault_gva, PxeNotPresent::Pde) - )) if fault_gva == gva + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotPresent { gva: fault_gva, which_pxe: PxeKind::Pde } + ))) if fault_gva == gva )); let gva = 0xffffffff_ffffffff.into(); assert!(matches!( parser.virt_translate(gva), - Err(KdmpParserError::AddrTranslation( - AddrTranslationError::Virt(fault_gva, PxeNotPresent::Pte) - )) if fault_gva == gva + Err(KdmpParserError::MemoryRead(MemoryReadError::PageRead( + PageReadError::NotPresent { gva: fault_gva, which_pxe: PxeKind::Pte } + ))) if fault_gva == gva )); }