use crate::diagnostics;
#[cfg(test)]
use crate::dmp::IMAGE_FILE_MACHINE_AMD64;
use crate::dmp::structs::{ExceptionRecord64, Header64, KdDebuggerData64};
use crate::dmp::{
DmpContext, DmpException, DmpInfo, DmpSystemInfo, IMAGE_FILE_MACHINE_ARM64, UnloadedDriver,
clamp_processors,
};
use crate::error::{Error, Result};
use crate::guest::ModuleInfo;
use crate::kd::context;
use crate::kd::context_arm64;
use crate::kd::wire::{read_u16, read_u32, read_u64};
use crate::types::VirtAddr;
use std::mem::{offset_of, size_of};
const DUMP_HEADER64_SIZE: usize = size_of::<Header64>();
const SIGNATURE_PAGEDU64: &[u8; 8] = b"PAGEDU64";
const DUMP_TYPE_TRIAGE: u32 = 0x4;
const OFF_MAJOR_VERSION: usize = offset_of!(Header64, major_version);
const OFF_MINOR_VERSION: usize = offset_of!(Header64, minor_version);
const OFF_DIRECTORY_TABLE_BASE: usize = offset_of!(Header64, directory_table_base);
const OFF_PS_LOADED_MODULE_LIST: usize = offset_of!(Header64, ps_loaded_module_list);
const OFF_PS_ACTIVE_PROCESS_HEAD: usize = offset_of!(Header64, ps_active_process_head);
const OFF_MACHINE_IMAGE_TYPE: usize = offset_of!(Header64, machine_image_type);
const OFF_NUMBER_PROCESSORS: usize = offset_of!(Header64, number_processors);
const OFF_BUG_CHECK_CODE: usize = offset_of!(Header64, bug_check_code);
const OFF_BUG_CHECK_PARAMETERS: usize = offset_of!(Header64, bug_check_code_parameters);
const OFF_KD_DEBUGGER_DATA_BLOCK: usize = offset_of!(Header64, kd_debugger_data_block);
const OFF_CONTEXT_RECORD: usize = offset_of!(Header64, context_record_buffer);
const OFF_EXCEPTION_RECORD: usize = offset_of!(Header64, exception);
const OFF_DUMP_TYPE: usize = offset_of!(Header64, dump_type);
const OFF_SYSTEM_TIME: usize = offset_of!(Header64, system_time);
const OFF_SYSTEM_UP_TIME: usize = offset_of!(Header64, system_up_time);
const OFF_PRODUCT_TYPE: usize = offset_of!(Header64, product_type);
const OFF_SUITE_MASK: usize = offset_of!(Header64, suite_mask);
const EXCEPTION_CODE: usize = offset_of!(ExceptionRecord64, exception_code);
const EXCEPTION_FLAGS: usize = offset_of!(ExceptionRecord64, exception_flags);
const EXCEPTION_ADDRESS: usize = offset_of!(ExceptionRecord64, exception_address);
const EXCEPTION_NUM_PARAMS: usize = offset_of!(ExceptionRecord64, number_parameters);
const EXCEPTION_INFORMATION: usize = offset_of!(ExceptionRecord64, exception_information);
const TRIAGE_SERVICE_PACK_BUILD: usize = 0x00;
#[cfg(test)]
const TRIAGE_SIZE_OF_DUMP: usize = 0x04;
const TRIAGE_VALID_OFFSET: usize = 0x08;
const TRIAGE_CONTEXT_OFFSET: usize = 0x0C;
const TRIAGE_EXCEPTION_OFFSET: usize = 0x10;
const TRIAGE_UNLOADED_DRIVERS_OFFSET: usize = 0x18;
const TRIAGE_PRCB_OFFSET: usize = 0x1C;
const TRIAGE_PROCESS_OFFSET: usize = 0x20;
const TRIAGE_THREAD_OFFSET: usize = 0x24;
const TRIAGE_CALL_STACK_OFFSET: usize = 0x28;
const TRIAGE_SIZE_OF_CALL_STACK: usize = 0x2C;
const TRIAGE_DRIVER_LIST_OFFSET: usize = 0x30;
const TRIAGE_DRIVER_COUNT: usize = 0x34;
const TRIAGE_STRING_POOL_OFFSET: usize = 0x38;
const TRIAGE_STRING_POOL_SIZE: usize = 0x3C;
const TRIAGE_BROKEN_DRIVER_OFFSET: usize = 0x40;
const TRIAGE_OPTIONS: usize = 0x44;
const TRIAGE_OPTION_OVERFLOWED: u32 = 0x0100;
const TRIAGE_TOP_OF_STACK: usize = 0x48;
const TRIAGE_DATA_PAGE_ADDRESS: usize = 0x60;
const TRIAGE_DATA_PAGE_OFFSET: usize = 0x68;
const TRIAGE_DATA_PAGE_SIZE: usize = 0x6C;
const TRIAGE_DEBUGGER_DATA_OFFSET: usize = 0x70;
const TRIAGE_DEBUGGER_DATA_SIZE: usize = 0x74;
const TRIAGE_DATA_BLOCKS_OFFSET: usize = 0x78;
const TRIAGE_DATA_BLOCKS_COUNT: usize = 0x7C;
const DATA_BLOCK_SIZE: usize = 16;
const DRIVER_ENTRY_SIZE: usize = 0x90;
const DRIVER_OFF_NAME_OFFSET: usize = 0x00;
const DRIVER_OFF_DLL_BASE: usize = 0x38;
const DRIVER_OFF_ENTRY_POINT: usize = 0x40;
const DRIVER_OFF_SIZE_OF_IMAGE: usize = 0x48;
const DRIVER_OFF_CHECKSUM: usize = 0x80;
const DRIVER_OFF_TIME_DATE_STAMP: usize = 0x88;
const UNLOADED_DRIVER_ENTRY_SIZE: usize = 0x38;
const UNLOADED_DRIVER_OFF_NAME: usize = 0x10;
const UNLOADED_DRIVER_NAME_LEN: usize = 12;
const UNLOADED_DRIVER_OFF_START: usize = 0x28;
const UNLOADED_DRIVER_OFF_END: usize = 0x30;
const MAX_UNLOADED_DRIVERS: usize = 50;
#[derive(Debug, Clone)]
pub struct TriageBlock {
pub address: u64,
pub offset: u64,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct TriageDriver {
pub name: String,
pub base: u64,
pub entry_point: u64,
pub size: u32,
pub checksum: u32,
pub time_date_stamp: u32,
}
impl TriageDriver {
pub fn to_module_info(&self) -> ModuleInfo {
ModuleInfo::new(self.name.clone(), VirtAddr(self.base), self.size)
.with_time_date_stamp(self.time_date_stamp)
.with_checksum(self.checksum)
}
}
#[derive(Debug, Clone, Default)]
pub struct TriagePrcbInfo {
pub current_thread: u64,
pub processor_number: u16,
pub mhz: u32,
pub cpu_type: u16,
pub vendor_string: String,
}
pub fn is_triage_dump(mmap: &[u8]) -> bool {
mmap.len() > DUMP_HEADER64_SIZE + 0x80
&& &mmap[..8] == SIGNATURE_PAGEDU64
&& read_u32(mmap, OFF_DUMP_TYPE) == DUMP_TYPE_TRIAGE
}
pub fn parse_triage(mmap: &[u8]) -> Result<(DmpInfo, Vec<TriageBlock>)> {
if !is_triage_dump(mmap) {
return Err(Error::InvalidDump("not a PAGEDU64 triage dump".into()));
}
let directory_table_base = read_u64(mmap, OFF_DIRECTORY_TABLE_BASE);
let number_processors = read_u32(mmap, OFF_NUMBER_PROCESSORS);
let bug_check_code = read_u32(mmap, OFF_BUG_CHECK_CODE);
let machine_image_type = read_u32(mmap, OFF_MACHINE_IMAGE_TYPE);
let bug_check_parameters = [
read_u64(mmap, OFF_BUG_CHECK_PARAMETERS),
read_u64(mmap, OFF_BUG_CHECK_PARAMETERS + 8),
read_u64(mmap, OFF_BUG_CHECK_PARAMETERS + 16),
read_u64(mmap, OFF_BUG_CHECK_PARAMETERS + 24),
];
let triage_hdr = &mmap[DUMP_HEADER64_SIZE..];
let ctx_offset = read_u32(triage_hdr, TRIAGE_CONTEXT_OFFSET) as usize;
let pc_offset = if machine_image_type == IMAGE_FILE_MACHINE_ARM64 {
context_arm64::OFFSET_PC
} else {
context::OFFSET_RIP
};
let ctx_offset = if ctx_offset > 0
&& ctx_offset
.checked_add(pc_offset + 8)
.is_some_and(|end| end <= mmap.len())
{
ctx_offset
} else {
OFF_CONTEXT_RECORD
};
if mmap.len() < ctx_offset.saturating_add(pc_offset + 8) {
return Err(Error::InvalidDump(
"dump too small for context record".into(),
));
}
let context = if machine_image_type == IMAGE_FILE_MACHINE_ARM64 {
DmpContext::from_arm64_bytes(&mmap[ctx_offset..])
} else {
DmpContext::from_bytes(&mmap[ctx_offset..])
};
let call_stack_offset = read_u32(triage_hdr, TRIAGE_CALL_STACK_OFFSET) as u64;
let size_of_call_stack = read_u32(triage_hdr, TRIAGE_SIZE_OF_CALL_STACK);
let top_of_stack = read_u64(triage_hdr, TRIAGE_TOP_OF_STACK);
let data_blocks_offset = read_u32(triage_hdr, TRIAGE_DATA_BLOCKS_OFFSET) as usize;
let data_blocks_count = read_u32(triage_hdr, TRIAGE_DATA_BLOCKS_COUNT) as usize;
let blocks_end = data_blocks_offset + data_blocks_count * DATA_BLOCK_SIZE;
if blocks_end > mmap.len() {
return Err(Error::InvalidDump(
"data blocks extend past dump file".into(),
));
}
if data_blocks_count > 0 && data_blocks_offset < DUMP_HEADER64_SIZE {
return Err(Error::InvalidDump(
"data blocks offset falls inside the dump header".into(),
));
}
let mut blocks = Vec::with_capacity(data_blocks_count + 1);
if size_of_call_stack > 0
&& call_stack_offset > 0
&& top_of_stack > size_of_call_stack as u64
&& call_stack_offset + size_of_call_stack as u64 <= mmap.len() as u64
{
blocks.push(TriageBlock {
address: top_of_stack - size_of_call_stack as u64,
offset: call_stack_offset,
size: size_of_call_stack,
});
}
for i in 0..data_blocks_count {
let base = data_blocks_offset + i * DATA_BLOCK_SIZE;
let address = read_u64(mmap, base);
let offset = read_u32(mmap, base + 8) as u64;
let size = read_u32(mmap, base + 12);
if size == 0 || offset + size as u64 > mmap.len() as u64 {
continue;
}
blocks.push(TriageBlock {
address,
offset,
size,
});
}
let dp_address = read_u64(triage_hdr, TRIAGE_DATA_PAGE_ADDRESS);
let dp_offset = read_u32(triage_hdr, TRIAGE_DATA_PAGE_OFFSET) as u64;
let dp_size = read_u32(triage_hdr, TRIAGE_DATA_PAGE_SIZE);
if dp_size > 0 && dp_offset + dp_size as u64 <= mmap.len() as u64 && dp_address != 0 {
blocks.push(TriageBlock {
address: dp_address,
offset: dp_offset,
size: dp_size,
});
}
blocks.sort_by_key(|b| b.address);
blocks.dedup_by(|b, a| {
if a.address == b.address {
if b.size > a.size {
a.size = b.size;
a.offset = b.offset;
}
true
} else {
false
}
});
validate_triage_signature(mmap, triage_hdr);
let exception = parse_exception_record(mmap, triage_hdr);
let service_pack_build = read_u32(triage_hdr, TRIAGE_SERVICE_PACK_BUILD);
let system_info = Some(DmpSystemInfo {
major_version: read_u32(mmap, OFF_MAJOR_VERSION),
minor_version: read_u32(mmap, OFF_MINOR_VERSION),
system_time: read_u64(mmap, OFF_SYSTEM_TIME) as i64,
system_up_time: read_u64(mmap, OFF_SYSTEM_UP_TIME) as i64,
product_type: read_u32(mmap, OFF_PRODUCT_TYPE),
suite_mask: read_u32(mmap, OFF_SUITE_MASK),
machine_image_type,
service_pack_build,
});
let unloaded_drivers = parse_unloaded_drivers(mmap, triage_hdr);
let debugger_data = parse_debugger_data(mmap, triage_hdr);
let (triage_process_snapshot, triage_thread_snapshot) = match &debugger_data {
Some(dd) => {
let proc = extract_snapshot(
mmap,
read_u32(triage_hdr, TRIAGE_PROCESS_OFFSET) as usize,
dd.size_eprocess as usize,
);
let thread = extract_snapshot(
mmap,
read_u32(triage_hdr, TRIAGE_THREAD_OFFSET) as usize,
dd.size_ethread as usize,
);
(proc, thread)
}
None => (None, None),
};
let info = DmpInfo {
directory_table_base,
bug_check_code,
bug_check_parameters,
offset_prcb_context: debugger_data.as_ref().and_then(|d| d.offset_prcb_context),
number_processors: clamp_processors(number_processors),
is_triage: true,
ps_loaded_module_list: read_u64(mmap, OFF_PS_LOADED_MODULE_LIST),
ps_active_process_head: read_u64(mmap, OFF_PS_ACTIVE_PROCESS_HEAD),
debugger_data_block: match read_u64(mmap, OFF_KD_DEBUGGER_DATA_BLOCK) {
0 => None,
address => Some(address),
},
triage_drivers: Vec::new(),
exception,
system_info,
unloaded_drivers,
blackbox_streams: Vec::new(),
triage_process_snapshot,
triage_thread_snapshot,
triage_prcb_info: debugger_data
.as_ref()
.and_then(|dd| parse_prcb_info(mmap, triage_hdr, dd)),
broken_driver: parse_broken_driver(mmap, triage_hdr),
triage_overflowed: read_u32(triage_hdr, TRIAGE_OPTIONS) & TRIAGE_OPTION_OVERFLOWED != 0,
kern_base: debugger_data.as_ref().and_then(|d| d.kern_base),
context,
};
Ok((info, blocks))
}
pub fn parse_drivers(mmap: &[u8]) -> Vec<TriageDriver> {
let mmap_len = mmap.len();
if mmap_len < DUMP_HEADER64_SIZE + TRIAGE_STRING_POOL_SIZE + 4 {
return Vec::new();
}
let triage_hdr = &mmap[DUMP_HEADER64_SIZE..];
let dl_offset = read_u32(triage_hdr, TRIAGE_DRIVER_LIST_OFFSET) as usize;
let dl_count = read_u32(triage_hdr, TRIAGE_DRIVER_COUNT) as usize;
let sp_offset = read_u32(triage_hdr, TRIAGE_STRING_POOL_OFFSET) as usize;
let sp_size = read_u32(triage_hdr, TRIAGE_STRING_POOL_SIZE) as usize;
if dl_offset >= mmap_len || sp_offset >= mmap_len || dl_count == 0 || dl_count > 4096 {
return Vec::new();
}
let mut drivers = Vec::with_capacity(dl_count);
for i in 0..dl_count {
let entry_off = dl_offset + i * DRIVER_ENTRY_SIZE;
if entry_off + DRIVER_ENTRY_SIZE > mmap_len {
break;
}
let base = read_u64(mmap, entry_off + DRIVER_OFF_DLL_BASE);
if base >> 48 != 0xFFFF {
break;
}
let entry_point = read_u64(mmap, entry_off + DRIVER_OFF_ENTRY_POINT);
let size = read_u32(mmap, entry_off + DRIVER_OFF_SIZE_OF_IMAGE);
let checksum = read_u32(mmap, entry_off + DRIVER_OFF_CHECKSUM);
let time_date_stamp = read_u32(mmap, entry_off + DRIVER_OFF_TIME_DATE_STAMP);
let name_offset = read_u32(mmap, entry_off + DRIVER_OFF_NAME_OFFSET) as usize;
let name = read_string_pool_entry(mmap, sp_offset, sp_size, name_offset, mmap_len)
.unwrap_or_default();
if name.is_empty() {
break;
}
drivers.push(TriageDriver {
name,
base,
entry_point,
size,
checksum,
time_date_stamp,
});
}
drivers
}
fn read_string_pool_entry(
data: &[u8],
sp_offset: usize,
sp_size: usize,
name_offset: usize,
data_len: usize,
) -> Option<String> {
let pos = name_offset;
let sp_end = (sp_offset + sp_size).min(data_len);
if pos < sp_offset || pos + 4 > sp_end {
return None;
}
let char_count = read_u32(data, pos) as usize;
if char_count == 0 || char_count > 500 || pos + 4 + char_count * 2 > sp_end {
return None;
}
let wchar_buf = &data[pos + 4..pos + 4 + char_count * 2];
let code_units: Vec<u16> = wchar_buf
.chunks_exact(2)
.map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
.take_while(|&c| c != 0)
.collect();
if code_units.is_empty() {
return None;
}
Some(String::from_utf16_lossy(&code_units))
}
fn validate_triage_signature(mmap: &[u8], triage_hdr: &[u8]) {
let valid_offset = read_u32(triage_hdr, TRIAGE_VALID_OFFSET) as usize;
if valid_offset == 0 || valid_offset + 4 > mmap.len() {
return;
}
let sig = &mmap[valid_offset..valid_offset + 4];
if sig != b"TRGD" {
diagnostics::eprint_warning(
"triage dump DGRT integrity signature mismatch — dump may be truncated or corrupt",
);
}
}
fn parse_broken_driver(mmap: &[u8], triage_hdr: &[u8]) -> Option<String> {
let name_offset = read_u32(triage_hdr, TRIAGE_BROKEN_DRIVER_OFFSET) as usize;
if name_offset == 0 {
return None;
}
let sp_offset = read_u32(triage_hdr, TRIAGE_STRING_POOL_OFFSET) as usize;
let sp_size = read_u32(triage_hdr, TRIAGE_STRING_POOL_SIZE) as usize;
read_string_pool_entry(mmap, sp_offset, sp_size, name_offset, mmap.len())
}
fn parse_exception_record(mmap: &[u8], triage_hdr: &[u8]) -> Option<DmpException> {
let exc_offset = read_u32(triage_hdr, TRIAGE_EXCEPTION_OFFSET) as usize;
let exc_buf = if exc_offset > 0 && exc_offset + EXCEPTION_INFORMATION + 15 * 8 <= mmap.len() {
&mmap[exc_offset..]
} else if mmap.len() >= OFF_EXCEPTION_RECORD + EXCEPTION_INFORMATION + 15 * 8 {
&mmap[OFF_EXCEPTION_RECORD..]
} else {
return None;
};
let code = read_u32(exc_buf, EXCEPTION_CODE);
let address = read_u64(exc_buf, EXCEPTION_ADDRESS);
if code == 0 && address == 0 {
return None;
}
let n_params = (read_u32(exc_buf, EXCEPTION_NUM_PARAMS) as usize).min(15);
let parameters: Vec<u64> = (0..n_params)
.map(|i| read_u64(exc_buf, EXCEPTION_INFORMATION + i * 8))
.collect();
Some(DmpException {
code,
flags: read_u32(exc_buf, EXCEPTION_FLAGS),
address,
parameters,
})
}
fn extract_snapshot(mmap: &[u8], offset: usize, size: usize) -> Option<Vec<u8>> {
if offset == 0 || size == 0 {
return None;
}
let end = offset.checked_add(size)?;
if end > mmap.len() {
return None;
}
Some(mmap[offset..end].to_vec())
}
fn parse_debugger_data(mmap: &[u8], triage_hdr: &[u8]) -> Option<DebuggerDataFields> {
let offset = read_u32(triage_hdr, TRIAGE_DEBUGGER_DATA_OFFSET) as usize;
let size = read_u32(triage_hdr, TRIAGE_DEBUGGER_DATA_SIZE) as usize;
let min_size = offset_of!(KdDebuggerData64, offset_prcb_context) + 2;
if offset == 0 || size < min_size {
return None;
}
let end = offset.checked_add(size)?;
if end > mmap.len() {
return None;
}
let base = offset;
let off_prcb_ctx = base + std::mem::offset_of!(KdDebuggerData64, offset_prcb_context);
let off_sz_eprocess = base + std::mem::offset_of!(KdDebuggerData64, size_eprocess);
let off_sz_ethread = base + std::mem::offset_of!(KdDebuggerData64, size_ethread);
let kern_base_raw = read_u64(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, kern_base),
);
let kern_base = if kern_base_raw >= 0xfffff800_00000000 {
Some(kern_base_raw)
} else {
None
};
let prcb_context = read_u16(mmap, off_prcb_ctx);
let size_eprocess = read_u16(mmap, off_sz_eprocess);
let size_ethread = read_u16(mmap, off_sz_ethread);
let size_prcb = read_u16(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, size_prcb),
);
let offset_prcb_current_thread = read_u16(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, offset_prcb_current_thread),
);
let offset_prcb_mhz = read_u16(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, offset_prcb_mhz),
);
let offset_prcb_cpu_type = read_u16(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, offset_prcb_cpu_type),
);
let offset_prcb_vendor_string = read_u16(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, offset_prcb_vendor_string),
);
let offset_prcb_number = read_u16(
mmap,
base + std::mem::offset_of!(KdDebuggerData64, offset_prcb_number),
);
Some(DebuggerDataFields {
kern_base,
offset_prcb_context: if prcb_context > 0 {
Some(prcb_context)
} else {
None
},
size_eprocess,
size_ethread,
size_prcb,
offset_prcb_current_thread,
offset_prcb_mhz,
offset_prcb_cpu_type,
offset_prcb_vendor_string,
offset_prcb_number,
})
}
struct DebuggerDataFields {
kern_base: Option<u64>,
offset_prcb_context: Option<u16>,
size_eprocess: u16,
size_ethread: u16,
size_prcb: u16,
offset_prcb_current_thread: u16,
offset_prcb_mhz: u16,
offset_prcb_cpu_type: u16,
offset_prcb_vendor_string: u16,
offset_prcb_number: u16,
}
fn parse_prcb_info(
mmap: &[u8],
triage_hdr: &[u8],
dd: &DebuggerDataFields,
) -> Option<TriagePrcbInfo> {
let prcb_offset = read_u32(triage_hdr, TRIAGE_PRCB_OFFSET) as usize;
if prcb_offset == 0 || dd.size_prcb == 0 {
return None;
}
let prcb_size = dd.size_prcb as usize;
let end = prcb_offset.checked_add(prcb_size)?;
if end > mmap.len() {
return None;
}
let prcb = &mmap[prcb_offset..];
let current_thread = if dd.offset_prcb_current_thread > 0
&& (dd.offset_prcb_current_thread as usize) + 8 <= prcb_size
{
read_u64(prcb, dd.offset_prcb_current_thread as usize)
} else {
0
};
let processor_number =
if dd.offset_prcb_number > 0 && (dd.offset_prcb_number as usize) + 2 <= prcb_size {
read_u16(prcb, dd.offset_prcb_number as usize)
} else {
0
};
let mhz = if dd.offset_prcb_mhz > 0 && (dd.offset_prcb_mhz as usize) + 4 <= prcb_size {
read_u32(prcb, dd.offset_prcb_mhz as usize)
} else {
0
};
let cpu_type =
if dd.offset_prcb_cpu_type > 0 && (dd.offset_prcb_cpu_type as usize) + 2 <= prcb_size {
read_u16(prcb, dd.offset_prcb_cpu_type as usize)
} else {
0
};
let vendor_string = if dd.offset_prcb_vendor_string > 0
&& (dd.offset_prcb_vendor_string as usize) + 13 <= prcb_size
{
let off = dd.offset_prcb_vendor_string as usize;
let bytes = &prcb[off..off + 13];
let end = bytes.iter().position(|&b| b == 0).unwrap_or(13);
String::from_utf8_lossy(&bytes[..end]).to_string()
} else {
String::new()
};
Some(TriagePrcbInfo {
current_thread,
processor_number,
mhz,
cpu_type,
vendor_string,
})
}
fn parse_unloaded_drivers(mmap: &[u8], triage_hdr: &[u8]) -> Vec<UnloadedDriver> {
let offset = read_u32(triage_hdr, TRIAGE_UNLOADED_DRIVERS_OFFSET) as usize;
if offset == 0 {
return Vec::new();
}
let mut drivers = Vec::new();
for i in 0..MAX_UNLOADED_DRIVERS {
let entry = offset + i * UNLOADED_DRIVER_ENTRY_SIZE;
if entry + UNLOADED_DRIVER_ENTRY_SIZE > mmap.len() {
break;
}
let start = read_u64(mmap, entry + UNLOADED_DRIVER_OFF_START);
let end = read_u64(mmap, entry + UNLOADED_DRIVER_OFF_END);
if start == 0 && end == 0 {
break;
}
let name_buf = &mmap[entry + UNLOADED_DRIVER_OFF_NAME..];
let code_units: Vec<u16> = (0..UNLOADED_DRIVER_NAME_LEN)
.map(|j| read_u16(name_buf, j * 2))
.take_while(|&c| c != 0)
.collect();
if code_units.is_empty() {
break;
}
drivers.push(UnloadedDriver {
name: String::from_utf16_lossy(&code_units),
start_address: start,
end_address: end,
});
}
drivers
}
#[cfg(test)]
pub(crate) fn make_triage_dump(blocks: &[TriageBlock], mem_regions: &[(u64, &[u8])]) -> Vec<u8> {
let mut buf = vec![0u8; 0x10000];
buf[..8].copy_from_slice(SIGNATURE_PAGEDU64);
buf[OFF_DUMP_TYPE..OFF_DUMP_TYPE + 4].copy_from_slice(&DUMP_TYPE_TRIAGE.to_le_bytes());
buf[OFF_BUG_CHECK_CODE..OFF_BUG_CHECK_CODE + 4].copy_from_slice(&0x50u32.to_le_bytes());
buf[OFF_NUMBER_PROCESSORS..OFF_NUMBER_PROCESSORS + 4].copy_from_slice(&1u32.to_le_bytes());
buf[OFF_MACHINE_IMAGE_TYPE..OFF_MACHINE_IMAGE_TYPE + 4]
.copy_from_slice(&IMAGE_FILE_MACHINE_AMD64.to_le_bytes());
buf[OFF_DIRECTORY_TABLE_BASE..OFF_DIRECTORY_TABLE_BASE + 8]
.copy_from_slice(&0x1ad000u64.to_le_bytes());
let ctx_base = OFF_CONTEXT_RECORD;
buf[ctx_base + context::OFFSET_RIP..ctx_base + context::OFFSET_RIP + 8]
.copy_from_slice(&0xfffff80012345678u64.to_le_bytes());
let triage_base = DUMP_HEADER64_SIZE;
let db_offset: u32 = (DUMP_HEADER64_SIZE + 0x80) as u32;
buf[triage_base + TRIAGE_DATA_BLOCKS_OFFSET..triage_base + TRIAGE_DATA_BLOCKS_OFFSET + 4]
.copy_from_slice(&db_offset.to_le_bytes());
buf[triage_base + TRIAGE_DATA_BLOCKS_COUNT..triage_base + TRIAGE_DATA_BLOCKS_COUNT + 4]
.copy_from_slice(&(blocks.len() as u32).to_le_bytes());
let mut data_cursor = (DUMP_HEADER64_SIZE + 0x200) as u32;
for (i, block) in blocks.iter().enumerate() {
let entry_off = db_offset as usize + i * DATA_BLOCK_SIZE;
buf[entry_off..entry_off + 8].copy_from_slice(&block.address.to_le_bytes());
buf[entry_off + 8..entry_off + 12].copy_from_slice(&data_cursor.to_le_bytes());
buf[entry_off + 12..entry_off + 16].copy_from_slice(&block.size.to_le_bytes());
if let Some((_, content)) = mem_regions.iter().find(|(a, _)| *a == block.address) {
let file_off = data_cursor as usize;
let len = content.len().min(block.size as usize);
buf[file_off..file_off + len].copy_from_slice(&content[..len]);
}
data_cursor += block.size;
}
let total = data_cursor as usize - DUMP_HEADER64_SIZE + 0x200;
buf[triage_base + TRIAGE_SIZE_OF_DUMP..triage_base + TRIAGE_SIZE_OF_DUMP + 4]
.copy_from_slice(&(total as u32).to_le_bytes());
buf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_triage_dump_detects_signature_and_type() {
let dump = make_triage_dump(&[], &[]);
assert!(is_triage_dump(&dump));
let mut bad = dump.clone();
bad[0] = b'X';
assert!(!is_triage_dump(&bad));
let mut bad = dump.clone();
bad[OFF_DUMP_TYPE] = 0x01;
assert!(!is_triage_dump(&bad));
}
#[test]
fn parse_triage_extracts_header_fields() {
let dump = make_triage_dump(&[], &[]);
let (info, blocks) = parse_triage(&dump).unwrap();
assert_eq!(info.bug_check_code, 0x50);
assert_eq!(info.number_processors, 1);
assert!(info.is_triage);
assert_eq!(info.context.rip, 0xfffff80012345678);
assert!(blocks.is_empty());
}
#[test]
fn parse_triage_extracts_arm64_context_layout() {
let mut dump = make_triage_dump(&[], &[]);
dump[OFF_MACHINE_IMAGE_TYPE..OFF_MACHINE_IMAGE_TYPE + 4]
.copy_from_slice(&IMAGE_FILE_MACHINE_ARM64.to_le_bytes());
let ctx_base = OFF_CONTEXT_RECORD;
dump[ctx_base + context_arm64::OFFSET_PC..ctx_base + context_arm64::OFFSET_PC + 8]
.copy_from_slice(&0xffff_0000_0000_1000u64.to_le_bytes());
dump[ctx_base + context_arm64::OFFSET_SP..ctx_base + context_arm64::OFFSET_SP + 8]
.copy_from_slice(&0xffff_0000_1234_5000u64.to_le_bytes());
dump[ctx_base + context_arm64::OFFSET_BCR0..ctx_base + context_arm64::OFFSET_BCR0 + 4]
.copy_from_slice(&0x0000_e9e1u32.to_le_bytes());
let (info, _) = parse_triage(&dump).unwrap();
let arm = info.context.arm64.as_ref().unwrap();
assert_eq!(arm.pc, 0xffff_0000_0000_1000);
assert_eq!(arm.sp, 0xffff_0000_1234_5000);
assert_eq!(arm.bcr[0], 0x0000_e9e1);
}
#[test]
fn parse_triage_builds_sorted_block_map() {
let blk_b = TriageBlock {
address: 0x2000,
offset: 0,
size: 0x100,
};
let blk_a = TriageBlock {
address: 0x1000,
offset: 0,
size: 0x80,
};
let data_a = vec![0xAAu8; 0x80];
let data_b = vec![0xBBu8; 0x100];
let dump = make_triage_dump(
&[blk_b.clone(), blk_a.clone()],
&[(0x2000, &data_b), (0x1000, &data_a)],
);
let (_, blocks) = parse_triage(&dump).unwrap();
assert_eq!(blocks.len(), 2);
assert!(blocks[0].address < blocks[1].address);
assert_eq!(blocks[0].address, 0x1000);
assert_eq!(blocks[1].address, 0x2000);
}
}