Skip to content

Commit 00011ea

Browse files
committed
kvm-ioctls: Fix a bug in enable_dirty_log_ring and add test
Fixes a bug in enable_dirty_log_ring that incorrectly checks if the size passed is a multiple of the element size. Adds a example / doctest that checks basic functionality of enable_dirty_log_ring Signed-off-by: David Kleymann <[email protected]>
1 parent 79956e8 commit 00011ea

File tree

3 files changed

+235
-16
lines changed

3 files changed

+235
-16
lines changed

kvm-ioctls/src/ioctls/mod.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,6 @@ pub(crate) struct KvmDirtyLogRing {
4444
use_acq_rel: bool,
4545
}
4646

47-
// // SAFETY: For each vcpu we only allow creating a single instance of the KvmDirtyLogRing,
48-
// // therefore ownership can safely be transferred between threads (`Send`).
49-
// unsafe impl Send for KvmDirtyLogRing {}
50-
51-
// // SAFETY: TBD
52-
// unsafe impl Sync for KvmDirtyLogRing {}
5347
impl KvmDirtyLogRing {
5448
/// Maps the KVM dirty log ring from the vCPU file descriptor.
5549
///
@@ -94,14 +88,14 @@ impl KvmDirtyLogRing {
9488
offset as i64,
9589
) as *mut kvm_dirty_gfn)
9690
.filter(|addr| addr.as_ptr() != libc::MAP_FAILED as *mut kvm_dirty_gfn)
97-
.ok_or_else(|| errno::Error::last())?
91+
.ok_or_else(errno::Error::last)?
9892
};
99-
return Ok(Self {
93+
Ok(Self {
10094
next_dirty: 0,
10195
gfns,
10296
mask: (slots - 1) as u64,
10397
use_acq_rel,
104-
});
98+
})
10599
}
106100
}
107101

kvm-ioctls/src/ioctls/vcpu.rs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2202,7 +2202,7 @@ pub fn new_vcpu(
22022202
vcpu,
22032203
kvm_run_ptr,
22042204
coalesced_mmio_ring: None,
2205-
dirty_log_ring: dirty_log_ring,
2205+
dirty_log_ring,
22062206
}
22072207
}
22082208

@@ -2874,6 +2874,144 @@ mod tests {
28742874
}
28752875
}
28762876

