Skip to content

Commit 7a551bc

Browse files
authored
v0.1 (#1)
1 parent dba3b8e commit 7a551bc

File tree

17 files changed

+2605
-2
lines changed

17 files changed

+2605
-2
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
name: Builds
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
testdatas:
7+
env:
8+
TESTDATA_URL: https://github.com/0vercl0k/kdmp-parser/releases/download/v0.1/testdatas.7z
9+
10+
name: fetch testdatas
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Cache Artifacts
14+
id: cache-testdatas
15+
uses: actions/cache@v4
16+
with:
17+
key: kdmp-parser-testdatas-cache
18+
path: .
19+
- if: steps.cache-testdatas.outputs.cache-hit != 'true'
20+
run: |
21+
sudo apt-get -y update; sudo apt-get install -y p7zip-full;
22+
curl ${{ env.TESTDATA_URL }} -O -L
23+
7z x testdatas.7z; rm testdatas.7z
24+
- name: Upload artifacts
25+
uses: actions/upload-artifact@v4
26+
with:
27+
if-no-files-found: error
28+
name: kdmp-parser-testdatas-cache
29+
path: .
30+
31+
fmt:
32+
runs-on: ubuntu-latest
33+
name: fmt
34+
steps:
35+
- name: Checkout
36+
uses: actions/checkout@v4
37+
38+
- name: Set up rust
39+
run: rustup default nightly
40+
41+
- name: Install rustfmt
42+
run: rustup component add rustfmt
43+
44+
- name: cargo fmt
45+
run: cargo +nightly fmt --check
46+
47+
clippy:
48+
name: clippy
49+
runs-on: ubuntu-latest
50+
steps:
51+
- name: Checkout
52+
uses: actions/checkout@v4
53+
54+
- name: Set up rust
55+
run: rustup default stable
56+
57+
- name: cargo clippy
58+
env:
59+
RUSTFLAGS: "-Dwarnings"
60+
run: cargo clippy --example parser
61+
62+
doc:
63+
name: doc
64+
runs-on: ubuntu-latest
65+
steps:
66+
- name: Checkout
67+
uses: actions/checkout@v4
68+
69+
- name: Set up rust
70+
run: rustup default stable
71+
72+
- name: cargo doc
73+
env:
74+
RUSTDOCFLAGS: "-Dwarnings"
75+
run: cargo doc
76+
77+
build:
78+
strategy:
79+
fail-fast: false
80+
matrix:
81+
os: [ubuntu-latest, windows-latest, macos-latest]
82+
83+
needs: testdatas
84+
runs-on: ${{ matrix.os }}
85+
name: build & test / ${{ matrix.os }}
86+
steps:
87+
- name: Checkout
88+
uses: actions/checkout@v4
89+
90+
- name: Set up rust
91+
run: rustup default stable
92+
93+
- name: Retrieve testdatas
94+
uses: actions/download-artifact@v4
95+
with:
96+
name: kdmp-parser-testdatas-cache
97+
path: .
98+
99+
- name: cargo test
100+
env:
101+
TESTDATAS: "."
102+
run: cargo test
103+
104+
- name: cargo check
105+
run: cargo check
106+
107+
- name: cargo build
108+
run: cargo build --release --example parser
109+
110+
- name: Upload artifacts
111+
uses: actions/upload-artifact@v4
112+
with:
113+
name: parser-${{ matrix.os }}
114+
path: |
115+
target/release/examples/parser.exe
116+
target/release/examples/parser.pdb
117+
target/release/examples/parser

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
debug/
4+
target/
5+
6+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8+
Cargo.lock
9+
10+
# These are backup files generated by rustfmt
11+
**/*.rs.bk
12+
13+
# MSVC Windows builds of rustc generate these, which store debugging information
14+
*.pdb

Cargo.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "kdmp-parser"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["Axel '0vercl0k' Souchet"]
6+
categories = ["parser-implementations"]
7+
description = "A Rust crate for parsing Windows kernel crashdumps"
8+
include = [
9+
"/Cargo.toml",
10+
"/LICENSE",
11+
"/src/**",
12+
"/examples/**",
13+
"README.md",
14+
]
15+
keywords = ["windows", "kernel", "crashdump"]
16+
license = "MIT"
17+
repository = "https://github.com/0vercl0k/kdmp-parser-rs"
18+
rust-version = "1.70"
19+
20+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
21+
[dependencies]
22+
bitflags = "2.4.2"
23+
thiserror = "1.0.58"
24+
25+
[dev-dependencies]
26+
anyhow = "1.0.80"
27+
clap = { version = "4.5.1", features = ["derive"] }
28+
29+
[[example]]
30+
name = "parser"

README.md

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,69 @@
1-
# kdmp-parser-rs
2-
A KISS Rust crate to parse Windows kernel crash-dumps created by Windows & its debugger.
1+
<div align='center'>
2+
<h1><code>kdmp-parser-rs</code></h1>
3+
<p>
4+
<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>
5+
</p>
6+
<p>
7+
<img src="https://github.com/0vercl0k/kdmp-parser-rs/raw/main/pics/kdmp-parser.gif" />
8+
<a href="https://crates.io/crates/kdmp-parser-rs"><img src="https://img.shields.io/crates/v/kdmp-parser-rs.svg" /></a>
9+
<a href="https://docs.rs/kdmp-parser-rs/"><img src="https://docs.rs/kdmp-parser-rs/badge.svg"></a>
10+
<img src="https://github.com/0vercl0k/kdmp-parser-rs/workflows/Builds/badge.svg"/>
11+
</p>
12+
</div>
13+
14+
This is a cross-platform crate that parses Windows **kernel** crash-dumps that Windows / WinDbg generates. It exposes read-only access to the physical memory pages as well as the register / exception context. It can also read virtual memory addresses by walking the [page tables](https://en.wikipedia.org/wiki/Page_table).
15+
16+
Compiled binaries are available in the [releases](https://github.com/0vercl0k/kdmp-parser-rs/releases) section.
17+
18+
## Parser
19+
The [parser](src/examples/parser.rs) application is a small utility to show-case how to use the library and demonstrate its features. You can use it to dump memory, etc.
20+
21+
![parser-usage](https://github.com/0vercl0k/kdmp-parser-rs/raw/main/pics/parser-usage.gif)
22+
23+
Here are the options supported:
24+
```text
25+
A Rust crate for parsing Windows kernel crashdumps
26+
27+
Usage: parser.exe [OPTIONS] <DUMP_PATH>
28+
29+
Arguments:
30+
<DUMP_PATH>
31+
The dump path
32+
33+
Options:
34+
-c, --context-record
35+
Show the context record
36+
37+
-e, --exception-record
38+
Show the exception record
39+
40+
-m, --mem[=<MEM>]
41+
Dump the first `len` bytes of every physical pages, unless an address is specified
42+
43+
--virt
44+
The address specified is interpreted as a virtual address, not a physical address
45+
46+
--len <LEN>
47+
[default: 16]
48+
49+
-r, --reader <READER>
50+
[default: mmap]
51+
52+
Possible values:
53+
- mmap: The crash-dump is memory-mapped
54+
- file: The crash-dump is read as a file on disk
55+
56+
-h, --help
57+
Print help (see a summary with '-h')
58+
59+
-V, --version
60+
Print version
61+
```
62+
63+
# Authors
64+
65+
* Axel '[@0vercl0k](https://twitter.com/0vercl0k)' Souchet
66+
67+
# Contributors
68+
69+
[ ![contributors-img](https://contrib.rocks/image?repo=0vercl0k/kdmp-parser-rs) ](https://github.com/0vercl0k/kdmp-parser-rs/graphs/contributors)

examples/parser.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Axel '0vercl0k' Souchet - February 25 2024
2+
use core::default::Default;
3+
use std::fs::File;
4+
use std::path::PathBuf;
5+
6+
use anyhow::{anyhow, Context, Result};
7+
use clap::{Parser, ValueEnum};
8+
use kdmp_parser::{Gpa, Gva, Gxa, KernelDumpParser, MappedFileReader};
9+
10+
#[derive(Debug, Default, Clone, Copy, ValueEnum)]
11+
enum ReaderMode {
12+
#[default]
13+
/// The crash-dump is memory-mapped.
14+
Mmap,
15+
/// The crash-dump is read as a file on disk.
16+
File,
17+
}
18+
19+
#[derive(Parser, Debug)]
20+
#[command(version, about)]
21+
struct Args {
22+
/// The dump path.
23+
dump_path: PathBuf,
24+
/// Show the context record.
25+
#[arg(short, long)]
26+
context_record: bool,
27+
/// Show the exception record.
28+
#[arg(short, long)]
29+
exception_record: bool,
30+
/// Dump the first `len` bytes of every physical pages, unless an address is
31+
/// specified.
32+
#[arg(short, long, num_args = 0..=1, require_equals = true, default_missing_value = "0xffffffffffffffff")]
33+
mem: Option<String>,
34+
/// The address specified is interpreted as a virtual address, not a
35+
/// physical address.
36+
#[arg(long, default_value_t = false)]
37+
virt: bool,
38+
/// The number of bytes to dump out.
39+
#[arg(long, default_value_t = 0x10)]
40+
len: usize,
41+
/// Reader mode.
42+
#[arg(short, long, value_enum, default_value_t = ReaderMode::Mmap)]
43+
reader: ReaderMode,
44+
}
45+
46+
/// Print a hexdump of data that started at `address`.
47+
fn hexdump(address: u64, data: &[u8]) {
48+
let len = data.len();
49+
let mut it = data.iter();
50+
for i in (0..len).step_by(16) {
51+
print!("{:016x}: ", address + (i as u64 * 16));
52+
let mut row = [None; 16];
53+
for item in row.iter_mut() {
54+
if let Some(c) = it.next() {
55+
*item = Some(*c);
56+
print!("{:02x}", c);
57+
} else {
58+
print!(" ");
59+
}
60+
}
61+
print!(" |");
62+
for item in &row {
63+
if let Some(c) = item {
64+
let c = char::from(*c);
65+
print!("{}", if c.is_ascii_graphic() { c } else { '.' });
66+
} else {
67+
print!(" ");
68+
}
69+
}
70+
println!("|");
71+
}
72+
}
73+
74+
/// Convert an hexadecimal string to a `u64`.
75+
fn to_hex(s: &str) -> Result<u64> {
76+
u64::from_str_radix(s.trim_start_matches("0x"), 16).context("failed to convert string to u64")
77+
}
78+
79+
fn main() -> Result<()> {
80+
let args = Args::parse();
81+
let parser = match args.reader {
82+
ReaderMode::Mmap => {
83+
let mapped_file = MappedFileReader::new(args.dump_path)?;
84+
KernelDumpParser::with_reader(mapped_file)
85+
}
86+
ReaderMode::File => {
87+
let file = File::open(args.dump_path)?;
88+
KernelDumpParser::with_reader(file)
89+
}
90+
}
91+
.context("failed to parse the kernel dump")?;
92+
93+
if args.context_record {
94+
println!("{:#x?}", parser.context_record());
95+
}
96+
97+
if args.exception_record {
98+
println!("{:#x?}", parser.exception_record());
99+
}
100+
101+
if let Some(addr) = args.mem {
102+
let mut buffer = vec![0; args.len];
103+
let addr = to_hex(&addr)?;
104+
if addr == u64::MAX {
105+
for (gpa, _) in parser.physmem() {
106+
parser
107+
.phys_read_exact(gpa, &mut buffer)
108+
.ok_or_else(|| anyhow!("failed to read {gpa}"))?;
109+
hexdump(gpa.u64(), &buffer)
110+
}
111+
} else {
112+
let amount = if args.virt {
113+
parser.virt_read(Gva::new(addr), &mut buffer)
114+
} else {
115+
parser.phys_read(Gpa::new(addr), &mut buffer)
116+
};
117+
118+
if let Some(amount) = amount {
119+
hexdump(addr, &buffer[..amount]);
120+
} else {
121+
println!(
122+
"There is no {} memory available for {addr:#x}",
123+
if args.virt { "virtual" } else { "physical" }
124+
);
125+
}
126+
}
127+
}
128+
129+
Ok(())
130+
}

pics/kdmp-parser.gif

598 KB
Loading

pics/parser.gif

279 KB
Loading

rustfmt.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
reorder_modules = true
2+
use_field_init_shorthand = true
3+
4+
unstable_features = true
5+
indent_style = "Block"
6+
reorder_imports = true
7+
imports_granularity = "Module"
8+
normalize_comments = true
9+
normalize_doc_attributes = true
10+
overflow_delimited_expr = true
11+
reorder_impl_items = true
12+
group_imports = "StdExternalCrate"
13+
wrap_comments = true

0 commit comments

Comments
 (0)