use bytemuck::Pod;
use memf_format::PhysicalMemoryProvider;
use memf_symbols::SymbolResolver;
use crate::vas::VirtualAddressSpace;
use crate::{Error, Result};
const MAX_LIST_ITERATIONS: usize = 100_000;
pub struct ObjectReader<P: PhysicalMemoryProvider> {
vas: VirtualAddressSpace<P>,
symbols: Box<dyn SymbolResolver>,
kernel_base: u64,
}
impl<P: PhysicalMemoryProvider> ObjectReader<P> {
pub fn new(vas: VirtualAddressSpace<P>, symbols: Box<dyn SymbolResolver>) -> Self {
Self {
vas,
symbols,
kernel_base: 0,
}
}
#[must_use]
pub fn with_kernel_base(mut self, kernel_base: u64) -> Self {
self.kernel_base = kernel_base;
self
}
pub fn symbols(&self) -> &dyn SymbolResolver {
self.symbols.as_ref()
}
pub fn vas(&self) -> &VirtualAddressSpace<P> {
&self.vas
}
pub fn with_cr3(&self, cr3: u64) -> Self
where
P: Clone,
{
let vas = VirtualAddressSpace::new(self.vas.physical().clone(), cr3, self.vas.mode());
Self {
vas,
symbols: self.symbols.clone_boxed(),
kernel_base: self.kernel_base,
}
}
#[must_use]
pub fn map_symbols(
self,
f: impl FnOnce(Box<dyn SymbolResolver>) -> Box<dyn SymbolResolver>,
) -> Self {
let Self {
vas,
symbols,
kernel_base,
} = self;
Self {
vas,
symbols: f(symbols),
kernel_base,
}
}
pub fn read_field<T: Pod + Default>(
&self,
base_vaddr: u64,
struct_name: &str,
field_name: &str,
) -> Result<T> {
let offset = self
.symbols
.field_offset(struct_name, field_name)
.ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))?;
let size = std::mem::size_of::<T>();
let mut buf = vec![0u8; size];
self.vas
.read_virt(base_vaddr.wrapping_add(offset), &mut buf)?;
if buf.len() != size {
return Err(Error::SizeMismatch {
expected: size,
got: buf.len(),
});
}
Ok(*bytemuck::from_bytes::<T>(&buf))
}
pub fn read_pointer(
&self,
base_vaddr: u64,
struct_name: &str,
field_name: &str,
) -> Result<u64> {
self.read_field::<u64>(base_vaddr, struct_name, field_name)
}
pub fn read_string(&self, vaddr: u64, max_len: usize) -> Result<String> {
let mut buf = vec![0u8; max_len];
self.vas.read_virt(vaddr, &mut buf)?;
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
Ok(String::from_utf8_lossy(&buf[..end]).into_owned())
}
pub fn read_field_string(
&self,
base_vaddr: u64,
struct_name: &str,
field_name: &str,
max_len: usize,
) -> Result<String> {
let offset = self
.symbols
.field_offset(struct_name, field_name)
.ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))?;
self.read_string(base_vaddr.wrapping_add(offset), max_len)
}
pub fn walk_list(
&self,
head_vaddr: u64,
struct_name: &str,
list_field: &str,
) -> Result<Vec<u64>> {
self.walk_list_with(head_vaddr, "list_head", "next", struct_name, list_field)
}
pub fn walk_list_with(
&self,
head_vaddr: u64,
list_struct: &str,
next_field: &str,
container_struct: &str,
list_field: &str,
) -> Result<Vec<u64>> {
let list_offset = self
.symbols
.field_offset(container_struct, list_field)
.ok_or_else(|| Error::MissingSymbol(format!("{container_struct}.{list_field}")))?;
let next_offset = self
.symbols
.field_offset(list_struct, next_field)
.ok_or_else(|| Error::MissingSymbol(format!("{list_struct}.{next_field}")))?;
let mut current = self.read_u64_at(head_vaddr.wrapping_add(next_offset))?;
let mut result = Vec::new();
for _ in 0..MAX_LIST_ITERATIONS {
if current == head_vaddr {
return Ok(result);
}
if current == 0 {
return Ok(result);
}
let Ok(next) = self.read_u64_at(current.wrapping_add(next_offset)) else {
return Ok(result);
};
result.push(current.wrapping_sub(list_offset));
current = next;
}
Err(Error::ListCycle(MAX_LIST_ITERATIONS))
}
pub fn walk_list_bidirectional(
&self,
head_vaddr: u64,
list_struct: &str,
next_field: &str,
prev_field: &str,
container_struct: &str,
list_field: &str,
) -> Result<Vec<u64>> {
let mut forward = self.walk_list_with(
head_vaddr,
list_struct,
next_field,
container_struct,
list_field,
)?;
let backward = self.walk_list_with(
head_vaddr,
list_struct,
prev_field,
container_struct,
list_field,
)?;
let mut seen: std::collections::HashSet<u64> = forward.iter().copied().collect();
for container in backward {
if seen.insert(container) {
forward.push(container);
}
}
Ok(forward)
}
pub fn read_bytes(&self, vaddr: u64, len: usize) -> Result<Vec<u8>> {
let mut buf = vec![0u8; len];
self.vas.read_virt(vaddr, &mut buf)?;
Ok(buf)
}
pub fn required_symbol(&self, name: &str) -> Result<u64> {
self.symbols()
.symbol_address(name)
.map(|rva| self.kernel_base.wrapping_add(rva))
.ok_or_else(|| Error::MissingSymbol(name.to_owned()))
}
pub fn required_field_offset(&self, struct_name: &str, field_name: &str) -> Result<usize> {
self.symbols()
.field_offset(struct_name, field_name)
.map(|v| v as usize)
.ok_or_else(|| Error::MissingSymbol(format!("{struct_name}.{field_name}")))
}
fn read_u64_at(&self, vaddr: u64) -> Result<u64> {
let mut buf = [0u8; 8];
self.vas.read_virt(vaddr, &mut buf)?;
Ok(u64::from_le_bytes(buf))
}
pub fn iter_list<'a>(
&'a self,
head_vaddr: u64,
container_struct: &'a str,
list_field: &'a str,
) -> ListIter<'a, P> {
let list_offset = self
.symbols
.field_offset(container_struct, list_field)
.unwrap_or(0);
let next_offset = self.symbols.field_offset("list_head", "next").unwrap_or(0);
let current = match self.read_u64_at(head_vaddr.wrapping_add(next_offset)) {
Ok(v) => v,
Err(_) => head_vaddr, };
ListIter {
reader: self,
head_vaddr,
current,
list_offset,
next_offset,
seen: std::collections::HashSet::new(),
done: false,
}
}
}
pub struct ListIter<'a, P: PhysicalMemoryProvider> {
reader: &'a ObjectReader<P>,
head_vaddr: u64,
current: u64,
list_offset: u64,
next_offset: u64,
seen: std::collections::HashSet<u64>,
done: bool,
}
impl<P: PhysicalMemoryProvider> Iterator for ListIter<'_, P> {
type Item = crate::Result<u64>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if self.current == self.head_vaddr {
return None;
}
if !self.seen.insert(self.current) {
self.done = true;
return None; }
if self.seen.len() > MAX_LIST_ITERATIONS {
self.done = true;
return Some(Err(crate::Error::ListCycle(MAX_LIST_ITERATIONS)));
}
let container = self.current.wrapping_sub(self.list_offset);
match self
.reader
.read_u64_at(self.current.wrapping_add(self.next_offset))
{
Ok(next) => self.current = next,
Err(e) => {
self.done = true;
return Some(Err(e));
}
}
Some(Ok(container))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_builders::{flags, PageTableBuilder};
use crate::vas::TranslationMode;
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
fn make_reader(
isf: &IsfBuilder,
builder: PageTableBuilder,
) -> ObjectReader<crate::test_builders::SyntheticPhysMem> {
let json = isf.build_json();
let resolver = IsfResolver::from_value(&json).unwrap();
let (cr3, mem) = builder.build();
let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
ObjectReader::new(vas, Box::new(resolver))
}
#[test]
fn read_field_u32() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"pid",
0,
"int",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys_u64(paddr, u64::from(42u32));
let reader = make_reader(&isf, ptb);
let pid: u32 = reader.read_field(vaddr, "task_struct", "pid").unwrap();
assert_eq!(pid, 42);
}
#[test]
fn read_field_u64() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"mm",
8,
"pointer",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mm_value: u64 = 0xFFFF_8000_DEAD_BEEF;
let ptb = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys_u64(paddr + 8, mm_value);
let reader = make_reader(&isf, ptb);
let mm: u64 = reader.read_field(vaddr, "task_struct", "mm").unwrap();
assert_eq!(mm, mm_value);
}
#[test]
fn read_field_missing_symbol() {
let isf = IsfBuilder::new().add_struct("task_struct", 128);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
let result = reader.read_field::<u32>(vaddr, "task_struct", "nonexistent");
assert!(result.is_err());
match result.unwrap_err() {
Error::MissingSymbol(s) => assert_eq!(s, "task_struct.nonexistent"),
other => panic!("unexpected error: {other}"),
}
}
#[test]
fn map_symbols_applies_the_transform() {
let isf_old = IsfBuilder::new().add_symbol("old_sym", 0x1000);
let reader = make_reader(&isf_old, PageTableBuilder::new());
assert_eq!(reader.symbols().symbol_address("old_sym"), Some(0x1000));
assert_eq!(reader.symbols().symbol_address("new_sym"), None);
let new_json = IsfBuilder::new().add_symbol("new_sym", 0x2000).build_json();
let reader =
reader.map_symbols(|_old| Box::new(IsfResolver::from_value(&new_json).unwrap()));
assert_eq!(
reader.symbols().symbol_address("new_sym"),
Some(0x2000),
"map_symbols must apply f to the resolver"
);
assert_eq!(
reader.symbols().symbol_address("old_sym"),
None,
"the old resolver must be replaced by f's result"
);
}
#[test]
fn read_field_string_test() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"comm",
16,
"char",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys(paddr + 16, b"systemd\0");
let reader = make_reader(&isf, ptb);
let comm = reader
.read_field_string(vaddr, "task_struct", "comm", 16)
.unwrap();
assert_eq!(comm, "systemd");
}
#[test]
fn read_string_with_null() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"comm",
16,
"char",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys(paddr + 16, b"init\0\0\0\0\0\0\0\0\0\0\0\0");
let reader = make_reader(&isf, ptb);
let s = reader.read_string(vaddr + 16, 16).unwrap();
assert_eq!(s, "init");
}
#[test]
fn walk_list_simple() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 8, "list_head")
.add_field("task_struct", "comm", 16, "char")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let b_paddr: u64 = 0x0080_2000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
let list_offset: u64 = 8;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.map_4k(b_vaddr, b_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, 0) .write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset) .write_phys_u64(a_paddr, 100) .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset) .write_phys_u64(b_paddr, 200) .write_phys_u64(b_paddr + list_offset, head_vaddr + list_offset);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list(head_vaddr + list_offset, "task_struct", "tasks")
.unwrap();
assert_eq!(containers.len(), 2);
assert_eq!(containers[0], a_vaddr);
assert_eq!(containers[1], b_vaddr);
}
#[test]
fn read_pointer_test() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"mm",
8,
"pointer",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let mm_value: u64 = 0xFFFF_8000_CAFE_BABE;
let ptb = PageTableBuilder::new()
.map_4k(vaddr, paddr, flags::WRITABLE)
.write_phys_u64(paddr + 8, mm_value);
let reader = make_reader(&isf, ptb);
let ptr = reader.read_pointer(vaddr, "task_struct", "mm").unwrap();
assert_eq!(ptr, mm_value);
}
#[test]
fn read_field_invalid_struct_name() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"pid",
0,
"int",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
let result = reader.read_field::<u32>(vaddr, "nonexistent_struct", "pid");
assert!(result.is_err());
match result.unwrap_err() {
Error::MissingSymbol(s) => assert_eq!(s, "nonexistent_struct.pid"),
other => panic!("unexpected error: {other}"),
}
}
#[test]
fn walk_list_empty_list() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "tasks", 8, "list_head")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let list_offset: u64 = 8;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr + list_offset, head_vaddr + list_offset);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list(head_vaddr + list_offset, "task_struct", "tasks")
.unwrap();
assert!(containers.is_empty());
}
#[test]
fn walk_list_with_windows_list_entry() {
let isf = IsfBuilder::new()
.add_struct("_EPROCESS", 256)
.add_field("_EPROCESS", "UniqueProcessId", 0, "pointer")
.add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
.add_struct("_LIST_ENTRY", 16)
.add_field("_LIST_ENTRY", "Flink", 0, "pointer")
.add_field("_LIST_ENTRY", "Blink", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let b_paddr: u64 = 0x0080_2000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000; let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
let list_offset: u64 = 0x10;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.map_4k(b_vaddr, b_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, a_vaddr + list_offset) .write_phys_u64(a_paddr, 4) .write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset) .write_phys_u64(b_paddr, 100) .write_phys_u64(b_paddr + list_offset, head_vaddr);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list_with(
head_vaddr,
"_LIST_ENTRY",
"Flink",
"_EPROCESS",
"ActiveProcessLinks",
)
.unwrap();
assert_eq!(containers.len(), 2);
assert_eq!(containers[0], a_vaddr);
assert_eq!(containers[1], b_vaddr);
}
#[test]
fn walk_list_with_tolerates_smeared_null_link() {
let isf = IsfBuilder::new()
.add_struct("_EPROCESS", 256)
.add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
.add_struct("_LIST_ENTRY", 16)
.add_field("_LIST_ENTRY", "Flink", 0, "pointer")
.add_field("_LIST_ENTRY", "Blink", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let b_paddr: u64 = 0x0080_2000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
let list_offset: u64 = 0x10;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.map_4k(b_vaddr, b_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, a_vaddr + list_offset)
.write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
.write_phys_u64(b_paddr + list_offset, 0);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list_with(
head_vaddr,
"_LIST_ENTRY",
"Flink",
"_EPROCESS",
"ActiveProcessLinks",
)
.expect("a smeared null link must terminate the walk gracefully, not error");
assert_eq!(containers, vec![a_vaddr, b_vaddr]);
}
#[test]
fn walk_list_with_stops_on_non_canonical_kernel_pointer() {
let isf = IsfBuilder::new()
.add_struct("_EPROCESS", 256)
.add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
.add_struct("_LIST_ENTRY", 16)
.add_field("_LIST_ENTRY", "Flink", 0, "pointer")
.add_field("_LIST_ENTRY", "Blink", 8, "pointer");
let lo: u64 = 0x10;
let head_p = 0x0080_0000u64;
let a_p = 0x0080_1000u64;
let head_v = 0xFFFF_8000_0010_0000u64;
let a_v = 0xFFFF_8000_0010_1000u64;
let ptb = PageTableBuilder::new()
.map_4k(head_v, head_p, flags::WRITABLE)
.map_4k(a_v, a_p, flags::WRITABLE)
.write_phys_u64(head_p, a_v + lo)
.write_phys_u64(a_p + lo, 0x0000_0000_5A28_9000);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list_with(
head_v,
"_LIST_ENTRY",
"Flink",
"_EPROCESS",
"ActiveProcessLinks",
)
.expect("non-canonical link terminates the walk, not errors");
assert_eq!(
containers,
vec![a_v],
"only the real node A; no bogus container"
);
}
#[test]
fn walk_list_bidirectional_recovers_forward_orphans() {
let isf = IsfBuilder::new()
.add_struct("_EPROCESS", 256)
.add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
.add_struct("_LIST_ENTRY", 16)
.add_field("_LIST_ENTRY", "Flink", 0, "pointer")
.add_field("_LIST_ENTRY", "Blink", 8, "pointer");
let lo: u64 = 0x10;
let head_p = 0x0080_0000u64;
let a_p = 0x0080_1000u64;
let b_p = 0x0080_2000u64;
let c_p = 0x0080_3000u64;
let head_v = 0xFFFF_8000_0010_0000u64;
let a_v = 0xFFFF_8000_0010_1000u64;
let b_v = 0xFFFF_8000_0010_2000u64;
let c_v = 0xFFFF_8000_0010_3000u64;
let ptb = PageTableBuilder::new()
.map_4k(head_v, head_p, flags::WRITABLE)
.map_4k(a_v, a_p, flags::WRITABLE)
.map_4k(b_v, b_p, flags::WRITABLE)
.map_4k(c_v, c_p, flags::WRITABLE)
.write_phys_u64(head_p, a_v + lo)
.write_phys_u64(head_p + 8, c_v + lo)
.write_phys_u64(a_p + lo, b_v + lo)
.write_phys_u64(a_p + lo + 8, head_v)
.write_phys_u64(b_p + lo, 0)
.write_phys_u64(b_p + lo + 8, a_v + lo)
.write_phys_u64(c_p + lo, head_v)
.write_phys_u64(c_p + lo + 8, b_v + lo);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list_bidirectional(
head_v,
"_LIST_ENTRY",
"Flink",
"Blink",
"_EPROCESS",
"ActiveProcessLinks",
)
.unwrap();
assert_eq!(
containers.len(),
3,
"all three nodes recovered: {containers:x?}"
);
assert!(containers.contains(&a_v));
assert!(containers.contains(&b_v));
assert!(
containers.contains(&c_v),
"forward-orphaned C recovered via Blink"
);
}
#[test]
fn walk_list_with_empty() {
let isf = IsfBuilder::new()
.add_struct("_EPROCESS", 256)
.add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
.add_struct("_LIST_ENTRY", 16)
.add_field("_LIST_ENTRY", "Flink", 0, "pointer")
.add_field("_LIST_ENTRY", "Blink", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, head_vaddr);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list_with(
head_vaddr,
"_LIST_ENTRY",
"Flink",
"_EPROCESS",
"ActiveProcessLinks",
)
.unwrap();
assert!(containers.is_empty());
}
#[test]
fn walk_list_still_works_after_refactor() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 8, "list_head")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let list_offset: u64 = 8;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
.write_phys_u64(a_paddr, 42) .write_phys_u64(a_paddr + list_offset, head_vaddr + list_offset);
let reader = make_reader(&isf, ptb);
let containers = reader
.walk_list(head_vaddr + list_offset, "task_struct", "tasks")
.unwrap();
assert_eq!(containers.len(), 1);
assert_eq!(containers[0], a_vaddr);
}
#[test]
fn symbols_accessor() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_symbol("init_task", 0xFFFF_0000);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
assert_eq!(reader.symbols().backend_name(), "ISF JSON");
assert_eq!(reader.symbols().field_offset("task_struct", "pid"), Some(0));
}
#[test]
fn required_symbol_ok() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_symbol("init_task", 0xFFFF_8000_CAFE_0000);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
assert_eq!(
reader.required_symbol("init_task").unwrap(),
0xFFFF_8000_CAFE_0000
);
}
#[test]
fn required_symbol_rebases_by_kernel_base() {
let isf = IsfBuilder::new()
.add_struct("x", 1)
.add_symbol("PsActiveProcessHead", 0x002b_00a0);
let reader =
make_reader(&isf, PageTableBuilder::new()).with_kernel_base(0xFFFF_F800_CBE0_0000);
assert_eq!(
reader.required_symbol("PsActiveProcessHead").unwrap(),
0xFFFF_F800_CC0B_00A0
);
}
#[test]
fn required_symbol_missing_returns_error() {
let isf = IsfBuilder::new().add_struct("task_struct", 128);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
assert!(reader.required_symbol("nonexistent").is_err());
}
#[test]
fn required_field_offset_ok() {
let isf = IsfBuilder::new().add_struct("task_struct", 128).add_field(
"task_struct",
"pid",
4,
"int",
);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
assert_eq!(
reader.required_field_offset("task_struct", "pid").unwrap(),
4
);
}
#[test]
fn required_field_offset_missing_returns_error() {
let isf = IsfBuilder::new().add_struct("task_struct", 128);
let vaddr: u64 = 0xFFFF_8000_0010_0000;
let paddr: u64 = 0x0080_0000;
let ptb = PageTableBuilder::new().map_4k(vaddr, paddr, flags::WRITABLE);
let reader = make_reader(&isf, ptb);
assert!(reader
.required_field_offset("task_struct", "nonexistent")
.is_err());
}
#[test]
fn walk_list_cycle_detection() {
let isf = IsfBuilder::new()
.add_struct("_EPROCESS", 256)
.add_field("_EPROCESS", "ActiveProcessLinks", 0x10, "_LIST_ENTRY")
.add_struct("_LIST_ENTRY", 16)
.add_field("_LIST_ENTRY", "Flink", 0, "pointer")
.add_field("_LIST_ENTRY", "Blink", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let b_paddr: u64 = 0x0080_2000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
let list_offset: u64 = 0x10;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.map_4k(b_vaddr, b_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, a_vaddr + list_offset)
.write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
.write_phys_u64(b_paddr + list_offset, a_vaddr + list_offset);
let reader = make_reader(&isf, ptb);
let result = reader.walk_list_with(
head_vaddr,
"_LIST_ENTRY",
"Flink",
"_EPROCESS",
"ActiveProcessLinks",
);
assert!(
matches!(result, Err(Error::ListCycle(_))),
"expected ListCycle error, got: {result:?}"
);
}
#[test]
fn iter_list_yields_same_as_walk_list() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 8, "list_head")
.add_field("task_struct", "comm", 16, "char")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let b_paddr: u64 = 0x0080_2000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
let list_offset: u64 = 8;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.map_4k(b_vaddr, b_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, 0)
.write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
.write_phys_u64(a_paddr, 100)
.write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
.write_phys_u64(b_paddr, 200)
.write_phys_u64(b_paddr + list_offset, head_vaddr + list_offset);
let reader = make_reader(&isf, ptb);
let head = head_vaddr + list_offset;
let walk_result = reader.walk_list(head, "task_struct", "tasks").unwrap();
let iter_result: Vec<u64> = reader
.iter_list(head, "task_struct", "tasks")
.collect::<crate::Result<Vec<_>>>()
.unwrap();
assert_eq!(iter_result, walk_result);
}
#[test]
fn iter_list_take_stops_early() {
let isf = IsfBuilder::new()
.add_struct("task_struct", 128)
.add_field("task_struct", "pid", 0, "int")
.add_field("task_struct", "tasks", 8, "list_head")
.add_field("task_struct", "comm", 16, "char")
.add_struct("list_head", 16)
.add_field("list_head", "next", 0, "pointer")
.add_field("list_head", "prev", 8, "pointer");
let head_paddr: u64 = 0x0080_0000;
let a_paddr: u64 = 0x0080_1000;
let b_paddr: u64 = 0x0080_2000;
let head_vaddr: u64 = 0xFFFF_8000_0010_0000;
let a_vaddr: u64 = 0xFFFF_8000_0010_1000;
let b_vaddr: u64 = 0xFFFF_8000_0010_2000;
let list_offset: u64 = 8;
let ptb = PageTableBuilder::new()
.map_4k(head_vaddr, head_paddr, flags::WRITABLE)
.map_4k(a_vaddr, a_paddr, flags::WRITABLE)
.map_4k(b_vaddr, b_paddr, flags::WRITABLE)
.write_phys_u64(head_paddr, 0)
.write_phys_u64(head_paddr + list_offset, a_vaddr + list_offset)
.write_phys_u64(a_paddr, 100)
.write_phys_u64(a_paddr + list_offset, b_vaddr + list_offset)
.write_phys_u64(b_paddr, 200)
.write_phys_u64(b_paddr + list_offset, head_vaddr + list_offset);
let reader = make_reader(&isf, ptb);
let head = head_vaddr + list_offset;
let first_two: Vec<u64> = reader
.iter_list(head, "task_struct", "tasks")
.take(2)
.collect::<crate::Result<Vec<_>>>()
.unwrap();
assert_eq!(first_two.len(), 2);
}
}