use core::default::Default;
use std::cmp::min;
use std::collections::BTreeMap;
use std::fs::File;
use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::{Parser, ValueEnum};
use kdmp_parser::gxa::{Gpa, Gva, Gxa};
use kdmp_parser::map::MappedFileReader;
use kdmp_parser::parse::KernelDumpParser;
use kdmp_parser::{phys, virt};
#[derive(Debug, Default, Clone, Copy, ValueEnum)]
enum ReaderMode {
#[default]
Mmap,
File,
}
#[expect(clippy::struct_excessive_bools)]
#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
#[arg(last = true)]
dump_path: PathBuf,
#[arg(long, default_value_t = false)]
dump_headers: bool,
#[arg(short, long)]
context_record: bool,
#[arg(short, long)]
exception_record: bool,
#[arg(short, long, num_args = 0..=1, default_missing_value = "0xffffffffffffffff")]
mem: Option<String>,
#[arg(long, default_value_t = false)]
virt: bool,
#[arg(long, default_value_t = 128)]
len: usize,
#[arg(long)]
dtb: Option<Gpa>,
#[arg(short, long, value_enum, default_value_t = ReaderMode::Mmap)]
reader: ReaderMode,
#[arg(long, default_value_t = false)]
modules: bool,
}
fn hexdump(address: u64, data: &[u8], wanted_len: usize) {
assert!(wanted_len >= data.len());
let mut row = [None; 16];
let mut data_it = data.iter();
for i in (0..wanted_len).step_by(16) {
let wanted_left = wanted_len - i;
let left_to_display = min(wanted_left, 16);
print!("{:016x}: ", address + i as u64);
let mut row_it = row.iter_mut().enumerate().peekable();
while let Some((idx, item)) = row_it.next() {
if let Some(c) = data_it.next() {
*item = Some(*c);
print!("{c:02x}");
} else {
*item = None;
let displayed_amount = i + idx;
if displayed_amount >= wanted_len {
print!(" ");
} else {
print!("??");
}
}
if left_to_display >= 9 && idx == 7 {
print!("-");
} else if row_it.peek().is_some() {
print!(" ");
}
}
print!(" ");
for item in &row[..left_to_display] {
if let Some(c) = item {
let c = char::from(*c);
print!("{}", if c.is_ascii_graphic() { c } else { '.' });
} else {
print!("?");
}
}
println!();
}
}
fn to_hex(s: &str) -> Result<u64> {
let s = s.replace('`', "");
u64::from_str_radix(s.trim_start_matches("0x"), 16).context("failed to convert string to u64")
}
fn main() -> Result<()> {
let args = Args::parse();
let parser = match args.reader {
ReaderMode::Mmap => {
let mapped_file = MappedFileReader::new(args.dump_path)?;
KernelDumpParser::with_reader(mapped_file)
}
ReaderMode::File => {
let file = File::open(args.dump_path)?;
KernelDumpParser::with_reader(file)
}
}
.context("failed to parse the kernel dump")?;
if args.dump_headers {
println!("{:#?}", parser.headers());
}
if args.context_record {
println!("{:#x?}", parser.context_record());
}
if args.exception_record {
println!("{:#x?}", parser.exception_record());
}
if args.modules {
let modules = parser
.user_modules()
.chain(parser.kernel_modules())
.map(|(at, v)| (at.start, (v, at.end)))
.collect::<BTreeMap<_, _>>();
for (start, (module, end)) in modules {
println!("{:#018x}-{:#018x}: {module}", start.u64(), end.u64());
}
}
if let Some(addr) = args.mem {
let mut buffer = vec![0; args.len];
let addr = to_hex(&addr)?;
let phys_reader = phys::Reader::new(&parser);
let virt_reader = virt::Reader::with_dtb(
&parser,
args.dtb
.unwrap_or(Gpa::new(parser.headers().directory_table_base)),
);
if addr == u64::MAX {
for (gpa, _) in parser.physmem() {
phys_reader.read_exact(gpa, &mut buffer)?;
hexdump(gpa.u64(), &buffer, args.len);
}
} else {
let amount = if args.virt {
virt_reader.read(Gva::new(addr), &mut buffer)
} else {
phys_reader.read(Gpa::new(addr), &mut buffer)
};
if let Ok(amount) = amount {
hexdump(addr, &buffer[..amount], args.len);
} else {
println!(
"There is no {} memory available at {addr:#x}",
if args.virt { "virtual" } else { "physical" }
);
}
}
}
Ok(())
}