macho_core/model/addr/
map.rs1use crate::error::{Error, Result};
2use crate::model::addr::types::{Rva, ThinFileOffset, Va};
3
4#[derive(Debug, Clone)]
6pub struct MappingEntry {
7 file_offset: ThinFileOffset,
8 file_size: u64,
9 vm_addr: Va,
10 vm_size: u64,
11}
12
13impl MappingEntry {
14 pub fn try_new(
16 file_offset: ThinFileOffset,
17 file_size: u64,
18 vm_addr: Va,
19 vm_size: u64,
20 ) -> Result<Self> {
21 file_offset
22 .0
23 .checked_add(file_size)
24 .ok_or_else(|| Error::address("mapping file range overflows"))?;
25 vm_addr
26 .0
27 .checked_add(vm_size)
28 .ok_or_else(|| Error::address("mapping virtual range overflows"))?;
29 if file_size > vm_size {
30 return Err(Error::address(format!(
31 "mapping file size {file_size:#x} exceeds virtual size {vm_size:#x}"
32 )));
33 }
34 Ok(Self {
35 file_offset,
36 file_size,
37 vm_addr,
38 vm_size,
39 })
40 }
41
42 pub fn file_offset(&self) -> ThinFileOffset {
44 self.file_offset
45 }
46
47 pub fn file_size(&self) -> u64 {
49 self.file_size
50 }
51
52 pub fn vm_addr(&self) -> Va {
54 self.vm_addr
55 }
56
57 pub fn vm_size(&self) -> u64 {
59 self.vm_size
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct AddressMap {
66 entries: Vec<MappingEntry>,
67}
68
69impl AddressMap {
70 pub fn try_new(mut entries: Vec<MappingEntry>) -> Result<Self> {
72 entries.sort_by_key(|entry| (entry.file_offset.0, entry.vm_addr.0));
73 for pair in entries.windows(2) {
74 let left_file_end = pair[0]
75 .file_offset
76 .0
77 .checked_add(pair[0].file_size)
78 .ok_or_else(|| Error::address("mapping file range overflows"))?;
79 if pair[0].file_size != 0
80 && pair[1].file_size != 0
81 && pair[1].file_offset.0 < left_file_end
82 {
83 return Err(Error::address("mapping file ranges overlap"));
84 }
85 }
86 let mut virtual_order: Vec<_> = entries.iter().collect();
87 virtual_order.sort_by_key(|entry| entry.vm_addr.0);
88 for pair in virtual_order.windows(2) {
89 let left_vm_end = pair[0]
90 .vm_addr
91 .0
92 .checked_add(pair[0].vm_size)
93 .ok_or_else(|| Error::address("mapping virtual range overflows"))?;
94 if pair[0].vm_size != 0 && pair[1].vm_size != 0 && pair[1].vm_addr.0 < left_vm_end {
95 return Err(Error::address("mapping virtual ranges overlap"));
96 }
97 }
98 Ok(Self { entries })
99 }
100
101 pub fn entries(&self) -> &[MappingEntry] {
103 &self.entries
104 }
105
106 pub fn thin_offset_to_va(&self, offset: ThinFileOffset) -> Result<Va> {
108 for entry in &self.entries {
109 if entry.file_size == 0 {
110 continue;
111 }
112 let Some(relative) = offset.0.checked_sub(entry.file_offset.0) else {
113 continue;
114 };
115 if relative < entry.file_size {
116 return entry
117 .vm_addr
118 .0
119 .checked_add(relative)
120 .map(Va)
121 .ok_or_else(|| Error::address("mapped virtual address overflows"));
122 }
123 }
124 Err(Error::address(format!(
125 "file offset {offset} is not mapped to any segment"
126 )))
127 }
128
129 pub fn va_to_thin_offset(&self, va: Va) -> Result<ThinFileOffset> {
131 for entry in &self.entries {
132 if entry.vm_size == 0 {
133 continue;
134 }
135 let Some(relative) = va.0.checked_sub(entry.vm_addr.0) else {
136 continue;
137 };
138 if relative < entry.vm_size {
139 if relative >= entry.file_size {
140 return Err(Error::address(format!(
141 "VA {va} maps to zero-fill region (beyond file-backed portion)"
142 )));
143 }
144 return entry
145 .file_offset
146 .0
147 .checked_add(relative)
148 .map(ThinFileOffset)
149 .ok_or_else(|| Error::address("mapped file offset overflows"));
150 }
151 }
152 Err(Error::address(format!(
153 "VA {va} is not mapped to any segment"
154 )))
155 }
156
157 pub fn rva_to_va(rva: Rva, image_base: Va) -> Result<Va> {
159 image_base
160 .0
161 .checked_add(rva.0)
162 .map(Va)
163 .ok_or_else(|| Error::address("RVA to VA conversion overflows"))
164 }
165
166 pub fn va_to_rva(va: Va, image_base: Va) -> Result<Rva> {
168 va.0.checked_sub(image_base.0)
169 .map(Rva)
170 .ok_or_else(|| Error::address("VA precedes image base"))
171 }
172
173 pub fn rva_to_thin_offset(&self, rva: Rva, image_base: Va) -> Result<ThinFileOffset> {
175 let va = Self::rva_to_va(rva, image_base)?;
176 self.va_to_thin_offset(va)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 fn entry(file: u64, size: u64, va: u64) -> MappingEntry {
185 MappingEntry::try_new(ThinFileOffset(file), size, Va(va), size).unwrap()
186 }
187
188 fn test_map() -> AddressMap {
189 AddressMap::try_new(vec![
190 entry(0, 0x1000, 0x1_0000_0000),
191 entry(0x1000, 0x2000, 0x1_0000_1000),
192 ])
193 .unwrap()
194 }
195
196 #[test]
197 fn conversions_are_checked_and_round_trip() {
198 let map = test_map();
199 let va = map.thin_offset_to_va(ThinFileOffset(0x1500)).unwrap();
200 assert_eq!(va, Va(0x1_0000_1500));
201 assert_eq!(map.va_to_thin_offset(va).unwrap(), ThinFileOffset(0x1500));
202 assert_eq!(
203 AddressMap::va_to_rva(va, Va(0x1_0000_0000)).unwrap(),
204 Rva(0x1500)
205 );
206 }
207
208 #[test]
209 fn overlap_and_overflow_are_rejected() {
210 assert!(
211 AddressMap::try_new(vec![entry(0, 0x1000, 0x1000), entry(0x800, 0x1000, 0x3000)])
212 .is_err()
213 );
214 assert!(MappingEntry::try_new(ThinFileOffset(u64::MAX), 2, Va(0), 2).is_err());
215 }
216}