diff options
| author | iximeow <me@iximeow.net> | 2026-05-24 01:07:25 +0000 |
|---|---|---|
| committer | iximeow <me@iximeow.net> | 2026-05-24 01:07:25 +0000 |
| commit | 347d8f4b677d6879856241a1c2b39783a61df026 (patch) | |
| tree | 4c605f554484cb77eef19a35455065c923bb5a26 /src | |
| parent | 3a006f5b596da90b876d320b8d48f278b88a5ec1 (diff) | |
hide some of that 64b/32b/16b sharpness behind a helpful api maybe
Diffstat (limited to 'src')
| -rw-r--r-- | src/x86_64.rs | 79 |
1 files changed, 54 insertions, 25 deletions
diff --git a/src/x86_64.rs b/src/x86_64.rs index 0e08446..13b6555 100644 --- a/src/x86_64.rs +++ b/src/x86_64.rs @@ -552,9 +552,9 @@ impl Vm { match this.settings.isa_mode { IsaMode::Long => { unsafe { - this.configure_identity_paging(Some(&mut vcpu_sregs)); - this.configure_selectors(&mut vcpu_sregs); - this.configure_idt(&mut vcpu_regs, &mut vcpu_sregs); + this.configure_identity_paging_64b(Some(&mut vcpu_sregs)); + this.configure_selectors_64b(&mut vcpu_sregs); + this.configure_idt_64b(&mut vcpu_regs, &mut vcpu_sregs); let mut xcrs = this.get_xcrs()?; this.configure_extensions(&mut vcpu_sregs, &mut xcrs); this.set_xcrs(&xcrs)?; @@ -600,9 +600,9 @@ impl Vm { /// map and add a region of size `size` at guest-physical address `gpa`. /// - /// this will not update page tables, so if the newly-added memory is not already mapped due to - /// a previous `configure_identity_paging` call and it is not mapped due to explicit page table - /// management, it will not yet be accessible by guest code. + /// this will not update page tables. if the newly-added memory is not already mapped due to + /// a previous `configure_identity_paging` call, and you have not written to page tables with + /// [`Vm::write_mem`], it will not yet be accessible by guest code. pub fn add_memory(&mut self, gpa: GuestAddress, size: u64) -> Result<(), VmError> { let new_mapping_end = gpa.0.checked_add(size) .map(|addr| GuestAddress(addr)) @@ -1207,6 +1207,36 @@ impl Vm { } /// configure page tables for identity mapping of all memory from guest address zero up to the + /// end of added memory regions. you probably want to call this after a call to + /// [`Vm::add_memory`], so the added region is addressable. + /// + /// if `sregs` is provided, update `cr0`, `cr3`, and `cr4` in support of protected-mode or + /// long-mode paging. this is a fixed pattern: if control registers have not been changed since + /// `Vm::create` then there will be no change to these control registers and `sregs` can be + /// omitted. + /// + /// paging is established with 4GiB pages in long mode, and 4MiB pages in protected mode. + /// + /// panics if the end of added memory regions is above 512 GiB in long mode. + pub unsafe fn configure_identity_paging(&mut self, sregs: Option<&mut kvm_sregs>) { + match self.settings.isa_mode { + IsaMode::Long => { + unsafe { + self.configure_identity_paging_64b(sregs); + } + } + IsaMode::Protected => { + unsafe { + self.configure_identity_paging_32b(sregs); + } + } + IsaMode::Real => { + panic!("paging does not exist in real mode"); + } + } + } + + /// configure page tables for identity mapping of all memory from guest address zero up to the /// end of added memory regions, rounded up to the next GiB. /// /// if `sregs` is provided, update `cr0`, `cr3`, and `cr4` in support of protected-mode or @@ -1215,7 +1245,8 @@ impl Vm { /// omitted. /// /// panics if the end of added memory regions is above 512 GiB. - pub unsafe fn configure_identity_paging(&mut self, sregs: Option<&mut kvm_sregs>) { + unsafe fn configure_identity_paging_64b(&mut self, sregs: Option<&mut kvm_sregs>) { + assert_eq!(self.settings.isa_mode, IsaMode::Long); // we're only setting up one PDPT, which can have up to 512 PDPTE covering 1G each. assert!(self.mem_ceiling() <= 512 * GB); @@ -1283,7 +1314,8 @@ impl Vm { /// if `sregs` is provided, update `cr0`, `cr3`, and `cr4` in support of protected-mode paging. /// this is a fixed pattern: if control registers have not been changed since `Vm::create` then /// there will be no change to these control registers and `sregs` can be omitted. - pub unsafe fn configure_identity_paging_32b(&mut self, sregs: Option<&mut kvm_sregs>) { + unsafe fn configure_identity_paging_32b(&mut self, sregs: Option<&mut kvm_sregs>) { + assert_eq!(self.settings.isa_mode, IsaMode::Protected); // because we'll set PDEs to map 4M pages and cr3 points at a page-aligned block of 1024 // 4-byte PDEs, that gives us 4KiB of memory used to map 4GiB of address space. that's all // of 32-bit, so we don't need to check an upper bound. @@ -1334,7 +1366,8 @@ impl Vm { } } - unsafe fn configure_selectors(&mut self, sregs: &mut kvm_sregs) { + unsafe fn configure_selectors_64b(&mut self, sregs: &mut kvm_sregs) { + assert_eq!(self.settings.isa_mode, IsaMode::Long); // we have to set descriptor information directly. this avoids having to load selectors // as the first instructions on the vCPU, which is simplifying. but if we want the // information in these selectors to match with anything in a GDT (i do!) we'll have to @@ -1386,6 +1419,7 @@ impl Vm { /// configure selectors for 32-bit code exceution. this is basically the same as 64-bit, but we /// set a limit and set `cs.db` so that the default operand size is a normal 32-bit. unsafe fn configure_selectors_32b(&mut self, sregs: &mut kvm_sregs) { + assert_eq!(self.settings.isa_mode, IsaMode::Protected); // we have to set descriptor information directly. this avoids having to load selectors // as the first instructions on the vCPU, which is simplifying. but if we want the // information in these selectors to match with anything in a GDT (i do!) we'll have to @@ -1441,6 +1475,7 @@ impl Vm { /// 16-bit code the VM can simply be configured to `ip = 0`, and code addresses match data /// addresses. additionally, clear `cs.db` so that the default operand size is 16-bit. unsafe fn configure_selectors_16b(&mut self, sregs: &mut kvm_sregs) { + assert_eq!(self.settings.isa_mode, IsaMode::Real); // we have to set descriptor information directly. this avoids having to load selectors // as the first instructions on the vCPU, which is simplifying. but if we want the // information in these selectors to match with anything in a GDT (i do!) we'll have to @@ -1502,6 +1537,7 @@ impl Vm { interrupt_handler_cs: u16, interrupt_handler_addr: GuestAddress ) { + assert_eq!(self.settings.isa_mode, IsaMode::Long); let idt_ptr = self.idt_entry_mut(intr_nr); // entries in the IDT, interrupt and trap descriptors (in the AMD APM, "interrupt-gate" @@ -1543,6 +1579,8 @@ impl Vm { interrupt_handler_cs: u16, interrupt_handler_addr: GuestAddress ) { + assert!(self.settings.isa_mode != IsaMode::Long); + assert!(interrupt_handler_addr.0 <= u32::MAX as u64); let idt_ptr = self.idt_entry_legacy_mut(intr_nr); @@ -1570,7 +1608,8 @@ impl Vm { } } - fn configure_idt(&mut self, regs: &mut kvm_regs, sregs: &mut kvm_sregs) { + fn configure_idt_64b(&mut self, regs: &mut kvm_regs, sregs: &mut kvm_sregs) { + assert_eq!(self.settings.isa_mode, IsaMode::Long); sregs.idt.base = self.idt_addr().0; sregs.idt.limit = IDT_ENTRIES * 16 - 1; // IDT is 256 entries of 16 bytes each @@ -1606,9 +1645,10 @@ impl Vm { self.idt_configured = true; } - /// IDT configuration in 32-bit mode is funky because the interrupt handlers live in a totally - /// different region of memory and need a different value in `cs`. + /// IDT configuration in 32-bit mode differs because IDT entries are smaller, but broadly is + /// similar to 64-bit. fn configure_idt_32b(&mut self, regs: &mut kvm_regs, sregs: &mut kvm_sregs) { + assert_eq!(self.settings.isa_mode, IsaMode::Protected); sregs.idt.base = self.idt_addr().0; sregs.idt.limit = IDT_ENTRIES * 8 - 1; // legacy IDT is 256 entries of 8 bytes each @@ -1634,12 +1674,6 @@ impl Vm { // AMD APM section "8.9.3 Interrupt Stack Frame") to actually enter the interrupt // handler. if we didn't do this, rsp will probably be zero or something, underflow, // page fault on push to 0xffffffff_ffffffff, and just triple fault. - // - // TODO: this is our option in 16- and 32-bit modes, but in long mode all the interrupt - // descriptors could set something in IST to switch stacks outright for exception - // handling. this might be nice to test rsp permutations in 64-bit code? alternatively - // we might just have to limit possible rsp permutations so as to be able to test in - // 16- and 32-bit modes anyway. regs.rsp = self.stack_addr().0; self.idt_configured = true; } @@ -1647,6 +1681,7 @@ impl Vm { /// IDT configuration in 16-bit mode is funky because the interrupt handlers live in a totally /// different region of memory and need a different value in `cs`. fn configure_idt_16b(&mut self, regs: &mut kvm_regs, sregs: &mut kvm_sregs) { + assert_eq!(self.settings.isa_mode, IsaMode::Real); sregs.idt.base = self.idt_addr().0; sregs.idt.limit = IDT_ENTRIES * 8 - 1; // IDT is 256 entries of 8 bytes each @@ -1672,12 +1707,6 @@ impl Vm { // AMD APM section "8.9.3 Interrupt Stack Frame") to actually enter the interrupt // handler. if we didn't do this, rsp will probably be zero or something, underflow, // page fault on push to 0xffffffff_ffffffff, and just triple fault. - // - // TODO: this is our option in 16- and 32-bit modes, but in long mode all the interrupt - // descriptors could set something in IST to switch stacks outright for exception - // handling. this might be nice to test rsp permutations in 64-bit code? alternatively - // we might just have to limit possible rsp permutations so as to be able to test in - // 16- and 32-bit modes anyway. regs.rsp = self.stack_addr().0; self.idt_configured = true; } @@ -2122,7 +2151,7 @@ fn kvm_hugepage_bug() { let mut vm = Vm::create(1024 * 1024).expect("can create vm"); vm.add_memory(GuestAddress(0x1_0000_0000), 128 * 1024).expect("can add test mem region"); unsafe { - vm.configure_identity_paging(None); + vm.configure_identity_paging_64b(None); } // `add [rsp], al; add [rcx], al; pop [rcx]; hlt` |
