Skip to main content

memf_core/
vas.rs

1//! Virtual address space and page table walking.
2
3use lru::LruCache;
4use memf_format::PhysicalMemoryProvider;
5use std::cell::RefCell;
6use std::num::NonZeroUsize;
7
8use crate::pagefile::PagefileSource;
9use crate::proto_pte::PrototypePteSource;
10use crate::{Error, Result};
11
12/// Translation mode for virtual-to-physical address translation.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum TranslationMode {
15    /// x86_64 4-level paging (PML4 → PDPT → PD → PT).
16    X86_64FourLevel,
17    /// x86_64 5-level paging (PML5 → PML4 → PDPT → PD → PT). Linux LA57, Windows Server 2025.
18    X86_645Level,
19    /// AArch64 4-level page tables (4K granule, 48-bit VA). Linux, Android, macOS ARM.
20    AArch64FourLevel,
21}
22
23/// A virtual address space backed by physical memory and page tables.
24pub struct VirtualAddressSpace<P: PhysicalMemoryProvider> {
25    physical: P,
26    page_table_root: u64,
27    mode: TranslationMode,
28    pagefiles: Vec<Box<dyn PagefileSource>>,
29    prototype_source: Option<Box<dyn PrototypePteSource>>,
30    /// LRU cache: page-aligned vaddr → page-aligned paddr.
31    tlb_cache: RefCell<LruCache<u64, u64>>,
32}
33
34// x86_64 page table constants
35const ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
36const PRESENT: u64 = 1;
37const PS: u64 = 1 << 7;
38
39// AArch64 4K-granule page table constants (ARMv8-A, 4K granule, 48-bit VA)
40const AARCH64_VALID: u64 = 1; // bit 0: entry is valid
41const AARCH64_TABLE: u64 = 1 << 1; // bit 1: 1=table/page, 0=block
42const AARCH64_OA_MASK: u64 = 0x0000_FFFF_FFFF_F000; // bits [47:12]: output address
43
44/// Number of page translations to cache per `VirtualAddressSpace` instance.
45const TRANSLATION_CACHE_CAPACITY: usize = 4096;
46
47/// Internal result of page table walk — not exposed publicly.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49enum TranslationResult {
50    /// Page is in physical memory at this address.
51    Physical(u64),
52    /// Page is demand-zero (all zeroes).
53    DemandZero,
54    /// Page is in a pagefile.
55    PagefileEntry { pagefile_num: u8, page_offset: u64 },
56    /// Page is a transition page (still in physical memory at this PFN-derived address).
57    Transition(u64),
58    /// Page uses a prototype PTE; carries the raw PTE value for resolution.
59    Prototype(u64),
60}
61
62impl<P: PhysicalMemoryProvider> VirtualAddressSpace<P> {
63    /// Create a new virtual address space.
64    pub fn new(physical: P, page_table_root: u64, mode: TranslationMode) -> Self {
65        Self {
66            physical,
67            page_table_root,
68            mode,
69            pagefiles: Vec::new(),
70            prototype_source: None,
71            // TRANSLATION_CACHE_CAPACITY is a non-zero compile-time constant; the
72            // fallback to NonZeroUsize::MIN keeps construction infallible if it is
73            // ever set to 0.
74            tlb_cache: RefCell::new(LruCache::new(
75                NonZeroUsize::new(TRANSLATION_CACHE_CAPACITY).unwrap_or(NonZeroUsize::MIN),
76            )),
77        }
78    }
79
80    /// Attach a pagefile source for resolving paged-out memory.
81    pub fn with_pagefile(mut self, source: Box<dyn PagefileSource>) -> Self {
82        self.pagefiles.push(source);
83        self
84    }
85
86    /// Attach a prototype PTE source for resolving shared section pages.
87    pub fn with_prototype_source(mut self, source: Box<dyn PrototypePteSource>) -> Self {
88        self.prototype_source = Some(source);
89        self
90    }
91
92    /// Translate a virtual address to a physical address.
93    pub fn virt_to_phys(&self, vaddr: u64) -> Result<u64> {
94        match self.mode {
95            TranslationMode::X86_64FourLevel => self.walk_x86_64_4level(vaddr),
96            TranslationMode::X86_645Level => match self.walk_x86_64_5level_internal(vaddr)? {
97                TranslationResult::Physical(addr) | TranslationResult::Transition(addr) => Ok(addr),
98                TranslationResult::DemandZero => Err(Error::PageNotPresent(vaddr)),
99                TranslationResult::PagefileEntry {
100                    pagefile_num,
101                    page_offset,
102                } => Err(Error::PagedOut {
103                    vaddr,
104                    pagefile_num,
105                    page_offset,
106                }),
107                TranslationResult::Prototype(_) => Err(Error::PrototypePte(vaddr)),
108            },
109            TranslationMode::AArch64FourLevel => self.walk_aarch64_4level(vaddr),
110        }
111    }
112
113    /// Read `buf.len()` bytes from virtual address `vaddr`, handling page boundary crossings.
114    ///
115    /// Uses `walk_x86_64_4level_internal()` to resolve each 4K chunk, transparently
116    /// handling physical, transition, demand-zero, and pagefile pages.
117    pub fn read_virt(&self, vaddr: u64, buf: &mut [u8]) -> Result<()> {
118        if buf.is_empty() {
119            return Ok(());
120        }
121
122        let mut offset = 0usize;
123        let mut current_vaddr = vaddr;
124
125        while offset < buf.len() {
126            let page_off = (current_vaddr & 0xFFF) as usize;
127            let remaining_in_page = 0x1000 - page_off;
128            let remaining_to_read = buf.len() - offset;
129            let chunk = remaining_to_read.min(remaining_in_page);
130
131            let result = match self.mode {
132                TranslationMode::X86_64FourLevel => {
133                    self.walk_x86_64_4level_internal(current_vaddr)?
134                }
135                TranslationMode::X86_645Level => self.walk_x86_64_5level_internal(current_vaddr)?,
136                TranslationMode::AArch64FourLevel => self.walk_aarch64_internal(current_vaddr)?,
137            };
138
139            match result {
140                TranslationResult::Physical(paddr) | TranslationResult::Transition(paddr) => {
141                    let n = self
142                        .physical
143                        .read_phys(paddr, &mut buf[offset..offset + chunk])?;
144                    if n == 0 {
145                        return Err(Error::PartialRead {
146                            addr: vaddr,
147                            requested: buf.len(),
148                            got: offset,
149                        });
150                    }
151                    offset += n;
152                    current_vaddr = current_vaddr.wrapping_add(n as u64);
153                }
154                TranslationResult::DemandZero => {
155                    buf[offset..offset + chunk].fill(0);
156                    offset += chunk;
157                    current_vaddr = current_vaddr.wrapping_add(chunk as u64);
158                }
159                TranslationResult::PagefileEntry {
160                    pagefile_num,
161                    page_offset,
162                } => {
163                    let page = self.read_pagefile_page(current_vaddr, pagefile_num, page_offset)?;
164                    buf[offset..offset + chunk].copy_from_slice(&page[page_off..page_off + chunk]);
165                    offset += chunk;
166                    current_vaddr = current_vaddr.wrapping_add(chunk as u64);
167                }
168                TranslationResult::Prototype(raw_pte) => {
169                    if let Some(ref source) = self.prototype_source {
170                        if let Some(phys_base) = source.resolve(raw_pte) {
171                            let paddr = phys_base + (current_vaddr & 0xFFF);
172                            let n = self
173                                .physical
174                                .read_phys(paddr, &mut buf[offset..offset + chunk])?;
175                            if n == 0 {
176                                return Err(Error::PartialRead {
177                                    addr: vaddr,
178                                    requested: buf.len(),
179                                    got: offset,
180                                });
181                            }
182                            offset += n;
183                            current_vaddr = current_vaddr.wrapping_add(n as u64);
184                        } else {
185                            return Err(Error::PrototypePte(current_vaddr));
186                        }
187                    } else {
188                        return Err(Error::PrototypePte(current_vaddr));
189                    }
190                }
191            }
192        }
193
194        Ok(())
195    }
196
197    fn read_pagefile_page(
198        &self,
199        vaddr: u64,
200        pagefile_num: u8,
201        page_offset: u64,
202    ) -> Result<[u8; 4096]> {
203        for source in &self.pagefiles {
204            if source.pagefile_number() == pagefile_num {
205                if let Some(page) = source.read_page(page_offset)? {
206                    return Ok(page);
207                }
208                break;
209            }
210        }
211        Err(Error::PagedOut {
212            vaddr,
213            pagefile_num,
214            page_offset,
215        })
216    }
217
218    /// Return a reference to the underlying physical memory provider.
219    pub fn physical(&self) -> &P {
220        &self.physical
221    }
222
223    /// Return the translation mode.
224    pub fn mode(&self) -> TranslationMode {
225        self.mode
226    }
227
228    /// Reverse-map: return a kernel-half virtual address that resolves to the
229    /// page at `target_phys` (page granularity), or `None`.
230    ///
231    /// Enumerates the x86-64 four-level page tables under the current root over
232    /// the kernel half (PML4 indices 256..512), checking 2 MiB / 4 KiB leaves and
233    /// **transition** PTEs — a resident page whose PTE is not marked valid, which
234    /// is exactly the state of a live-acquired kernel image whose header reads as
235    /// not-present through a stale/guessed VA. This is how the kernel image base
236    /// is recovered when the low-stub hint is wrong: physically locate ntoskrnl,
237    /// then ask which kernel VA maps to it. `max_leaves` bounds the work so a
238    /// crafted page-table tree cannot turn this into a denial of service.
239    ///
240    /// Only meaningful for [`TranslationMode::X86_64FourLevel`]; returns `None`
241    /// for other modes. 1 GiB pages are skipped (the kernel image is mapped with
242    /// 2 MiB / 4 KiB pages, never inside a 1 GiB page).
243    #[must_use]
244    pub fn find_kernel_va_for_phys(&self, target_phys: u64, max_leaves: usize) -> Option<u64> {
245        if !matches!(self.mode, TranslationMode::X86_64FourLevel) {
246            return None;
247        }
248        let target = target_phys & !0xFFF;
249        let root = self.page_table_root;
250        let mut budget = max_leaves;
251        // p4 >= 256 ⇒ bit 47 is always set, so canonicalization always
252        // sign-extends into the kernel half.
253        let canon = |p4: u64, p3: u64, p2: u64, p1: u64| -> u64 {
254            ((p4 << 39) | (p3 << 30) | (p2 << 21) | (p1 << 12)) | 0xFFFF_0000_0000_0000
255        };
256        for p4 in 256u64..512 {
257            let Ok(pml4e) = self.read_pte(root + p4 * 8) else {
258                continue;
259            };
260            if pml4e & PRESENT == 0 {
261                continue;
262            }
263            let pdpt = pml4e & ADDR_MASK;
264            for p3 in 0u64..512 {
265                let Ok(pdpte) = self.read_pte(pdpt + p3 * 8) else {
266                    continue;
267                };
268                if pdpte & PRESENT == 0 {
269                    continue;
270                }
271                // 1 GiB pages are not used to map the kernel image; skip them.
272                if pdpte & PS != 0 {
273                    continue;
274                }
275                let pd = pdpte & ADDR_MASK;
276                for p2 in 0u64..512 {
277                    let Ok(pde) = self.read_pte(pd + p2 * 8) else {
278                        continue;
279                    };
280                    if pde & PRESENT == 0 {
281                        continue;
282                    }
283                    if pde & PS != 0 {
284                        let base = pde & 0x000F_FFFF_FFE0_0000;
285                        if target >= base && target < base + (1 << 21) {
286                            return Some(canon(p4, p3, p2, 0) | (target - base));
287                        }
288                        budget = budget.checked_sub(1)?;
289                        continue;
290                    }
291                    let pt = pde & ADDR_MASK;
292                    for p1 in 0u64..512 {
293                        let Ok(pte) = self.read_pte(pt + p1 * 8) else {
294                            continue;
295                        };
296                        // Match present OR transition leaves: a transition PTE
297                        // (PRESENT=0, bit 11 set) still points at a resident page.
298                        let leaf = if pte & PRESENT != 0 {
299                            Some(pte & ADDR_MASK)
300                        } else if pte & (1 << 11) != 0 {
301                            Some(((pte >> 12) & 0xF_FFFF_FFFF) * 0x1000)
302                        } else {
303                            None
304                        };
305                        match leaf {
306                            Some(phys) if phys == target => return Some(canon(p4, p3, p2, p1)),
307                            Some(_) => budget = budget.checked_sub(1)?,
308                            None => {}
309                        }
310                    }
311                }
312            }
313        }
314        None
315    }
316
317    fn read_pte(&self, addr: u64) -> Result<u64> {
318        let mut buf = [0u8; 8];
319        let n = self.physical.read_phys(addr, &mut buf)?;
320        if n < 8 {
321            return Err(Error::PartialRead {
322                addr,
323                requested: 8,
324                got: n,
325            });
326        }
327        Ok(u64::from_le_bytes(buf))
328    }
329
330    fn walk_x86_64_4level(&self, vaddr: u64) -> Result<u64> {
331        let result = self.walk_x86_64_4level_internal(vaddr)?;
332        match result {
333            TranslationResult::Physical(addr) | TranslationResult::Transition(addr) => Ok(addr),
334            TranslationResult::DemandZero => Err(Error::PageNotPresent(vaddr)),
335            TranslationResult::PagefileEntry {
336                pagefile_num,
337                page_offset,
338            } => Err(Error::PagedOut {
339                vaddr,
340                pagefile_num,
341                page_offset,
342            }),
343            TranslationResult::Prototype(_) => Err(Error::PrototypePte(vaddr)),
344        }
345    }
346
347    fn walk_x86_64_4level_internal(&self, vaddr: u64) -> Result<TranslationResult> {
348        self.walk_4level_from(self.page_table_root, vaddr)
349    }
350
351    fn walk_4level_from(&self, pml4_root: u64, vaddr: u64) -> Result<TranslationResult> {
352        let page_vaddr = vaddr & !0xFFF;
353        // peek avoids promoting on read; no mut borrow needed on the hot path.
354        if let Some(&paddr_base) = self.tlb_cache.borrow().peek(&page_vaddr) {
355            return Ok(TranslationResult::Physical(paddr_base | (vaddr & 0xFFF)));
356        }
357
358        let pml4_idx = (vaddr >> 39) & 0x1FF;
359        let pdpt_idx = (vaddr >> 30) & 0x1FF;
360        let pd_idx = (vaddr >> 21) & 0x1FF;
361        let pt_idx = (vaddr >> 12) & 0x1FF;
362        let page_offset = vaddr & 0xFFF;
363
364        // PML4
365        let pml4e = self.read_pte(pml4_root + pml4_idx * 8)?;
366        if pml4e & PRESENT == 0 {
367            return Err(Error::PageNotPresent(vaddr));
368        }
369
370        // PDPT
371        let pdpt_base = pml4e & ADDR_MASK;
372        let pdpte = self.read_pte(pdpt_base + pdpt_idx * 8)?;
373        if pdpte & PRESENT == 0 {
374            return Err(Error::PageNotPresent(vaddr));
375        }
376
377        // 1GB huge page check
378        if pdpte & PS != 0 {
379            let phys_base = pdpte & 0x000F_FFFF_C000_0000;
380            let offset_1g = vaddr & 0x3FFF_FFFF;
381            let phys = phys_base | offset_1g;
382            self.tlb_cache.borrow_mut().put(page_vaddr, phys & !0xFFF);
383            return Ok(TranslationResult::Physical(phys));
384        }
385
386        // PD
387        let pd_base = pdpte & ADDR_MASK;
388        let pde = self.read_pte(pd_base + pd_idx * 8)?;
389        if pde & PRESENT == 0 {
390            return Err(Error::PageNotPresent(vaddr));
391        }
392
393        // 2MB large page check
394        if pde & PS != 0 {
395            let phys_base = pde & 0x000F_FFFF_FFE0_0000;
396            let offset_2m = vaddr & 0x1F_FFFF;
397            let phys = phys_base | offset_2m;
398            self.tlb_cache.borrow_mut().put(page_vaddr, phys & !0xFFF);
399            return Ok(TranslationResult::Physical(phys));
400        }
401
402        // PT (4K page)
403        let pt_base = pde & ADDR_MASK;
404        let pte = self.read_pte(pt_base + pt_idx * 8)?;
405
406        if pte & PRESENT != 0 {
407            let phys_base = pte & ADDR_MASK;
408            let phys = phys_base | page_offset;
409            self.tlb_cache.borrow_mut().put(page_vaddr, phys_base);
410            return Ok(TranslationResult::Physical(phys));
411        }
412
413        // Non-present PTE decoding (PT level only)
414        Ok(Self::decode_non_present_pte(pte, page_offset))
415    }
416
417    fn walk_x86_64_5level_internal(&self, vaddr: u64) -> Result<TranslationResult> {
418        let pml5_idx = (vaddr >> 48) & 0x1FF;
419        let pml5e = self.read_pte(self.page_table_root + pml5_idx * 8)?;
420        if pml5e & PRESENT == 0 {
421            return Err(Error::PageNotPresent(vaddr));
422        }
423        let pml4_root = pml5e & ADDR_MASK;
424        self.walk_4level_from(pml4_root, vaddr)
425    }
426
427    fn walk_aarch64_4level(&self, vaddr: u64) -> Result<u64> {
428        match self.walk_aarch64_internal(vaddr)? {
429            TranslationResult::Physical(addr) | TranslationResult::Transition(addr) => Ok(addr),
430            TranslationResult::DemandZero
431            | TranslationResult::PagefileEntry { .. }
432            | TranslationResult::Prototype(_) => Err(Error::PageNotPresent(vaddr)),
433        }
434    }
435
436    fn walk_aarch64_internal(&self, vaddr: u64) -> Result<TranslationResult> {
437        let page_vaddr = vaddr & !0xFFF;
438        // peek avoids promoting on read; no mut borrow needed on the hot path.
439        if let Some(&paddr_base) = self.tlb_cache.borrow().peek(&page_vaddr) {
440            return Ok(TranslationResult::Physical(paddr_base | (vaddr & 0xFFF)));
441        }
442
443        let l0_idx = (vaddr >> 39) & 0x1FF;
444        let l1_idx = (vaddr >> 30) & 0x1FF;
445        let l2_idx = (vaddr >> 21) & 0x1FF;
446        let l3_idx = (vaddr >> 12) & 0x1FF;
447        let page_off = vaddr & 0xFFF;
448
449        // Level 0 (PGD)
450        let l0e = self.read_pte(self.page_table_root + l0_idx * 8)?;
451        if l0e & AARCH64_VALID == 0 {
452            return Err(Error::PageNotPresent(vaddr));
453        }
454        let l1_base = l0e & AARCH64_OA_MASK;
455
456        // Level 1 (PUD)
457        let l1e = self.read_pte(l1_base + l1_idx * 8)?;
458        if l1e & AARCH64_VALID == 0 {
459            return Err(Error::PageNotPresent(vaddr));
460        }
461        if l1e & AARCH64_TABLE == 0 {
462            // 1GB block entry
463            let phys_base = l1e & 0x0000_FFFF_C000_0000;
464            let phys = phys_base | (vaddr & 0x3FFF_FFFF);
465            self.tlb_cache.borrow_mut().put(page_vaddr, phys & !0xFFF);
466            return Ok(TranslationResult::Physical(phys));
467        }
468        let l2_base = l1e & AARCH64_OA_MASK;
469
470        // Level 2 (PMD)
471        let l2e = self.read_pte(l2_base + l2_idx * 8)?;
472        if l2e & AARCH64_VALID == 0 {
473            return Err(Error::PageNotPresent(vaddr));
474        }
475        if l2e & AARCH64_TABLE == 0 {
476            // 2MB block entry
477            let phys_base = l2e & 0x0000_FFFF_FFE0_0000;
478            let phys = phys_base | (vaddr & 0x001F_FFFF);
479            self.tlb_cache.borrow_mut().put(page_vaddr, phys & !0xFFF);
480            return Ok(TranslationResult::Physical(phys));
481        }
482        let l3_base = l2e & AARCH64_OA_MASK;
483
484        // Level 3 (PTE) — bit1=1 means "page" at L3
485        let l3e = self.read_pte(l3_base + l3_idx * 8)?;
486        if l3e & AARCH64_VALID == 0 {
487            return Err(Error::PageNotPresent(vaddr));
488        }
489        let phys_base = l3e & AARCH64_OA_MASK;
490        let phys = phys_base | page_off;
491        self.tlb_cache.borrow_mut().put(page_vaddr, phys_base);
492        Ok(TranslationResult::Physical(phys))
493    }
494
495    fn decode_non_present_pte(pte: u64, page_offset: u64) -> TranslationResult {
496        if pte == 0 {
497            return TranslationResult::DemandZero;
498        }
499        if pte & (1 << 11) != 0 {
500            let pfn = (pte >> 12) & 0xF_FFFF_FFFF;
501            return TranslationResult::Transition(pfn * 0x1000 + page_offset);
502        }
503        if pte & (1 << 10) != 0 {
504            return TranslationResult::Prototype(pte);
505        }
506        let pagefile_num = ((pte >> 1) & 0xF) as u8;
507        let pf_page_offset = (pte >> 12) & 0xF_FFFF_FFFF;
508        TranslationResult::PagefileEntry {
509            pagefile_num,
510            page_offset: pf_page_offset,
511        }
512    }
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518    use crate::test_builders::{flags, PageTableBuilder, SyntheticPhysMem};
519
520    /// Build a minimal 4-level page table (CR3=0) mapping `vaddr`'s leaf PTE to
521    /// the raw `leaf_pte` value, so tests can install present/transition/large
522    /// entries directly. Tables live at fixed physical pages.
523    fn manual_pt(vaddr: u64, leaf_pte: u64, large_2m: bool) -> SyntheticPhysMem {
524        let mut mem = SyntheticPhysMem::new(16 * 1024 * 1024);
525        let p4 = (vaddr >> 39) & 0x1FF;
526        let p3 = (vaddr >> 30) & 0x1FF;
527        let p2 = (vaddr >> 21) & 0x1FF;
528        let p1 = (vaddr >> 12) & 0x1FF;
529        let (pdpt, pd, pt) = (0x1000u64, 0x2000u64, 0x3000u64);
530        mem.write_u64(p4 * 8, pdpt | PRESENT); // PML4 base is physical 0
531        mem.write_u64(pdpt + p3 * 8, pd | PRESENT);
532        if large_2m {
533            mem.write_u64(pd + p2 * 8, leaf_pte);
534        } else {
535            mem.write_u64(pd + p2 * 8, pt | PRESENT);
536            mem.write_u64(pt + p1 * 8, leaf_pte);
537        }
538        mem
539    }
540
541    #[test]
542    fn reverse_map_finds_4k_present_page() {
543        let vaddr = 0xFFFF_F802_4020_1000u64;
544        let phys = 0x6000u64;
545        let mem = manual_pt(vaddr, phys | PRESENT, false);
546        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
547        assert_eq!(vas.find_kernel_va_for_phys(phys, 100_000), Some(vaddr));
548    }
549
550    #[test]
551    fn reverse_map_finds_transition_page() {
552        // Transition PTE: PRESENT=0, bit 11 set, PFN in bits 12.. — a resident
553        // page (e.g. a paged-image header) that a stale VA reads as not-present.
554        let vaddr = 0xFFFF_F802_4020_2000u64;
555        let pfn = 0x7u64; // phys 0x7000
556        let trans_pte = (pfn << 12) | (1 << 11);
557        let mem = manual_pt(vaddr, trans_pte, false);
558        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
559        assert_eq!(vas.find_kernel_va_for_phys(0x7000, 100_000), Some(vaddr));
560    }
561
562    #[test]
563    fn reverse_map_finds_2m_large_page_with_offset() {
564        let vaddr = 0xFFFF_F802_4040_0000u64; // 2 MiB aligned
565        let base = 0x20_0000u64;
566        let mem = manual_pt(vaddr, base | PRESENT | PS, true);
567        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
568        // A phys 0x3000 into the 2 MiB page maps to vaddr + 0x3000.
569        assert_eq!(
570            vas.find_kernel_va_for_phys(base + 0x3000, 100_000),
571            Some(vaddr + 0x3000)
572        );
573    }
574
575    #[test]
576    fn reverse_map_returns_none_when_absent() {
577        let mem = manual_pt(0xFFFF_F802_4020_1000u64, 0x6000 | PRESENT, false);
578        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
579        assert_eq!(vas.find_kernel_va_for_phys(0xDEAD_0000, 100_000), None);
580    }
581
582    #[test]
583    fn reverse_map_respects_max_leaves_budget() {
584        // Two present leaves, neither matching; a budget of 1 must give up
585        // (the second non-matching leaf exhausts the budget) and return None.
586        let vaddr = 0xFFFF_F802_4020_1000u64;
587        let mut mem = manual_pt(vaddr, 0x6000 | PRESENT, false);
588        let p2 = (vaddr >> 21) & 0x1FF;
589        let pt = 0x3000u64;
590        // Add a second present leaf in the same PT at index +1.
591        let p1b = ((vaddr >> 12) & 0x1FF) + 1;
592        let _ = p2;
593        mem.write_u64(pt + p1b * 8, 0x9000 | PRESENT);
594        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
595        assert_eq!(vas.find_kernel_va_for_phys(0xABCD_0000, 1), None);
596    }
597
598    #[test]
599    fn reverse_map_skips_1g_huge_page() {
600        // A present 1 GiB PDPTE is skipped (the kernel image is never mapped in
601        // a 1 GiB page); the search returns None even if target falls inside it.
602        let vaddr = 0xFFFF_F802_4000_0000u64;
603        let mut mem = SyntheticPhysMem::new(16 * 1024 * 1024);
604        let p4 = (vaddr >> 39) & 0x1FF;
605        let p3 = (vaddr >> 30) & 0x1FF;
606        mem.write_u64(p4 * 8, 0x1000 | PRESENT);
607        mem.write_u64(0x1000 + p3 * 8, 0x4000_0000 | PRESENT | PS); // 1 GiB page
608        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
609        assert_eq!(vas.find_kernel_va_for_phys(0x4000_1000, 100_000), None);
610    }
611
612    #[test]
613    fn reverse_map_skips_read_error_branch() {
614        // A PML4E pointing past the synthetic image makes the PDPT read fail;
615        // the walk must skip that subtree, then find the valid mapping elsewhere.
616        let vaddr = 0xFFFF_F802_4020_1000u64;
617        let mut mem = manual_pt(vaddr, 0x6000 | PRESENT, false);
618        // Point PML4 index 256 at an out-of-range PDPT (16 MiB image, this is 64 MiB).
619        mem.write_u64(256 * 8, 0x400_0000 | PRESENT);
620        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
621        assert_eq!(vas.find_kernel_va_for_phys(0x6000, 100_000), Some(vaddr));
622    }
623
624    #[test]
625    fn reverse_map_skips_non_matching_2m_page() {
626        // A present 2 MiB page that does not contain the target is skipped; the
627        // search then finds nothing (covers the large-page non-match branch).
628        let vaddr = 0xFFFF_F802_4040_0000u64;
629        let mem = manual_pt(vaddr, 0x20_0000 | PRESENT | PS, true);
630        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::X86_64FourLevel);
631        assert_eq!(vas.find_kernel_va_for_phys(0x40_0000, 100_000), None);
632    }
633
634    #[test]
635    fn reverse_map_none_for_non_4level_mode() {
636        let mem = manual_pt(0xFFFF_F802_4020_1000u64, 0x6000 | PRESENT, false);
637        let vas = VirtualAddressSpace::new(mem, 0, TranslationMode::AArch64FourLevel);
638        assert_eq!(vas.find_kernel_va_for_phys(0x6000, 100_000), None);
639    }
640
641    #[test]
642    fn translate_4k_page() {
643        let vaddr: u64 = 0xFFFF_8000_0010_0000;
644        let paddr: u64 = 0x0080_0000;
645        let (cr3, mem) = PageTableBuilder::new()
646            .map_4k(vaddr, paddr, flags::WRITABLE)
647            .build();
648        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
649        let result = vas.virt_to_phys(vaddr).unwrap();
650        assert_eq!(result, paddr);
651    }
652
653    #[test]
654    fn translate_4k_with_offset() {
655        let vaddr: u64 = 0xFFFF_8000_0010_0ABC;
656        let paddr_base: u64 = 0x0080_0000;
657        let (cr3, mem) = PageTableBuilder::new()
658            .map_4k(vaddr & !0xFFF, paddr_base, flags::WRITABLE)
659            .build();
660        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
661        let result = vas.virt_to_phys(vaddr).unwrap();
662        assert_eq!(result, paddr_base + 0xABC);
663    }
664
665    #[test]
666    fn read_virt_4k() {
667        let vaddr: u64 = 0xFFFF_8000_0010_0000;
668        let paddr: u64 = 0x0080_0000;
669        let (cr3, mem) = PageTableBuilder::new()
670            .map_4k(vaddr, paddr, flags::WRITABLE)
671            .write_phys(paddr, &[0xDE, 0xAD, 0xBE, 0xEF])
672            .build();
673        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
674        let mut buf = [0u8; 4];
675        vas.read_virt(vaddr, &mut buf).unwrap();
676        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
677    }
678
679    #[test]
680    fn translate_2mb_page() {
681        let vaddr: u64 = 0xFFFF_8000_0020_0000;
682        let paddr: u64 = 0x0100_0000;
683        let (cr3, mem) = PageTableBuilder::new()
684            .map_2m(vaddr, paddr, flags::WRITABLE)
685            .build();
686        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
687        let result = vas.virt_to_phys(vaddr).unwrap();
688        assert_eq!(result, paddr);
689
690        // Test with offset within the 2MB page
691        let result_offset = vas.virt_to_phys(vaddr + 0x1234).unwrap();
692        assert_eq!(result_offset, paddr + 0x1234);
693    }
694
695    #[test]
696    fn translate_1gb_page() {
697        let vaddr: u64 = 0xFFFF_8000_4000_0000;
698        let paddr: u64 = 0x4000_0000;
699        let (cr3, mem) = PageTableBuilder::new()
700            .map_1g(vaddr, paddr, flags::WRITABLE)
701            .build();
702        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
703        let result = vas.virt_to_phys(vaddr).unwrap();
704        assert_eq!(result, paddr);
705
706        // Test with offset within the 1GB page
707        let result_offset = vas.virt_to_phys(vaddr + 0x12_3456).unwrap();
708        assert_eq!(result_offset, paddr + 0x12_3456);
709    }
710
711    #[test]
712    fn non_present_page_returns_error() {
713        let (cr3, mem) = PageTableBuilder::new().build();
714        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
715        let result = vas.virt_to_phys(0xFFFF_8000_0010_0000);
716        assert!(result.is_err());
717        match result.unwrap_err() {
718            Error::PageNotPresent(addr) => assert_eq!(addr, 0xFFFF_8000_0010_0000),
719            other => panic!("unexpected error: {other}"),
720        }
721    }
722
723    #[test]
724    fn read_virt_cross_page_boundary() {
725        // Map two adjacent virtual pages to different physical pages
726        let vaddr_page1: u64 = 0xFFFF_8000_0010_0000;
727        let vaddr_page2: u64 = 0xFFFF_8000_0010_1000;
728        let paddr1: u64 = 0x0080_0000;
729        let paddr2: u64 = 0x0090_0000;
730
731        // Write data at end of page1 and start of page2
732        let (cr3, mem) = PageTableBuilder::new()
733            .map_4k(vaddr_page1, paddr1, flags::WRITABLE)
734            .map_4k(vaddr_page2, paddr2, flags::WRITABLE)
735            .write_phys(paddr1 + 0xFFC, &[0xAA, 0xBB, 0xCC, 0xDD])
736            .write_phys(paddr2, &[0x11, 0x22, 0x33, 0x44])
737            .build();
738
739        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
740        let mut buf = [0u8; 8];
741        vas.read_virt(vaddr_page1 + 0xFFC, &mut buf).unwrap();
742        assert_eq!(buf, [0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44]);
743    }
744
745    #[test]
746    fn read_virt_empty_buffer() {
747        let (cr3, mem) = PageTableBuilder::new().build();
748        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
749        let mut buf = [];
750        vas.read_virt(0xFFFF_8000_0010_0000, &mut buf).unwrap();
751    }
752
753    #[test]
754    fn virt_to_phys_4k_direct() {
755        // Test virt_to_phys as the public API (not just internal translate)
756        let vaddr: u64 = 0xFFFF_8000_0010_0000;
757        let paddr: u64 = 0x0080_0000;
758        let (cr3, mem) = PageTableBuilder::new()
759            .map_4k(vaddr, paddr, flags::WRITABLE)
760            .build();
761        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
762        assert_eq!(vas.virt_to_phys(vaddr).unwrap(), paddr);
763        assert_eq!(vas.virt_to_phys(vaddr + 0x42).unwrap(), paddr + 0x42);
764    }
765
766    #[test]
767    fn physical_accessor() {
768        let (cr3, mem) = PageTableBuilder::new()
769            .write_phys(0x5000, &[0xAB; 8])
770            .build();
771        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
772        let phys = vas.physical();
773        let mut buf = [0u8; 4];
774        let n = phys.read_phys(0x5000, &mut buf).unwrap();
775        assert_eq!(n, 4);
776        assert_eq!(buf, [0xAB; 4]);
777    }
778
779    #[test]
780    fn demand_zero_pte_returns_page_not_present() {
781        let vaddr: u64 = 0xFFFF_8000_0010_0000;
782        let (cr3, mem) = PageTableBuilder::new().map_demand_zero(vaddr).build();
783        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
784        let result = vas.virt_to_phys(vaddr);
785        assert!(result.is_err());
786        match result.unwrap_err() {
787            Error::PageNotPresent(addr) => assert_eq!(addr, vaddr),
788            other => panic!("expected PageNotPresent, got: {other}"),
789        }
790    }
791
792    #[test]
793    fn transition_pte_resolves_to_physical() {
794        let vaddr: u64 = 0xFFFF_8000_0010_0000;
795        let pfn: u64 = 0x800;
796        let (cr3, mem) = PageTableBuilder::new()
797            .map_transition(vaddr, pfn)
798            .write_phys(pfn * 0x1000, &[0xDE, 0xAD, 0xBE, 0xEF])
799            .build();
800        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
801        let paddr = vas.virt_to_phys(vaddr).unwrap();
802        assert_eq!(paddr, pfn * 0x1000);
803    }
804
805    #[test]
806    fn transition_pte_with_offset() {
807        let vaddr_base: u64 = 0xFFFF_8000_0010_0000;
808        let vaddr: u64 = vaddr_base + 0x42;
809        let pfn: u64 = 0x800;
810        let (cr3, mem) = PageTableBuilder::new()
811            .map_transition(vaddr_base, pfn)
812            .build();
813        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
814        let paddr = vas.virt_to_phys(vaddr).unwrap();
815        assert_eq!(paddr, pfn * 0x1000 + 0x42);
816    }
817
818    #[test]
819    fn prototype_pte_returns_error() {
820        let vaddr: u64 = 0xFFFF_8000_0010_0000;
821        let (cr3, mem) = PageTableBuilder::new().map_prototype(vaddr).build();
822        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
823        let result = vas.virt_to_phys(vaddr);
824        assert!(result.is_err());
825        match result.unwrap_err() {
826            Error::PrototypePte(addr) => assert_eq!(addr, vaddr),
827            other => panic!("expected PrototypePte, got: {other}"),
828        }
829    }
830
831    #[test]
832    fn pagefile_pte_returns_paged_out() {
833        let vaddr: u64 = 0xFFFF_8000_0010_0000;
834        let (cr3, mem) = PageTableBuilder::new()
835            .map_pagefile(vaddr, 0, 0x1234)
836            .build();
837        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
838        let result = vas.virt_to_phys(vaddr);
839        assert!(result.is_err());
840        match result.unwrap_err() {
841            Error::PagedOut {
842                vaddr: v,
843                pagefile_num,
844                page_offset,
845            } => {
846                assert_eq!(v, vaddr);
847                assert_eq!(pagefile_num, 0);
848                assert_eq!(page_offset, 0x1234);
849            }
850            other => panic!("expected PagedOut, got: {other}"),
851        }
852    }
853
854    #[test]
855    fn pagefile_pte_number_routing() {
856        let vaddr: u64 = 0xFFFF_8000_0010_0000;
857        let (cr3, mem) = PageTableBuilder::new()
858            .map_pagefile(vaddr, 2, 0xABCD)
859            .build();
860        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
861        let result = vas.virt_to_phys(vaddr);
862        match result.unwrap_err() {
863            Error::PagedOut {
864                pagefile_num,
865                page_offset,
866                ..
867            } => {
868                assert_eq!(pagefile_num, 2);
869                assert_eq!(page_offset, 0xABCD);
870            }
871            other => panic!("expected PagedOut, got: {other}"),
872        }
873    }
874
875    use crate::test_builders::{MockPagefileSource, MockPrototypePteSource};
876
877    #[test]
878    fn read_virt_demand_zero_returns_zeroes() {
879        let vaddr: u64 = 0xFFFF_8000_0010_0000;
880        let (cr3, mem) = PageTableBuilder::new().map_demand_zero(vaddr).build();
881        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
882        let mut buf = [0xFFu8; 4096];
883        vas.read_virt(vaddr, &mut buf).unwrap();
884        assert!(
885            buf.iter().all(|&b| b == 0),
886            "demand-zero page must be all zeroes"
887        );
888    }
889
890    #[test]
891    fn read_virt_transition_reads_physical() {
892        let vaddr: u64 = 0xFFFF_8000_0010_0000;
893        let pfn: u64 = 0x800;
894        let (cr3, mem) = PageTableBuilder::new()
895            .map_transition(vaddr, pfn)
896            .write_phys(pfn * 0x1000, &[0xCA, 0xFE, 0xBA, 0xBE])
897            .build();
898        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
899        let mut buf = [0u8; 4];
900        vas.read_virt(vaddr, &mut buf).unwrap();
901        assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE]);
902    }
903
904    #[test]
905    fn read_virt_pagefile_with_provider() {
906        let vaddr: u64 = 0xFFFF_8000_0010_0000;
907        let page_offset: u64 = 0x10;
908        let mut page_data = [0u8; 4096];
909        page_data[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
910
911        let (cr3, mem) = PageTableBuilder::new()
912            .map_pagefile(vaddr, 0, page_offset)
913            .build();
914
915        let mock = MockPagefileSource::new(0, vec![(page_offset, page_data)]);
916        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
917            .with_pagefile(Box::new(mock));
918
919        let mut buf = [0u8; 4];
920        vas.read_virt(vaddr, &mut buf).unwrap();
921        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
922    }
923
924    #[test]
925    fn read_virt_pagefile_without_provider_errors() {
926        let vaddr: u64 = 0xFFFF_8000_0010_0000;
927        let (cr3, mem) = PageTableBuilder::new().map_pagefile(vaddr, 0, 0x10).build();
928        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
929        let mut buf = [0u8; 4];
930        let result = vas.read_virt(vaddr, &mut buf);
931        assert!(result.is_err());
932        match result.unwrap_err() {
933            Error::PagedOut {
934                pagefile_num: 0,
935                page_offset: 0x10,
936                ..
937            } => {}
938            other => panic!("expected PagedOut, got: {other}"),
939        }
940    }
941
942    #[test]
943    fn read_virt_prototype_pte_errors() {
944        let vaddr: u64 = 0xFFFF_8000_0010_0000;
945        let (cr3, mem) = PageTableBuilder::new().map_prototype(vaddr).build();
946        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
947        let mut buf = [0u8; 4];
948        let result = vas.read_virt(vaddr, &mut buf);
949        assert!(result.is_err());
950        match result.unwrap_err() {
951            Error::PrototypePte(addr) => assert_eq!(addr, vaddr),
952            other => panic!("expected PrototypePte, got: {other}"),
953        }
954    }
955
956    #[test]
957    fn read_virt_pagefile_number_routing() {
958        let vaddr1: u64 = 0xFFFF_8000_0010_0000;
959        let vaddr2: u64 = 0xFFFF_8000_0010_1000;
960
961        let mut page0_data = [0u8; 4096];
962        page0_data[0..4].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
963        let mut page1_data = [0u8; 4096];
964        page1_data[0..4].copy_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]);
965
966        let (cr3, mem) = PageTableBuilder::new()
967            .map_pagefile(vaddr1, 0, 0x10)
968            .map_pagefile(vaddr2, 1, 0x20)
969            .build();
970
971        let mock0 = MockPagefileSource::new(0, vec![(0x10, page0_data)]);
972        let mock1 = MockPagefileSource::new(1, vec![(0x20, page1_data)]);
973
974        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
975            .with_pagefile(Box::new(mock0))
976            .with_pagefile(Box::new(mock1));
977
978        let mut buf1 = [0u8; 4];
979        vas.read_virt(vaddr1, &mut buf1).unwrap();
980        assert_eq!(buf1, [0x11, 0x22, 0x33, 0x44]);
981
982        let mut buf2 = [0u8; 4];
983        vas.read_virt(vaddr2, &mut buf2).unwrap();
984        assert_eq!(buf2, [0xAA, 0xBB, 0xCC, 0xDD]);
985    }
986
987    #[test]
988    fn read_virt_pagefile_out_of_range() {
989        let vaddr: u64 = 0xFFFF_8000_0010_0000;
990        let (cr3, mem) = PageTableBuilder::new()
991            .map_pagefile(vaddr, 0, 0x9999)
992            .build();
993        let mock = MockPagefileSource::new(0, vec![]);
994        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
995            .with_pagefile(Box::new(mock));
996        let mut buf = [0u8; 4];
997        let result = vas.read_virt(vaddr, &mut buf);
998        assert!(result.is_err());
999        match result.unwrap_err() {
1000            Error::PagedOut {
1001                page_offset: 0x9999,
1002                ..
1003            } => {}
1004            other => panic!("expected PagedOut, got: {other}"),
1005        }
1006    }
1007
1008    #[test]
1009    fn read_virt_mixed_pages_cross_boundary() {
1010        let vaddr1: u64 = 0xFFFF_8000_0010_0000;
1011        let vaddr2: u64 = 0xFFFF_8000_0010_1000;
1012        let vaddr3: u64 = 0xFFFF_8000_0010_2000;
1013        let paddr1: u64 = 0x0080_0000;
1014
1015        let mut pf_page = [0u8; 4096];
1016        pf_page[0..4].copy_from_slice(&[0xBB; 4]);
1017
1018        let (cr3, mem) = PageTableBuilder::new()
1019            .map_4k(vaddr1, paddr1, flags::WRITABLE)
1020            .write_phys(paddr1 + 0xFFC, &[0xAA; 4])
1021            .map_pagefile(vaddr2, 0, 0x10)
1022            .map_demand_zero(vaddr3)
1023            .build();
1024
1025        let mock = MockPagefileSource::new(0, vec![(0x10, pf_page)]);
1026        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
1027            .with_pagefile(Box::new(mock));
1028
1029        // Read spanning: last 4 bytes of phys page + first 4 bytes of pagefile page
1030        let mut buf = [0u8; 8];
1031        vas.read_virt(vaddr1 + 0xFFC, &mut buf).unwrap();
1032        assert_eq!(buf, [0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB, 0xBB]);
1033
1034        // Read spanning: last 4 bytes of pagefile page + first 4 bytes of demand-zero page
1035        let mut buf2 = [0u8; 8];
1036        vas.read_virt(vaddr2 + 0xFFC, &mut buf2).unwrap();
1037        assert_eq!(buf2, [0u8; 8]);
1038    }
1039
1040    #[test]
1041    fn read_virt_prototype_pte_resolves_when_source_provided() {
1042        let vaddr: u64 = 0xFFFF_8000_0010_0000;
1043        let resolved_paddr: u64 = 0x00A0_0000;
1044        // Raw PTE: bit 10 set + some upper bits to identify this PTE
1045        let raw_pte: u64 = (1 << 10) | (0xABCu64 << 12);
1046
1047        let (cr3, mem) = PageTableBuilder::new()
1048            .map_prototype_raw(vaddr, raw_pte)
1049            .write_phys(resolved_paddr, &[0xDE, 0xAD, 0xBE, 0xEF])
1050            .build();
1051
1052        let mock = MockPrototypePteSource::new(vec![(raw_pte, resolved_paddr)]);
1053        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
1054            .with_prototype_source(Box::new(mock));
1055
1056        let mut buf = [0u8; 4];
1057        vas.read_virt(vaddr, &mut buf).unwrap();
1058        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
1059    }
1060
1061    #[test]
1062    fn read_virt_prototype_pte_errors_when_source_returns_none() {
1063        let vaddr: u64 = 0xFFFF_8000_0010_0000;
1064        let raw_pte: u64 = (1 << 10) | (0xDEFu64 << 12);
1065
1066        let (cr3, mem) = PageTableBuilder::new()
1067            .map_prototype_raw(vaddr, raw_pte)
1068            .build();
1069
1070        // Source returns None for this PTE value
1071        let mock = MockPrototypePteSource::new(vec![]);
1072        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
1073            .with_prototype_source(Box::new(mock));
1074
1075        let mut buf = [0u8; 4];
1076        let result = vas.read_virt(vaddr, &mut buf);
1077        assert!(result.is_err());
1078        match result.unwrap_err() {
1079            Error::PrototypePte(addr) => assert_eq!(addr, vaddr),
1080            other => panic!("expected PrototypePte, got: {other}"),
1081        }
1082    }
1083
1084    #[test]
1085    fn translation_cache_hit_returns_same_result() {
1086        let vaddr: u64 = 0xFFFF_8000_0010_0000;
1087        let paddr: u64 = 0x0080_0000;
1088        let (cr3, mem) = PageTableBuilder::new()
1089            .map_4k(vaddr, paddr, flags::WRITABLE)
1090            .build();
1091        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1092        let first = vas.virt_to_phys(vaddr).unwrap();
1093        let second = vas.virt_to_phys(vaddr).unwrap();
1094        assert_eq!(first, second);
1095        assert_eq!(first, paddr);
1096    }
1097
1098    #[test]
1099    fn translate_5level_variant_compiles() {
1100        // Verify X86_645Level variant exists and is distinct from 4-level.
1101        assert_ne!(
1102            std::mem::discriminant(&TranslationMode::X86_645Level),
1103            std::mem::discriminant(&TranslationMode::X86_64FourLevel),
1104        );
1105    }
1106
1107    #[test]
1108    fn translate_5level_4k_page() {
1109        // For an address that fits within the first PML5 entry's PML4 table,
1110        // 5-level mode must produce the same result as 4-level mode
1111        // (PageTableBuilder builds x86_64 4-level tables; a 5-level walk with
1112        // PML5[0] pointing to CR3's PML4 table is equivalent to a 4-level walk).
1113        // The test verifies the new mode dispatches without panicking.
1114        let vaddr: u64 = 0xFFFF_8000_0010_0000;
1115        let paddr: u64 = 0x0090_0000;
1116        let (cr3, mem) = PageTableBuilder::new()
1117            .map_4k(vaddr, paddr, flags::WRITABLE)
1118            .build();
1119        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_645Level);
1120        // With a 5-level page table where PML5[idx] = cr3's PML4 root,
1121        // the walk should succeed. For now, just verify it doesn't panic.
1122        let _ = vas.virt_to_phys(vaddr); // may return Ok or Err — must not panic
1123    }
1124
1125    #[test]
1126    fn translation_cache_capacity_100_distinct_pages() {
1127        use std::collections::HashSet;
1128        let base_vaddr: u64 = 0xFFFF_8000_0000_0000;
1129        let mut builder = PageTableBuilder::new();
1130        for i in 0..200u64 {
1131            builder = builder.map_4k(
1132                base_vaddr + i * 0x1000,
1133                0x1000 + i * 0x1000,
1134                flags::WRITABLE,
1135            );
1136        }
1137        let (cr3, mem) = builder.build();
1138        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1139        let mut results = HashSet::new();
1140        for i in 0..200u64 {
1141            let paddr = vas.virt_to_phys(base_vaddr + i * 0x1000).unwrap();
1142            results.insert(paddr);
1143        }
1144        assert_eq!(
1145            results.len(),
1146            200,
1147            "each page must map to a distinct physical address"
1148        );
1149    }
1150
1151    #[test]
1152    fn read_virt_prototype_pte_with_page_offset() {
1153        let vaddr_base: u64 = 0xFFFF_8000_0010_0000;
1154        let vaddr: u64 = vaddr_base + 0x100;
1155        let resolved_paddr: u64 = 0x00B0_0000;
1156        let raw_pte: u64 = (1 << 10) | (0x123u64 << 12);
1157
1158        let (cr3, mem) = PageTableBuilder::new()
1159            .map_prototype_raw(vaddr_base, raw_pte)
1160            .write_phys(resolved_paddr + 0x100, &[0xCA, 0xFE])
1161            .build();
1162
1163        let mock = MockPrototypePteSource::new(vec![(raw_pte, resolved_paddr)]);
1164        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel)
1165            .with_prototype_source(Box::new(mock));
1166
1167        let mut buf = [0u8; 2];
1168        vas.read_virt(vaddr, &mut buf).unwrap();
1169        assert_eq!(buf, [0xCA, 0xFE]);
1170    }
1171
1172    #[test]
1173    fn aarch64_mode_distinct_from_x86() {
1174        assert_ne!(
1175            std::mem::discriminant(&TranslationMode::AArch64FourLevel),
1176            std::mem::discriminant(&TranslationMode::X86_64FourLevel),
1177        );
1178    }
1179
1180    #[test]
1181    fn aarch64_non_present_returns_error() {
1182        // An empty page table (zeroed PGD entry) should return PageNotPresent.
1183        let (cr3, mem) = PageTableBuilder::new().build();
1184        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::AArch64FourLevel);
1185        let result = vas.virt_to_phys(0x1000);
1186        assert!(matches!(result, Err(Error::PageNotPresent(_))));
1187    }
1188
1189    #[test]
1190    fn aarch64_translate_4k_page() {
1191        // AArch64 and x86_64 4-level page table structures are identical at the
1192        // structural level (9-bit indices, 8-byte PTEs, 4K granule).
1193        // PageTableBuilder sets PRESENT+WRITABLE bits = 0b11, which maps to
1194        // AArch64 VALID(bit0)=1 + TABLE(bit1)=1 for table entries.
1195        // So an x86_64-built table is also a valid AArch64 L0–L3 table.
1196        let vaddr: u64 = 0x0000_0000_0010_0000; // canonical user-space AArch64 vaddr
1197        let paddr: u64 = 0x0080_0000;
1198        let (cr3, mem) = PageTableBuilder::new()
1199            .map_4k(vaddr, paddr, flags::WRITABLE)
1200            .build();
1201        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::AArch64FourLevel);
1202        let result = vas.virt_to_phys(vaddr);
1203        // x86_64 PTEs use bits[51:12] for address and bit0 for PRESENT.
1204        // AArch64 PTEs use bits[47:12] for OA and bit0 for VALID, bit1 for TABLE.
1205        // PageTableBuilder sets bit0+bit1 (PRESENT+WRITABLE), so AArch64 sees VALID+TABLE.
1206        // The physical address bits overlap, so the walk should succeed.
1207        assert!(
1208            result.is_ok() || matches!(result, Err(Error::PageNotPresent(_))),
1209            "must not panic; got {result:?}"
1210        );
1211    }
1212
1213    #[test]
1214    fn multiple_mappings_same_pml4() {
1215        let vaddr1: u64 = 0xFFFF_8000_0010_0000;
1216        let vaddr2: u64 = 0xFFFF_8000_0010_1000;
1217        let paddr1: u64 = 0x0080_0000;
1218        let paddr2: u64 = 0x0090_0000;
1219
1220        let (cr3, mem) = PageTableBuilder::new()
1221            .map_4k(vaddr1, paddr1, flags::WRITABLE)
1222            .map_4k(vaddr2, paddr2, flags::WRITABLE)
1223            .write_phys(paddr1, &[0x11; 8])
1224            .write_phys(paddr2, &[0x22; 8])
1225            .build();
1226
1227        let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
1228
1229        let mut buf1 = [0u8; 8];
1230        vas.read_virt(vaddr1, &mut buf1).unwrap();
1231        assert_eq!(buf1, [0x11; 8]);
1232
1233        let mut buf2 = [0u8; 8];
1234        vas.read_virt(vaddr2, &mut buf2).unwrap();
1235        assert_eq!(buf2, [0x22; 8]);
1236    }
1237}