Skip to content
Open

v0.8 #13

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align='center'>
<h1><code>kdmp-parser</code></h1>
<p>
<strong>A <a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a> Rust crate to parse Windows kernel crash-dumps created by Windows & its debugger.</strong>
<strong>A <a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a>, dependency free Rust crate to parse Windows kernel crash-dumps created by Windows & its debugger.</strong>
</p>
<p>
<a href="https://crates.io/crates/kdmp-parser"><img src="https://img.shields.io/crates/v/kdmp-parser.svg" /></a>
Expand Down
6 changes: 3 additions & 3 deletions examples/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
2 changes: 2 additions & 0 deletions src/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize>) -> Self;

/// Get a bit.
#[must_use]
fn bit(&self, n: usize) -> Self {
self.bits(n..=n)
}
Expand Down
216 changes: 178 additions & 38 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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<R> = std::result::Result<R, KdmpParserError>;

#[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<PxeKind>)>,
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<io::Error> for KdmpParserError {
fn from(value: io::Error) -> Self {
KdmpParserError::Io(value)
}
}

impl From<FromUtf16Error> for KdmpParserError {
fn from(value: FromUtf16Error) -> Self {
KdmpParserError::Utf16(value)
}
}

impl From<MemoryReadError> for KdmpParserError {
fn from(value: MemoryReadError) -> Self {
KdmpParserError::MemoryRead(value)
}
}

impl From<PageReadError> 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,
}
}
}
Loading
Loading