use std::fs::File;
use std::mem::offset_of;
use std::path::Path;
use std::time::Duration;
use memmap2::Mmap;
use zerocopy::FromBytes;
use crate::kd::wire::{
read_u16, read_u32, read_u64, read_u64 as buffer_u64, write_u16, write_u32, write_u64,
};
use crate::backend::MemoryOps;
use crate::cpu_state;
use crate::dbg_backend::{BackendCapability, DebugBackend, DebugCapability, StopEvent};
use crate::debugger_data::{DebuggerDataCandidate, MetadataSource};
use crate::error::{Error, Result};
use crate::gdb::RegisterMap;
use crate::kd::context;
use crate::kd::context_arm64;
use crate::memory::PAGE_SIZE;
use crate::session::processor_index_from_backend_thread_id;
use crate::symbols::{ParsedType, TypeInfo};
use crate::target::Target;
use crate::triage::{
TriageBlock, TriageDriver, TriagePrcbInfo, is_triage_dump, parse_drivers, parse_triage,
};
use crate::types::{PhysAddr, VirtAddr};
pub mod parse;
pub mod structs;
use parse::ParsedDump;
use structs::{Context, KdDebuggerData64};
pub const IMAGE_FILE_MACHINE_AMD64: u32 = 0x8664;
pub const IMAGE_FILE_MACHINE_ARM64: u32 = 0xaa64;
fn require_supported_dump(machine_type: u32) -> Result<()> {
if matches!(
machine_type,
IMAGE_FILE_MACHINE_AMD64 | IMAGE_FILE_MACHINE_ARM64
) {
return Ok(());
}
let name = match machine_type {
0x014c => "I386",
0xaa64 => "ARM64",
_ => "unknown",
};
Err(Error::UnsupportedArchitecture(format!(
"{name} crash dump (machine {machine_type:#06x})"
)))
}
#[derive(Debug, Clone, Default)]
pub struct DmpArm64Context {
pub x: [u64; 31],
pub cpsr: u32,
pub sp: u64,
pub pc: u64,
pub v: [u128; 32],
pub fpcr: u32,
pub fpsr: u32,
pub bcr: [u32; 8],
pub bvr: [u64; 8],
pub wcr: [u32; 2],
pub wvr: [u64; 2],
}
#[derive(Debug, Clone, Default)]
pub struct DmpContext {
pub rax: u64,
pub rbx: u64,
pub rcx: u64,
pub rdx: u64,
pub rsi: u64,
pub rdi: u64,
pub rbp: u64,
pub rsp: u64,
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
pub rip: u64,
pub eflags: u32,
pub cs: u16,
pub ds: u16,
pub es: u16,
pub fs: u16,
pub gs: u16,
pub ss: u16,
pub dr0: u64,
pub dr1: u64,
pub dr2: u64,
pub dr3: u64,
pub dr6: u64,
pub dr7: u64,
pub mxcsr: u32,
pub xmm: [u128; 16],
pub debug_control: u64,
pub last_branch_to_rip: u64,
pub last_branch_from_rip: u64,
pub last_exception_to_rip: u64,
pub last_exception_from_rip: u64,
pub arm64: Option<DmpArm64Context>,
}
#[derive(Debug, Clone, Default)]
pub struct DmpException {
pub code: u32,
pub flags: u32,
pub address: u64,
pub parameters: Vec<u64>,
}
#[derive(Debug, Clone, Default)]
pub struct DmpSystemInfo {
pub major_version: u32,
pub minor_version: u32,
pub system_time: i64,
pub system_up_time: i64,
pub product_type: u32,
pub suite_mask: u32,
pub machine_image_type: u32,
pub service_pack_build: u32,
}
#[derive(Debug, Clone)]
pub struct UnloadedDriver {
pub name: String,
pub start_address: u64,
pub end_address: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DmpBlackboxStream {
pub name: String,
pub size: u64,
}
impl DmpContext {
pub fn from_bytes(buf: &[u8]) -> Self {
let mut xmm = [0u128; 16];
if buf.len() >= context::OFFSET_XMM0 + 16 * 16 {
for (i, slot) in xmm.iter_mut().enumerate() {
let off = context::OFFSET_XMM0 + i * 16;
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&buf[off..off + 16]);
*slot = u128::from_le_bytes(bytes);
}
}
let has_lbr = buf.len() >= context::OFFSET_LAST_EXCEPTION_FROM_RIP + 8;
Self {
rax: read_u64(buf, context::OFFSET_RAX),
rbx: read_u64(buf, context::OFFSET_RBX),
rcx: read_u64(buf, context::OFFSET_RCX),
rdx: read_u64(buf, context::OFFSET_RDX),
rsi: read_u64(buf, context::OFFSET_RSI),
rdi: read_u64(buf, context::OFFSET_RDI),
rbp: read_u64(buf, context::OFFSET_RBP),
rsp: read_u64(buf, context::OFFSET_RSP),
r8: read_u64(buf, context::OFFSET_R8),
r9: read_u64(buf, context::OFFSET_R9),
r10: read_u64(buf, context::OFFSET_R10),
r11: read_u64(buf, context::OFFSET_R11),
r12: read_u64(buf, context::OFFSET_R12),
r13: read_u64(buf, context::OFFSET_R13),
r14: read_u64(buf, context::OFFSET_R14),
r15: read_u64(buf, context::OFFSET_R15),
rip: read_u64(buf, context::OFFSET_RIP),
eflags: read_u32(buf, context::OFFSET_EFLAGS),
cs: read_u16(buf, context::OFFSET_SEG_CS),
ds: read_u16(buf, context::OFFSET_SEG_DS),
es: read_u16(buf, context::OFFSET_SEG_ES),
fs: read_u16(buf, context::OFFSET_SEG_FS),
gs: read_u16(buf, context::OFFSET_SEG_GS),
ss: read_u16(buf, context::OFFSET_SEG_SS),
dr0: read_u64(buf, context::OFFSET_DR0),
dr1: read_u64(buf, context::OFFSET_DR1),
dr2: read_u64(buf, context::OFFSET_DR2),
dr3: read_u64(buf, context::OFFSET_DR3),
dr6: read_u64(buf, context::OFFSET_DR6),
dr7: read_u64(buf, context::OFFSET_DR7),
mxcsr: read_u32(buf, context::OFFSET_MX_CSR),
xmm,
debug_control: if has_lbr {
read_u64(buf, context::OFFSET_DEBUG_CONTROL)
} else {
0
},
last_branch_to_rip: if has_lbr {
read_u64(buf, context::OFFSET_LAST_BRANCH_TO_RIP)
} else {
0
},
last_branch_from_rip: if has_lbr {
read_u64(buf, context::OFFSET_LAST_BRANCH_FROM_RIP)
} else {
0
},
last_exception_to_rip: if has_lbr {
read_u64(buf, context::OFFSET_LAST_EXCEPTION_TO_RIP)
} else {
0
},
last_exception_from_rip: if has_lbr {
read_u64(buf, context::OFFSET_LAST_EXCEPTION_FROM_RIP)
} else {
0
},
arm64: None,
}
}
pub fn from_arm64_bytes(buf: &[u8]) -> Self {
if buf.len() < context_arm64::CONTEXT_SIZE {
return Self {
arm64: Some(DmpArm64Context::default()),
..Self::default()
};
}
let mut x = [0u64; 31];
for (i, slot) in x.iter_mut().enumerate() {
*slot = read_u64(buf, context_arm64::OFFSET_X0 + i * 8);
}
let mut v = [0u128; 32];
for (i, slot) in v.iter_mut().enumerate() {
let off = context_arm64::OFFSET_V0 + i * 16;
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&buf[off..off + 16]);
*slot = u128::from_le_bytes(bytes);
}
let mut bcr = [0u32; 8];
for (i, slot) in bcr.iter_mut().enumerate() {
*slot = read_u32(buf, context_arm64::OFFSET_BCR0 + i * 4);
}
let mut bvr = [0u64; 8];
for (i, slot) in bvr.iter_mut().enumerate() {
*slot = read_u64(buf, context_arm64::OFFSET_BVR0 + i * 8);
}
let mut wcr = [0u32; 2];
for (i, slot) in wcr.iter_mut().enumerate() {
*slot = read_u32(buf, context_arm64::OFFSET_WCR0 + i * 4);
}
let mut wvr = [0u64; 2];
for (i, slot) in wvr.iter_mut().enumerate() {
*slot = read_u64(buf, context_arm64::OFFSET_WVR0 + i * 8);
}
let cpsr = read_u32(buf, context_arm64::OFFSET_CPSR);
let sp = read_u64(buf, context_arm64::OFFSET_SP);
let pc = read_u64(buf, context_arm64::OFFSET_PC);
let fpcr = read_u32(buf, context_arm64::OFFSET_FPCR);
let fpsr = read_u32(buf, context_arm64::OFFSET_FPSR);
Self {
arm64: Some(DmpArm64Context {
x,
cpsr,
sp,
pc,
v,
fpcr,
fpsr,
bcr,
bvr,
wcr,
wvr,
}),
..Self::default()
}
}
pub fn is_arm64(&self) -> bool {
self.arm64.is_some()
}
pub fn instruction_pointer(&self) -> u64 {
self.arm64.as_ref().map_or(self.rip, |ctx| ctx.pc)
}
pub fn stack_pointer(&self) -> u64 {
self.arm64.as_ref().map_or(self.rsp, |ctx| ctx.sp)
}
pub fn to_register_buffer(&self, directory_table_base: u64) -> Vec<u8> {
if let Some(ctx) = &self.arm64 {
let mut data = vec![0u8; context_arm64::REGISTER_BUFFER_SIZE];
data[context_arm64::OFFSET_CONTEXT_FLAGS..context_arm64::OFFSET_CONTEXT_FLAGS + 4]
.copy_from_slice(&context_arm64::CONTEXT_ALL.to_le_bytes());
data[context_arm64::OFFSET_CPSR..context_arm64::OFFSET_CPSR + 4]
.copy_from_slice(&ctx.cpsr.to_le_bytes());
for (i, &value) in ctx.x.iter().enumerate() {
let off = context_arm64::OFFSET_X0 + i * 8;
data[off..off + 8].copy_from_slice(&value.to_le_bytes());
}
data[context_arm64::OFFSET_SP..context_arm64::OFFSET_SP + 8]
.copy_from_slice(&ctx.sp.to_le_bytes());
data[context_arm64::OFFSET_PC..context_arm64::OFFSET_PC + 8]
.copy_from_slice(&ctx.pc.to_le_bytes());
for (i, &value) in ctx.v.iter().enumerate() {
let off = context_arm64::OFFSET_V0 + i * 16;
data[off..off + 16].copy_from_slice(&value.to_le_bytes());
}
data[context_arm64::OFFSET_FPCR..context_arm64::OFFSET_FPCR + 4]
.copy_from_slice(&ctx.fpcr.to_le_bytes());
data[context_arm64::OFFSET_FPSR..context_arm64::OFFSET_FPSR + 4]
.copy_from_slice(&ctx.fpsr.to_le_bytes());
for (i, &value) in ctx.bcr.iter().enumerate() {
let off = context_arm64::OFFSET_BCR0 + i * 4;
data[off..off + 4].copy_from_slice(&value.to_le_bytes());
}
for (i, &value) in ctx.bvr.iter().enumerate() {
let off = context_arm64::OFFSET_BVR0 + i * 8;
data[off..off + 8].copy_from_slice(&value.to_le_bytes());
}
for (i, &value) in ctx.wcr.iter().enumerate() {
let off = context_arm64::OFFSET_WCR0 + i * 4;
data[off..off + 4].copy_from_slice(&value.to_le_bytes());
}
for (i, &value) in ctx.wvr.iter().enumerate() {
let off = context_arm64::OFFSET_WVR0 + i * 8;
data[off..off + 8].copy_from_slice(&value.to_le_bytes());
}
data[context_arm64::OFFSET_CR3..context_arm64::OFFSET_CR3 + 8]
.copy_from_slice(&directory_table_base.to_le_bytes());
return data;
}
let mut data = vec![0u8; context::REGISTER_BUFFER_SIZE];
write_u64(&mut data, context::OFFSET_RAX, self.rax);
write_u64(&mut data, context::OFFSET_RBX, self.rbx);
write_u64(&mut data, context::OFFSET_RCX, self.rcx);
write_u64(&mut data, context::OFFSET_RDX, self.rdx);
write_u64(&mut data, context::OFFSET_RSI, self.rsi);
write_u64(&mut data, context::OFFSET_RDI, self.rdi);
write_u64(&mut data, context::OFFSET_RBP, self.rbp);
write_u64(&mut data, context::OFFSET_RSP, self.rsp);
write_u64(&mut data, context::OFFSET_R8, self.r8);
write_u64(&mut data, context::OFFSET_R9, self.r9);
write_u64(&mut data, context::OFFSET_R10, self.r10);
write_u64(&mut data, context::OFFSET_R11, self.r11);
write_u64(&mut data, context::OFFSET_R12, self.r12);
write_u64(&mut data, context::OFFSET_R13, self.r13);
write_u64(&mut data, context::OFFSET_R14, self.r14);
write_u64(&mut data, context::OFFSET_R15, self.r15);
write_u64(&mut data, context::OFFSET_RIP, self.rip);
write_u32(&mut data, context::OFFSET_EFLAGS, self.eflags);
write_u16(&mut data, context::OFFSET_SEG_CS, self.cs);
write_u16(&mut data, context::OFFSET_SEG_DS, self.ds);
write_u16(&mut data, context::OFFSET_SEG_ES, self.es);
write_u16(&mut data, context::OFFSET_SEG_FS, self.fs);
write_u16(&mut data, context::OFFSET_SEG_GS, self.gs);
write_u16(&mut data, context::OFFSET_SEG_SS, self.ss);
write_u64(&mut data, context::OFFSET_DR0, self.dr0);
write_u64(&mut data, context::OFFSET_DR1, self.dr1);
write_u64(&mut data, context::OFFSET_DR2, self.dr2);
write_u64(&mut data, context::OFFSET_DR3, self.dr3);
write_u64(&mut data, context::OFFSET_DR6, self.dr6);
write_u64(&mut data, context::OFFSET_DR7, self.dr7);
write_u64(&mut data, context::OFFSET_CR3, directory_table_base);
write_u32(&mut data, context::OFFSET_MX_CSR, self.mxcsr);
for (i, &val) in self.xmm.iter().enumerate() {
let off = context::OFFSET_XMM0 + i * 16;
data[off..off + 16].copy_from_slice(&val.to_le_bytes());
}
write_u64(&mut data, context::OFFSET_DEBUG_CONTROL, self.debug_control);
write_u64(
&mut data,
context::OFFSET_LAST_BRANCH_TO_RIP,
self.last_branch_to_rip,
);
write_u64(
&mut data,
context::OFFSET_LAST_BRANCH_FROM_RIP,
self.last_branch_from_rip,
);
write_u64(
&mut data,
context::OFFSET_LAST_EXCEPTION_TO_RIP,
self.last_exception_to_rip,
);
write_u64(
&mut data,
context::OFFSET_LAST_EXCEPTION_FROM_RIP,
self.last_exception_from_rip,
);
data
}
}
pub fn clamp_processors(n: u32) -> u32 {
n.clamp(1, u32::from(cpu_state::MAX_PROCESSORS))
}
#[derive(Debug, Clone)]
pub struct DmpInfo {
pub directory_table_base: u64,
pub bug_check_code: u32,
pub bug_check_parameters: [u64; 4],
pub context: DmpContext,
pub offset_prcb_context: Option<u16>,
pub number_processors: u32,
pub is_triage: bool,
pub ps_loaded_module_list: u64,
pub debugger_data_block: Option<u64>,
pub ps_active_process_head: u64,
pub triage_drivers: Vec<TriageDriver>,
pub exception: Option<DmpException>,
pub system_info: Option<DmpSystemInfo>,
pub unloaded_drivers: Vec<UnloadedDriver>,
pub blackbox_streams: Vec<DmpBlackboxStream>,
pub triage_process_snapshot: Option<Vec<u8>>,
pub triage_thread_snapshot: Option<Vec<u8>>,
pub triage_prcb_info: Option<TriagePrcbInfo>,
pub broken_driver: Option<String>,
pub triage_overflowed: bool,
pub triage_signature_valid: bool,
pub kern_base: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct TriageCrashInfo {
pub process_name: Option<String>,
pub process_id: Option<u64>,
pub parent_process_id: Option<u64>,
pub exit_status: Option<i32>,
pub create_time: Option<u64>,
pub thread_id: Option<u64>,
pub thread_exit_status: Option<i32>,
}
pub struct DmpMem {
mmap: Mmap,
storage: DmpStorage,
info: DmpInfo,
}
enum DmpStorage {
Pages(Vec<(u64, u64)>),
Blocks(Vec<TriageBlock>),
}
impl DmpMem {
pub fn open(path: &Path) -> Result<Self> {
let file = File::open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
if is_triage_dump(&mmap) {
return Self::open_triage(mmap);
}
let parsed = parse::parse(&mmap)?;
Self::open_full(mmap, parsed)
}
fn open_full(mmap: Mmap, parsed: ParsedDump) -> Result<Self> {
let hdr = &parsed.headers;
require_supported_dump(hdr.machine_image_type)?;
let pages: Vec<(u64, u64)> = parsed.physmem.into_iter().collect();
let context = if hdr.machine_image_type == IMAGE_FILE_MACHINE_ARM64 {
DmpContext::from_arm64_bytes(&hdr.context_record_buffer)
} else {
let (ctx, _) = Context::read_from_prefix(&hdr.context_record_buffer)
.map_err(|_| Error::InvalidDump("context record is truncated".to_string()))?;
DmpContext {
rax: ctx.rax,
rbx: ctx.rbx,
rcx: ctx.rcx,
rdx: ctx.rdx,
rsi: ctx.rsi,
rdi: ctx.rdi,
rbp: ctx.rbp,
rsp: ctx.rsp,
r8: ctx.r8,
r9: ctx.r9,
r10: ctx.r10,
r11: ctx.r11,
r12: ctx.r12,
r13: ctx.r13,
r14: ctx.r14,
r15: ctx.r15,
rip: ctx.rip,
eflags: ctx.eflags,
cs: ctx.seg_cs,
ds: ctx.seg_ds,
es: ctx.seg_es,
fs: ctx.seg_fs,
gs: ctx.seg_gs,
ss: ctx.seg_ss,
dr0: ctx.dr0,
dr1: ctx.dr1,
dr2: ctx.dr2,
dr3: ctx.dr3,
dr6: ctx.dr6,
dr7: ctx.dr7,
mxcsr: ctx.mxcsr,
xmm: ctx.xmm_registers,
debug_control: ctx.debug_control,
last_branch_to_rip: ctx.last_branch_to_rip,
last_branch_from_rip: ctx.last_branch_from_rip,
last_exception_to_rip: ctx.last_exception_to_rip,
last_exception_from_rip: ctx.last_exception_from_rip,
arm64: None,
}
};
let exc = &hdr.exception;
let n_params = (exc.number_parameters as usize).min(15);
let exception = if exc.exception_code != 0 || exc.exception_address != 0 {
Some(DmpException {
code: exc.exception_code,
flags: exc.exception_flags,
address: exc.exception_address,
parameters: exc.exception_information[..n_params].to_vec(),
})
} else {
None
};
let system_info = Some(DmpSystemInfo {
major_version: hdr.major_version,
minor_version: hdr.minor_version,
system_time: hdr.system_time,
system_up_time: hdr.system_up_time,
product_type: hdr.product_type,
suite_mask: hdr.suite_mask,
machine_image_type: hdr.machine_image_type,
service_pack_build: 0,
});
let info = DmpInfo {
directory_table_base: hdr.directory_table_base,
bug_check_code: hdr.bug_check_code,
bug_check_parameters: hdr.bug_check_code_parameters,
offset_prcb_context: None,
number_processors: clamp_processors(hdr.number_processors),
is_triage: false,
ps_loaded_module_list: hdr.ps_loaded_module_list,
ps_active_process_head: hdr.ps_active_process_head,
debugger_data_block: (hdr.kd_debugger_data_block != 0)
.then_some(hdr.kd_debugger_data_block),
triage_drivers: Vec::new(),
exception,
system_info,
unloaded_drivers: Vec::new(),
blackbox_streams: Vec::new(),
triage_process_snapshot: None,
triage_thread_snapshot: None,
triage_prcb_info: None,
broken_driver: None,
triage_overflowed: false,
triage_signature_valid: true,
kern_base: None,
context,
};
Ok(Self {
mmap,
storage: DmpStorage::Pages(pages),
info,
})
}
fn open_triage(mmap: Mmap) -> Result<Self> {
let (mut info, blocks) = parse_triage(&mmap)?;
if let Some(system_info) = info.system_info.as_ref() {
require_supported_dump(system_info.machine_image_type)?;
}
info.triage_drivers = parse_drivers(&mmap);
Ok(Self {
mmap,
storage: DmpStorage::Blocks(blocks),
info,
})
}
pub fn info(&self) -> &DmpInfo {
&self.info
}
#[cfg(test)]
pub fn new_for_test(pages: Vec<(u64, u64)>, info: DmpInfo) -> Self {
use memmap2::MmapMut;
let mmap = MmapMut::map_anon(1).unwrap().make_read_only().unwrap();
Self {
mmap,
storage: DmpStorage::Pages(pages),
info,
}
}
#[cfg(test)]
pub fn new_triage_for_test(data: Vec<u8>, blocks: Vec<TriageBlock>, info: DmpInfo) -> Self {
use memmap2::MmapMut;
let mut mmap_mut = MmapMut::map_anon(data.len()).unwrap();
mmap_mut.copy_from_slice(&data);
let mmap = mmap_mut.make_read_only().unwrap();
Self {
mmap,
storage: DmpStorage::Blocks(blocks),
info,
}
}
fn lookup(&self, addr: u64) -> Option<(u64, usize)> {
match &self.storage {
DmpStorage::Pages(pages) => {
let page_gpa = addr & !(PAGE_SIZE as u64 - 1);
let page_offset = (addr as usize) & (PAGE_SIZE - 1);
let idx = pages.binary_search_by_key(&page_gpa, |&(g, _)| g).ok()?;
Some((pages[idx].1 + page_offset as u64, PAGE_SIZE - page_offset))
}
DmpStorage::Blocks(blocks) => {
let idx = blocks.partition_point(|b| b.address <= addr);
for i in (0..idx).rev() {
let block = &blocks[i];
let offset_in_block = addr - block.address;
if offset_in_block < block.size as u64 {
return Some((
block.offset + offset_in_block,
(block.size as u64 - offset_in_block) as usize,
));
}
}
None
}
}
}
}
impl DmpMem {
fn bad_address_error(&self, addr: u64) -> Error {
match &self.storage {
DmpStorage::Blocks(_) => Error::AddressNotInDump(VirtAddr(addr)),
DmpStorage::Pages(_) => Error::BadPhysicalAddress(addr),
}
}
}
impl MemoryOps<PhysAddr> for DmpMem {
fn read_bytes(&self, addr: PhysAddr, buf: &mut [u8]) -> Result<()> {
let mut offset = 0usize;
while offset < buf.len() {
let cur_addr = addr + offset as u64;
let (file_offset, available) = self
.lookup(cur_addr)
.ok_or_else(|| self.bad_address_error(cur_addr))?;
let chunk = available.min(buf.len() - offset);
let start = file_offset as usize;
let end = start + chunk;
if end > self.mmap.len() {
return Err(self.bad_address_error(cur_addr));
}
buf[offset..offset + chunk].copy_from_slice(&self.mmap[start..end]);
offset += chunk;
}
Ok(())
}
fn write_bytes(&self, _addr: PhysAddr, _buf: &[u8]) -> Result<()> {
Err(Error::ReadOnlyDump)
}
}
pub struct DmpBackend {
register_map: RegisterMap,
per_cpu_registers: Vec<Vec<u8>>,
current_processor: usize,
number_processors: u32,
prcb_context_offset: Option<u16>,
header_context: DmpContext,
directory_table_base: u64,
triage_crash_info: Option<TriageCrashInfo>,
debugger_data_hint: Option<DebuggerDataCandidate>,
notices: Vec<String>,
}
impl DmpBackend {
pub fn new(info: &DmpInfo) -> Self {
let is_arm64 = info.context.is_arm64();
let register_map = if is_arm64 {
context_arm64::build_register_map()
} else {
context::build_register_map()
};
let n = info.number_processors.max(1) as usize;
let cpu0_data = Self::build_register_buffer(&info.context, info.directory_table_base);
let mut per_cpu = Vec::with_capacity(n);
per_cpu.push(cpu0_data);
for _ in 1..n {
let mut data = if is_arm64 {
vec![0u8; context_arm64::REGISTER_BUFFER_SIZE]
} else {
vec![0u8; context::REGISTER_BUFFER_SIZE]
};
let dtb_offset = if is_arm64 {
context_arm64::OFFSET_CR3
} else {
context::OFFSET_CR3
};
data[dtb_offset..dtb_offset + 8]
.copy_from_slice(&info.directory_table_base.to_le_bytes());
per_cpu.push(data);
}
Self {
register_map,
per_cpu_registers: per_cpu,
current_processor: 0,
number_processors: info.number_processors,
prcb_context_offset: info.offset_prcb_context,
header_context: info.context.clone(),
directory_table_base: info.directory_table_base,
debugger_data_hint: info
.debugger_data_block
.map(|address| DebuggerDataCandidate {
address: VirtAddr(address),
source: MetadataSource::DumpHeader,
}),
triage_crash_info: None,
notices: (!info.triage_signature_valid)
.then(|| {
"triage dump DGRT integrity signature mismatch; the dump may be truncated \
or corrupt"
.to_string()
})
.into_iter()
.collect(),
}
}
fn build_register_buffer(ctx: &DmpContext, directory_table_base: u64) -> Vec<u8> {
ctx.to_register_buffer(directory_table_base)
}
fn read_prcb_contexts(&mut self, target: &Target, prcb_ctx_offset: u16) -> Result<()> {
let memory = target.guest()?.ntoskrnl.memory();
self.read_prcb_contexts_with(target, context::CONTEXT_SIZE, "PRCB", |prcb| {
memory.read(prcb + u64::from(prcb_ctx_offset))
})
}
fn read_prcb_contexts_with<F>(
&mut self,
target: &Target,
context_size: usize,
context_label: &str,
context_pointer: F,
) -> Result<()>
where
F: Fn(VirtAddr) -> Result<VirtAddr>,
{
let memory = target.guest()?.ntoskrnl.memory();
for i in 0..self.per_cpu_registers.len() {
let index = u16::try_from(i).map_err(|_| {
Error::DebugInfo(format!("processor index {i} exceeds the supported bound"))
})?;
let prcb = match cpu_state::kprcb_for_processor(target, index) {
Ok(prcb) => prcb,
Err(error) => {
self.notices
.push(format!("KiProcessorBlock[{i}] unavailable: {error}"));
continue;
}
};
let context_ptr = match context_pointer(prcb) {
Ok(context_ptr) if !context_ptr.is_zero() => context_ptr,
Ok(_) => {
self.notices
.push(format!("PRCB[{i}] Context pointer is null, skipping"));
continue;
}
Err(error) => {
self.notices.push(format!(
"failed to read {context_label}[{i}] context pointer: {error}"
));
continue;
}
};
let mut ctx_buf = vec![0u8; context_size];
if let Err(error) = memory.read_bytes(context_ptr, &mut ctx_buf) {
self.notices.push(format!(
"failed to read {context_label}[{i}] context: {error}"
));
continue;
}
self.per_cpu_registers[i][..context_size].copy_from_slice(&ctx_buf);
}
Ok(())
}
fn arm64_prcb_context_offset(target: &Target) -> Result<u64> {
let symbols = &target.symbols;
let prcb = symbols
.find_type_across_modules(target.kernel_dtb(), "_KPRCB")
.ok_or_else(|| Error::StructNotFound("_KPRCB".into()))?;
let processor_state = prcb
.fields
.get("ProcessorState")
.ok_or_else(|| Error::FieldNotFound("ProcessorState".into()))?;
let state_name = match &processor_state.type_data {
ParsedType::Struct(name) | ParsedType::Union(name) => name,
other => {
return Err(Error::FieldTypeMismatch(
format!("ProcessorState ({other})"),
"struct".into(),
));
}
};
let state = symbols
.find_type_across_modules(target.kernel_dtb(), state_name)
.ok_or_else(|| Error::StructNotFound(state_name.clone()))?;
let context_frame = state
.fields
.get("ContextFrame")
.ok_or_else(|| Error::FieldNotFound("ContextFrame".into()))?;
let offset = processor_state.offset as u64 + context_frame.offset as u64;
let context_end = offset
.checked_add(context_arm64::CONTEXT_SIZE as u64)
.ok_or_else(|| Error::DebugInfo("ARM64 PRCB ContextFrame offset overflow".into()))?;
if context_end > prcb.size as u64 {
return Err(Error::DebugInfo(format!(
"ARM64 PRCB ContextFrame at {offset:#x} exceeds _KPRCB size {:#x}",
prcb.size
)));
}
Ok(offset)
}
fn read_arm64_prcb_contexts(&mut self, target: &Target) -> Result<()> {
let context_offset = Self::arm64_prcb_context_offset(target)?;
self.read_prcb_contexts_with(target, context_arm64::CONTEXT_SIZE, "ARM64 PRCB", |prcb| {
Ok(prcb + context_offset)
})
}
fn select_crash_processor(&mut self) {
if self.header_context.instruction_pointer() == 0 {
return;
}
let matches_header = |regs: &Vec<u8>| {
if self.header_context.is_arm64() {
buffer_u64(regs, context_arm64::OFFSET_PC)
== self.header_context.instruction_pointer()
&& buffer_u64(regs, context_arm64::OFFSET_SP)
== self.header_context.stack_pointer()
} else {
buffer_u64(regs, context::OFFSET_RIP) == self.header_context.instruction_pointer()
&& buffer_u64(regs, context::OFFSET_RSP) == self.header_context.stack_pointer()
}
};
match self.per_cpu_registers.iter().position(matches_header) {
Some(i) => self.current_processor = i,
None => {
self.per_cpu_registers[0] =
Self::build_register_buffer(&self.header_context, self.directory_table_base);
self.current_processor = 0;
}
}
}
fn read_u64_field(snap: &[u8], layout: &TypeInfo, field: &str) -> Option<u64> {
let off = layout.field_offset(field).ok()? as usize;
if off + 8 <= snap.len() {
Some(u64::from_le_bytes(snap[off..off + 8].try_into().ok()?))
} else {
None
}
}
fn read_i32_field(snap: &[u8], layout: &TypeInfo, field: &str) -> Option<i32> {
let off = layout.field_offset(field).ok()? as usize;
if off + 4 <= snap.len() {
Some(i32::from_le_bytes(snap[off..off + 4].try_into().ok()?))
} else {
None
}
}
fn extract_triage_crash_info(target: &Target, info: &DmpInfo) -> Option<TriageCrashInfo> {
let proc_snap = info.triage_process_snapshot.as_deref()?;
let dtb = target.kernel_dtb();
let eprocess_layout = target.symbols.find_type_across_modules(dtb, "_EPROCESS")?;
let process_name = eprocess_layout
.field_offset("ImageFileName")
.ok()
.and_then(|off| {
let off = off as usize;
if off + 15 <= proc_snap.len() {
let name_buf = &proc_snap[off..off + 15];
let end = name_buf.iter().position(|&c| c == 0).unwrap_or(15);
let s = String::from_utf8_lossy(&name_buf[..end]).to_string();
if s.is_empty() { None } else { Some(s) }
} else {
None
}
});
let process_id = Self::read_u64_field(proc_snap, &eprocess_layout, "UniqueProcessId");
let parent_process_id =
Self::read_u64_field(proc_snap, &eprocess_layout, "InheritedFromUniqueProcessId");
let exit_status = Self::read_i32_field(proc_snap, &eprocess_layout, "ExitStatus");
let create_time = Self::read_u64_field(proc_snap, &eprocess_layout, "CreateTime");
let (thread_id, thread_exit_status) = info
.triage_thread_snapshot
.as_deref()
.and_then(|thread_snap| {
let ethread_layout = target.symbols.find_type_across_modules(dtb, "_ETHREAD")?;
let cid_off = ethread_layout.field_offset("Cid").ok()? as usize;
let client_id_layout =
target.symbols.find_type_across_modules(dtb, "_CLIENT_ID")?;
let ut_off = client_id_layout.field_offset("UniqueThread").ok()? as usize;
let off = cid_off + ut_off;
let tid = if off + 8 <= thread_snap.len() {
Some(u64::from_le_bytes(
thread_snap[off..off + 8].try_into().ok()?,
))
} else {
None
};
let exit_st = Self::read_i32_field(thread_snap, ðread_layout, "ExitStatus");
Some((tid, exit_st))
})
.unwrap_or((None, None));
Some(TriageCrashInfo {
process_name,
process_id,
parent_process_id,
exit_status,
create_time,
thread_id,
thread_exit_status,
})
}
pub fn triage_crash_info(&self) -> Option<&TriageCrashInfo> {
self.triage_crash_info.as_ref()
}
fn resolve_prcb_context_offset(&self, target: &Target) -> Option<u16> {
let kdbg = self.debugger_data_hint?.address;
let memory = target.guest().ok()?.ntoskrnl.memory();
let field = offset_of!(KdDebuggerData64, offset_prcb_context) as u64;
let offset: u16 = memory.read(kdbg + field).ok()?;
(offset > 0).then_some(offset)
}
fn unsupported(operation: &str) -> Error {
Error::DebugInfo(format!(
"crash dump is a static snapshot; {operation} is not available"
))
}
}
impl DebugBackend for DmpBackend {
fn name(&self) -> &'static str {
"dmp"
}
fn take_notices(&mut self) -> Vec<String> {
std::mem::take(&mut self.notices)
}
fn initialize_from_target(&mut self, target: &Target) {
if self.header_context.is_arm64() {
if let Err(e) = self.read_arm64_prcb_contexts(target) {
self.notices.push(format!(
"could not read ARM64 PRCB ContextFrame contexts from dump: {e}"
));
}
self.select_crash_processor();
} else {
let offset = self
.prcb_context_offset
.or_else(|| self.resolve_prcb_context_offset(target));
if let Some(offset) = offset {
self.prcb_context_offset = Some(offset);
if let Err(e) = self.read_prcb_contexts(target, offset) {
self.notices
.push(format!("could not read PRCB contexts from dump: {e}"));
}
self.select_crash_processor();
}
}
if let Some(info) = target.phys.dmp_info() {
self.triage_crash_info = Self::extract_triage_crash_info(target, info);
}
}
fn triage_crash_info(&self) -> Option<&TriageCrashInfo> {
self.triage_crash_info.as_ref()
}
fn target_debugger_data_hint(&mut self) -> Result<Option<DebuggerDataCandidate>> {
Ok(self.debugger_data_hint)
}
fn register_map(&self) -> &RegisterMap {
&self.register_map
}
fn capabilities(&self) -> Vec<BackendCapability> {
vec![
BackendCapability::supported(DebugCapability::MemoryIntrospection),
BackendCapability::supported(DebugCapability::ReadRegisters),
BackendCapability::unsupported(DebugCapability::ExecutionControl),
BackendCapability::unsupported(DebugCapability::InterruptTarget),
BackendCapability::unsupported(DebugCapability::SingleStep),
BackendCapability::unsupported(DebugCapability::WriteRegisters),
BackendCapability::supported(DebugCapability::ThreadList),
BackendCapability::supported(DebugCapability::ThreadSelection),
BackendCapability::unsupported(DebugCapability::KernelBreakpoints),
BackendCapability::unsupported(DebugCapability::UserModeBreakpoints),
BackendCapability::unsupported(DebugCapability::TargetReloadDetection),
BackendCapability::unsupported(DebugCapability::KernelBaseHint),
BackendCapability::supported(DebugCapability::BugcheckDetection),
BackendCapability::supported(DebugCapability::BugcheckDetails),
BackendCapability::unsupported(DebugCapability::DebugOutput),
BackendCapability::unsupported(DebugCapability::Msr),
BackendCapability::unsupported(DebugCapability::TargetControl),
BackendCapability::unsupported(DebugCapability::TargetFileIo),
]
}
fn read_registers(&mut self) -> Result<Vec<u8>> {
Ok(self.per_cpu_registers[self.current_processor].clone())
}
fn write_registers(&mut self, _data: &[u8]) -> Result<()> {
Err(Self::unsupported("register writes"))
}
fn set_breakpoint(&mut self, _addr: u64) -> Result<()> {
Err(Self::unsupported("breakpoints"))
}
fn remove_breakpoint(&mut self, _addr: u64) -> Result<()> {
Err(Self::unsupported("breakpoints"))
}
fn continue_execution(&mut self) -> Result<()> {
Err(Self::unsupported("continue"))
}
fn step(&mut self) -> Result<()> {
Err(Self::unsupported("single-step"))
}
fn interrupt(&mut self) -> Result<StopEvent> {
Err(Self::unsupported("target interrupt"))
}
fn wait_for_stop(&mut self) -> Result<StopEvent> {
Err(Self::unsupported("waiting for target stops"))
}
fn try_wait_for_stop(&mut self, _timeout: Duration) -> Result<Option<StopEvent>> {
Ok(None)
}
fn thread_list(&mut self) -> Result<Vec<String>> {
Ok((0..self.number_processors as u16)
.map(|i| format!("p1.{:x}", i + 1))
.collect())
}
fn set_current_thread(&mut self, thread_id: &str) -> Result<()> {
let processor = processor_index_from_backend_thread_id(thread_id)
.ok_or_else(|| Error::DebugInfo(format!("invalid thread id: {thread_id}")))?;
if (processor as u32) >= self.number_processors {
return Err(Error::DebugInfo(format!(
"processor {} out of range (dump has {} processor(s))",
processor, self.number_processors
)));
}
self.current_processor = processor as usize;
Ok(())
}
fn stopped_thread_id(&mut self) -> Result<String> {
Ok(format!("p1.{:x}", self.current_processor as u16 + 1))
}
fn target_kernel_base_hint(&mut self) -> Result<Option<VirtAddr>> {
Ok(None)
}
fn is_running(&self) -> bool {
false
}
fn prepare_for_exit(&mut self, _leave_running: bool) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dump_open_rejects_unsupported_machine_types() {
assert!(require_supported_dump(IMAGE_FILE_MACHINE_AMD64).is_ok());
assert!(require_supported_dump(IMAGE_FILE_MACHINE_ARM64).is_ok());
let error = require_supported_dump(0x014c).unwrap_err();
assert!(error.to_string().contains("I386 crash dump"));
}
#[test]
fn arm64_context_header_round_trips_registers_and_debug_state() {
let mut raw = vec![0u8; context_arm64::CONTEXT_SIZE];
raw[context_arm64::OFFSET_CPSR..context_arm64::OFFSET_CPSR + 4]
.copy_from_slice(&0x6000_03c5u32.to_le_bytes());
raw[context_arm64::OFFSET_X0..context_arm64::OFFSET_X0 + 8]
.copy_from_slice(&0x1122_3344_5566_7788u64.to_le_bytes());
raw[context_arm64::OFFSET_SP..context_arm64::OFFSET_SP + 8]
.copy_from_slice(&0xffff_0000_1234_5000u64.to_le_bytes());
raw[context_arm64::OFFSET_PC..context_arm64::OFFSET_PC + 8]
.copy_from_slice(&0xffff_0000_0000_1000u64.to_le_bytes());
raw[context_arm64::OFFSET_BCR0..context_arm64::OFFSET_BCR0 + 4]
.copy_from_slice(&0x0000_e9e1u32.to_le_bytes());
raw[context_arm64::OFFSET_WVR0..context_arm64::OFFSET_WVR0 + 8]
.copy_from_slice(&0x4000u64.to_le_bytes());
let context = DmpContext::from_arm64_bytes(&raw);
let arm = context.arm64.as_ref().unwrap();
assert_eq!(arm.x[0], 0x1122_3344_5566_7788);
assert_eq!(arm.sp, 0xffff_0000_1234_5000);
assert_eq!(arm.pc, 0xffff_0000_0000_1000);
assert_eq!(arm.bcr[0], 0x0000_e9e1);
assert_eq!(arm.wvr[0], 0x4000);
let map = context_arm64::build_register_map();
let data = context.to_register_buffer(0x1234_5000);
assert_eq!(map.read_u64("x0", &data).unwrap(), arm.x[0]);
assert_eq!(map.read_u64("pc", &data).unwrap(), arm.pc);
assert_eq!(map.read_u64("sp", &data).unwrap(), arm.sp);
assert_eq!(map.read_u64("bcr0", &data).unwrap(), arm.bcr[0] as u64);
assert_eq!(map.read_u64("wvr0", &data).unwrap(), arm.wvr[0]);
assert_eq!(map.read_u64("cr3", &data).unwrap(), 0x1234_5000);
}
fn make_test_info() -> DmpInfo {
DmpInfo {
directory_table_base: 0x1ad000,
bug_check_code: 0x50,
bug_check_parameters: [0xdead, 0, 0, 0],
offset_prcb_context: None,
number_processors: 1,
is_triage: false,
ps_loaded_module_list: 0,
ps_active_process_head: 0,
debugger_data_block: None,
triage_drivers: Vec::new(),
exception: None,
system_info: None,
unloaded_drivers: Vec::new(),
blackbox_streams: Vec::new(),
triage_process_snapshot: None,
triage_thread_snapshot: None,
triage_prcb_info: None,
broken_driver: None,
triage_overflowed: false,
triage_signature_valid: true,
kern_base: None,
context: DmpContext {
rax: 0x1111111111111111,
rbx: 0x2222222222222222,
rcx: 0x3333333333333333,
rdx: 0x4444444444444444,
rsi: 0x5555555555555555,
rdi: 0x6666666666666666,
rbp: 0x7777777777777777,
rsp: 0x8888888888888888,
r8: 0x0808080808080808,
r9: 0x0909090909090909,
r10: 0x1010101010101010,
r11: 0x1111111111111111,
r12: 0x1212121212121212,
r13: 0x1313131313131313,
r14: 0x1414141414141414,
r15: 0x1515151515151515,
rip: 0xfffff80012345678,
eflags: 0x246,
cs: 0x10,
ds: 0x2b,
es: 0x2b,
fs: 0x53,
gs: 0x2b,
ss: 0x18,
dr0: 0,
dr1: 0,
dr2: 0,
dr3: 0,
dr6: 0,
dr7: 0,
mxcsr: 0,
xmm: [0; 16],
debug_control: 0,
last_branch_to_rip: 0,
last_branch_from_rip: 0,
last_exception_to_rip: 0,
last_exception_from_rip: 0,
arm64: None,
},
}
}
#[test]
fn dmp_backend_register_round_trip() {
let info = make_test_info();
let mut backend = DmpBackend::new(&info);
let map = backend.register_map().clone();
let data = backend.read_registers().unwrap();
assert_eq!(map.read_u64("rax", &data).unwrap(), 0x1111111111111111);
assert_eq!(map.read_u64("rbx", &data).unwrap(), 0x2222222222222222);
assert_eq!(map.read_u64("rcx", &data).unwrap(), 0x3333333333333333);
assert_eq!(map.read_u64("rdx", &data).unwrap(), 0x4444444444444444);
assert_eq!(map.read_u64("rsi", &data).unwrap(), 0x5555555555555555);
assert_eq!(map.read_u64("rdi", &data).unwrap(), 0x6666666666666666);
assert_eq!(map.read_u64("rbp", &data).unwrap(), 0x7777777777777777);
assert_eq!(map.read_u64("rsp", &data).unwrap(), 0x8888888888888888);
assert_eq!(map.read_u64("r8", &data).unwrap(), 0x0808080808080808);
assert_eq!(map.read_u64("r9", &data).unwrap(), 0x0909090909090909);
assert_eq!(map.read_u64("r10", &data).unwrap(), 0x1010101010101010);
assert_eq!(map.read_u64("r11", &data).unwrap(), 0x1111111111111111);
assert_eq!(map.read_u64("r12", &data).unwrap(), 0x1212121212121212);
assert_eq!(map.read_u64("r13", &data).unwrap(), 0x1313131313131313);
assert_eq!(map.read_u64("r14", &data).unwrap(), 0x1414141414141414);
assert_eq!(map.read_u64("r15", &data).unwrap(), 0x1515151515151515);
assert_eq!(map.read_u64("rip", &data).unwrap(), 0xfffff80012345678);
assert_eq!(map.read_u64("eflags", &data).unwrap(), 0x246);
assert_eq!(map.read_u64("cs", &data).unwrap(), 0x10);
assert_eq!(map.read_u64("ss", &data).unwrap(), 0x18);
assert_eq!(
map.read_u64("cr3", &data).unwrap(),
info.directory_table_base
);
}
#[test]
fn dmp_backend_thread_list_and_switching() {
let mut info = make_test_info();
info.number_processors = 4;
let mut backend = DmpBackend::new(&info);
let threads = backend.thread_list().unwrap();
assert_eq!(threads, vec!["p1.1", "p1.2", "p1.3", "p1.4"]);
assert_eq!(backend.stopped_thread_id().unwrap(), "p1.1");
backend.set_current_thread("p1.3").unwrap();
assert_eq!(backend.stopped_thread_id().unwrap(), "p1.3");
assert!(backend.set_current_thread("p1.5").is_err());
assert!(backend.set_current_thread("garbage").is_err());
}
#[test]
fn dmp_mem_lookup() {
let pages = vec![
(0x0000u64, 0x2000u64),
(0x1000, 0x3000),
(0x2000, 0x4000),
(0x5000, 0x5000),
(0x10000, 0x6000),
];
let mem = DmpMem::new_for_test(pages, make_test_info());
assert_eq!(mem.lookup(0x0000), Some((0x2000, 0x1000)));
assert_eq!(mem.lookup(0x0100), Some((0x2100, 0x0F00)));
assert_eq!(mem.lookup(0x0FFF), Some((0x2FFF, 0x0001)));
assert_eq!(mem.lookup(0x1000), Some((0x3000, 0x1000)));
assert_eq!(mem.lookup(0x1500), Some((0x3500, 0x0B00)));
assert_eq!(mem.lookup(0x5000), Some((0x5000, 0x1000)));
assert_eq!(mem.lookup(0x3000), None);
assert_eq!(mem.lookup(0x4000), None);
assert_eq!(mem.lookup(0x8000), None);
}
#[test]
fn crash_processor_selected_from_matching_prcb_context() {
let mut info = make_test_info();
info.number_processors = 4;
let mut backend = DmpBackend::new(&info);
for (i, regs) in backend.per_cpu_registers.iter_mut().enumerate() {
let (rip, rsp) = if i == 2 {
(info.context.rip, info.context.rsp)
} else {
(0xfffff800aaaa0000 + i as u64, 0xfffff800bbbb0000 + i as u64)
};
regs[context::OFFSET_RIP..context::OFFSET_RIP + 8].copy_from_slice(&rip.to_le_bytes());
regs[context::OFFSET_RSP..context::OFFSET_RSP + 8].copy_from_slice(&rsp.to_le_bytes());
}
backend.select_crash_processor();
assert_eq!(backend.current_processor, 2);
assert_eq!(backend.stopped_thread_id().unwrap(), "p1.3");
}
#[test]
fn crash_processor_falls_back_to_header_context() {
let mut info = make_test_info();
info.number_processors = 2;
let mut backend = DmpBackend::new(&info);
for regs in backend.per_cpu_registers.iter_mut() {
regs[context::OFFSET_RIP..context::OFFSET_RIP + 8]
.copy_from_slice(&0xfffff800cccc0000u64.to_le_bytes());
}
backend.select_crash_processor();
assert_eq!(backend.current_processor, 0);
let data = backend.read_registers().unwrap();
let map = backend.register_map().clone();
assert_eq!(map.read_u64("rip", &data).unwrap(), info.context.rip);
assert_eq!(map.read_u64("rax", &data).unwrap(), info.context.rax);
assert_eq!(
map.read_u64("cr3", &data).unwrap(),
info.directory_table_base
);
}
#[test]
fn triage_mem_read_within_block() {
let mut data = vec![0u8; 0x4000];
for i in 0..0x100usize {
data[0x3000 + i] = i as u8;
}
let blocks = vec![TriageBlock {
address: 0xfffff80000001000,
offset: 0x3000,
size: 0x100,
}];
let mut info = make_test_info();
info.is_triage = true;
let mem = DmpMem::new_triage_for_test(data, blocks, info);
let mut buf = [0u8; 4];
mem.read_bytes(0xfffff80000001010u64, &mut buf).unwrap();
assert_eq!(buf, [0x10, 0x11, 0x12, 0x13]);
let mut buf = [0u8; 1];
assert!(mem.read_bytes(0xfffff80000001100u64, &mut buf).is_err());
}
#[test]
fn triage_overlapping_blocks_fallback() {
let mut data = vec![0u8; 0x8000];
for i in 0..0x3000usize {
data[0x2000 + i] = 0xAA;
}
for i in 0..0x1000usize {
data[0x5000 + i] = 0xBB;
}
let blocks = vec![
TriageBlock {
address: 0x1000,
offset: 0x2000,
size: 0x3000,
},
TriageBlock {
address: 0x2000,
offset: 0x5000,
size: 0x1000,
},
];
let mut info = make_test_info();
info.is_triage = true;
let mem = DmpMem::new_triage_for_test(data, blocks, info);
let mut buf = [0u8; 1];
mem.read_bytes(0x1500u64, &mut buf).unwrap();
assert_eq!(buf[0], 0xAA);
mem.read_bytes(0x2500u64, &mut buf).unwrap();
assert_eq!(buf[0], 0xBB);
mem.read_bytes(0x3100u64, &mut buf).unwrap();
assert_eq!(buf[0], 0xAA);
assert!(mem.read_bytes(0x4100u64, &mut buf).is_err());
}
}