use core::slice;
use std::collections::HashMap;
use std::ops::Range;
use crate::error::{Error, Result};
use crate::gxa::{Gva, Gxa};
use crate::parse::KernelDumpParser;
use crate::structs::{Context, KdDebuggerData64, LdrDataTableEntry, ListEntry, Pod, UnicodeString};
use crate::virt::{self, ignore_non_fatal};
trait HasCheckedAdd: Sized + Copy + Into<u64> + From<u32> {
fn checked_add(self, rhs: Self) -> Option<Self>;
}
macro_rules! impl_checked_add {
($($ty:ident),*) => {
$(impl HasCheckedAdd for $ty {
fn checked_add(self, rhs: $ty) -> Option<Self> {
$ty::checked_add(self, rhs)
}
})*
};
}
impl_checked_add!(u32, u64);
fn read_unicode_string(
reader: &virt::Reader,
unicode_str: &UnicodeString<impl Pod + HasCheckedAdd>,
) -> Result<String> {
if !unicode_str.length.is_multiple_of(2) {
return Err(Error::InvalidUnicodeString);
}
let mut buffer = vec![0; unicode_str.length.into()];
reader.read_exact(Gva::new(unicode_str.buffer.into()), &mut buffer)?;
let n = unicode_str.length / 2;
Ok(String::from_utf16(unsafe {
slice::from_raw_parts(buffer.as_ptr().cast(), n.into())
})?)
}
fn try_read_unicode_string(
reader: &virt::Reader,
unicode_str: &UnicodeString<impl HasCheckedAdd + Pod>,
) -> Result<Option<String>> {
ignore_non_fatal(read_unicode_string(reader, unicode_str))
}
pub type ModuleMap = HashMap<Range<Gva>, String>;
fn try_read_module_map<P: Pod + HasCheckedAdd>(
reader: &virt::Reader,
head: Gva,
) -> Result<Option<ModuleMap>> {
let mut modules = ModuleMap::new();
let Some(entry) = reader.try_read_struct::<ListEntry<P>>(head)? else {
return Ok(None);
};
let mut entry_addr = Gva::new(entry.flink.into());
while entry_addr != head {
let Some(data) = reader.try_read_struct::<LdrDataTableEntry<P>>(entry_addr)? else {
return Ok(None);
};
let Some(dll_name) =
try_read_unicode_string(reader, &data.full_dll_name).and_then(|s| {
if s.is_none() {
try_read_unicode_string(reader, &data.base_dll_name)
} else {
Ok(s)
}
})?
else {
return Ok(None);
};
let dll_end_addr = data
.dll_base
.checked_add(data.size_of_image.into())
.ok_or(Error::Overflow("module address"))?;
let at = Gva::new(data.dll_base.into())..Gva::new(dll_end_addr.into());
let inserted = modules.insert(at, dll_name);
debug_assert!(inserted.is_none());
entry_addr = Gva::new(data.in_load_order_links.flink.into());
}
Ok(Some(modules))
}
pub(crate) fn try_extract_kernel_modules(parser: &KernelDumpParser) -> Result<Option<ModuleMap>> {
try_read_module_map::<u64>(
&virt::Reader::new(parser),
parser.headers().ps_loaded_module_list.into(),
)
}
pub(crate) fn try_find_prcb(
parser: &KernelDumpParser,
kd_debugger_data_block: &KdDebuggerData64,
) -> Result<Option<Gva>> {
let reader = virt::Reader::new(parser);
let mut processor_block = kd_debugger_data_block.ki_processor_block;
for _ in 0..parser.headers().number_processors {
let Some(kprcb_addr) = reader.try_read_struct::<u64>(processor_block.into())? else {
return Ok(None);
};
let kprcb_context_addr = kprcb_addr
.checked_add(kd_debugger_data_block.offset_prcb_context.into())
.ok_or(Error::Overflow("offset_prcb"))?;
let Some(kprcb_context_addr) = reader.try_read_struct::<u64>(kprcb_context_addr.into())?
else {
return Ok(None);
};
let Some(kprcb_context) = reader.try_read_struct::<Context>(kprcb_context_addr.into())?
else {
return Ok(None);
};
let kprcb_context = Box::new(kprcb_context);
if kprcb_context.rsp == parser.context_record().rsp {
return Ok(Some(kprcb_addr.into()));
}
processor_block = processor_block
.checked_add(size_of::<u64>() as _)
.ok_or(Error::Overflow("kprcb ptr"))?;
}
Ok(None)
}
pub(crate) fn try_extract_user_modules(
reader: &virt::Reader,
kd_debugger_data_block: &KdDebuggerData64,
prcb_addr: Gva,
) -> Result<Option<ModuleMap>> {
let kthread_addr = prcb_addr
.u64()
.checked_add(kd_debugger_data_block.offset_prcb_current_thread.into())
.ok_or(Error::Overflow("offset prcb current thread"))?;
let Some(kthread_addr) = reader.try_read_struct::<u64>(kthread_addr.into())? else {
return Ok(None);
};
let teb_addr = kthread_addr
.checked_add(kd_debugger_data_block.offset_kthread_teb.into())
.ok_or(Error::Overflow("offset kthread teb"))?;
let Some(teb_addr) = reader.try_read_struct::<u64>(teb_addr.into())? else {
return Ok(None);
};
if teb_addr == 0 {
return Ok(None);
}
let peb_offset = 0x60;
let peb_addr = teb_addr
.checked_add(peb_offset)
.ok_or(Error::Overflow("peb offset"))?;
let Some(peb_addr) = reader.try_read_struct::<u64>(peb_addr.into())? else {
return Ok(None);
};
let ldr_offset = 0x18;
let peb_ldr_addr = peb_addr
.checked_add(ldr_offset)
.ok_or(Error::Overflow("ldr offset"))?;
let Some(peb_ldr_addr) = reader.try_read_struct::<u64>(peb_ldr_addr.into())? else {
return Ok(None);
};
let in_load_order_module_list_offset = 0x10;
let module_list_entry_addr = peb_ldr_addr
.checked_add(in_load_order_module_list_offset)
.ok_or(Error::Overflow("in load order module list offset"))?;
let Some(mut modules) = try_read_module_map::<u64>(reader, module_list_entry_addr.into())?
else {
return Ok(None);
};
let teb32_offset = 0x2_000;
let teb32_addr = teb_addr
.checked_add(teb32_offset)
.ok_or(Error::Overflow("teb32 offset"))?;
let peb32_offset = 0x30;
let peb32_addr = teb32_addr
.checked_add(peb32_offset)
.ok_or(Error::Overflow("peb32 offset"))?;
let Some(peb32_addr) = reader.try_read_struct::<u32>(peb32_addr.into())? else {
return Ok(Some(modules));
};
let ldr_offset = 0x0c;
let peb32_ldr_addr = peb32_addr
.checked_add(ldr_offset)
.ok_or(Error::Overflow("ldr32 offset"))?;
let Some(peb32_ldr_addr) = reader.try_read_struct::<u32>(Gva::new(peb32_ldr_addr.into()))?
else {
return Ok(Some(modules));
};
let in_load_order_module_list_offset = 0xc;
let module_list_entry_addr = peb32_ldr_addr
.checked_add(in_load_order_module_list_offset)
.ok_or(Error::Overflow("in load order module list offset"))?;
let Some(modules32) =
try_read_module_map::<u32>(reader, Gva::new(module_list_entry_addr.into()))?
else {
return Ok(Some(modules));
};
modules.extend(modules32);
Ok(Some(modules))
}