Skip to main content

memf_core/
test_builders.rs

1//! Test builders for synthetic page tables and kernel structs.
2
3use memf_format::{PhysicalMemoryProvider, PhysicalRange};
4
5/// A synthetic physical memory image for testing.
6#[derive(Debug, Clone)]
7pub struct SyntheticPhysMem {
8    data: Vec<u8>,
9}
10
11impl SyntheticPhysMem {
12    /// Create a new synthetic image of `size` bytes, zero-filled.
13    pub fn new(size: usize) -> Self {
14        Self {
15            data: vec![0u8; size],
16        }
17    }
18
19    /// Write bytes at a physical address.
20    pub fn write_bytes(&mut self, addr: u64, bytes: &[u8]) {
21        let start = addr as usize;
22        self.data[start..start + bytes.len()].copy_from_slice(bytes);
23    }
24
25    /// Write a u64 value at a physical address (little-endian).
26    pub fn write_u64(&mut self, addr: u64, value: u64) {
27        self.write_bytes(addr, &value.to_le_bytes());
28    }
29
30    /// Read a u64 from a physical address (little-endian).
31    pub fn read_u64(&self, addr: u64) -> u64 {
32        let start = addr as usize;
33        u64::from_le_bytes(self.data[start..start + 8].try_into().unwrap())
34    }
35
36    /// Return the raw data slice.
37    pub fn data(&self) -> &[u8] {
38        &self.data
39    }
40}
41
42impl PhysicalMemoryProvider for SyntheticPhysMem {
43    fn read_phys(&self, addr: u64, buf: &mut [u8]) -> memf_format::Result<usize> {
44        if buf.is_empty() {
45            return Ok(0);
46        }
47        let start = addr as usize;
48        if start >= self.data.len() {
49            return Ok(0);
50        }
51        let available = self.data.len() - start;
52        let to_read = buf.len().min(available);
53        buf[..to_read].copy_from_slice(&self.data[start..start + to_read]);
54        Ok(to_read)
55    }
56
57    fn ranges(&self) -> &[PhysicalRange] {
58        &[]
59    }
60    fn format_name(&self) -> &str {
61        "Synthetic"
62    }
63}
64
65/// Page table entry flags for x86_64.
66pub mod flags {
67    /// Page is present in physical memory.
68    pub const PRESENT: u64 = 1 << 0;
69    /// Page is writable.
70    pub const WRITABLE: u64 = 1 << 1;
71    /// Page is accessible from user mode.
72    pub const USER: u64 = 1 << 2;
73    /// Page Size bit: indicates a large/huge page at PD/PDPT level.
74    pub const PS: u64 = 1 << 7;
75}
76
77/// Builder for x86_64 4-level page tables.
78pub struct PageTableBuilder {
79    mem: SyntheticPhysMem,
80    next_page: u64,
81    cr3: u64,
82}
83
84impl PageTableBuilder {
85    /// Physical address of the PML4 table (CR3).
86    pub const CR3: u64 = 0x0000_0000;
87    const ADDR_MASK: u64 = 0x000F_FFFF_FFFF_F000;
88
89    /// Create a new builder with a 16 MB synthetic memory image.
90    pub fn new() -> Self {
91        let mut mem = SyntheticPhysMem::new(16 * 1024 * 1024);
92        let cr3 = Self::CR3;
93        let next_page = 0x1000;
94        // Zero-initialize the PML4 table at CR3
95        for i in 0..512 {
96            mem.write_u64(cr3 + i * 8, 0);
97        }
98        Self {
99            mem,
100            next_page,
101            cr3,
102        }
103    }
104
105    fn alloc_page(&mut self) -> u64 {
106        let addr = self.next_page;
107        self.next_page += 0x1000;
108        // Zero-initialize the new page
109        for i in 0..512 {
110            self.mem.write_u64(addr + i * 8, 0);
111        }
112        addr
113    }
114
115    /// Map a 4K virtual address to a physical address with given flags.
116    pub fn map_4k(mut self, vaddr: u64, paddr: u64, page_flags: u64) -> Self {
117        let pml4_idx = (vaddr >> 39) & 0x1FF;
118        let pdpt_idx = (vaddr >> 30) & 0x1FF;
119        let pd_idx = (vaddr >> 21) & 0x1FF;
120        let pt_idx = (vaddr >> 12) & 0x1FF;
121
122        // PML4 -> PDPT
123        let pml4e_addr = self.cr3 + pml4_idx * 8;
124        let mut pml4e = self.mem.read_u64(pml4e_addr);
125        if pml4e & flags::PRESENT == 0 {
126            let pdpt_page = self.alloc_page();
127            pml4e = pdpt_page | flags::PRESENT | flags::WRITABLE;
128            self.mem.write_u64(pml4e_addr, pml4e);
129        }
130        let pdpt_base = pml4e & Self::ADDR_MASK;
131
132        // PDPT -> PD
133        let pdpte_addr = pdpt_base + pdpt_idx * 8;
134        let mut pdpte = self.mem.read_u64(pdpte_addr);
135        if pdpte & flags::PRESENT == 0 {
136            let pd_page = self.alloc_page();
137            pdpte = pd_page | flags::PRESENT | flags::WRITABLE;
138            self.mem.write_u64(pdpte_addr, pdpte);
139        }
140        let pd_base = pdpte & Self::ADDR_MASK;
141
142        // PD -> PT
143        let pde_addr = pd_base + pd_idx * 8;
144        let mut pde = self.mem.read_u64(pde_addr);
145        if pde & flags::PRESENT == 0 {
146            let pt_page = self.alloc_page();
147            pde = pt_page | flags::PRESENT | flags::WRITABLE;
148            self.mem.write_u64(pde_addr, pde);
149        }
150        let pt_base = pde & Self::ADDR_MASK;
151
152        // PT entry -> physical page
153        let pte_addr = pt_base + pt_idx * 8;
154        let pte = (paddr & Self::ADDR_MASK) | page_flags | flags::PRESENT;
155        self.mem.write_u64(pte_addr, pte);
156        self
157    }
158
159    /// Map a 2MB large page (sets PS bit at PD level).
160    pub fn map_2m(mut self, vaddr: u64, paddr: u64, page_flags: u64) -> Self {
161        let pml4_idx = (vaddr >> 39) & 0x1FF;
162        let pdpt_idx = (vaddr >> 30) & 0x1FF;
163        let pd_idx = (vaddr >> 21) & 0x1FF;
164
165        // PML4 -> PDPT
166        let pml4e_addr = self.cr3 + pml4_idx * 8;
167        let mut pml4e = self.mem.read_u64(pml4e_addr);
168        if pml4e & flags::PRESENT == 0 {
169            let pdpt_page = self.alloc_page();
170            pml4e = pdpt_page | flags::PRESENT | flags::WRITABLE;
171            self.mem.write_u64(pml4e_addr, pml4e);
172        }
173        let pdpt_base = pml4e & Self::ADDR_MASK;
174
175        // PDPT -> PD
176        let pdpte_addr = pdpt_base + pdpt_idx * 8;
177        let mut pdpte = self.mem.read_u64(pdpte_addr);
178        if pdpte & flags::PRESENT == 0 {
179            let pd_page = self.alloc_page();
180            pdpte = pd_page | flags::PRESENT | flags::WRITABLE;
181            self.mem.write_u64(pdpte_addr, pdpte);
182        }
183        let pd_base = pdpte & Self::ADDR_MASK;
184
185        // PD entry with PS bit set for 2MB page
186        let pde_addr = pd_base + pd_idx * 8;
187        let pde = (paddr & 0x000F_FFFF_FFE0_0000) | page_flags | flags::PRESENT | flags::PS;
188        self.mem.write_u64(pde_addr, pde);
189        self
190    }
191
192    /// Map a 1GB huge page (sets PS bit at PDPT level).
193    pub fn map_1g(mut self, vaddr: u64, paddr: u64, page_flags: u64) -> Self {
194        let pml4_idx = (vaddr >> 39) & 0x1FF;
195        let pdpt_idx = (vaddr >> 30) & 0x1FF;
196
197        // PML4 -> PDPT
198        let pml4e_addr = self.cr3 + pml4_idx * 8;
199        let mut pml4e = self.mem.read_u64(pml4e_addr);
200        if pml4e & flags::PRESENT == 0 {
201            let pdpt_page = self.alloc_page();
202            pml4e = pdpt_page | flags::PRESENT | flags::WRITABLE;
203            self.mem.write_u64(pml4e_addr, pml4e);
204        }
205        let pdpt_base = pml4e & Self::ADDR_MASK;
206
207        // PDPT entry with PS bit set for 1GB page
208        let pdpte_addr = pdpt_base + pdpt_idx * 8;
209        let pdpte = (paddr & 0x000F_FFFF_C000_0000) | page_flags | flags::PRESENT | flags::PS;
210        self.mem.write_u64(pdpte_addr, pdpte);
211        self
212    }
213
214    /// Ensure PML4→PDPT→PD→PT hierarchy exists for a 4K vaddr, returning the PT entry address.
215    fn ensure_pt_entry(&mut self, vaddr: u64) -> u64 {
216        let pml4_idx = (vaddr >> 39) & 0x1FF;
217        let pdpt_idx = (vaddr >> 30) & 0x1FF;
218        let pd_idx = (vaddr >> 21) & 0x1FF;
219        let pt_idx = (vaddr >> 12) & 0x1FF;
220
221        // PML4 -> PDPT
222        let pml4e_addr = self.cr3 + pml4_idx * 8;
223        let mut pml4e = self.mem.read_u64(pml4e_addr);
224        if pml4e & flags::PRESENT == 0 {
225            let pdpt_page = self.alloc_page();
226            pml4e = pdpt_page | flags::PRESENT | flags::WRITABLE;
227            self.mem.write_u64(pml4e_addr, pml4e);
228        }
229        let pdpt_base = pml4e & Self::ADDR_MASK;
230
231        // PDPT -> PD
232        let pdpte_addr = pdpt_base + pdpt_idx * 8;
233        let mut pdpte = self.mem.read_u64(pdpte_addr);
234        if pdpte & flags::PRESENT == 0 {
235            let pd_page = self.alloc_page();
236            pdpte = pd_page | flags::PRESENT | flags::WRITABLE;
237            self.mem.write_u64(pdpte_addr, pdpte);
238        }
239        let pd_base = pdpte & Self::ADDR_MASK;
240
241        // PD -> PT
242        let pde_addr = pd_base + pd_idx * 8;
243        let mut pde = self.mem.read_u64(pde_addr);
244        if pde & flags::PRESENT == 0 {
245            let pt_page = self.alloc_page();
246            pde = pt_page | flags::PRESENT | flags::WRITABLE;
247            self.mem.write_u64(pde_addr, pde);
248        }
249        let pt_base = pde & Self::ADDR_MASK;
250
251        pt_base + pt_idx * 8
252    }
253
254    /// Map a demand-zero PTE: upper levels present, PT entry = 0.
255    pub fn map_demand_zero(mut self, vaddr: u64) -> Self {
256        let pte_addr = self.ensure_pt_entry(vaddr);
257        // PT entry is already zero from page allocation; write explicitly for clarity
258        self.mem.write_u64(pte_addr, 0);
259        self
260    }
261
262    /// Map a transition PTE: PRESENT=0, bit 11 (TRANSITION)=1, PFN in bits [12..48].
263    pub fn map_transition(mut self, vaddr: u64, pfn: u64) -> Self {
264        let pte_addr = self.ensure_pt_entry(vaddr);
265        let pte = (pfn << 12) | (1 << 11);
266        self.mem.write_u64(pte_addr, pte);
267        self
268    }
269
270    /// Map a pagefile PTE: PRESENT=0, pagefile_num in bits [1..5], page_offset in bits [12..48].
271    pub fn map_pagefile(mut self, vaddr: u64, pagefile_num: u8, page_offset: u64) -> Self {
272        let pte_addr = self.ensure_pt_entry(vaddr);
273        let pte = ((u64::from(pagefile_num) & 0xF) << 1) | (page_offset << 12);
274        self.mem.write_u64(pte_addr, pte);
275        self
276    }
277
278    /// Map a prototype PTE: PRESENT=0, bit 10 (PROTOTYPE)=1.
279    pub fn map_prototype(mut self, vaddr: u64) -> Self {
280        let pte_addr = self.ensure_pt_entry(vaddr);
281        let pte: u64 = 1 << 10;
282        self.mem.write_u64(pte_addr, pte);
283        self
284    }
285
286    /// Map a prototype PTE with a specific raw PTE value. Bit 10 must be set.
287    pub fn map_prototype_raw(mut self, vaddr: u64, raw_pte: u64) -> Self {
288        assert!(raw_pte & (1 << 10) != 0, "prototype bit (10) must be set");
289        assert!(raw_pte & 1 == 0, "PRESENT bit must be clear");
290        let pte_addr = self.ensure_pt_entry(vaddr);
291        self.mem.write_u64(pte_addr, raw_pte);
292        self
293    }
294
295    /// Write data bytes at a physical address in the synthetic memory.
296    pub fn write_phys(mut self, addr: u64, data: &[u8]) -> Self {
297        self.mem.write_bytes(addr, data);
298        self
299    }
300
301    /// Write a u64 value at a physical address.
302    pub fn write_phys_u64(mut self, addr: u64, value: u64) -> Self {
303        self.mem.write_u64(addr, value);
304        self
305    }
306
307    /// Consume the builder and return the CR3 value + synthetic memory.
308    pub fn build(self) -> (u64, SyntheticPhysMem) {
309        (self.cr3, self.mem)
310    }
311}
312
313impl Default for PageTableBuilder {
314    fn default() -> Self {
315        Self::new()
316    }
317}
318
319/// Mock pagefile source for testing pagefile PTE resolution.
320pub struct MockPagefileSource {
321    pagefile_num: u8,
322    pages: std::collections::HashMap<u64, [u8; 4096]>,
323}
324
325impl MockPagefileSource {
326    /// Create a mock with the given pagefile number and pre-loaded pages.
327    /// Each tuple is `(page_offset, page_data)`.
328    pub fn new(pagefile_num: u8, pages: Vec<(u64, [u8; 4096])>) -> Self {
329        Self {
330            pagefile_num,
331            pages: pages.into_iter().collect(),
332        }
333    }
334}
335
336/// Mock prototype PTE source for testing prototype PTE resolution.
337pub struct MockPrototypePteSource {
338    /// Maps raw PTE value -> resolved physical address.
339    entries: std::collections::HashMap<u64, u64>,
340}
341
342impl MockPrototypePteSource {
343    /// Create a mock with the given `(pte_value, phys_addr)` pairs.
344    pub fn new(entries: Vec<(u64, u64)>) -> Self {
345        Self {
346            entries: entries.into_iter().collect(),
347        }
348    }
349}
350
351impl crate::proto_pte::PrototypePteSource for MockPrototypePteSource {
352    fn resolve(&self, pte_value: u64) -> Option<u64> {
353        self.entries.get(&pte_value).copied()
354    }
355}
356
357impl crate::pagefile::PagefileSource for MockPagefileSource {
358    fn pagefile_number(&self) -> u8 {
359        self.pagefile_num
360    }
361
362    fn read_page(&self, page_offset: u64) -> crate::Result<Option<[u8; 4096]>> {
363        Ok(self.pages.get(&page_offset).copied())
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn synthetic_mem_read_write() {
373        let mut mem = SyntheticPhysMem::new(4096);
374        mem.write_bytes(0x100, &[0xAA, 0xBB, 0xCC, 0xDD]);
375        let mut buf = [0u8; 4];
376        let n = mem.read_phys(0x100, &mut buf).unwrap();
377        assert_eq!(n, 4);
378        assert_eq!(buf, [0xAA, 0xBB, 0xCC, 0xDD]);
379    }
380
381    #[test]
382    fn synthetic_mem_u64() {
383        let mut mem = SyntheticPhysMem::new(4096);
384        mem.write_u64(0x200, 0xDEAD_BEEF_CAFE_BABE);
385        assert_eq!(mem.read_u64(0x200), 0xDEAD_BEEF_CAFE_BABE);
386    }
387
388    #[test]
389    fn page_table_builder_creates_pml4() {
390        let (cr3, mem) = PageTableBuilder::new().build();
391        assert_eq!(cr3, 0);
392        for i in 0..512 {
393            assert_eq!(mem.read_u64(cr3 + i * 8), 0);
394        }
395    }
396
397    #[test]
398    fn page_table_builder_map_4k() {
399        let vaddr: u64 = 0xFFFF_8000_0010_0000;
400        let paddr: u64 = 0x0080_0000;
401        let (cr3, mem) = PageTableBuilder::new()
402            .map_4k(vaddr, paddr, flags::WRITABLE)
403            .write_phys(paddr, &[0x42; 64])
404            .build();
405        let pml4_idx = (vaddr >> 39) & 0x1FF;
406        let pml4e = mem.read_u64(cr3 + pml4_idx * 8);
407        assert_ne!(pml4e & flags::PRESENT, 0);
408        let mut buf = [0u8; 4];
409        mem.read_phys(paddr, &mut buf).unwrap();
410        assert_eq!(buf, [0x42; 4]);
411    }
412
413    #[test]
414    fn page_table_builder_map_2m() {
415        let vaddr: u64 = 0xFFFF_8000_0020_0000;
416        let paddr: u64 = 0x0100_0000;
417        let (cr3, mem) = PageTableBuilder::new()
418            .map_2m(vaddr, paddr, flags::WRITABLE)
419            .build();
420        let pml4_idx = (vaddr >> 39) & 0x1FF;
421        let pml4e = mem.read_u64(cr3 + pml4_idx * 8);
422        assert_ne!(pml4e & flags::PRESENT, 0);
423    }
424
425    #[test]
426    fn mock_pagefile_source_read_page() {
427        use crate::pagefile::PagefileSource;
428
429        let mut page_data = [0xABu8; 4096];
430        page_data[0] = 0x42;
431        let mock = MockPagefileSource::new(0, vec![(0x10, page_data)]);
432        assert_eq!(mock.pagefile_number(), 0);
433        let page = mock.read_page(0x10).unwrap().unwrap();
434        assert_eq!(page[0], 0x42);
435        assert_eq!(page[1], 0xAB);
436    }
437
438    #[test]
439    fn mock_pagefile_source_missing_page() {
440        use crate::pagefile::PagefileSource;
441
442        let mock = MockPagefileSource::new(1, vec![]);
443        assert_eq!(mock.pagefile_number(), 1);
444        assert!(mock.read_page(0x999).unwrap().is_none());
445    }
446
447    #[test]
448    fn page_table_builder_map_demand_zero() {
449        let vaddr: u64 = 0xFFFF_8000_0010_0000;
450        let (cr3, mem) = PageTableBuilder::new().map_demand_zero(vaddr).build();
451        let pml4_idx = (vaddr >> 39) & 0x1FF;
452        let pml4e = mem.read_u64(cr3 + pml4_idx * 8);
453        assert_ne!(pml4e & flags::PRESENT, 0, "PML4 entry should be present");
454        let pdpt_base = pml4e & PageTableBuilder::ADDR_MASK;
455        let pdpt_idx = (vaddr >> 30) & 0x1FF;
456        let pdpte = mem.read_u64(pdpt_base + pdpt_idx * 8);
457        assert_ne!(pdpte & flags::PRESENT, 0, "PDPT entry should be present");
458        let pd_base = pdpte & PageTableBuilder::ADDR_MASK;
459        let pd_idx = (vaddr >> 21) & 0x1FF;
460        let pde = mem.read_u64(pd_base + pd_idx * 8);
461        assert_ne!(pde & flags::PRESENT, 0, "PD entry should be present");
462        let pt_base = pde & PageTableBuilder::ADDR_MASK;
463        let pt_idx = (vaddr >> 12) & 0x1FF;
464        let pte = mem.read_u64(pt_base + pt_idx * 8);
465        assert_eq!(pte, 0, "demand-zero PTE must be all zeros");
466    }
467
468    #[test]
469    fn page_table_builder_map_transition_pte() {
470        let vaddr: u64 = 0xFFFF_8000_0010_0000;
471        let pfn: u64 = 0x800;
472        let (cr3, mem) = PageTableBuilder::new().map_transition(vaddr, pfn).build();
473        let pml4_idx = (vaddr >> 39) & 0x1FF;
474        let pml4e = mem.read_u64(cr3 + pml4_idx * 8);
475        let pdpt_base = pml4e & PageTableBuilder::ADDR_MASK;
476        let pdpt_idx = (vaddr >> 30) & 0x1FF;
477        let pdpte = mem.read_u64(pdpt_base + pdpt_idx * 8);
478        let pd_base = pdpte & PageTableBuilder::ADDR_MASK;
479        let pd_idx = (vaddr >> 21) & 0x1FF;
480        let pde = mem.read_u64(pd_base + pd_idx * 8);
481        let pt_base = pde & PageTableBuilder::ADDR_MASK;
482        let pt_idx = (vaddr >> 12) & 0x1FF;
483        let pte = mem.read_u64(pt_base + pt_idx * 8);
484        assert_eq!(pte & 1, 0, "PRESENT must be clear");
485        assert_ne!(pte & (1 << 11), 0, "TRANSITION bit must be set");
486        assert_eq!((pte >> 12) & 0xF_FFFF_FFFF, pfn, "PFN must match");
487    }
488
489    #[test]
490    fn page_table_builder_map_pagefile_pte() {
491        let vaddr: u64 = 0xFFFF_8000_0010_0000;
492        let pagefile_num: u8 = 0;
493        let page_offset: u64 = 0x5678;
494        let (cr3, mem) = PageTableBuilder::new()
495            .map_pagefile(vaddr, pagefile_num, page_offset)
496            .build();
497        let pml4_idx = (vaddr >> 39) & 0x1FF;
498        let pml4e = mem.read_u64(cr3 + pml4_idx * 8);
499        let pdpt_base = pml4e & PageTableBuilder::ADDR_MASK;
500        let pdpt_idx = (vaddr >> 30) & 0x1FF;
501        let pdpte = mem.read_u64(pdpt_base + pdpt_idx * 8);
502        let pd_base = pdpte & PageTableBuilder::ADDR_MASK;
503        let pd_idx = (vaddr >> 21) & 0x1FF;
504        let pde = mem.read_u64(pd_base + pd_idx * 8);
505        let pt_base = pde & PageTableBuilder::ADDR_MASK;
506        let pt_idx = (vaddr >> 12) & 0x1FF;
507        let pte = mem.read_u64(pt_base + pt_idx * 8);
508        assert_eq!(pte & 1, 0, "PRESENT must be clear");
509        assert_eq!((pte >> 1) & 0xF, u64::from(pagefile_num), "pagefile_num");
510        assert_eq!(pte & (1 << 10), 0, "prototype bit must be clear");
511        assert_eq!(pte & (1 << 11), 0, "transition bit must be clear");
512        assert_eq!((pte >> 12) & 0xF_FFFF_FFFF, page_offset, "page_offset");
513    }
514
515    #[test]
516    fn page_table_builder_map_prototype_pte() {
517        let vaddr: u64 = 0xFFFF_8000_0010_0000;
518        let (cr3, mem) = PageTableBuilder::new().map_prototype(vaddr).build();
519        let pml4_idx = (vaddr >> 39) & 0x1FF;
520        let pml4e = mem.read_u64(cr3 + pml4_idx * 8);
521        let pdpt_base = pml4e & PageTableBuilder::ADDR_MASK;
522        let pdpt_idx = (vaddr >> 30) & 0x1FF;
523        let pdpte = mem.read_u64(pdpt_base + pdpt_idx * 8);
524        let pd_base = pdpte & PageTableBuilder::ADDR_MASK;
525        let pd_idx = (vaddr >> 21) & 0x1FF;
526        let pde = mem.read_u64(pd_base + pd_idx * 8);
527        let pt_base = pde & PageTableBuilder::ADDR_MASK;
528        let pt_idx = (vaddr >> 12) & 0x1FF;
529        let pte = mem.read_u64(pt_base + pt_idx * 8);
530        assert_eq!(pte & 1, 0, "PRESENT must be clear");
531        assert_ne!(pte & (1 << 10), 0, "PROTOTYPE bit must be set");
532    }
533}