use crate::error::{Error, Result};
use crate::model::addr::types::{Rva, ThinFileOffset, Va};
#[derive(Debug, Clone)]
pub struct MappingEntry {
file_offset: ThinFileOffset,
file_size: u64,
vm_addr: Va,
vm_size: u64,
}
impl MappingEntry {
pub fn try_new(
file_offset: ThinFileOffset,
file_size: u64,
vm_addr: Va,
vm_size: u64,
) -> Result<Self> {
file_offset
.0
.checked_add(file_size)
.ok_or_else(|| Error::address("mapping file range overflows"))?;
vm_addr
.0
.checked_add(vm_size)
.ok_or_else(|| Error::address("mapping virtual range overflows"))?;
if file_size > vm_size {
return Err(Error::address(format!(
"mapping file size {file_size:#x} exceeds virtual size {vm_size:#x}"
)));
}
Ok(Self {
file_offset,
file_size,
vm_addr,
vm_size,
})
}
pub fn file_offset(&self) -> ThinFileOffset {
self.file_offset
}
pub fn file_size(&self) -> u64 {
self.file_size
}
pub fn vm_addr(&self) -> Va {
self.vm_addr
}
pub fn vm_size(&self) -> u64 {
self.vm_size
}
}
#[derive(Debug, Clone)]
pub struct AddressMap {
entries: Vec<MappingEntry>,
}
impl AddressMap {
pub fn try_new(mut entries: Vec<MappingEntry>) -> Result<Self> {
entries.sort_by_key(|entry| (entry.file_offset.0, entry.vm_addr.0));
for pair in entries.windows(2) {
let left_file_end = pair[0]
.file_offset
.0
.checked_add(pair[0].file_size)
.ok_or_else(|| Error::address("mapping file range overflows"))?;
if pair[0].file_size != 0
&& pair[1].file_size != 0
&& pair[1].file_offset.0 < left_file_end
{
return Err(Error::address("mapping file ranges overlap"));
}
}
let mut virtual_order: Vec<_> = entries.iter().collect();
virtual_order.sort_by_key(|entry| entry.vm_addr.0);
for pair in virtual_order.windows(2) {
let left_vm_end = pair[0]
.vm_addr
.0
.checked_add(pair[0].vm_size)
.ok_or_else(|| Error::address("mapping virtual range overflows"))?;
if pair[0].vm_size != 0 && pair[1].vm_size != 0 && pair[1].vm_addr.0 < left_vm_end {
return Err(Error::address("mapping virtual ranges overlap"));
}
}
Ok(Self { entries })
}
pub fn entries(&self) -> &[MappingEntry] {
&self.entries
}
pub fn thin_offset_to_va(&self, offset: ThinFileOffset) -> Result<Va> {
for entry in &self.entries {
if entry.file_size == 0 {
continue;
}
let Some(relative) = offset.0.checked_sub(entry.file_offset.0) else {
continue;
};
if relative < entry.file_size {
return entry
.vm_addr
.0
.checked_add(relative)
.map(Va)
.ok_or_else(|| Error::address("mapped virtual address overflows"));
}
}
Err(Error::address(format!(
"file offset {offset} is not mapped to any segment"
)))
}
pub fn va_to_thin_offset(&self, va: Va) -> Result<ThinFileOffset> {
for entry in &self.entries {
if entry.vm_size == 0 {
continue;
}
let Some(relative) = va.0.checked_sub(entry.vm_addr.0) else {
continue;
};
if relative < entry.vm_size {
if relative >= entry.file_size {
return Err(Error::address(format!(
"VA {va} maps to zero-fill region (beyond file-backed portion)"
)));
}
return entry
.file_offset
.0
.checked_add(relative)
.map(ThinFileOffset)
.ok_or_else(|| Error::address("mapped file offset overflows"));
}
}
Err(Error::address(format!(
"VA {va} is not mapped to any segment"
)))
}
pub fn rva_to_va(rva: Rva, image_base: Va) -> Result<Va> {
image_base
.0
.checked_add(rva.0)
.map(Va)
.ok_or_else(|| Error::address("RVA to VA conversion overflows"))
}
pub fn va_to_rva(va: Va, image_base: Va) -> Result<Rva> {
va.0.checked_sub(image_base.0)
.map(Rva)
.ok_or_else(|| Error::address("VA precedes image base"))
}
pub fn rva_to_thin_offset(&self, rva: Rva, image_base: Va) -> Result<ThinFileOffset> {
let va = Self::rva_to_va(rva, image_base)?;
self.va_to_thin_offset(va)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(file: u64, size: u64, va: u64) -> MappingEntry {
MappingEntry::try_new(ThinFileOffset(file), size, Va(va), size).unwrap()
}
fn test_map() -> AddressMap {
AddressMap::try_new(vec![
entry(0, 0x1000, 0x1_0000_0000),
entry(0x1000, 0x2000, 0x1_0000_1000),
])
.unwrap()
}
#[test]
fn conversions_are_checked_and_round_trip() {
let map = test_map();
let va = map.thin_offset_to_va(ThinFileOffset(0x1500)).unwrap();
assert_eq!(va, Va(0x1_0000_1500));
assert_eq!(map.va_to_thin_offset(va).unwrap(), ThinFileOffset(0x1500));
assert_eq!(
AddressMap::va_to_rva(va, Va(0x1_0000_0000)).unwrap(),
Rva(0x1500)
);
}
#[test]
fn overlap_and_overflow_are_rejected() {
assert!(
AddressMap::try_new(vec![entry(0, 0x1000, 0x1000), entry(0x800, 0x1000, 0x3000)])
.is_err()
);
assert!(MappingEntry::try_new(ThinFileOffset(u64::MAX), 2, Va(0), 2).is_err());
}
}