Skip to main content

memf_core/
vas.rs

1//! Virtual address space and page table walking.
2
3use memf_format::PhysicalMemoryProvider;
4
5use crate::pagefile::PagefileSource;
6use crate::proto_pte::PrototypePteSource;
7use crate::{Error, Result};
8
9/// Translation mode for virtual-to-physical address translation.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TranslationMode {
12    /// x86_64 4-level paging (PML4 -> PDPT -> PD -> PT).
13    X86_64FourLevel,
14}
15
16/// A virtual address space backed by physical memory and page tables.
17pub struct VirtualAddressSpace<P: PhysicalMemoryProvider> {
18    physical: P,
19    page_table_root: u64,
20    mode: TranslationMode,
21    pagefiles: Vec<Box<dyn PagefileSource>>,
22    prototype_source: Option<Box<dyn PrototypePteSource>>,
23}
24
25// x86_64 page table constants
26const ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
27const PRESENT: u64 = 1;
28const PS: u64 = 1 << 7;
29
30/// Internal result of page table walk — not exposed publicly.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32enum TranslationResult {
33    /// Page is in physical memory at this address.
34    Physical(u64),
35    /// Page is demand-zero (all zeroes).
36    DemandZero,
37    /// Page is in a pagefile.
38    PagefileEntry { pagefile_num: u8, page_offset: u64 },
39    /// Page is a transition page (still in physical memory at this PFN-derived address).
40    Transition(u64),
41    /// Page uses a prototype PTE; carries the raw PTE value for resolution.
42    Prototype(u64),
43}
44
45impl<P: PhysicalMemoryProvider> VirtualAddressSpace<P> {
46    /// Create a new virtual address space.
47    pub fn new(physical: P, page_table_root: u64, mode: TranslationMode) -> Self {
48        Self {
49            physical,
50            page_table_root,
51            mode,
52            pagefiles: Vec::new(),
53            prototype_source: None,
54        }
55    }
56
57    /// Attach a pagefile source for resolving paged-out memory.
58    pub fn with_pagefile(mut self, source: Box<dyn PagefileSource>) -> Self {
59        self.pagefiles.push(source);
60        self
61    }
62
63    /// Attach a prototype PTE source for resolving shared section pages.
64    pub fn with_prototype_source(mut self, source: Box<dyn PrototypePteSource>) -> Self {
65        self.prototype_source = Some(source);
66        self
67    }
68
69    /// Translate a virtual address to a physical address.
70    pub fn virt_to_phys(&self, vaddr: u64) -> Result<u64> {
71        match self.mode {
72            TranslationMode::X86_64FourLevel => self.walk_x86_64_4level(vaddr),
73        }
74    }
75
76    /// Read `buf.len()` bytes from virtual address `vaddr`, handling page boundary crossings.
77    ///
78    /// Uses `walk_x86_64_4level_internal()` to resolve each 4K chunk, transparently
79    /// handling physical, transition, demand-zero, and pagefile pages.
80    pub fn read_virt(&self, vaddr: u64, buf: &mut [u8]) -> Result<()> {
81        if buf.is_empty() {
82            return Ok(());
83        }
84
85        let mut offset = 0usize;
86        let mut current_vaddr = vaddr;
87
88        while offset < buf.len() {
89            let page_off = (current_vaddr & 0xFFF) as usize;
90            let remaining_in_page = 0x1000 - page_off;
91            let remaining_to_read = buf.len() - offset;
92            let chunk = remaining_to_read.min(remaining_in_page);
93
94            let result = match self.mode {
95                TranslationMode::X86_64FourLevel => {
96                    self.walk_x86_64_4level_internal(current_vaddr)?
97                }
98            };
99
100            match result {
101                TranslationResult::Physical(paddr) | TranslationResult::Transition(paddr) => {
102                    let n = self
103                        .physical
104                        .read_phys(paddr, &mut buf[offset..offset + chunk])?;
105                    if n == 0 {
106                        return Err(Error::PartialRead {
107                            addr: vaddr,
108                            requested: buf.len(),
109                            got: offset,
110                        });
111                    }
112                    offset += n;
113                    current_vaddr = current_vaddr.wrapping_add(n as u64);
114                }
115                TranslationResult::DemandZero => {
116                    buf[offset..offset + chunk].fill(0);
117                    offset += chunk;
118                    current_vaddr = current_vaddr.wrapping_add(chunk as u64);
119                }
120                TranslationResult::PagefileEntry {
121                    pagefile_num,
122                    page_offset,
123                } => {
124                    let page = self.read_pagefile_page(current_vaddr, pagefile_num, page_offset)?;
125                    buf[offset..offset + chunk].copy_from_slice(&page[page_off..page_off + chunk]);
126                    offset += chunk;
127                    current_vaddr = current_vaddr.wrapping_add(chunk as u64);
128                }
129                TranslationResult::Prototype(raw_pte) => {
130                    if let Some(ref source) = self.prototype_source {
131                        if let Some(phys_base) = source.resolve(raw_pte) {
132                            let paddr = phys_base + (current_vaddr & 0xFFF);
133                            let n = self
134                                .physical
135                                .read_phys(paddr, &mut buf[offset..offset + chunk])?;
136                            if n == 0 {
137                                return Err(Error::PartialRead {
138                                    addr: vaddr,
139                                    requested: buf.len(),
140                                    got: offset,
141                                });
142                            }
143                            offset += n;
144                            current_vaddr = current_vaddr.wrapping_add(n as u64);
145                        } else {
146                            return Err(Error::PrototypePte(current_vaddr));
147                        }
148                    } else {
149                        return Err(Error::PrototypePte(current_vaddr));
150                    }
151                }
152            }
153        }
154
155        Ok(())
156    }
157
158    fn read_pagefile_page(
159        &self,
160        vaddr: u64,
161        pagefile_num: u8,
162        page_offset: u64,
163    ) -> Result<[u8; 4096]> {
164        for source in &self.pagefiles {
165            if source.pagefile_number() == pagefile_num {
166                if let Some(page) = source.read_page(page_offset)? {
167                    return Ok(page);
168                }
169                break;
170            }
171        }
172        Err(Error::PagedOut {
173            vaddr,
174            pagefile_num,
175            page_offset,
176        })
177    }
178
179    /// Return a reference to the underlying physical memory provider.
180    pub fn physical(&self) -> &P {
181        &self.physical
182    }
183
184    /// Return the translation mode.
185    pub fn mode(&self) -> TranslationMode {
186        self.mode
187    }
188
189    fn read_pte(&self, addr: u64) -> Result<u64> {
190        let mut buf = [0u8; 8];
191        let n = self.physical.read_phys(addr, &mut buf)?;
192        if n < 8 {
193            return Err(Error::PartialRead {
194                addr,
195                requested: 8,
196                got: n,
197            });
198        }
199        Ok(u64::from_le_bytes(buf))
200    }
201
202    fn walk_x86_64_4level(&self, vaddr: u64) -> Result<u64> {
203        let result = self.walk_x86_64_4level_internal(vaddr)?;
204        match result {
205            TranslationResult::Physical(addr) | TranslationResult::Transition(addr) => Ok(addr),
206            TranslationResult::DemandZero => Err(Error::PageNotPresent(vaddr)),
207            TranslationResult::PagefileEntry {
208                pagefile_num,
209                page_offset,
210            } => Err(Error::PagedOut {
211                vaddr,
212                pagefile_num,
213                page_offset,
214            }),
215            TranslationResult::Prototype(_) => Err(Error::PrototypePte(vaddr)),
216        }
217    }
218
219    fn walk_x86_64_4level_internal(&self, vaddr: u64) -> Result<TranslationResult> {
220        let pml4_idx = (vaddr >> 39) & 0x1FF;
221        let pdpt_idx = (vaddr >> 30) & 0x1FF;
222        let pd_idx = (vaddr >> 21) & 0x1FF;
223        let pt_idx = (vaddr >> 12) & 0x1FF;
224        let page_offset = vaddr & 0xFFF;
225
226        // PML4
227        let pml4e = self.read_pte(self.page_table_root + pml4_idx * 8)?;
228        if pml4e & PRESENT == 0 {
229            return Err(Error::PageNotPresent(vaddr));
230        }
231
232        // PDPT
233        let pdpt_base = pml4e & ADDR_MASK;
234        let pdpte = self.read_pte(pdpt_base + pdpt_idx * 8)?;
235        if pdpte & PRESENT == 0 {
236            return Err(Error::PageNotPresent(vaddr));
237        }
238
239        // 1GB huge page check
240        if pdpte & PS != 0 {
241            let phys_base = pdpte & 0x000F_FFFF_C000_0000;
242            let offset_1g = vaddr & 0x3FFF_FFFF;
243            return Ok(TranslationResult::Physical(phys_base | offset_1g));
244        }
245
246        // PD
247        let pd_base = pdpte & ADDR_MASK;
248        let pde = self.read_pte(pd_base + pd_idx * 8)?;
249        if pde & PRESENT == 0 {
250            return Err(Error::PageNotPresent(vaddr));
251        }
252
253        // 2MB large page check
254        if pde & PS != 0 {
255            let phys_base = pde & 0x000F_FFFF_FFE0_0000;
256            let offset_2m = vaddr & 0x1F_FFFF;
257            return Ok(TranslationResult::Physical(phys_base | offset_2m));
258        }
259
260        // PT (4K page)
261        let pt_base = pde & ADDR_MASK;
262        let pte = self.read_pte(pt_base + pt_idx * 8)?;
263
264        if pte & PRESENT != 0 {
265            let phys_base = pte & ADDR_MASK;
266            return Ok(TranslationResult::Physical(phys_base | page_offset));
267        }
268
269        // Non-present PTE decoding (PT level only)
270        Ok(Self::decode_non_present_pte(pte, page_offset))
271    }
272
273    fn decode_non_present_pte(pte: u64, page_offset: u64) -> TranslationResult {
274        if pte == 0 {
275            return TranslationResult::DemandZero;
276        }
277        if pte & (1 << 11) != 0 {
278            let pfn = (pte >> 12) & 0xF_FFFF_FFFF;
279            return TranslationResult::Transition(pfn * 0x1000 + page_offset);
280        }
281        if pte & (1 << 10) != 0 {
282            return TranslationResult::Prototype(pte);
283        }
284        let pagefile_num = ((pte >> 1) & 0xF) as u8;
285        let pf_page_offset = (pte >> 12) & 0xF_FFFF_FFFF;
286        TranslationResult::PagefileEntry {
287            pagefile_num,
288            page_offset: pf_page_offset,
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use crate::test_builders::{flags, PageTableBuilder};
297
298    #[test]
299    fn translate_4k_page() {
300        let vaddr: u64 = 0xFFFF_8000_0010_0000;
301        let paddr: u64 = 0x0080_0000;
302        let (cr3, mem) = PageTableBuilder::new()
303            .map_4k(vaddr, paddr, flags::WRITABLE)
304            .build();
305        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
306        let result = vas.virt_to_phys(vaddr).unwrap();
307        assert_eq!(result, paddr);
308    }
309
310    #[test]
311    fn translate_4k_with_offset() {
312        let vaddr: u64 = 0xFFFF_8000_0010_0ABC;
313        let paddr_base: u64 = 0x0080_0000;
314        let (cr3, mem) = PageTableBuilder::new()
315            .map_4k(vaddr & !0xFFF, paddr_base, flags::WRITABLE)
316            .build();
317        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
318        let result = vas.virt_to_phys(vaddr).unwrap();
319        assert_eq!(result, paddr_base + 0xABC);
320    }
321
322    #[test]
323    fn read_virt_4k() {
324        let vaddr: u64 = 0xFFFF_8000_0010_0000;
325        let paddr: u64 = 0x0080_0000;
326        let (cr3, mem) = PageTableBuilder::new()
327            .map_4k(vaddr, paddr, flags::WRITABLE)
328            .write_phys(paddr, &[0xDE, 0xAD, 0xBE, 0xEF])
329            .build();
330        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
331        let mut buf = [0u8; 4];
332        vas.read_virt(vaddr, &mut buf).unwrap();
333        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
334    }
335
336    #[test]
337    fn translate_2mb_page() {
338        let vaddr: u64 = 0xFFFF_8000_0020_0000;
339        let paddr: u64 = 0x0100_0000;
340        let (cr3, mem) = PageTableBuilder::new()
341            .map_2m(vaddr, paddr, flags::WRITABLE)
342            .build();
343        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
344        let result = vas.virt_to_phys(vaddr).unwrap();
345        assert_eq!(result, paddr);
346
347        // Test with offset within the 2MB page
348        let result_offset = vas.virt_to_phys(vaddr + 0x1234).unwrap();
349        assert_eq!(result_offset, paddr + 0x1234);
350    }
351
352    #[test]
353    fn translate_1gb_page() {
354        let vaddr: u64 = 0xFFFF_8000_4000_0000;
355        let paddr: u64 = 0x4000_0000;
356        let (cr3, mem) = PageTableBuilder::new()
357            .map_1g(vaddr, paddr, flags::WRITABLE)
358            .build();
359        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
360        let result = vas.virt_to_phys(vaddr).unwrap();
361        assert_eq!(result, paddr);
362
363        // Test with offset within the 1GB page
364        let result_offset = vas.virt_to_phys(vaddr + 0x12_3456).unwrap();
365        assert_eq!(result_offset, paddr + 0x12_3456);
366    }
367
368    #[test]
369    fn non_present_page_returns_error() {
370        let (cr3, mem) = PageTableBuilder::new().build();
371        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
372        let result = vas.virt_to_phys(0xFFFF_8000_0010_0000);
373        assert!(result.is_err());
374        match result.unwrap_err() {
375            Error::PageNotPresent(addr) => assert_eq!(addr, 0xFFFF_8000_0010_0000),
376            other => panic!("unexpected error: {other}"),
377        }
378    }
379
380    #[test]
381    fn read_virt_cross_page_boundary() {
382        // Map two adjacent virtual pages to different physical pages
383        let vaddr_page1: u64 = 0xFFFF_8000_0010_0000;
384        let vaddr_page2: u64 = 0xFFFF_8000_0010_1000;
385        let paddr1: u64 = 0x0080_0000;
386        let paddr2: u64 = 0x0090_0000;
387
388        // Write data at end of page1 and start of page2
389        let (cr3, mem) = PageTableBuilder::new()
390            .map_4k(vaddr_page1, paddr1, flags::WRITABLE)
391            .map_4k(vaddr_page2, paddr2, flags::WRITABLE)
392            .write_phys(paddr1 + 0xFFC, &[0xAA, 0xBB, 0xCC, 0xDD])
393            .write_phys(paddr2, &[0x11, 0x22, 0x33, 0x44])
394            .build();
395
396        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
397        let mut buf = [0u8; 8];
398        vas.read_virt(vaddr_page1 + 0xFFC, &mut buf).unwrap();
399        assert_eq!(buf, [0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44]);
400    }
401
402    #[test]
403    fn read_virt_empty_buffer() {
404        let (cr3, mem) = PageTableBuilder::new().build();
405        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
406        let mut buf = [];
407        vas.read_virt(0xFFFF_8000_0010_0000, &mut buf).unwrap();
408    }
409
410    #[test]
411    fn virt_to_phys_4k_direct() {
412        // Test virt_to_phys as the public API (not just internal translate)
413        let vaddr: u64 = 0xFFFF_8000_0010_0000;
414        let paddr: u64 = 0x0080_0000;
415        let (cr3, mem) = PageTableBuilder::new()
416            .map_4k(vaddr, paddr, flags::WRITABLE)
417            .build();
418        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
419        assert_eq!(vas.virt_to_phys(vaddr).unwrap(), paddr);
420        assert_eq!(vas.virt_to_phys(vaddr + 0x42).unwrap(), paddr + 0x42);
421    }
422
423    #[test]
424    fn physical_accessor() {
425        let (cr3, mem) = PageTableBuilder::new()
426            .write_phys(0x5000, &[0xAB; 8])
427            .build();
428        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
429        let phys = vas.physical();
430        let mut buf = [0u8; 4];
431        let n = phys.read_phys(0x5000, &mut buf).unwrap();
432        assert_eq!(n, 4);
433        assert_eq!(buf, [0xAB; 4]);
434    }
435
436    #[test]
437    fn demand_zero_pte_returns_page_not_present() {
438        let vaddr: u64 = 0xFFFF_8000_0010_0000;
439        let (cr3, mem) = PageTableBuilder::new().map_demand_zero(vaddr).build();
440        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
441        let result = vas.virt_to_phys(vaddr);
442        assert!(result.is_err());
443        match result.unwrap_err() {
444            Error::PageNotPresent(addr) => assert_eq!(addr, vaddr),
445            other => panic!("expected PageNotPresent, got: {other}"),
446        }
447    }
448
449    #[test]
450    fn transition_pte_resolves_to_physical() {
451        let vaddr: u64 = 0xFFFF_8000_0010_0000;
452        let pfn: u64 = 0x800;
453        let (cr3, mem) = PageTableBuilder::new()
454            .map_transition(vaddr, pfn)
455            .write_phys(pfn * 0x1000, &[0xDE, 0xAD, 0xBE, 0xEF])
456            .build();
457        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
458        let paddr = vas.virt_to_phys(vaddr).unwrap();
459        assert_eq!(paddr, pfn * 0x1000);
460    }
461
462    #[test]
463    fn transition_pte_with_offset() {
464        let vaddr_base: u64 = 0xFFFF_8000_0010_0000;
465        let vaddr: u64 = vaddr_base + 0x42;
466        let pfn: u64 = 0x800;
467        let (cr3, mem) = PageTableBuilder::new()
468            .map_transition(vaddr_base, pfn)
469            .build();
470        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
471        let paddr = vas.virt_to_phys(vaddr).unwrap();
472        assert_eq!(paddr, pfn * 0x1000 + 0x42);
473    }
474
475    #[test]
476    fn prototype_pte_returns_error() {
477        let vaddr: u64 = 0xFFFF_8000_0010_0000;
478        let (cr3, mem) = PageTableBuilder::new().map_prototype(vaddr).build();
479        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
480        let result = vas.virt_to_phys(vaddr);
481        assert!(result.is_err());
482        match result.unwrap_err() {
483            Error::PrototypePte(addr) => assert_eq!(addr, vaddr),
484            other => panic!("expected PrototypePte, got: {other}"),
485        }
486    }
487
488    #[test]
489    fn pagefile_pte_returns_paged_out() {
490        let vaddr: u64 = 0xFFFF_8000_0010_0000;
491        let (cr3, mem) = PageTableBuilder::new()
492            .map_pagefile(vaddr, 0, 0x1234)
493            .build();
494        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
495        let result = vas.virt_to_phys(vaddr);
496        assert!(result.is_err());
497        match result.unwrap_err() {
498            Error::PagedOut {
499                vaddr: v,
500                pagefile_num,
501                page_offset,
502            } => {
503                assert_eq!(v, vaddr);
504                assert_eq!(pagefile_num, 0);
505                assert_eq!(page_offset, 0x1234);
506            }
507            other => panic!("expected PagedOut, got: {other}"),
508        }
509    }
510
511    #[test]
512    fn pagefile_pte_number_routing() {
513        let vaddr: u64 = 0xFFFF_8000_0010_0000;
514        let (cr3, mem) = PageTableBuilder::new()
515            .map_pagefile(vaddr, 2, 0xABCD)
516            .build();
517        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
518        let result = vas.virt_to_phys(vaddr);
519        match result.unwrap_err() {
520            Error::PagedOut {
521                pagefile_num,
522                page_offset,
523                ..
524            } => {
525                assert_eq!(pagefile_num, 2);
526                assert_eq!(page_offset, 0xABCD);
527            }
528            other => panic!("expected PagedOut, got: {other}"),
529        }
530    }
531
532    use crate::test_builders::{MockPagefileSource, MockPrototypePteSource};
533
534    #[test]
535    fn read_virt_demand_zero_returns_zeroes() {
536        let vaddr: u64 = 0xFFFF_8000_0010_0000;
537        let (cr3, mem) = PageTableBuilder::new().map_demand_zero(vaddr).build();
538        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
539        let mut buf = [0xFFu8; 4096];
540        vas.read_virt(vaddr, &mut buf).unwrap();
541        assert!(
542            buf.iter().all(|&b| b == 0),
543            "demand-zero page must be all zeroes"
544        );
545    }
546
547    #[test]
548    fn read_virt_transition_reads_physical() {
549        let vaddr: u64 = 0xFFFF_8000_0010_0000;
550        let pfn: u64 = 0x800;
551        let (cr3, mem) = PageTableBuilder::new()
552            .map_transition(vaddr, pfn)
553            .write_phys(pfn * 0x1000, &[0xCA, 0xFE, 0xBA, 0xBE])
554            .build();
555        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
556        let mut buf = [0u8; 4];
557        vas.read_virt(vaddr, &mut buf).unwrap();
558        assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE]);
559    }
560
561    #[test]
562    fn read_virt_pagefile_with_provider() {
563        let vaddr: u64 = 0xFFFF_8000_0010_0000;
564        let page_offset: u64 = 0x10;
565        let mut page_data = [0u8; 4096];
566        page_data[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
567
568        let (cr3, mem) = PageTableBuilder::new()
569            .map_pagefile(vaddr, 0, page_offset)
570            .build();
571
572        let mock = MockPagefileSource::new(0, vec![(page_offset, page_data)]);
573        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
574            .with_pagefile(Box::new(mock));
575
576        let mut buf = [0u8; 4];
577        vas.read_virt(vaddr, &mut buf).unwrap();
578        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
579    }
580
581    #[test]
582    fn read_virt_pagefile_without_provider_errors() {
583        let vaddr: u64 = 0xFFFF_8000_0010_0000;
584        let (cr3, mem) = PageTableBuilder::new().map_pagefile(vaddr, 0, 0x10).build();
585        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
586        let mut buf = [0u8; 4];
587        let result = vas.read_virt(vaddr, &mut buf);
588        assert!(result.is_err());
589        match result.unwrap_err() {
590            Error::PagedOut {
591                pagefile_num: 0,
592                page_offset: 0x10,
593                ..
594            } => {}
595            other => panic!("expected PagedOut, got: {other}"),
596        }
597    }
598
599    #[test]
600    fn read_virt_prototype_pte_errors() {
601        let vaddr: u64 = 0xFFFF_8000_0010_0000;
602        let (cr3, mem) = PageTableBuilder::new().map_prototype(vaddr).build();
603        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
604        let mut buf = [0u8; 4];
605        let result = vas.read_virt(vaddr, &mut buf);
606        assert!(result.is_err());
607        match result.unwrap_err() {
608            Error::PrototypePte(addr) => assert_eq!(addr, vaddr),
609            other => panic!("expected PrototypePte, got: {other}"),
610        }
611    }
612
613    #[test]
614    fn read_virt_pagefile_number_routing() {
615        let vaddr1: u64 = 0xFFFF_8000_0010_0000;
616        let vaddr2: u64 = 0xFFFF_8000_0010_1000;
617
618        let mut page0_data = [0u8; 4096];
619        page0_data[0..4].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
620        let mut page1_data = [0u8; 4096];
621        page1_data[0..4].copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
622
623        let (cr3, mem) = PageTableBuilder::new()
624            .map_pagefile(vaddr1, 0, 0x10)
625            .map_pagefile(vaddr2, 1, 0x20)
626            .build();
627
628        let mock0 = MockPagefileSource::new(0, vec![(0x10, page0_data)]);
629        let mock1 = MockPagefileSource::new(1, vec![(0x20, page1_data)]);
630
631        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
632            .with_pagefile(Box::new(mock0))
633            .with_pagefile(Box::new(mock1));
634
635        let mut buf1 = [0u8; 4];
636        vas.read_virt(vaddr1, &mut buf1).unwrap();
637        assert_eq!(buf1, [0x11, 0x22, 0x33, 0x44]);
638
639        let mut buf2 = [0u8; 4];
640        vas.read_virt(vaddr2, &mut buf2).unwrap();
641        assert_eq!(buf2, [0xAA, 0xBB, 0xCC, 0xDD]);
642    }
643
644    #[test]
645    fn read_virt_pagefile_out_of_range() {
646        let vaddr: u64 = 0xFFFF_8000_0010_0000;
647        let (cr3, mem) = PageTableBuilder::new()
648            .map_pagefile(vaddr, 0, 0x9999)
649            .build();
650        let mock = MockPagefileSource::new(0, vec![]);
651        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
652            .with_pagefile(Box::new(mock));
653        let mut buf = [0u8; 4];
654        let result = vas.read_virt(vaddr, &mut buf);
655        assert!(result.is_err());
656        match result.unwrap_err() {
657            Error::PagedOut {
658                page_offset: 0x9999,
659                ..
660            } => {}
661            other => panic!("expected PagedOut, got: {other}"),
662        }
663    }
664
665    #[test]
666    fn read_virt_mixed_pages_cross_boundary() {
667        let vaddr1: u64 = 0xFFFF_8000_0010_0000;
668        let vaddr2: u64 = 0xFFFF_8000_0010_1000;
669        let vaddr3: u64 = 0xFFFF_8000_0010_2000;
670        let paddr1: u64 = 0x0080_0000;
671
672        let mut pf_page = [0u8; 4096];
673        pf_page[0..4].copy_from_slice(&[0xBB; 4]);
674
675        let (cr3, mem) = PageTableBuilder::new()
676            .map_4k(vaddr1, paddr1, flags::WRITABLE)
677            .write_phys(paddr1 + 0xFFC, &[0xAA; 4])
678            .map_pagefile(vaddr2, 0, 0x10)
679            .map_demand_zero(vaddr3)
680            .build();
681
682        let mock = MockPagefileSource::new(0, vec![(0x10, pf_page)]);
683        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
684            .with_pagefile(Box::new(mock));
685
686        // Read spanning: last 4 bytes of phys page + first 4 bytes of pagefile page
687        let mut buf = [0u8; 8];
688        vas.read_virt(vaddr1 + 0xFFC, &mut buf).unwrap();
689        assert_eq!(buf, [0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB]);
690
691        // Read spanning: last 4 bytes of pagefile page + first 4 bytes of demand-zero page
692        let mut buf2 = [0u8; 8];
693        vas.read_virt(vaddr2 + 0xFFC, &mut buf2).unwrap();
694        assert_eq!(buf2, [0u8; 8]);
695    }
696
697    #[test]
698    fn read_virt_prototype_pte_resolves_when_source_provided() {
699        let vaddr: u64 = 0xFFFF_8000_0010_0000;
700        let resolved_paddr: u64 = 0x00A0_0000;
701        // Raw PTE: bit 10 set + some upper bits to identify this PTE
702        let raw_pte: u64 = (1 << 10) | (0xABCu64 << 12);
703
704        let (cr3, mem) = PageTableBuilder::new()
705            .map_prototype_raw(vaddr, raw_pte)
706            .write_phys(resolved_paddr, &[0xDE, 0xAD, 0xBE, 0xEF])
707            .build();
708
709        let mock = MockPrototypePteSource::new(vec![(raw_pte, resolved_paddr)]);
710        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
711            .with_prototype_source(Box::new(mock));
712
713        let mut buf = [0u8; 4];
714        vas.read_virt(vaddr, &mut buf).unwrap();
715        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
716    }
717
718    #[test]
719    fn read_virt_prototype_pte_errors_when_source_returns_none() {
720        let vaddr: u64 = 0xFFFF_8000_0010_0000;
721        let raw_pte: u64 = (1 << 10) | (0xDEFu64 << 12);
722
723        let (cr3, mem) = PageTableBuilder::new()
724            .map_prototype_raw(vaddr, raw_pte)
725            .build();
726
727        // Source returns None for this PTE value
728        let mock = MockPrototypePteSource::new(vec![]);
729        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
730            .with_prototype_source(Box::new(mock));
731
732        let mut buf = [0u8; 4];
733        let result = vas.read_virt(vaddr, &mut buf);
734        assert!(result.is_err());
735        match result.unwrap_err() {
736            Error::PrototypePte(addr) => assert_eq!(addr, vaddr),
737            other => panic!("expected PrototypePte, got: {other}"),
738        }
739    }
740
741    #[test]
742    fn read_virt_prototype_pte_with_page_offset() {
743        let vaddr_base: u64 = 0xFFFF_8000_0010_0000;
744        let vaddr: u64 = vaddr_base + 0x100;
745        let resolved_paddr: u64 = 0x00B0_0000;
746        let raw_pte: u64 = (1 << 10) | (0x123u64 << 12);
747
748        let (cr3, mem) = PageTableBuilder::new()
749            .map_prototype_raw(vaddr_base, raw_pte)
750            .write_phys(resolved_paddr + 0x100, &[0xCA, 0xFE])
751            .build();
752
753        let mock = MockPrototypePteSource::new(vec![(raw_pte, resolved_paddr)]);
754        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
755            .with_prototype_source(Box::new(mock));
756
757        let mut buf = [0u8; 2];
758        vas.read_virt(vaddr, &mut buf).unwrap();
759        assert_eq!(buf, [0xCA, 0xFE]);
760    }
761
762    #[test]
763    fn multiple_mappings_same_pml4() {
764        let vaddr1: u64 = 0xFFFF_8000_0010_0000;
765        let vaddr2: u64 = 0xFFFF_8000_0010_1000;
766        let paddr1: u64 = 0x0080_0000;
767        let paddr2: u64 = 0x0090_0000;
768
769        let (cr3, mem) = PageTableBuilder::new()
770            .map_4k(vaddr1, paddr1, flags::WRITABLE)
771            .map_4k(vaddr2, paddr2, flags::WRITABLE)
772            .write_phys(paddr1, &[0x11; 8])
773            .write_phys(paddr2, &[0x22; 8])
774            .build();
775
776        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
777
778        let mut buf1 = [0u8; 8];
779        vas.read_virt(vaddr1, &mut buf1).unwrap();
780        assert_eq!(buf1, [0x11; 8]);
781
782        let mut buf2 = [0u8; 8];
783        vas.read_virt(vaddr2, &mut buf2).unwrap();
784        assert_eq!(buf2, [0x22; 8]);
785    }
786}