aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2026-02-23 21:53:28 +0000
committeriximeow <me@iximeow.net>2026-02-23 21:53:28 +0000
commit953fb84fca06f34f1ae70481f6449c39f734f24b (patch)
treee104eb43d062414ea727fd97b11f258f71292cdc
parent1822c7d0de9b14d87d937b89ec63b17f6b485718 (diff)
set up an IDT, and try to use it, but just discover the GDT is actually broken
also shrink the GDT to 256 entries because i really won't use 8k of them. this makes the GDT entries only 0x400 bytes but i still skip a page from gdt_addr() to idt_addr().
-rw-r--r--test/long_mode/behavior.rs218
1 files changed, 200 insertions, 18 deletions
diff --git a/test/long_mode/behavior.rs b/test/long_mode/behavior.rs
index 4cabe28..2478b9c 100644
--- a/test/long_mode/behavior.rs
+++ b/test/long_mode/behavior.rs
@@ -107,6 +107,7 @@ mod kvm {
unsafe {
this.configure_identity_paging(&mut vcpu_sregs);
this.configure_selectors(&mut vcpu_sregs);
+ this.configure_idt(&mut vcpu_sregs);
}
vcpu_sregs.efer = 0x0000_0500; // LME | LMA
@@ -138,7 +139,15 @@ mod kvm {
}
fn gdt_addr(&self) -> GuestAddress {
- GuestAddress(0)
+ GuestAddress(0x1000)
+ }
+
+ fn idt_addr(&self) -> GuestAddress {
+ GuestAddress(0x2000)
+ }
+
+ fn interrupt_handlers_start(&self) -> GuestAddress {
+ GuestAddress(0x3000)
}
fn page_table_addr(&self) -> GuestAddress {
@@ -182,8 +191,8 @@ mod kvm {
// > descriptor table is variable in length and can contain up to 8192 (2^13) 8-byte
// > descriptors.
+ assert!(idx < 4096 / 8);
let addr = GuestAddress(self.gdt_addr().0 + (idx as u64 * 8));
- assert!(idx < 8192);
self.check_range(addr, std::mem::size_of::<u64>() as u64);
// SAFETY: idx * 8 can't overflow isize, and we've asserted the end of the pointer is
@@ -193,6 +202,18 @@ mod kvm {
}
}
+ // note this returns a u32, but an IDT is four u32. the u32 this points at is the first of
+ // the four for the entry.
+ fn idt_entry_mut(&mut self, idx: u16) -> *mut u32 {
+ let addr = GuestAddress(self.idt_addr().0 + (idx as u64 * 16));
+ assert!(idx < 256);
+ self.check_range(addr, std::mem::size_of::<[u64; 2]>() as u64);
+
+ unsafe {
+ self.host_ptr(addr) as *mut u32
+ }
+ }
+
fn page_tables(&self) -> VmPageTables<'_> {
let base = self.page_table_addr();
@@ -274,19 +295,19 @@ mod kvm {
sregs.cs.base = 0;
sregs.cs.limit = 0;
sregs.cs.selector = 4 * 8;
- sregs.cs.type_ = 0b1010; // see SDM table 3-1 Code- and Data-Segment Types
+ sregs.cs.type_ = 0b1011; // see SDM table 3-1 Code- and Data-Segment Types
sregs.cs.present = 1;
sregs.cs.dpl = 0;
sregs.cs.db = 0;
sregs.cs.s = 1;
sregs.cs.l = 1;
- sregs.cs.g = 0;
- sregs.cs.avl = 1;
+ sregs.cs.g = 1;
+ sregs.cs.avl = 0;
sregs.ds.base = 0;
sregs.ds.limit = 0;
sregs.ds.selector = 5 * 8;
- sregs.ds.type_ = 0b0010; // see SDM table 3-1 Code- and Data-Segment Types
+ sregs.ds.type_ = 0b0011; // see SDM table 3-1 Code- and Data-Segment Types
sregs.ds.present = 1;
sregs.ds.dpl = 0;
sregs.ds.db = 1;
@@ -311,26 +332,85 @@ mod kvm {
let base_mid = (base >> 16) & 0xff;
let base_high = (base >> 24) & 0xff;
+ let access_byte = (seg.type_ as u64)
+ | (seg.s as u64) << 4
+ | (seg.dpl as u64) << 5
+ | (seg.present as u64) << 7;
+ let flaglim_byte = lim_high
+ | (seg.avl as u64) << 4
+ | (seg.l as u64) << 5
+ | (seg.db as u64) << 6
+ | (seg.g as u64) << 7;
let desc_high = base_mid
- | (seg.type_ as u64) << 8
- | (seg.s as u64) << 12
- | (seg.dpl as u64) << 13
- | (seg.present as u64) << 15
- | lim_high << 16
- | (seg.avl as u64) << 20
- | (seg.l as u64) << 21
- | (seg.db as u64) << 22
- | (seg.g as u64) << 23
+ | access_byte << 8
+ | flaglim_byte << 16
| base_high << 24;
desc_low | (desc_high << 32)
}
sregs.gdt.base = self.gdt_addr().0;
- sregs.gdt.limit = 0xffff;
+ sregs.gdt.limit = 0x256 * 8 - 1;
- self.gdt_entry_mut(4).write(encode_segment(&sregs.cs));
+ for i in 0..256 {
+ self.gdt_entry_mut(i).write(encode_segment(&sregs.cs));
+ }
+ let buf = unsafe { (self.gdt_entry_mut(4) as *const [u8; 8]).read() };
+ eprint!("programmed gdt entry ({:016x}) to bytes: ", self.gdt_entry_mut(4) as u64);
+ for b in buf {
+ eprint!("{:02x} ", b);
+ }
+ eprintln!("");
self.gdt_entry_mut(5).write(encode_segment(&sregs.ds));
+ let buf = unsafe { (self.gdt_entry_mut(5) as *const [u8; 8]).read() };
+ eprint!("programmed gdt entry ({:016x}) to bytes: ", self.gdt_entry_mut(5) as u64);
+ for b in buf {
+ eprint!("{:02x} ", b);
+ }
+ eprintln!("");
+ }
+
+ fn configure_idt(&mut self, sregs: &mut kvm_sregs) {
+ sregs.idt.base = self.idt_addr().0;
+ sregs.idt.limit = 256 * 16 - 1; // IDT is 256 entries of 16 bytes each
+
+ let write_idt_entry = |idt_ptr: *mut u32, interrupt_handler_addr: GuestAddress| {
+ let low_hi = interrupt_handler_addr.0 as u32 & 0xffff_0000 |
+ 1 << 15 |
+ 0b00 << 13 |
+ 0 << 12 |
+ 0b1110 << 8 |
+ 0 << 3 |
+ 0;
+ let low_lo = ((4 * 8) << 16 | interrupt_handler_addr.0 & 0x0000_ffff) as u32;
+ unsafe {
+ idt_ptr.offset(0).write(low_lo);
+ idt_ptr.offset(1).write(low_hi);
+ idt_ptr.offset(2).write((interrupt_handler_addr.0 >> 32) as u32);
+ idt_ptr.offset(3).write(0); // reserved
+ }
+ unsafe {
+ if false {
+ let buf = (idt_ptr as *const [u8; 16]).read();
+ eprint!("programmed idt entry ({:016x}) to handler at {:08x}, bytes: ", idt_ptr as u64, interrupt_handler_addr.0);
+ for b in buf {
+ eprint!("{:02x} ", b);
+ }
+ eprintln!("");
+ }
+ }
+ };
+
+ // TODO
+ for i in 0..32 {
+ let interrupt_handler_addr = GuestAddress(self.interrupt_handlers_start().0 + i as u64);
+ write_idt_entry(self.idt_entry_mut(i), interrupt_handler_addr);
+ }
+
+ unsafe {
+ std::slice::from_raw_parts_mut(self.host_ptr(self.interrupt_handlers_start()), 256)
+ .fill(0xf4);
+ }
}
}
@@ -448,11 +528,58 @@ mod kvm {
}
}
+ fn run(vm: &mut TestVm) {
+ let mut exits = 0;
+ let end_pc = loop {
+ eprintln!("about to run! here's some state:");
+ let regs = vm.vcpu.get_regs().unwrap();
+ eprintln!("regs: {:?}", regs);
+ let sregs = vm.vcpu.get_sregs().unwrap();
+ eprintln!("sregs: {:?}", sregs);
+ let exit = vm.run();
+ exits += 1;
+ match exit {
+ VcpuExit::MmioRead(addr, buf) => {
+ eprintln!("mmio: [{:08x}:{}] <- ..", addr, buf.len());
+ // TODO: better
+ buf.fill(1);
+ }
+ VcpuExit::MmioWrite(addr, buf) => {
+ eprintln!("mmio: .. -> [{:08x}:{}]", addr, buf.len());
+ }
+ VcpuExit::Debug(info) => {
+ break info.pc;
+ }
+ VcpuExit::Hlt => {
+ eprintln!("hit hlt");
+ let regs = vm.vcpu.get_regs().unwrap();
+ break regs.rip;
+ }
+ other => {
+ eprintln!("unhandled exit: {:?} ... after {}", other, exits);
+ let regs = vm.vcpu.get_regs().unwrap();
+ eprintln!("regs: {:?}", regs);
+ let sregs = vm.vcpu.get_sregs().unwrap();
+ // eprintln!("sregs: {:?}", sregs);
+ panic!("stop");
+ }
+ }
+ };
+
+ eprintln!("run exits at {:08x}", end_pc);
+ return;
+ }
+
fn run_with_mem_checks(vm: &mut TestVm, expected_end: u64, expected_mem: &[ExpectedMemAccess]) {
let mut expected_mem = expected_mem.to_vec();
let mut unexpected_mem = Vec::new();
let mut exits = 0;
let end_pc = loop {
+ eprintln!("about to run! here's some state:");
+ let regs = vm.vcpu.get_regs().unwrap();
+ eprintln!("regs: {:?}", regs);
+ let sregs = vm.vcpu.get_sregs().unwrap();
+ eprintln!("sregs: {:?}", sregs);
let exit = vm.run();
exits += 1;
match exit {
@@ -490,7 +617,12 @@ mod kvm {
break regs.rip;
}
other => {
- panic!("unhandled exit: {:?} ... after {}", other, exits);
+ eprintln!("unhandled exit: {:?} ... after {}", other, exits);
+ let regs = vm.vcpu.get_regs().unwrap();
+ eprintln!("regs: {:?}", regs);
+ let sregs = vm.vcpu.get_sregs().unwrap();
+ eprintln!("sregs: {:?}", sregs);
+ panic!("stop");
}
}
};
@@ -839,6 +971,7 @@ mod kvm {
permute_dontcares(dontcare_regs.as_slice(), &mut regs);
+ eprintln!("setting regs to: {:?}", regs);
vm.vcpu.set_regs(&regs).unwrap();
run_with_mem_checks(vm, regs.rip + insts.len() as u64, expected_mem.as_slice());
@@ -904,6 +1037,55 @@ mod kvm {
}
#[test]
+ fn kvm_verify_ins() {
+ let mut vm = TestVm::create();
+
+ // `ins byte [rdi], dl`
+ let inst: &'static [u8] = &[0x6c];
+ check_behavior(&mut vm, inst);
+ }
+
+ #[test]
+ fn test_pf() {
+ use yaxpeax_arch::{Decoder, U8Reader};
+ use yaxpeax_x86::long_mode::{Instruction, InstDecoder};
+
+ let mut vm = TestVm::create();
+
+ let decoder = InstDecoder::default();
+ let mut buf = Instruction::default();
+
+// let inst = &[0xcc, 0xc7, 0x01, 0x0a, 0x00, 0x00, 0x00];
+ let inst = &[
+ // jmp to jmpf below..
+ 0xeb, 0x0e,
+ 0xf4, // hlt in case we didn't jump (??)
+ // set up a far call destination in the next 10 bytes here..
+ 0x20, 0x00, // GDT entry 4 (4 * 8 == 0x20)
+ 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // address 0x3000
+ 0x00, 0x00, 0x00,
+ 0x48, 0xff, 0x28, // jmpf [rax]
+ ];
+ let mut reader = U8Reader::new(inst.as_slice());
+ let decoded = decoder.decode(&mut reader).expect("can decode");
+
+ let before_sregs = vm.vcpu.get_sregs().unwrap();
+ let mut regs = vm.vcpu.get_regs().unwrap();
+
+ vm.program(inst.as_slice(), &mut regs);
+
+ regs.rax = regs.rip + 3;
+ // regs.rip = 0x3001;
+ vm.vcpu.set_regs(&regs).unwrap();
+
+ // vm.set_single_step(true);
+
+ run(&mut vm);
+
+ panic!("hi");
+ }
+
+ #[test]
fn behavior_verify_kvm() {
use yaxpeax_arch::{Decoder, U8Reader};
use yaxpeax_x86::long_mode::{Instruction, InstDecoder};