2877+
#[cfg(target_arch = "x86_64")]
2878+
#[test]
2879+
fn test_run_code_dirty_log_ring() {
2880+
use std::io::Write;
2881+
2882+
let kvm = Kvm::new().unwrap();
2883+
let mut vm = kvm.create_vm().unwrap();
2884+
2885+
// Enable dirty log ring
2886+
let need_bitmap = vm.enable_dirty_log_ring(None).unwrap();
2887+
2888+
// This example is based on https://lwn.net/Articles/658511/
2889+
#[rustfmt::skip]
2890+
let code = [
2891+
0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
2892+
0x00, 0xd8, /* add %bl, %al */
2893+
0x04, b'0', /* add $'0', %al */
2894+
0xee, /* out %al, %dx */
2895+
0xec, /* in %dx, %al */
2896+
0xc6, 0x06, 0x00, 0x80, 0x00, /* movl $0, (0x8000); This generates a MMIO Write.*/
2897+
0x8a, 0x16, 0x00, 0x80, /* movl (0x8000), %dl; This generates a MMIO Read.*/
2898+
0xc6, 0x06, 0x00, 0x20, 0x00, /* movl $0, (0x2000); Dirty one page in guest mem. */
2899+
0xf4, /* hlt */
2900+
];
2901+
let expected_rips: [u64; 3] = [0x1003, 0x1005, 0x1007];
2902+
2903+
let mem_size = 0x4000;
2904+
let load_addr = mmap_anonymous(mem_size).as_ptr();
2905+
let guest_addr: u64 = 0x1000;
2906+
let slot: u32 = 0;
2907+
let mem_region = kvm_userspace_memory_region {
2908+
slot,
2909+
guest_phys_addr: guest_addr,
2910+
memory_size: mem_size as u64,
2911+
userspace_addr: load_addr as u64,
2912+
flags: KVM_MEM_LOG_DIRTY_PAGES,
2913+
};
2914+
unsafe {
2915+
vm.set_user_memory_region(mem_region).unwrap();
2916+
}
2917+
2918+
unsafe {
2919+
// Get a mutable slice of `mem_size` from `load_addr`.
2920+
// This is safe because we mapped it before.
2921+
let mut slice = std::slice::from_raw_parts_mut(load_addr, mem_size);
2922+
slice.write_all(&code).unwrap();
2923+
}
2924+
2925+
let mut vcpu_fd = vm.create_vcpu(0).unwrap();
2926+
2927+
let mut vcpu_sregs = vcpu_fd.get_sregs().unwrap();
2928+
assert_ne!(vcpu_sregs.cs.base, 0);
2929+
assert_ne!(vcpu_sregs.cs.selector, 0);
2930+
vcpu_sregs.cs.base = 0;
2931+
vcpu_sregs.cs.selector = 0;
2932+
vcpu_fd.set_sregs(&vcpu_sregs).unwrap();
2933+
2934+
let mut vcpu_regs = vcpu_fd.get_regs().unwrap();
2935+
// Set the Instruction Pointer to the guest address where we loaded the code.
2936+
vcpu_regs.rip = guest_addr;
2937+
vcpu_regs.rax = 2;
2938+
vcpu_regs.rbx = 3;
2939+
vcpu_regs.rflags = 2;
2940+
vcpu_fd.set_regs(&vcpu_regs).unwrap();
2941+
2942+
let mut debug_struct = kvm_guest_debug {
2943+
control: KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP,
2944+
pad: 0,
2945+
arch: kvm_guest_debug_arch {
2946+
debugreg: [0, 0, 0, 0, 0, 0, 0, 0],
2947+
},
2948+
};
2949+
vcpu_fd.set_guest_debug(&debug_struct).unwrap();
2950+
2951+
let mut instr_idx = 0;
2952+
loop {
2953+
match vcpu_fd.run().expect("run failed") {
2954+
VcpuExit::IoIn(addr, data) => {
2955+
assert_eq!(addr, 0x3f8);
2956+
assert_eq!(data.len(), 1);
2957+
}
2958+
VcpuExit::IoOut(addr, data) => {
2959+
assert_eq!(addr, 0x3f8);
2960+
assert_eq!(data.len(), 1);
2961+
assert_eq!(data[0], b'5');
2962+
}
2963+
VcpuExit::MmioRead(addr, data) => {
2964+
assert_eq!(addr, 0x8000);
2965+
assert_eq!(data.len(), 1);
2966+
}
2967+
VcpuExit::MmioWrite(addr, data) => {
2968+
assert_eq!(addr, 0x8000);
2969+
assert_eq!(data.len(), 1);
2970+
assert_eq!(data[0], 0);
2971+
}
2972+
VcpuExit::Debug(debug) => {
2973+
if instr_idx == expected_rips.len() - 1 {
2974+
// Disabling debugging/single-stepping
2975+
debug_struct.control = 0;
2976+
vcpu_fd.set_guest_debug(&debug_struct).unwrap();
2977+
} else if instr_idx >= expected_rips.len() {
2978+
unreachable!();
2979+
}
2980+
let vcpu_regs = vcpu_fd.get_regs().unwrap();
2981+
assert_eq!(vcpu_regs.rip, expected_rips[instr_idx]);
2982+
assert_eq!(debug.exception, 1);
2983+
assert_eq!(debug.pc, expected_rips[instr_idx]);
2984+
// Check first 15 bits of DR6
2985+
let mask = (1 << 16) - 1;
2986+
assert_eq!(debug.dr6 & mask, 0b100111111110000);
2987+
// Bit 10 in DR7 is always 1
2988+
assert_eq!(debug.dr7, 1 << 10);
2989+
instr_idx += 1;
2990+
}
2991+
VcpuExit::Hlt => {
2992+
// The code snippet dirties 2 pages:
2993+
// * one when the code itself is loaded in memory;
2994+
// * and one more from the `movl` that writes to address 0x8000
2995+
2996+
let dirty_pages: u32 =
2997+
u32::try_from(vcpu_fd.dirty_log_ring_iter().unwrap().count()).unwrap()
2998+
+ if need_bitmap {
2999+
let dirty_pages_bitmap = vm.get_dirty_log(slot, mem_size).unwrap();
3000+
dirty_pages_bitmap
3001+
.into_iter()
3002+
.map(|page| page.count_ones())
3003+
.sum()
3004+
} else {
3005+
0
3006+
};
3007+
assert_eq!(dirty_pages, 2);
3008+
break;
3009+
}
3010+
r => panic!("unexpected exit reason: {:?}", r),
3011+
}
3012+
}
3013+
}
3014+
28773015
#[test]
28783016
#[cfg(target_arch = "aarch64")]
28793017
fn test_get_preferred_target() {

kvm-ioctls/src/ioctls/vm.rs

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,18 +1947,34 @@ impl VmFd {
19471947
/// Enables KVM's dirty log ring for new vCPUs created on this VM. Checks required capabilities and returns
19481948
/// a boolean `use_bitmap` as a result. `use_bitmap` is `true` if the ring needs to be used
19491949
/// together with a backup bitmap `KVM_GET_DIRTY_LOG`. Takes optional dirty ring size as bytes, if not supplied, will
1950-
/// use maximum supported dirty ring size. Enabling the dirty log ring is only allowed before any vCPU was
1951-
/// created on the VmFd.
1950+
/// use maximum supported dirty ring size. The size needs to be multiple of `std::mem::size_of::<kvm_dirty_gfn>()`
1951+
/// and power of two. Enabling the dirty log ring is only allowed before any vCPU was created on the VmFd.
1952+
///
19521953
/// # Arguments
19531954
///
1954-
/// * `bytes` - Size of the dirty log ring in bytes. Needs to be multiple of `std::mem::size_of::<kvm_dirty_gfn>()`
1955-
/// and power of two.
1955+
/// * `bytes` - Size of the dirty log ring in bytes.
1956+
///
1957+
/// # Example
1958+
///
1959+
/// ```
1960+
/// # extern crate kvm_bindings;
1961+
/// # extern crate kvm_ioctls;
1962+
/// # use kvm_ioctls::Kvm;
1963+
/// # use kvm_bindings::{ KVM_CAP_DIRTY_LOG_RING, };
1964+
///
1965+
/// let kvm = Kvm::new().unwrap();
1966+
/// let mut vm = kvm.create_vm().unwrap();
1967+
/// let max_supported_size = vm.check_extension_raw(KVM_CAP_DIRTY_LOG_RING.into());
1968+
/// vm.enable_dirty_log_ring(Some(max_supported_size));
1969+
/// // Create one vCPU with the ID=0 to cause a dirty log ring to be mapped.
1970+
/// let vcpu = vm.create_vcpu(0);
1971+
/// ```
19561972
pub fn enable_dirty_log_ring(&mut self, bytes: Option<i32>) -> Result<bool> {
19571973
// Check if requested size is larger than 0
19581974
if let Some(sz) = bytes {
19591975
if sz <= 0
19601976
|| !(sz as u32).is_power_of_two()
1961-
|| (sz as usize % std::mem::size_of::<kvm_dirty_gfn>() == 0)
1977+
|| (sz as usize % std::mem::size_of::<kvm_dirty_gfn>() != 0)
19621978
{
19631979
return Err(errno::Error::new(libc::EINVAL));
19641980
}
@@ -2034,8 +2050,10 @@ impl VmFd {
20342050
/// # use kvm_ioctls::{Cap, Kvm};
20352051
/// let kvm = Kvm::new().unwrap();
20362052
/// let mut vm = kvm.create_vm().unwrap();
2037-
/// vm.enable_dirty_log_ring(None).unwrap();
20382053
/// if kvm.check_extension(Cap::DirtyLogRing) {
2054+
/// vm.enable_dirty_log_ring(None).unwrap();
2055+
/// // Create one vCPU with the ID=0 to cause a dirty log ring to be mapped.
2056+
/// let vcpu = vm.create_vcpu(0);
20392057
/// vm.reset_dirty_rings().unwrap();
20402058
/// }
20412059
/// ```
@@ -3049,4 +3067,73 @@ mod tests {
30493067
vm.has_device_attr(&dist_attr).unwrap();
30503068
vm.set_device_attr(&dist_attr).unwrap();
30513069
}
3070+
3071+
#[test]
3072+
fn test_enable_dirty_log_rings() {
3073+
let kvm = Kvm::new().unwrap();
3074+
let mut vm = kvm.create_vm().unwrap();
3075+
if kvm.check_extension(Cap::DirtyLogRing) {
3076+
vm.enable_dirty_log_ring(None).unwrap();
3077+
// Create two vCPUs to cause two dirty log rings to be mapped.
3078+
let _0 = vm.create_vcpu(0).unwrap();
3079+
let _1 = vm.create_vcpu(1).unwrap();
3080+
vm.reset_dirty_rings().unwrap();
3081+
}
3082+
}
3083+
3084+
#[test]
3085+
fn test_enable_dirty_log_rings_sized() {
3086+
let kvm = Kvm::new().unwrap();
3087+
let mut vm = kvm.create_vm().unwrap();
3088+
if kvm.check_extension(Cap::DirtyLogRing) {
3089+
let max_supported_size = vm.check_extension_raw(KVM_CAP_DIRTY_LOG_RING.into());
3090+
let size = std::cmp::max(max_supported_size / 2, size_of::<kvm_dirty_gfn>() as i32);
3091+
vm.enable_dirty_log_ring(Some(size)).unwrap();
3092+
// Create two vCPUs to cause two dirty log rings to be mapped.
3093+
let _0 = vm.create_vcpu(0).unwrap();
3094+
let _1 = vm.create_vcpu(1).unwrap();
3095+
vm.reset_dirty_rings().unwrap();
3096+
}
3097+
}
3098+
3099+
#[test]
3100+
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
3101+
fn test_enable_dirty_log_rings_acq_rel() {
3102+
let kvm = Kvm::new().unwrap();
3103+
let mut vm = kvm.create_vm().unwrap();
3104+
if kvm.check_extension(Cap::DirtyLogRing) {
3105+
vm.enable_dirty_log_ring(None).unwrap();
3106+
3107+
// Manually enable Acq/Rel
3108+
vm.dirty_log_ring_info = vm
3109+
.dirty_log_ring_info
3110+
.map(|i| DirtyLogRingInfo { acq_rel: true, ..i });
3111+
3112+
// Create two vCPUs to cause two dirty log rings to be mapped.
3113+
let _0 = vm.create_vcpu(0).unwrap();
3114+
let _1 = vm.create_vcpu(1).unwrap();
3115+
3116+
// Reset dirty rings
3117+
vm.reset_dirty_rings().unwrap();
3118+
}
3119+
}
3120+
3121+
#[test]
3122+
fn test_illegal_dirty_ring() {
3123+
let kvm = Kvm::new().unwrap();
3124+
let mut vm = kvm.create_vm().unwrap();
3125+
if kvm.check_extension(Cap::DirtyLogRing) {
3126+
// Create one vCPU without dirty log ring
3127+
let _0 = vm.create_vcpu(0).unwrap();
3128+
3129+
// Not allowed after vCPU has been created
3130+
assert!(vm.enable_dirty_log_ring(None).is_err());
3131+
3132+
// Create another vCPU
3133+
let _1 = vm.create_vcpu(1).unwrap();
3134+
3135+
// Dirty ring should not be enabled
3136+
assert!(vm.reset_dirty_rings().is_err());
3137+
}
3138+
}
30523139
}

0 commit comments

Comments
 (0)