@@ -16,7 +16,7 @@ use libc::EINVAL;
1616use std:: fs:: File ;
1717use std:: os:: unix:: io:: { AsRawFd , RawFd } ;
1818
19- use crate :: ioctls:: { KvmCoalescedIoRing , KvmRunWrapper , Result } ;
19+ use crate :: ioctls:: { KvmCoalescedIoRing , KvmDirtyLogRing , KvmRunWrapper , Result } ;
2020use crate :: kvm_ioctls:: * ;
2121use vmm_sys_util:: errno;
2222use vmm_sys_util:: ioctl:: { ioctl, ioctl_with_mut_ref, ioctl_with_ref} ;
@@ -197,6 +197,9 @@ pub struct VcpuFd {
197197 kvm_run_ptr : KvmRunWrapper ,
198198 /// A pointer to the coalesced MMIO page
199199 coalesced_mmio_ring : Option < KvmCoalescedIoRing > ,
200+ /// A pointer to the dirty log ring
201+ #[ allow( unused) ]
202+ dirty_log_ring : Option < KvmDirtyLogRing > ,
200203}
201204
202205/// KVM Sync Registers used to tell KVM which registers to sync
@@ -2104,6 +2107,36 @@ impl VcpuFd {
21042107 }
21052108 }
21062109
2110+ /// Gets the dirty log ring iterator if one is mapped.
2111+ ///
2112+ /// Returns an iterator over dirty guest frame numbers as (slot, offset) tuples.
2113+ /// Returns `None` if no dirty log ring has been mapped.
2114+ ///
2115+ /// # Returns
2116+ ///
2117+ /// An optional iterator over the dirty log ring entries.
2118+ ///
2119+ /// # Example
2120+ ///
2121+ /// ```no_run
2122+ /// # use kvm_ioctls::Kvm;
2123+ /// # use kvm_ioctls::Cap;
2124+ /// let kvm = Kvm::new().unwrap();
2125+ /// let mut vm = kvm.create_vm().unwrap();
2126+ /// vm.enable_dirty_log_ring(None).unwrap();
2127+ /// let mut vcpu = vm.create_vcpu(0).unwrap();
2128+ /// if kvm.check_extension(Cap::DirtyLogRing) {
2129+ /// if let Some(mut iter) = vcpu.dirty_log_ring_iter() {
2130+ /// for (slot, offset) in iter {
2131+ /// println!("Dirty page in slot {} at offset {}", slot, offset);
2132+ /// }
2133+ /// }
2134+ /// }
2135+ /// ```
2136+ pub fn dirty_log_ring_iter ( & mut self ) -> Option < impl Iterator < Item = ( u32 , u64 ) > > {
2137+ self . dirty_log_ring . as_mut ( )
2138+ }
2139+
21072140 /// Maps the coalesced MMIO ring page. This allows reading entries from
21082141 /// the ring via [`coalesced_mmio_read()`](VcpuFd::coalesced_mmio_read).
21092142 ///
@@ -2159,11 +2192,16 @@ impl VcpuFd {
21592192/// This should not be exported as a public function because the preferred way is to use
21602193/// `create_vcpu` from `VmFd`. The function cannot be part of the `VcpuFd` implementation because
21612194/// then it would be exported with the public `VcpuFd` interface.
2162- pub fn new_vcpu ( vcpu : File , kvm_run_ptr : KvmRunWrapper ) -> VcpuFd {
2195+ pub fn new_vcpu (
2196+ vcpu : File ,
2197+ kvm_run_ptr : KvmRunWrapper ,
2198+ dirty_log_ring : Option < KvmDirtyLogRing > ,
2199+ ) -> VcpuFd {
21632200 VcpuFd {
21642201 vcpu,
21652202 kvm_run_ptr,
21662203 coalesced_mmio_ring : None ,
2204+ dirty_log_ring,
21672205 }
21682206}
21692207
@@ -2835,6 +2873,144 @@ mod tests {
28352873 }
28362874 }
28372875
2876+ #[ cfg( target_arch = "x86_64" ) ]
2877+ #[ test]
2878+ fn test_run_code_dirty_log_ring ( ) {
2879+ use std:: io:: Write ;
2880+
2881+ let kvm = Kvm :: new ( ) . unwrap ( ) ;
2882+ let mut vm = kvm. create_vm ( ) . unwrap ( ) ;
2883+
2884+ // Enable dirty log ring
2885+ let need_bitmap = vm. enable_dirty_log_ring ( None ) . unwrap ( ) ;
2886+
2887+ // This example is based on https://lwn.net/Articles/658511/
2888+ #[ rustfmt:: skip]
2889+ let code = [
2890+ 0xba , 0xf8 , 0x03 , /* mov $0x3f8, %dx */
2891+ 0x00 , 0xd8 , /* add %bl, %al */
2892+ 0x04 , b'0' , /* add $'0', %al */
2893+ 0xee , /* out %al, %dx */
2894+ 0xec , /* in %dx, %al */
2895+ 0xc6 , 0x06 , 0x00 , 0x80 , 0x00 , /* movl $0, (0x8000); This generates a MMIO Write.*/
2896+ 0x8a , 0x16 , 0x00 , 0x80 , /* movl (0x8000), %dl; This generates a MMIO Read.*/
2897+ 0xc6 , 0x06 , 0x00 , 0x20 , 0x00 , /* movl $0, (0x2000); Dirty one page in guest mem. */
2898+ 0xf4 , /* hlt */
2899+ ] ;
2900+ let expected_rips: [ u64 ; 3 ] = [ 0x1003 , 0x1005 , 0x1007 ] ;
2901+
2902+ let mem_size = 0x4000 ;
2903+ let load_addr = mmap_anonymous ( mem_size) . as_ptr ( ) ;
2904+ let guest_addr: u64 = 0x1000 ;
2905+ let slot: u32 = 0 ;
2906+ let mem_region = kvm_userspace_memory_region {
2907+ slot,
2908+ guest_phys_addr : guest_addr,
2909+ memory_size : mem_size as u64 ,
2910+ userspace_addr : load_addr as u64 ,
2911+ flags : KVM_MEM_LOG_DIRTY_PAGES ,
2912+ } ;
2913+ unsafe {
2914+ vm. set_user_memory_region ( mem_region) . unwrap ( ) ;
2915+ }
2916+
2917+ unsafe {
2918+ // Get a mutable slice of `mem_size` from `load_addr`.
2919+ // This is safe because we mapped it before.
2920+ let mut slice = std:: slice:: from_raw_parts_mut ( load_addr, mem_size) ;
2921+ slice. write_all ( & code) . unwrap ( ) ;
2922+ }
2923+
2924+ let mut vcpu_fd = vm. create_vcpu ( 0 ) . unwrap ( ) ;
2925+
2926+ let mut vcpu_sregs = vcpu_fd. get_sregs ( ) . unwrap ( ) ;
2927+ assert_ne ! ( vcpu_sregs. cs. base, 0 ) ;
2928+ assert_ne ! ( vcpu_sregs. cs. selector, 0 ) ;
2929+ vcpu_sregs. cs . base = 0 ;
2930+ vcpu_sregs. cs . selector = 0 ;
2931+ vcpu_fd. set_sregs ( & vcpu_sregs) . unwrap ( ) ;
2932+
2933+ let mut vcpu_regs = vcpu_fd. get_regs ( ) . unwrap ( ) ;
2934+ // Set the Instruction Pointer to the guest address where we loaded the code.
2935+ vcpu_regs. rip = guest_addr;
2936+ vcpu_regs. rax = 2 ;
2937+ vcpu_regs. rbx = 3 ;
2938+ vcpu_regs. rflags = 2 ;
2939+ vcpu_fd. set_regs ( & vcpu_regs) . unwrap ( ) ;
2940+
2941+ let mut debug_struct = kvm_guest_debug {
2942+ control : KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP ,
2943+ pad : 0 ,
2944+ arch : kvm_guest_debug_arch {
2945+ debugreg : [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ,
2946+ } ,
2947+ } ;
2948+ vcpu_fd. set_guest_debug ( & debug_struct) . unwrap ( ) ;
2949+
2950+ let mut instr_idx = 0 ;
2951+ loop {
2952+ match vcpu_fd. run ( ) . expect ( "run failed" ) {
2953+ VcpuExit :: IoIn ( addr, data) => {
2954+ assert_eq ! ( addr, 0x3f8 ) ;
2955+ assert_eq ! ( data. len( ) , 1 ) ;
2956+ }
2957+ VcpuExit :: IoOut ( addr, data) => {
2958+ assert_eq ! ( addr, 0x3f8 ) ;
2959+ assert_eq ! ( data. len( ) , 1 ) ;
2960+ assert_eq ! ( data[ 0 ] , b'5' ) ;
2961+ }
2962+ VcpuExit :: MmioRead ( addr, data) => {
2963+ assert_eq ! ( addr, 0x8000 ) ;
2964+ assert_eq ! ( data. len( ) , 1 ) ;
2965+ }
2966+ VcpuExit :: MmioWrite ( addr, data) => {
2967+ assert_eq ! ( addr, 0x8000 ) ;
2968+ assert_eq ! ( data. len( ) , 1 ) ;
2969+ assert_eq ! ( data[ 0 ] , 0 ) ;
2970+ }
2971+ VcpuExit :: Debug ( debug) => {
2972+ if instr_idx == expected_rips. len ( ) - 1 {
2973+ // Disabling debugging/single-stepping
2974+ debug_struct. control = 0 ;
2975+ vcpu_fd. set_guest_debug ( & debug_struct) . unwrap ( ) ;
2976+ } else if instr_idx >= expected_rips. len ( ) {
2977+ unreachable ! ( ) ;
2978+ }
2979+ let vcpu_regs = vcpu_fd. get_regs ( ) . unwrap ( ) ;
2980+ assert_eq ! ( vcpu_regs. rip, expected_rips[ instr_idx] ) ;
2981+ assert_eq ! ( debug. exception, 1 ) ;
2982+ assert_eq ! ( debug. pc, expected_rips[ instr_idx] ) ;
2983+ // Check first 15 bits of DR6
2984+ let mask = ( 1 << 16 ) - 1 ;
2985+ assert_eq ! ( debug. dr6 & mask, 0b100111111110000 ) ;
2986+ // Bit 10 in DR7 is always 1
2987+ assert_eq ! ( debug. dr7, 1 << 10 ) ;
2988+ instr_idx += 1 ;
2989+ }
2990+ VcpuExit :: Hlt => {
2991+ // The code snippet dirties 2 pages:
2992+ // * one when the code itself is loaded in memory;
2993+ // * and one more from the `movl` that writes to address 0x8000
2994+
2995+ let dirty_pages: u32 =
2996+ u32:: try_from ( vcpu_fd. dirty_log_ring_iter ( ) . unwrap ( ) . count ( ) ) . unwrap ( )
2997+ + if need_bitmap {
2998+ let dirty_pages_bitmap = vm. get_dirty_log ( slot, mem_size) . unwrap ( ) ;
2999+ dirty_pages_bitmap
3000+ . into_iter ( )
3001+ . map ( |page| page. count_ones ( ) )
3002+ . sum ( )
3003+ } else {
3004+ 0
3005+ } ;
3006+ assert_eq ! ( dirty_pages, 2 ) ;
3007+ break ;
3008+ }
3009+ r => panic ! ( "unexpected exit reason: {:?}" , r) ,
3010+ }
3011+ }
3012+ }
3013+
28383014 #[ test]
28393015 #[ cfg( target_arch = "aarch64" ) ]
28403016 fn test_get_preferred_target ( ) {
0 commit comments