1use std::collections::HashSet;
9
10use memf_core::object_reader::ObjectReader;
11use memf_format::PhysicalMemoryProvider;
12
13use crate::types::VmaFlags;
14
15const MAX_VMAS: usize = 1_000_000;
19
20#[derive(Debug, Clone)]
22pub struct VmaEntry {
23 pub vma_addr: u64,
25 pub start: u64,
27 pub end: u64,
29 pub flags: VmaFlags,
31 pub file_ptr: u64,
33}
34
35pub fn for_each_task_vma<P, F>(reader: &ObjectReader<P>, task_addr: u64, callback: &mut F)
47where
48 P: PhysicalMemoryProvider,
49 F: FnMut(VmaEntry),
50{
51 let mm_ptr: u64 = match reader.read_field(task_addr, "task_struct", "mm") {
52 Ok(v) => v,
53 Err(_) => return,
54 };
55 if mm_ptr == 0 {
56 return;
57 }
58 let mmap_ptr: u64 = match reader.read_field(mm_ptr, "mm_struct", "mmap") {
59 Ok(v) => v,
60 Err(_) => return,
61 };
62
63 let mut seen: HashSet<u64> = HashSet::new();
68 let mut vma_addr = mmap_ptr;
69 while vma_addr != 0 {
70 if seen.len() >= MAX_VMAS || !seen.insert(vma_addr) {
71 break;
72 }
73 let start: u64 = match reader.read_field(vma_addr, "vm_area_struct", "vm_start") {
74 Ok(v) => v,
75 Err(_) => break,
76 };
77 let end: u64 = match reader.read_field(vma_addr, "vm_area_struct", "vm_end") {
78 Ok(v) => v,
79 Err(_) => break,
80 };
81 let raw_flags: u64 = reader
82 .read_field(vma_addr, "vm_area_struct", "vm_flags")
83 .unwrap_or(0);
84 let file_ptr: u64 = reader
85 .read_field(vma_addr, "vm_area_struct", "vm_file")
86 .unwrap_or(0);
87
88 callback(VmaEntry {
89 vma_addr,
90 start,
91 end,
92 flags: VmaFlags::from_raw(raw_flags),
93 file_ptr,
94 });
95
96 vma_addr = reader
97 .read_field(vma_addr, "vm_area_struct", "vm_next")
98 .unwrap_or(0);
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use memf_core::test_builders::{flags as ptflags, PageTableBuilder};
106 use memf_symbols::test_builders::IsfBuilder;
107
108 fn make_reader_with_isf(
109 isf: &IsfBuilder,
110 ptb: PageTableBuilder,
111 ) -> memf_core::object_reader::ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
112 let json = isf.build_json();
113 let resolver = memf_symbols::isf::IsfResolver::from_value(&json).unwrap();
114 let (cr3, mem) = ptb.build();
115 let vas = memf_core::vas::VirtualAddressSpace::new(
116 mem,
117 cr3,
118 memf_core::vas::TranslationMode::X86_64FourLevel,
119 );
120 memf_core::object_reader::ObjectReader::new(vas, Box::new(resolver))
121 }
122
123 #[test]
128 fn task_with_null_mm_produces_no_vmas() {
129 let isf = IsfBuilder::new().add_struct("task_struct", 32).add_field(
131 "task_struct",
132 "mm",
133 0,
134 "pointer",
135 );
136 let task_vaddr: u64 = 0xFFFF_8000_0010_0000;
137 let task_paddr: u64 = 0x0010_0000;
138 let mut page = [0u8; 4096];
139 page[0..8].copy_from_slice(&0u64.to_le_bytes());
141 let ptb = PageTableBuilder::new()
142 .map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
143 .write_phys(task_paddr, &page);
144 let reader = make_reader_with_isf(&isf, ptb);
145
146 let mut entries: Vec<VmaEntry> = Vec::new();
147 for_each_task_vma(&reader, task_vaddr, &mut |e| entries.push(e));
148 assert!(
149 entries.is_empty(),
150 "kernel thread (mm=0) should yield no VMAs"
151 );
152 }
153
154 #[test]
155 fn unreadable_mm_field_produces_no_vmas() {
156 let isf = IsfBuilder::new().add_struct("task_struct", 32).add_field(
158 "task_struct",
159 "mm",
160 0,
161 "pointer",
162 );
163 let reader = memf_core::test_builders::make_reader(&isf);
164
165 let mut entries: Vec<VmaEntry> = Vec::new();
166 for_each_task_vma(&reader, 0xDEAD_BEEF_0000_0000, &mut |e| entries.push(e));
167 assert!(
168 entries.is_empty(),
169 "unreadable task_struct should yield no VMAs"
170 );
171 }
172
173 #[test]
174 fn single_vma_yields_correct_entry() {
175 let task_vaddr: u64 = 0xFFFF_8000_0001_0000;
177 let task_paddr: u64 = 0x0001_0000;
178 let mm_vaddr: u64 = 0xFFFF_8000_0002_0000;
179 let mm_paddr: u64 = 0x0002_0000;
180 let vma_vaddr: u64 = 0xFFFF_8000_0003_0000;
181 let vma_paddr: u64 = 0x0003_0000;
182
183 let isf = IsfBuilder::new()
188 .add_struct("task_struct", 16)
189 .add_field("task_struct", "mm", 0, "pointer")
190 .add_struct("mm_struct", 16)
191 .add_field("mm_struct", "mmap", 0, "pointer")
192 .add_struct("vm_area_struct", 48)
193 .add_field("vm_area_struct", "vm_start", 0, "unsigned long")
194 .add_field("vm_area_struct", "vm_end", 8, "unsigned long")
195 .add_field("vm_area_struct", "vm_flags", 16, "unsigned long")
196 .add_field("vm_area_struct", "vm_file", 24, "pointer")
197 .add_field("vm_area_struct", "vm_next", 32, "pointer");
198
199 let mut task_page = [0u8; 4096];
200 task_page[0..8].copy_from_slice(&mm_vaddr.to_le_bytes()); let mut mm_page = [0u8; 4096];
203 mm_page[0..8].copy_from_slice(&vma_vaddr.to_le_bytes()); let vm_start: u64 = 0x0000_7FFF_0000_0000;
206 let vm_end: u64 = 0x0000_7FFF_0001_0000;
207 let vm_flags: u64 = 0x3; let vm_file: u64 = 0; let vm_next: u64 = 0; let mut vma_page = [0u8; 4096];
212 vma_page[0..8].copy_from_slice(&vm_start.to_le_bytes());
213 vma_page[8..16].copy_from_slice(&vm_end.to_le_bytes());
214 vma_page[16..24].copy_from_slice(&vm_flags.to_le_bytes());
215 vma_page[24..32].copy_from_slice(&vm_file.to_le_bytes());
216 vma_page[32..40].copy_from_slice(&vm_next.to_le_bytes());
217
218 let ptb = PageTableBuilder::new()
219 .map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
220 .write_phys(task_paddr, &task_page)
221 .map_4k(mm_vaddr, mm_paddr, ptflags::WRITABLE)
222 .write_phys(mm_paddr, &mm_page)
223 .map_4k(vma_vaddr, vma_paddr, ptflags::WRITABLE)
224 .write_phys(vma_paddr, &vma_page);
225
226 let reader = make_reader_with_isf(&isf, ptb);
227
228 let mut entries: Vec<VmaEntry> = Vec::new();
229 for_each_task_vma(&reader, task_vaddr, &mut |e| entries.push(e));
230
231 assert_eq!(entries.len(), 1, "expected exactly one VMA");
232 let e = &entries[0];
233 assert_eq!(e.start, vm_start);
234 assert_eq!(e.end, vm_end);
235 assert_eq!(e.file_ptr, 0, "anonymous mapping");
236 assert!(e.flags.read, "read flag should be set");
237 assert!(e.flags.write, "write flag should be set");
238 assert!(!e.flags.exec, "exec flag should not be set");
239 }
240
241 #[test]
242 fn cyclic_vm_next_terminates_after_one_visit() {
243 let task_vaddr: u64 = 0xFFFF_8000_0001_0000;
247 let task_paddr: u64 = 0x0001_0000;
248 let mm_vaddr: u64 = 0xFFFF_8000_0002_0000;
249 let mm_paddr: u64 = 0x0002_0000;
250 let vma_vaddr: u64 = 0xFFFF_8000_0003_0000;
251 let vma_paddr: u64 = 0x0003_0000;
252
253 let isf = IsfBuilder::new()
254 .add_struct("task_struct", 16)
255 .add_field("task_struct", "mm", 0, "pointer")
256 .add_struct("mm_struct", 16)
257 .add_field("mm_struct", "mmap", 0, "pointer")
258 .add_struct("vm_area_struct", 48)
259 .add_field("vm_area_struct", "vm_start", 0, "unsigned long")
260 .add_field("vm_area_struct", "vm_end", 8, "unsigned long")
261 .add_field("vm_area_struct", "vm_flags", 16, "unsigned long")
262 .add_field("vm_area_struct", "vm_file", 24, "pointer")
263 .add_field("vm_area_struct", "vm_next", 32, "pointer");
264
265 let mut task_page = [0u8; 4096];
266 task_page[0..8].copy_from_slice(&mm_vaddr.to_le_bytes());
267 let mut mm_page = [0u8; 4096];
268 mm_page[0..8].copy_from_slice(&vma_vaddr.to_le_bytes());
269
270 let mut vma_page = [0u8; 4096];
271 vma_page[0..8].copy_from_slice(&0x0000_7FFF_0000_0000u64.to_le_bytes()); vma_page[8..16].copy_from_slice(&0x0000_7FFF_0001_0000u64.to_le_bytes()); vma_page[16..24].copy_from_slice(&3u64.to_le_bytes()); vma_page[24..32].copy_from_slice(&0u64.to_le_bytes()); vma_page[32..40].copy_from_slice(&vma_vaddr.to_le_bytes()); let ptb = PageTableBuilder::new()
278 .map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
279 .write_phys(task_paddr, &task_page)
280 .map_4k(mm_vaddr, mm_paddr, ptflags::WRITABLE)
281 .write_phys(mm_paddr, &mm_page)
282 .map_4k(vma_vaddr, vma_paddr, ptflags::WRITABLE)
283 .write_phys(vma_paddr, &vma_page);
284
285 let reader = make_reader_with_isf(&isf, ptb);
286
287 let mut count = 0usize;
288 for_each_task_vma(&reader, task_vaddr, &mut |_| count += 1);
289 assert_eq!(
290 count, 1,
291 "self-referencing vm_next must be visited once, then the walk stops"
292 );
293 }
294
295 #[test]
296 fn two_vmas_chained_via_vm_next() {
297 let task_vaddr: u64 = 0xFFFF_8000_0001_0000;
298 let task_paddr: u64 = 0x0001_0000;
299 let mm_vaddr: u64 = 0xFFFF_8000_0002_0000;
300 let mm_paddr: u64 = 0x0002_0000;
301 let vma1_vaddr: u64 = 0xFFFF_8000_0003_0000;
302 let vma1_paddr: u64 = 0x0003_0000;
303 let vma2_vaddr: u64 = 0xFFFF_8000_0004_0000;
304 let vma2_paddr: u64 = 0x0004_0000;
305
306 let isf = IsfBuilder::new()
307 .add_struct("task_struct", 16)
308 .add_field("task_struct", "mm", 0, "pointer")
309 .add_struct("mm_struct", 16)
310 .add_field("mm_struct", "mmap", 0, "pointer")
311 .add_struct("vm_area_struct", 48)
312 .add_field("vm_area_struct", "vm_start", 0, "unsigned long")
313 .add_field("vm_area_struct", "vm_end", 8, "unsigned long")
314 .add_field("vm_area_struct", "vm_flags", 16, "unsigned long")
315 .add_field("vm_area_struct", "vm_file", 24, "pointer")
316 .add_field("vm_area_struct", "vm_next", 32, "pointer");
317
318 let mut task_page = [0u8; 4096];
319 task_page[0..8].copy_from_slice(&mm_vaddr.to_le_bytes());
320 let mut mm_page = [0u8; 4096];
321 mm_page[0..8].copy_from_slice(&vma1_vaddr.to_le_bytes());
322
323 let mut vma1_page = [0u8; 4096];
325 vma1_page[0..8].copy_from_slice(&0x0000_7FFF_0000_0000u64.to_le_bytes());
326 vma1_page[8..16].copy_from_slice(&0x0000_7FFF_0001_0000u64.to_le_bytes());
327 vma1_page[16..24].copy_from_slice(&3u64.to_le_bytes()); vma1_page[24..32].copy_from_slice(&0u64.to_le_bytes());
329 vma1_page[32..40].copy_from_slice(&vma2_vaddr.to_le_bytes());
330
331 let fake_file_ptr: u64 = 0xFFFF_8888_0000_0000;
333 let mut vma2_page = [0u8; 4096];
334 vma2_page[0..8].copy_from_slice(&0x0000_7FFF_0010_0000u64.to_le_bytes());
335 vma2_page[8..16].copy_from_slice(&0x0000_7FFF_0020_0000u64.to_le_bytes());
336 vma2_page[16..24].copy_from_slice(&5u64.to_le_bytes()); vma2_page[24..32].copy_from_slice(&fake_file_ptr.to_le_bytes());
338 vma2_page[32..40].copy_from_slice(&0u64.to_le_bytes());
339
340 let ptb = PageTableBuilder::new()
341 .map_4k(task_vaddr, task_paddr, ptflags::WRITABLE)
342 .write_phys(task_paddr, &task_page)
343 .map_4k(mm_vaddr, mm_paddr, ptflags::WRITABLE)
344 .write_phys(mm_paddr, &mm_page)
345 .map_4k(vma1_vaddr, vma1_paddr, ptflags::WRITABLE)
346 .write_phys(vma1_paddr, &vma1_page)
347 .map_4k(vma2_vaddr, vma2_paddr, ptflags::WRITABLE)
348 .write_phys(vma2_paddr, &vma2_page);
349
350 let reader = make_reader_with_isf(&isf, ptb);
351
352 let mut entries: Vec<VmaEntry> = Vec::new();
353 for_each_task_vma(&reader, task_vaddr, &mut |e| entries.push(e));
354
355 assert_eq!(entries.len(), 2, "expected two VMAs");
356 assert_eq!(entries[0].file_ptr, 0, "vma1 is anonymous");
357 assert_eq!(entries[1].file_ptr, fake_file_ptr, "vma2 is file-backed");
358 assert!(entries[1].flags.read && entries[1].flags.exec, "vma2 is rx");
359 }
360}