use crate::{data::DebugExecutionFlags, util::parse_masm_value, *};
use bitflags::bitflags;
bitflags! {
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
pub struct MemoryType:u32 {
const Private = 0x20000;
const Mapped = 0x40000;
const Image = 0x1000000;
}
}
impl MemoryType {
fn from_varname(s: impl AsRef<str>) -> Self {
let s = s.as_ref();
let mut ret = Self::empty();
for x in s.split_whitespace() {
match x {
"MEM_PRIVATE" => ret |= Self::Private,
"MEM_MAPPED" => ret |= Self::Mapped,
"MEM_IMAGE" => ret |= Self::Image,
_ => {}
}
}
ret
}
}
bitflags! {
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
pub struct MemoryState: u32 {
const Commit = 0x1000;
const Reserve = 0x2000;
const Free = 0x10000;
}
}
impl MemoryState {
fn from_varname(s: impl AsRef<str>) -> Self {
let s = s.as_ref();
let mut ret = Self::empty();
for x in s.split_whitespace() {
match x {
"MEM_COMMIT" => ret |= Self::Commit,
"MEM_RESERVE" => ret |= Self::Reserve,
"MEM_FREE" => ret |= Self::Free,
_ => {}
}
}
ret
}
}
bitflags! {
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
pub struct MemoryProtection: u32 {
const NoAccess = 0x01;
const ReadOnly = 0x02;
const ReadWrite = 0x04;
const Guard = 0x100;
const NoCache = 0x200;
const WriteCombine = 0x400;
const Execute = 0x10;
const ExecuteRead = 0x20;
const ExecuteReadWrite = 0x40;
const ExecuteWriteCopy = 0x80;
const WriteCopy = 0x08;
const TargetsInvalid = 0x40000000;
}
}
impl MemoryProtection {
fn from_varname(s: impl AsRef<str>) -> Self {
let s = s.as_ref();
let mut ret = Self::empty();
for x in s.split_whitespace() {
match x {
"PAGE_NOACCESS" => ret |= Self::NoAccess,
"PAGE_READONLY" => ret |= Self::ReadOnly,
"PAGE_READWRITE" => ret |= Self::ReadWrite,
"PAGE_GUARD" => ret |= Self::Guard,
"PAGE_NOCACHE" => ret |= Self::NoCache,
"PAGE_WRITECOMBINE" => ret |= Self::WriteCombine,
"PAGE_EXECUTE" => ret |= Self::Execute,
"PAGE_EXECUTE_READ" => ret |= Self::ExecuteRead,
"PAGE_EXECUTE_READWRITE" => ret |= Self::ExecuteReadWrite,
"PAGE_EXECUTE_WRITECOPY" => ret |= Self::ExecuteWriteCopy,
"PAGE_WRITECOPY" => ret |= Self::WriteCopy,
"PAGE_TARGETS_INVALID" | "PAGE_TARGETS_NO_UPDATE" => {
ret |= Self::TargetsInvalid
}
_ => {}
}
}
ret
}
pub fn is_readable(&self) -> bool {
self.contains(Self::ReadOnly)
| self.contains(Self::ReadWrite)
| self.contains(Self::ExecuteRead)
| self.contains(Self::ExecuteReadWrite)
| self.contains(Self::ExecuteWriteCopy)
| self.contains(Self::WriteCopy)
}
pub fn is_writable(&self) -> bool {
self.contains(Self::ReadWrite)
| self.contains(Self::WriteCombine)
| self.contains(Self::ExecuteReadWrite)
| self.contains(Self::ExecuteWriteCopy)
| self.contains(Self::WriteCopy)
}
pub fn is_executable(&self) -> bool {
self.contains(Self::Execute)
| self.contains(Self::ExecuteRead)
| self.contains(Self::ExecuteReadWrite)
| self.contains(Self::ExecuteWriteCopy)
}
pub fn to_rwx_str(&self) -> &'static str {
match (self.is_readable(), self.is_writable(), self.is_executable()) {
(true, true, true) => "RWX",
(true, true, false) => "RW-",
(true, false, true) => "R-X",
(true, false, false) => "R--",
(false, true, true) => "-WX",
(false, true, false) => "-W-",
(false, false, true) => "--X",
(false, false, false) => "---",
}
}
}
#[derive(Default, Debug, Clone)]
pub enum RegionSummary {
Free,
Heap(HeapRegion),
Heap32(HeapRegion),
Heap64(HeapRegion),
Stack(StackRegion),
Stack32(StackRegion),
Stack64(StackRegion),
Peb(PebRegion),
Peb32(PebRegion),
Peb64(PebRegion),
Teb(TebRegion),
Teb32(TebRegion),
Teb64(TebRegion),
MappedFile(MappedFileRegion),
Image(ImageRegion),
#[default]
Unknown,
Other(OtherRegion),
Other32(OtherRegion),
Other64(OtherRegion),
Unknown2(String, Option<String>),
}
impl RegionSummary {
pub fn usage(&self) -> &str {
match self {
RegionSummary::Free => "Free",
RegionSummary::Heap(_) => "Heap",
RegionSummary::Heap32(_) => "Heap32",
RegionSummary::Heap64(_) => "Heap64",
RegionSummary::Stack(_) => "Stack",
RegionSummary::Stack32(_) => "Stack32",
RegionSummary::Stack64(_) => "Stack64",
RegionSummary::Peb(_) => "PEB",
RegionSummary::Peb32(_) => "PEB32",
RegionSummary::Peb64(_) => "PEB64",
RegionSummary::Teb(_) => "TEB",
RegionSummary::Teb32(_) => "TEB32",
RegionSummary::Teb64(_) => "TEB64",
RegionSummary::MappedFile(_) => "MappedFile",
RegionSummary::Image(_) => "Image",
RegionSummary::Unknown => "<unknown>",
RegionSummary::Other(_) => "Other",
RegionSummary::Other32(_) => "Other32",
RegionSummary::Other64(_) => "Other64",
RegionSummary::Unknown2(s, _) => s,
}
}
fn parse(usage: &str, summary: &str) -> Option<Self> {
match usage {
"Free" => Some(Self::Free),
"Heap" => Some(Self::Heap(HeapRegion::from_summary_str(summary)?)),
"Heap32" => {
Some(Self::Heap32(HeapRegion::from_summary_str(summary)?))
}
"Heap64" => {
Some(Self::Heap64(HeapRegion::from_summary_str(summary)?))
}
"Stack" => {
Some(Self::Stack(StackRegion::from_summary_str(summary)?))
}
"Stack32" => {
Some(Self::Stack32(StackRegion::from_summary_str(summary)?))
}
"Stack64" => {
Some(Self::Stack64(StackRegion::from_summary_str(summary)?))
}
"PEB" => Some(Self::Peb(PebRegion::from_summary_str(summary)?)),
"PEB32" => Some(Self::Peb32(PebRegion::from_summary_str(summary)?)),
"PEB64" => Some(Self::Peb64(PebRegion::from_summary_str(summary)?)),
"TEB" => Some(Self::Teb(TebRegion::from_summary_str(summary)?)),
"TEB32" => Some(Self::Teb32(TebRegion::from_summary_str(summary)?)),
"TEB64" => Some(Self::Teb64(TebRegion::from_summary_str(summary)?)),
"MappedFile" => Some(Self::MappedFile(
MappedFileRegion::from_summary_str(summary)?,
)),
"Image" => {
Some(Self::Image(ImageRegion::from_summary_str(summary)?))
}
"<unknown>" => Some(Self::Unknown),
"Other" => {
Some(Self::Other(OtherRegion::from_summary_str(summary)?))
}
"Other32" => {
Some(Self::Other32(OtherRegion::from_summary_str(summary)?))
}
"Other64" => {
Some(Self::Other64(OtherRegion::from_summary_str(summary)?))
}
x => Some(Self::Unknown2(x.to_string(), Some(summary.to_string()))),
}
}
fn parse2(usage: &str) -> Option<Self> {
match usage {
"Free" => Some(Self::Free),
"<unknown>" => Some(Self::Unknown),
x => Some(Self::Unknown2(x.to_string(), None)),
}
}
}
#[derive(Debug, Clone)]
pub struct HeapRegion {
pub id: u32,
pub handle: DebuggeeOffset,
pub r#type: String,
}
impl HeapRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let summary = summary.trim().strip_prefix('[')?.strip_suffix(']')?;
let mut sp = summary.split(';');
let mut sp2 = sp.next()?.split(':').map(|x| x.trim());
let x = sp2.next()?;
if x != "ID" {
return None;
}
let x = sp2.next()?;
let id = u32::from_str(x).ok()?;
let mut sp2 = sp.next()?.split(':').map(|x| x.trim());
let x = sp2.next()?;
if x != "Handle" {
return None;
}
let x = sp2.next()?;
let handle = u64::from_str_radix(x, 16).ok()?;
let mut sp2 = sp.next()?.split(':').map(|x| x.trim());
let x = sp2.next()?;
if x != "Type" {
return None;
}
let r#type = sp2.next()?;
Some(Self {
id,
handle,
r#type: r#type.to_string(),
})
}
}
#[derive(Debug, Clone)]
pub struct ImageRegion {
pub image_path: String,
pub module_name: String,
}
impl ImageRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let summary = summary.trim().strip_prefix('[')?.strip_suffix(']')?;
let mut us = summary.split(';');
let module_name = us.next()?.trim();
let s2 = us.next()?.trim();
let image_path = &s2[1..s2.len() - 1];
Some(Self {
image_path: image_path.to_string(),
module_name: module_name.to_string(),
})
}
}
#[derive(Debug, Clone)]
pub struct StackRegion {
pub engine_thread_id: EngineThreadId,
pub thread_id: ThreadId,
pub process_id: ProcessId,
}
impl StackRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let summary = summary.trim().strip_prefix('[')?.strip_suffix(']')?;
let mut sp = summary.split(';');
let l1 = sp.next()?;
let l1 = l1.strip_prefix('~')?;
let etid = u32::from_str(l1).ok()?;
let l2 = sp.next()?;
let mut sp = l2.split('.');
let l3 = sp.next()?;
let pid = u32::from_str_radix(l3.trim(), 16).ok()?;
let l4 = sp.next()?;
let tid = u32::from_str_radix(l4.trim(), 16).ok()?;
Some(Self {
engine_thread_id: etid.into(),
thread_id: tid.into(),
process_id: pid.into(),
})
}
}
#[derive(Debug, Clone)]
pub struct PebRegion {
pub pid: ProcessId,
}
impl PebRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let summary = summary.trim().strip_prefix('[')?.strip_suffix(']')?;
let pid = u32::from_str_radix(summary, 16).ok()?;
Some(Self {
pid: ProcessId(pid),
})
}
}
#[derive(Debug, Clone)]
pub struct TebRegion {
pub engine_thread_id: EngineThreadId,
pub thread_id: ThreadId,
pub process_id: ProcessId,
}
impl TebRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let r = StackRegion::from_summary_str(summary)?;
Some(Self {
engine_thread_id: r.engine_thread_id,
thread_id: r.thread_id,
process_id: r.process_id,
})
}
}
#[derive(Debug, Clone)]
pub struct MappedFileRegion {
pub mapped_file: String,
}
impl MappedFileRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let summary = summary.trim().strip_prefix('"')?.strip_suffix('"')?;
Some(Self {
mapped_file: summary.to_string(),
})
}
}
#[derive(Debug, Clone)]
pub struct OtherRegion {
pub info: String,
}
impl OtherRegion {
fn from_summary_str(summary: &str) -> Option<Self> {
let summary = summary.trim().strip_prefix('[')?.strip_suffix(']')?;
Some(Self {
info: summary.to_string(),
})
}
}
#[derive(Default, Debug, Clone)]
pub struct MemoryRegion {
pub base_address: DebuggeeOffset,
pub end_address: DebuggeeOffset,
pub region_size: DebuggeeOffset,
pub state: MemoryState,
pub protect: Option<MemoryProtection>,
pub r#type: Option<MemoryType>,
pub allocation_base: Option<DebuggeeOffset>,
pub allocation_protect: Option<u32>,
pub summary: RegionSummary,
}
#[derive(Debug, Clone)]
pub struct AddressMap {
regions: Vec<MemoryRegion>,
}
impl AddressMap {
fn from_command_output(s: impl AsRef<str>) -> WdResult<Self> {
let s = s.as_ref();
let mut regions = Vec::with_capacity(512);
let mut before_base = 0;
for l in s.lines().map(|l| l.trim()).filter(|l| !l.is_empty()) {
let mut region = MemoryRegion::default();
let mut st = l.split('\t');
let base_address = st.next().expect("Failed to get Base Address");
region.base_address = match parse_masm_value(base_address) {
Ok(x) => x,
Err(_) => {
continue;
}
};
assert!(before_base <= region.base_address);
before_base = region.base_address;
let end_address = st.next().expect("Failed to get End Address");
region.end_address = parse_masm_value(end_address)?;
let region_size = st.next().expect("Failed to get Region Size");
region.region_size = u64::from_str(region_size)?;
let ty = st.next().expect("Failed to get Type");
region.r#type =
(!ty.is_empty()).then_some(MemoryType::from_varname(ty));
let state = st.next().expect("Failed to get State");
region.state = MemoryState::from_varname(state);
let protect = st.next().expect("Failed to get Protect");
if !protect.is_empty() {
region.protect = Some(MemoryProtection::from_varname(protect));
}
let usage = st.next().expect("Failed to get Usage");
match st.next() {
None => {
region.summary = RegionSummary::parse2(usage)
.expect("Failed to parse summary");
}
Some(summary) => {
region.summary = RegionSummary::parse(usage, summary)
.expect("Failed to parse summary");
}
}
regions.push(region);
}
Ok(Self { regions })
}
pub fn get(ctrl: &WdControl) -> WdResult<Self> {
let out = ctrl.execute_and_capture_text(
DebugOutctlFlags::ThisClient,
"!address -o:tsv",
DebugExecutionFlags::NotLogged | DebugExecutionFlags::NoRepeat,
)?;
Self::from_command_output(out)
}
pub fn regions(&self) -> &[MemoryRegion] { &self.regions }
pub fn find_region(
&self,
address: DebuggeeOffset,
) -> Option<&MemoryRegion> {
let i = self.regions.partition_point(|r| r.base_address <= address);
if i == 0 {
return None;
}
let region = &self.regions[i - 1];
(address < region.end_address).then_some(region)
}
}
#[test]
fn test_address_map() {
let s = r#"
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
0x0000000000000000 0x000000007ffe0000 2147352576 MEM_FREE PAGE_NOACCESS Free
0x000000007ffe0000 0x000000007ffe1000 4096 MEM_PRIVATE MEM_COMMIT PAGE_READONLY Other [User Shared Data]
0x0000006f17334000 0x0000006f17335000 4096 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE PEB [17e0]
0x0000006f17335000 0x0000006f17337000 8192 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE TEB [~0; 17e0.77c0]
0x0000006f17337000 0x0000006f17339000 8192 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE TEB [~1; 17e0.3380]
0x0000006f17339000 0x0000006f1733d000 16384 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE TEB [~2; 17e0.5924]
0x0000006f1733d000 0x0000006f17400000 798720 MEM_PRIVATE MEM_RESERVE <unknown>
0x0000006f1747d000 0x0000006f1747e000 4096 MEM_PRIVATE MEM_COMMIT PAGE_READONLY | PAGE_GUARD <unknown>
0x0000006f1747e000 0x0000006f1747f000 4096 MEM_PRIVATE MEM_COMMIT PAGE_READONLY <unknown> [................]
0x000000720d660000 0x000000720d6cc000 442368 MEM_PRIVATE MEM_RESERVE Stack [~0; 17e0.77c0]
0x00000173ad390000 0x00000173ad3b8000 163840 MEM_MAPPED MEM_COMMIT PAGE_READONLY MappedFile "\Device\HarddiskVolume3\Windows\System32\C_932.NLS"
0x00000173ad3c0000 0x00000173ad3c3000 12288 MEM_MAPPED MEM_COMMIT PAGE_READONLY MappedFile "\Device\HarddiskVolume3\Windows\System32\l_intl.nls"
0x00000173ad410000 0x00000173ad420000 65536 MEM_MAPPED MEM_COMMIT PAGE_READWRITE MappedFile "PageFile"
0x00000173ad510000 0x00000173ad51c000 49152 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Heap [ID: 0; Handle: 00000173ad510000; Type: Segment]
0x00007ff9d2c60000 0x00007ff9d2c61000 4096 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [ntdll; "ntdll.dll"]
0x00007ff9d2c61000 0x00007ff9d2dd3000 1515520 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READ Image [ntdll; "ntdll.dll"]
0x00007ff9d2dd3000 0x00007ff9d2e2c000 364544 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [ntdll; "ntdll.dll"]
0x00007ff9d2e2c000 0x00007ff9d2e35000 36864 MEM_IMAGE MEM_COMMIT PAGE_READWRITE Image [ntdll; "ntdll.dll"]
0x00007ff9d2e35000 0x00007ff9d2ec7000 598016 MEM_IMAGE MEM_COMMIT PAGE_READONLY Image [ntdll; "ntdll.dll"]
"#;
let map = AddressMap::from_command_output(s).unwrap();
let regions = map.regions();
assert_eq!(regions.len(), 19);
{
let r = ®ions[0];
assert_eq!(r.base_address, 0);
assert_eq!(r.end_address, 0x7ffe0000);
assert_eq!(r.region_size, 2147352576);
assert_eq!(r.r#type, None);
assert_eq!(r.state, MemoryState::Free);
assert_eq!(r.protect, Some(MemoryProtection::NoAccess));
let RegionSummary::Free = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
}
{
let r = ®ions[1];
assert_eq!(r.base_address, 0x7ffe0000);
assert_eq!(r.end_address, 0x7ffe1000);
assert_eq!(r.region_size, 4096);
assert_eq!(r.r#type, Some(MemoryType::Private));
assert_eq!(r.state, MemoryState::Commit);
assert_eq!(r.protect, Some(MemoryProtection::ReadOnly));
let RegionSummary::Other(s) = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
assert_eq!(s.info, "User Shared Data");
}
{
let r = ®ions[2];
assert_eq!(r.base_address, 0x6f17334000);
assert_eq!(r.end_address, 0x6f17335000);
assert_eq!(r.region_size, 4096);
assert_eq!(r.r#type, Some(MemoryType::Private));
assert_eq!(r.state, MemoryState::Commit);
assert_eq!(r.protect, Some(MemoryProtection::ReadWrite));
let RegionSummary::Peb(s) = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
assert_eq!(s.pid, 0x17e0);
}
{
let r = ®ions[3];
assert_eq!(r.base_address, 0x6f17335000);
assert_eq!(r.end_address, 0x6f17337000);
assert_eq!(r.region_size, 8192);
assert_eq!(r.r#type, Some(MemoryType::Private));
assert_eq!(r.state, MemoryState::Commit);
assert_eq!(r.protect, Some(MemoryProtection::ReadWrite));
let RegionSummary::Teb(s) = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
assert_eq!(s.engine_thread_id, 0);
assert_eq!(s.process_id, 0x17e0);
assert_eq!(s.thread_id, 0x77c0);
}
{
let r = ®ions[6];
assert_eq!(r.base_address, 0x6f1733d000);
assert_eq!(r.end_address, 0x6f17400000);
assert_eq!(r.region_size, 798720);
assert_eq!(r.r#type, Some(MemoryType::Private));
assert_eq!(r.state, MemoryState::Reserve);
assert_eq!(r.protect, None);
let RegionSummary::Unknown = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
}
{
let r = ®ions[10];
assert_eq!(r.base_address, 0x173ad390000);
assert_eq!(r.end_address, 0x173ad3b8000);
assert_eq!(r.region_size, 163840);
assert_eq!(r.r#type, Some(MemoryType::Mapped));
assert_eq!(r.state, MemoryState::Commit);
assert_eq!(r.protect, Some(MemoryProtection::ReadOnly));
let RegionSummary::MappedFile(s) = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
assert_eq!(
s.mapped_file,
r#"\Device\HarddiskVolume3\Windows\System32\C_932.NLS"#
);
}
{
let r = ®ions[13];
assert_eq!(r.base_address, 0x173ad510000);
assert_eq!(r.end_address, 0x173ad51c000);
assert_eq!(r.region_size, 49152);
assert_eq!(r.r#type, Some(MemoryType::Private));
assert_eq!(r.state, MemoryState::Commit);
assert_eq!(r.protect, Some(MemoryProtection::ReadWrite));
let RegionSummary::Heap(s) = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
assert_eq!(s.id, 0);
assert_eq!(s.handle, 0x173ad510000);
assert_eq!(s.r#type, "Segment");
}
{
let r = ®ions[14];
assert_eq!(r.base_address, 0x7ff9d2c60000);
assert_eq!(r.end_address, 0x7ff9d2c61000);
assert_eq!(r.region_size, 4096);
assert_eq!(r.r#type, Some(MemoryType::Image));
assert_eq!(r.state, MemoryState::Commit);
assert_eq!(r.protect, Some(MemoryProtection::ReadOnly));
let RegionSummary::Image(s) = &r.summary else {
panic!("unexpected region summary: {:?}", r.summary);
};
assert_eq!(s.module_name, "ntdll");
assert_eq!(s.image_path, "ntdll.dll");
}
assert_eq!(
map.find_region(0x00007ff9d2c60000).unwrap().base_address,
0x00007ff9d2c60000
);
assert_eq!(
map.find_region(0x00007ff9d2c60001).unwrap().base_address,
0x00007ff9d2c60000
);
assert_eq!(
map.find_region(0x00007ff9d2c60fff).unwrap().base_address,
0x00007ff9d2c60000
);
assert_eq!(
map.find_region(0x00007ff9d2c61000).unwrap().base_address,
0x00007ff9d2c61000
);
assert!(map.find_region(0x00007ff9d2ec7000).is_none());
}