diff options
-rw-r--r-- | examples/long_mode.rs | 26 | ||||
-rw-r--r-- | examples/real_mode.rs | 1 | ||||
-rw-r--r-- | guest/Makefile | 17 | ||||
-rw-r--r-- | guest/guest64.S | 8 | ||||
-rw-r--r-- | src/fmt.rs | 3 | ||||
-rw-r--r-- | src/kvm_sys.rs | 73 | ||||
-rw-r--r-- | src/vcpu.rs | 66 | ||||
-rw-r--r-- | src/x86_64.rs | 5 | ||||
-rw-r--r-- | sysdeps/kvm.c | 22 |
9 files changed, 201 insertions, 20 deletions
diff --git a/examples/long_mode.rs b/examples/long_mode.rs index 0696ae4..5969a48 100644 --- a/examples/long_mode.rs +++ b/examples/long_mode.rs @@ -6,6 +6,7 @@ use kvm_rs::{PhysAddr, UserMem}; fn setup_long_mode_segments(sregs: &mut kvm_sys::kvm_sregs) { let code_seg = |seg: &mut kvm_sys::kvm_segment| { + // Segment base address (unused in 64bit). seg.base = 0x0; // Limit (unused in 64bit). seg.limit = 0x0; @@ -28,6 +29,7 @@ fn setup_long_mode_segments(sregs: &mut kvm_sys::kvm_sregs) { }; let data_seg = |seg: &mut kvm_sys::kvm_segment| { + // Segment base address (unused in 64bit). seg.base = 0x0; // Limit (unused in 64bit). seg.limit = 0x0; @@ -88,20 +90,21 @@ fn setup_long_mode_4level_paging(mem: &mut UserMem) -> PhysAddr { let mut w = |addr: PhysAddr, val: u64| mem.load(addr, &val.to_le_bytes()); // PML4E[0] refers to PDPE[0:4095]. - w(PhysAddr(0x0000), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x1000); + w(PhysAddr(0x0000), PAGE_ENTRY_PRESENT | PAGE_ENTRY_RW | 0x1000); // PDPE[0] refers to PDE[0:4095]. - w(PhysAddr(0x1000), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x2000); + w(PhysAddr(0x1000), PAGE_ENTRY_PRESENT | PAGE_ENTRY_RW | 0x2000); // PDE[0] refers to PTE[0:4095]. - w(PhysAddr(0x2000), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x3000); + w(PhysAddr(0x2000), PAGE_ENTRY_PRESENT | PAGE_ENTRY_RW | 0x3000); // PTE[0] maps Virt [0x0000:0x0fff] -> Phys [0x4000:0x4fff]. - w(PhysAddr(0x3000), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x4000); + // Just because we can, map this page readonly, as we loaded our guest sw here. + w(PhysAddr(0x3000), PAGE_ENTRY_PRESENT | 0x4000); // PTE[1] maps Virt [0x1000:0x1fff] -> Phys [0x5000:0x5fff]. - w(PhysAddr(0x3008), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x5000); + w(PhysAddr(0x3008), PAGE_ENTRY_PRESENT | PAGE_ENTRY_RW | 0x5000); // PTE[2] maps Virt [0x2000:0x2fff] -> Phys [0x6000:0x6fff]. - w(PhysAddr(0x3010), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x6000); + w(PhysAddr(0x3010), PAGE_ENTRY_PRESENT | PAGE_ENTRY_RW | 0x6000); // PTE[3] maps Virt [0x3000:0x3fff] -> Phys [0x7000:0x7fff]. - w(PhysAddr(0x3018), PAGE_ENTRY_PRESENT | PAGE_RENTRY_RW | 0x7000); + w(PhysAddr(0x3018), PAGE_ENTRY_PRESENT | PAGE_ENTRY_RW | 0x7000); // Return address of PML4. PhysAddr(0x0000) @@ -178,8 +181,17 @@ fn main() -> std::io::Result<()> { data ); } + KvmExit::Debug(_pc) => {} }; } + // The guest writes at virtual address [0x2000 - 0x2003] which will be visible in physical + // memory at [0x6000 - 0x6003] due to the paging structure we setup. + // See `setup_long_mode_4level_paging` above for details. + assert_eq!( + &mem.as_ref()[0x4000 + 0x2000..][..4], + &[0xaa, 0xbb, 0xcc, 0xdd] + ); + Ok(()) } diff --git a/examples/real_mode.rs b/examples/real_mode.rs index f37d45c..fa5fb19 100644 --- a/examples/real_mode.rs +++ b/examples/real_mode.rs @@ -51,6 +51,7 @@ fn main() -> std::io::Result<()> { data ); } + KvmExit::Debug(_pc) => {} }; } diff --git a/guest/Makefile b/guest/Makefile index 5dca740..635759a 100644 --- a/guest/Makefile +++ b/guest/Makefile @@ -1,17 +1,14 @@ +ARCH16 := i8086 +ARCH64 := i386:x86-64 + guest: guest16 guest64 disasm: disasm16 disasm64 -guest16: guest.ld guest16.S - $(CC) $(CFLAGS) -m16 -o $@ -nostdlib -ffreestanding -Wpedantic -Wall -Wextra -Werror -T guest.ld guest16.S - -guest64: guest.ld guest64.S - $(CC) $(CFLAGS) -m64 -o $@ -nostdlib -ffreestanding -Wpedantic -Wall -Wextra -Werror -T guest.ld guest64.S - -disasm16: guest16 - objdump -D -b binary -m i8086 -M intel $^ +guest%: guest.ld guest%.S + $(CC) $(CFLAGS) -m$* -o $@ -nostdlib -ffreestanding -Wpedantic -Wall -Wextra -Werror -T guest.ld guest$*.S -disasm64: guest64 - objdump -D -b binary -m i386:x86-64 -M intel $^ +disasm%: guest% + objdump -D -b binary -m $(ARCH$*) -M intel $^ clean: $(RM) guest16 guest64 diff --git a/guest/guest64.S b/guest/guest64.S index 6629273..533d186 100644 --- a/guest/guest64.S +++ b/guest/guest64.S @@ -9,9 +9,15 @@ // Trigger `KVM_EXIT_IO:KVM_EXIT_IO_IN` by reading byte to memory from input port. mov dx, 0x1000 // Input port. - lea di, [in_dest] // Destination address. + lea di, [rip + in_dest] // Destination address. insb // Read byte from input port dx to ds:di. + // Write to allocated virtual addresses. + mov byte ptr ds:[0x2000], 0xaa + mov byte ptr ds:[0x2001], 0xbb + mov byte ptr ds:[0x2002], 0xcc + mov byte ptr ds:[0x2003], 0xdd + // Trigger `KVM_EXIT_HLT`. hlt @@ -12,7 +12,7 @@ impl fmt::Display for kvm_regs { r8 : {:#018x} r9 : {:#018x} r10: {:#018x} r11: {:#018x}\n\ r12: {:#018x} r13: {:#018x} r14: {:#018x} r15: {:#018x}\n\ rsp: {:#018x} rbp: {:#018x}\n\ - rip: {:#018x} rfl: {:#018x} O({}) D({}) I({}) S({}) Z({}) P({}) C({})", + rip: {:#018x} rfl: {:#018x} O({}) D({}) I({}) T({}) S({}) Z({}) P({}) C({})", self.rax, self.rbx, self.rcx, @@ -34,6 +34,7 @@ impl fmt::Display for kvm_regs { rflags_of(self.rflags), rflags_df(self.rflags), rflags_if(self.rflags), + rflags_tf(self.rflags), rflags_sf(self.rflags), rflags_zf(self.rflags), rflags_pf(self.rflags), diff --git a/src/kvm_sys.rs b/src/kvm_sys.rs index d21da47..2ee97a1 100644 --- a/src/kvm_sys.rs +++ b/src/kvm_sys.rs @@ -91,6 +91,32 @@ pub(crate) struct kvm_userspace_memory_region { pub userspace_addr: u64, } +#[cfg(target_arch = "x86_64")] +#[repr(C)] +#[derive(Default, Debug)] +pub struct kvm_debugregs { + pub db: [u64; 4], + pub dr6: u64, + pub dr7: u64, + pub flags: u64, + pub reserved: [u64; 9], +} + +#[repr(C)] +#[derive(Default, Debug)] +pub(crate) struct kvm_guest_debug { + pub control: u32, + pad: u32, + pub arch: kvm_guest_debug_arch, +} + +#[cfg(target_arch = "x86_64")] +#[repr(C)] +#[derive(Default, Debug)] +pub(crate) struct kvm_guest_debug_arch { + pub debugreg: [u64; 8], +} + #[repr(C)] pub(crate) struct kvm_run { request_interrupt_window: u8, @@ -127,11 +153,23 @@ pub(crate) struct kvm_run_mmio { pub is_write: u8, } +#[cfg(target_arch = "x86_64")] +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub(crate) struct kvm_run_debug { + pub exception: u32, + pad: u32, + pub pc: u64, + pub dr6: u64, + pub dr7: u64, +} + // Only add the union fields used here. #[repr(C)] pub(crate) union kvm_run_union { pub io: kvm_run_io, pub mmio: kvm_run_mmio, + pub debug: kvm_run_debug, padding: [u8; 256], } @@ -194,4 +232,39 @@ mod tests { assert_eq!(mem::size_of::<kvm_run_mmio>(), TEST_KVM_RUN_MMIO_SIZE); assert_eq!(mem::size_of::<kvm_run_union_s>(), TEST_KVM_RUN_UNION_S_SIZE); } + + #[cfg(target_arch = "x86_64")] + #[test] + fn check_kvm_run_x86() { + assert_eq!(mem::size_of::<kvm_run_debug>(), TEST_KVM_RUN_DEBUG_SIZE); + } + + #[cfg(target_arch = "x86_64")] + #[test] + fn check_kvm_debugregs() { + assert_eq!(mem::size_of::<kvm_debugregs>(), TEST_KVM_DEBUGREGS_SIZE); + assert_eq!(mem::align_of::<kvm_debugregs>(), TEST_KVM_DEBUGREGS_ALIGN); + } + + #[test] + fn check_kvm_guest_dbg() { + assert_eq!(mem::size_of::<kvm_guest_debug>(), TEST_KVM_GUEST_DEBUG_SIZE); + assert_eq!( + mem::align_of::<kvm_guest_debug>(), + TEST_KVM_GUEST_DEBUG_ALIGN + ); + } + + #[cfg(target_arch = "x86_64")] + #[test] + fn check_kvm_guest_dbg_arch() { + assert_eq!( + mem::size_of::<kvm_guest_debug_arch>(), + TEST_KVM_GUEST_DEBUG_ARCH_SIZE + ); + assert_eq!( + mem::align_of::<kvm_guest_debug_arch>(), + TEST_KVM_GUEST_DEBUG_ARCH_ALIGN + ); + } } diff --git a/src/vcpu.rs b/src/vcpu.rs index f0d311e..4b25736 100644 --- a/src/vcpu.rs +++ b/src/vcpu.rs @@ -18,6 +18,7 @@ pub enum KvmExit<'cpu> { IoOut(u16, &'cpu [u8]), MmioRead(u64, &'cpu mut [u8]), MmioWrite(u64, &'cpu [u8]), + Debug(u64), } /// Wrapper for VCPU ioctls. @@ -86,6 +87,65 @@ impl Vcpu { .map(|_| ()) } + /// Get the debug registers with the [`KVM_GET_DEBUGREGS`][kvm-get-debugregs] ioctl in form of + /// [`kvm_debugregs`](crate::kvm_sys::kvm_debugregs). + /// + /// [kvm-get-debugregs]: + /// https://www.kernel.org/doc/html/latest/virt/kvm/api.html#kvm-get-debugregs + #[cfg(target_arch = "x86_64")] + pub fn get_debugregs(&self) -> io::Result<kvm_sys::kvm_debugregs> { + let mut dregs = kvm_sys::kvm_debugregs::default(); + ioctl( + &self.vcpu, + kvm_sys::KVM_GET_DEBUGREGS, + &mut dregs as *mut _ as u64, + )?; + Ok(dregs) + } + + /// Set the debug registers with the [`KVM_SET_DEBUGREGS`][kvm-set-debugregs] ioctl in form of + /// [`kvm_debugregs`](crate::kvm_sys::kvm_debugregs). + /// + /// [kvm-set-debugregs]: + /// https://www.kernel.org/doc/html/latest/virt/kvm/api.html#kvm-set-debugregs + #[cfg(target_arch = "x86_64")] + pub fn set_debugregs(&self, dregs: kvm_sys::kvm_debugregs) -> io::Result<()> { + ioctl( + &self.vcpu, + kvm_sys::KVM_SET_DEBUGREGS, + &dregs as *const _ as u64, + ) + .map(|_| ()) + } + + /// Enable or disable guest single steppig (debug) with the + /// [`KVM_GUESTDBG_ENABLE`][kvm-guest-debug] ioctl. + /// + /// [kvm-guest-debug]: https://www.kernel.org/doc/html/latest/virt/kvm/api.html#kvm-set-guest-debug + #[cfg(target_arch = "x86_64")] + pub fn set_single_step(&self, enable: bool) -> io::Result<()> { + let mut dbg = kvm_sys::kvm_guest_debug::default(); + + if enable { + // Enable guest debugging and single stepping. + dbg.control = kvm_sys::KVM_GUESTDBG_ENABLE | kvm_sys::KVM_GUESTDBG_SINGLESTEP; + } + + // Initialize debug registers based on current VCPUs debug register values. + let dregs = self.get_debugregs()?; + dbg.arch.debugreg[0..4].copy_from_slice(&dregs.db); + // DR4-DR5 are reserved. + dbg.arch.debugreg[6] = dregs.dr6; + dbg.arch.debugreg[7] = dregs.dr7; + + ioctl( + &self.vcpu, + kvm_sys::KVM_SET_GUEST_DEBUG, + &dbg as *const _ as u64, + ) + .map(|_| ()) + } + /// Run the guest VCPU with the [`KVM_RUN`][kvm-run] ioctl until it exits with one of the exit /// reasons described in [`KvmExit`](crate::vcpu::KvmExit). /// @@ -128,6 +188,12 @@ impl Vcpu { _ => unreachable!(), } } + kvm_sys::KVM_EXIT_DEBUG => { + // Safe to use union `debug` field, as Kernel instructed us to. + let debug = unsafe { kvm_run.inner.debug }; + + Ok(KvmExit::Debug(debug.pc)) + } r @ _ => { todo!("KVM_EXIT_... (exit_reason={}) not implemented!", r) } diff --git a/src/x86_64.rs b/src/x86_64.rs index ac153e9..d87319c 100644 --- a/src/x86_64.rs +++ b/src/x86_64.rs @@ -16,6 +16,8 @@ mod x86_64 { pub const RFLAGS_ZF: u64 = 1 << 6; /// Sign flag. pub const RFLAGS_SF: u64 = 1 << 7; + /// Trap flag. + pub const RFLAGS_TF: u64 = 1 << 8; /// Sign flag. pub const RFLAGS_IF: u64 = 1 << 9; /// Direction flag. @@ -32,6 +34,7 @@ mod x86_64 { pub const fn rflags_af(r: u64) -> u64 { (r & RFLAGS_AF) >> 4 } pub const fn rflags_zf(r: u64) -> u64 { (r & RFLAGS_ZF) >> 6 } pub const fn rflags_sf(r: u64) -> u64 { (r & RFLAGS_SF) >> 7 } + pub const fn rflags_tf(r: u64) -> u64 { (r & RFLAGS_TF) >> 8 } pub const fn rflags_if(r: u64) -> u64 { (r & RFLAGS_IF) >> 9 } pub const fn rflags_df(r: u64) -> u64 { (r & RFLAGS_DF) >> 10 } pub const fn rflags_of(r: u64) -> u64 { (r & RFLAGS_OF) >> 11 } @@ -155,5 +158,5 @@ mod x86_64 { /// Page region read/write. /// /// If set, region reference by paging entry is writeable. - pub const PAGE_RENTRY_RW: u64 = 1 << 1; + pub const PAGE_ENTRY_RW: u64 = 1 << 1; } diff --git a/sysdeps/kvm.c b/sysdeps/kvm.c index f7ff1a4..2be08a9 100644 --- a/sysdeps/kvm.c +++ b/sysdeps/kvm.c @@ -47,6 +47,20 @@ int main() { // param: struct kvm_sregs // ret : 0 success, -1 error printf("pub(crate) const KVM_SET_SREGS : u64 = 0x%lx;\n", KVM_SET_SREGS); + // param: struct kvm_debugregs + // ret : 0 success, -1 error + printf("pub(crate) const KVM_GET_DEBUGREGS : u64 = 0x%lx;\n", KVM_GET_DEBUGREGS); + // param: struct kvm_debugregs + // ret : 0 success, -1 error + printf("pub(crate) const KVM_SET_DEBUGREGS : u64 = 0x%lx;\n", KVM_SET_DEBUGREGS); + // param: struct kvm_guest_debug + // ret : 0 success, -1 error + printf("pub(crate) const KVM_SET_GUEST_DEBUG : u64 = 0x%lx;\n", KVM_SET_GUEST_DEBUG); + + /* struct kvm_guest_debug constants */ + + printf("pub(crate) const KVM_GUESTDBG_ENABLE : u32 = 0x%x;\n", KVM_GUESTDBG_ENABLE); + printf("pub(crate) const KVM_GUESTDBG_SINGLESTEP : u32 = 0x%x;\n", KVM_GUESTDBG_SINGLESTEP); /* struct kvm_run constants */ @@ -55,6 +69,7 @@ int main() { printf("pub(crate) const KVM_EXIT_IO_IN : u64 = 0x%x;\n", KVM_EXIT_IO_IN); printf("pub(crate) const KVM_EXIT_IO_OUT : u64 = 0x%x;\n", KVM_EXIT_IO_OUT); printf("pub(crate) const KVM_EXIT_MMIO : u64 = 0x%x;\n", KVM_EXIT_MMIO); + printf("pub(crate) const KVM_EXIT_DEBUG : u64 = 0x%x;\n", KVM_EXIT_DEBUG); /* Capabilities */ @@ -99,7 +114,14 @@ int main() { printf("#[cfg(test)] const TEST_KVM_RUN_ALIGN : usize = %ld;\n", alignof(struct kvm_run)); printf("#[cfg(test)] const TEST_KVM_RUN_IO_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->io)); printf("#[cfg(test)] const TEST_KVM_RUN_MMIO_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->mmio)); + printf("#[cfg(test)] const TEST_KVM_RUN_DEBUG_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->debug)); printf("#[cfg(test)] const TEST_KVM_RUN_UNION_S_SIZE : usize = %ld;\n", sizeof(((struct kvm_run*)0)->s)); + printf("#[cfg(test)] const TEST_KVM_DEBUGREGS_SIZE: usize = %ld;\n", sizeof(struct kvm_debugregs)); + printf("#[cfg(test)] const TEST_KVM_DEBUGREGS_ALIGN: usize = %ld;\n", alignof(struct kvm_debugregs)); + printf("#[cfg(test)] const TEST_KVM_GUEST_DEBUG_SIZE: usize = %ld;\n", sizeof(struct kvm_guest_debug)); + printf("#[cfg(test)] const TEST_KVM_GUEST_DEBUG_ALIGN: usize = %ld;\n", alignof(struct kvm_guest_debug)); + printf("#[cfg(test)] const TEST_KVM_GUEST_DEBUG_ARCH_SIZE: usize = %ld;\n", sizeof(struct kvm_guest_debug_arch)); + printf("#[cfg(test)] const TEST_KVM_GUEST_DEBUG_ARCH_ALIGN: usize = %ld;\n", alignof(struct kvm_guest_debug_arch)); return 0; } |