#![deny(unsafe_code)]
#![warn(missing_docs)]
pub mod lzo;
#[allow(missing_docs)]
pub mod framebuffer;
pub mod object_reader;
pub mod pagefile;
pub mod proto_pte;
pub mod test_builders;
pub mod vas;
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("physical memory error: {0}")]
Physical(#[from] memf_format::Error),
#[error("symbol error: {0}")]
Symbol(#[from] memf_symbols::Error),
#[error("page not present at virtual address {0:#018x}")]
PageNotPresent(u64),
#[error("partial read: got {got} of {requested} bytes at {addr:#018x}")]
PartialRead {
addr: u64,
requested: usize,
got: usize,
},
#[error("missing symbol or field: {0}")]
MissingSymbol(String),
#[error("type size mismatch: expected {expected}, got {got}")]
SizeMismatch {
expected: usize,
got: usize,
},
#[error("list walk exceeded {0} iterations (possible cycle)")]
ListCycle(usize),
#[error("page at {vaddr:#018x} paged out to pagefile {pagefile_num} offset {page_offset:#x}")]
PagedOut {
vaddr: u64,
pagefile_num: u8,
page_offset: u64,
},
#[error("prototype PTE at {0:#018x} (not yet supported)")]
PrototypePte(u64),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct WalkResult<T: serde::Serialize> {
pub items: Vec<T>,
pub skipped: u32,
}
impl<T: serde::Serialize> WalkResult<T> {
pub fn new(items: Vec<T>, skipped: u32) -> Self {
Self { items, skipped }
}
pub fn push(&mut self, item: T) {
self.items.push(item);
}
pub fn skip(&mut self) {
self.skipped += 1;
}
}
#[cfg(test)]
mod walk_result_tests {
use super::WalkResult;
#[test]
fn walk_result_new_has_correct_counts() {
let r: WalkResult<u32> = WalkResult::new(vec![1, 2, 3], 5);
assert_eq!(r.items.len(), 3);
assert_eq!(r.skipped, 5);
}
#[test]
fn walk_result_skip_increments_counter() {
let mut r: WalkResult<u32> = WalkResult::default();
r.skip();
r.skip();
assert_eq!(r.skipped, 2);
assert!(r.items.is_empty());
}
#[test]
fn walk_result_push_adds_item() {
let mut r: WalkResult<u32> = WalkResult::default();
r.push(42u32);
assert_eq!(r.items, vec![42]);
assert_eq!(r.skipped, 0);
}
#[test]
fn walk_result_serializes_with_skipped_field() {
let r = WalkResult::new(vec![1u32, 2], 3);
let json = serde_json::to_string(&r).unwrap();
assert!(json.contains("\"skipped\":3"), "json: {json}");
assert!(json.contains("\"items\""), "json: {json}");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display_page_not_present() {
let e = Error::PageNotPresent(0xFFFF_8000_0000_1000);
assert!(e.to_string().contains("0xffff800000001000"));
}
#[test]
fn error_display_partial_read() {
let e = Error::PartialRead {
addr: 0x1000,
requested: 8,
got: 4,
};
assert!(e.to_string().contains("4 of 8"));
}
#[test]
fn error_display_list_cycle() {
let e = Error::ListCycle(10000);
assert!(e.to_string().contains("10000"));
}
#[test]
fn error_display_missing_symbol() {
let e = Error::MissingSymbol("task_struct.pid".into());
assert!(e.to_string().contains("task_struct.pid"));
}
#[test]
fn error_display_size_mismatch() {
let e = Error::SizeMismatch {
expected: 8,
got: 4,
};
let msg = e.to_string();
assert!(msg.contains('8'));
assert!(msg.contains('4'));
}
#[test]
fn error_from_physical() {
let phys_err = memf_format::Error::UnknownFormat;
let e: Error = Error::from(phys_err);
assert!(matches!(e, Error::Physical(_)));
assert!(e.to_string().contains("unknown dump format"));
}
#[test]
fn error_from_symbol() {
let sym_err = memf_symbols::Error::NotFound("init_task".into());
let e: Error = Error::from(sym_err);
assert!(matches!(e, Error::Symbol(_)));
assert!(e.to_string().contains("init_task"));
}
#[test]
fn error_from_io_via_physical() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file gone");
let phys_err = memf_format::Error::from(io_err);
let e: Error = Error::from(phys_err);
assert!(matches!(e, Error::Physical(_)));
}
#[test]
fn error_display_paged_out() {
let e = Error::PagedOut {
vaddr: 0xFFFF_8000_0000_2000,
pagefile_num: 0,
page_offset: 0x1234,
};
let msg = e.to_string();
assert!(msg.contains("0xffff800000002000"));
assert!(msg.contains("pagefile 0"));
assert!(msg.contains("0x1234"));
}
#[test]
fn error_display_prototype_pte() {
let e = Error::PrototypePte(0xFFFF_8000_DEAD_0000);
let msg = e.to_string();
assert!(msg.contains("0xffff8000dead0000"));
assert!(msg.contains("prototype PTE"));
}
}