aboutsummaryrefslogtreecommitdiff
path: root/src/x86_64.rs
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2026-05-24 01:07:25 +0000
committeriximeow <me@iximeow.net>2026-05-24 01:07:25 +0000
commit347d8f4b677d6879856241a1c2b39783a61df026 (patch)
tree4c605f554484cb77eef19a35455065c923bb5a26 /src/x86_64.rs
parent3a006f5b596da90b876d320b8d48f278b88a5ec1 (diff)
hide some of that 64b/32b/16b sharpness behind a helpful api maybe
Diffstat (limited to 'src/x86_64.rs')
-rw-r--r--src/x86_64.rs79
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`