Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable usage of sync_regs #190

Merged
merged 1 commit into from
Feb 9, 2022
Merged

Conversation

ctfhacker
Copy link
Contributor

Enable the use of the sync_regs API from KVM to allow bulk getting and setting of general purpose registers, system registers, and vcpu events, reducing the number of ioctls needed.

Copy link
Member

@andreeaflorescu andreeaflorescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctfhacker thanks for your contribution. It looks like the tests are failing on arm, if this is only available on x86_64, could you mark the code as such so that the tests pass on arm as well? Can you also improve your commit message by:

  • Writing in the commit message what is the sync_regs functionality useful for
  • Sign your commit by running git commit -s --amend --no-edit

@ctfhacker ctfhacker force-pushed the sync_regs branch 4 times, most recently from 2ca05d6 to db82986 Compare January 26, 2022 19:33
@ctfhacker
Copy link
Contributor Author

Perfect! Let me know how this looks now with the checks passing.

Copy link
Member

@andreeaflorescu andreeaflorescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a really nice PR to review, thanks for your contribution. I really appreciate that you took the time to write documentation, examples, and tests! Also, really nice how you structured the reg parameter such that invalid values cannot be passed 👍

I have just a few small comments, otherwise LGTM.

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn sync_regs(&self) -> kvm_sync_regs {
let kvm_run: &mut kvm_run = self.kvm_run_ptr.as_mut_ref();
unsafe { kvm_run.s.regs }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe code needs to have a comment about why is it safe. You can take a look at some examples here:

The unsafeness in this case comes from accessing the union field, so we need to write about why is it safe to access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the perspective of an arbitrary vcpu, I'm not sure we have the safety guarantee that the kvm_run pointer was mmap'ed with enough space for the sync_regs itself.

I was looking at adding a sanity check that the KvmRunWrapper.mmap_size is large enough to fill the kvm_run structure, but wasn't quite sure if that was the best way.

Or, could we make the both sync_regs and sync_regs_mut themselves unsafe, since we can't guarantee that the pointer from kvm_run_ptr is large enough for the entire kvm_run structure?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking them as unsafe is a good path forward if we cannot ensure that using it is safe.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the mmap_size check didn't work? It looks like the next best thing if 'has space for sync_regs' can't be built in to the type. Returning an Result from sync_regs_mut might be awkward but maybe better than the unsafe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! KvmRunWrapper does keep the mmap_size around in the structure which we can check against. For some reason, I thought KvmRunWrapper could be created from an arbitrary pointer.

I'll see about adding that check in.

My thinking was because anyone can create a KvmRunWrapper, then calling sync_regs which relies on that kvm_run_ptr can't be trusted (or at least isn't safe in all cases). I think adding a check against the kvm_run_ptr.mmap_size should suffice. I don't see a problem adding a Result for that either. Only question would be looking into if the kvm_run parameters have changed in versions or how to get the correct offset/length for any given bindings.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having mmap_size be correct is guaranteed by KvmRunWrapper. Getting that bit of unsafe code right is already critical to the safety of the system and allows other parts to rely on the type being correct if it exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that explanation. I saw that KvmRunWrapper is exposed by the library, enabling the user to specify whatever mmap_size they wished. With that though, the user can't use this smaller KvmRunWrapper to construct an invalid Vcpu, so everything looks fine.

Thank you for spelling that out for me. Much appreciated! I'll remove those unsafes.

Comment on lines 2537 to 2559
vcpu.set_sync_valid_reg(SyncReg::Register);
assert_eq!(
vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs,
SyncReg::Register as u64
);
vcpu.clear_sync_valid_reg(SyncReg::Register);
assert_eq!(vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs, 0);

vcpu.set_sync_valid_reg(SyncReg::SystemRegister);
assert_eq!(
vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs,
SyncReg::SystemRegister as u64
);
vcpu.clear_sync_valid_reg(SyncReg::SystemRegister);
assert_eq!(vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs, 0);

vcpu.set_sync_valid_reg(SyncReg::VcpuEvents);
assert_eq!(
vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs,
SyncReg::VcpuEvents as u64
);
vcpu.clear_sync_valid_reg(SyncReg::VcpuEvents);
assert_eq!(vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code block and the ones below with sync_dirty_reg can each be reduced to the following (without loosing too much of the code readability):

        let sync_regs = [SyncReg::Register, SyncReg::SystemRegister, SyncReg::VcpuEvents];
        for reg in sync_regs {
            vcpu.set_sync_valid_reg(reg);
            assert_eq!(
                vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs,
                SyncReg::Register as u64
            );
            vcpu.clear_sync_valid_reg(reg);
            assert_eq!(vcpu.kvm_run_ptr.as_mut_ref().kvm_valid_regs, 0);
        }

Would you be okay with this change to remove some of the code duplication?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure. That makes total sense to me.

@ctfhacker ctfhacker force-pushed the sync_regs branch 5 times, most recently from 8092c2f to 7d6f26d Compare January 27, 2022 22:36
@andreeaflorescu
Copy link
Member

You'll need to do a borrow on the array used in tests to fix the problems reported by the CI. Once that is done, I think we can merge it.

@ctfhacker
Copy link
Contributor Author

The coverage score seemed to drop from 86.1 -> 85.4. Should I update that? I think all the new code has unit tests for them.

@andreeaflorescu
Copy link
Member

The coverage score seemed to drop from 86.1 -> 85.4. Should I update that? I think all the new code has unit tests for them.

Yes, you should just update the coverage score. The coverage is a bit too sensitive. We will work on addressing that at the project level.

@ctfhacker ctfhacker force-pushed the sync_regs branch 2 times, most recently from 33129ba to 4993392 Compare February 2, 2022 17:27
@ctfhacker
Copy link
Contributor Author

Roger that! Just updated and pushed that coverage change. Apologies if commit --amend with push --force wasn't the correct way of getting this PR ready to go.

andreeaflorescu
andreeaflorescu previously approved these changes Feb 5, 2022
@ctfhacker ctfhacker force-pushed the sync_regs branch 3 times, most recently from 44585ac to 03276fa Compare February 6, 2022 21:33
Comment on lines 2647 to 2655
// hlt is the only expected return from guest execution
assert!(matches!(vcpu.run().expect("run failed"), VcpuExit::Hlt));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running this test on my laptop, this is failing with:
thread 'ioctls::vcpu::tests::test_sync_regs_with_run' panicked at 'assertion failed: matches!(vcpu.run().expect(\"run failed\"), VcpuExit :: Hlt)', src/ioctls/vcpu.rs:2648:9
Is there something else (a capability, etc) we need to check before doing this assert?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, there is the KVM_CAP_SYNC_REGS capability, containing which sync_regs are available. We can add the assert in the unit tests with something like

let sync_regs_cap = unsafe {
        unsafe { ioctl_with_val(&vm, KVM_CHECK_EXTENSION(), Cap::SyncRegs as c_ulong) }
};
assert_eq!(sync_regs_cap, 7);

or opening up check_extension_int to pub rather than private.

Either way, I think we'll also need to add this to the example code to make sure the user knows to check the capability.

Any thoughts on opening check_extension_int to pub? I've never seen it, but supposedly that capability could come back with fewer than 3 bits set, signifying that one of the three sync_regs isn't in use. Opening up check_extension_int would give the user the ability to look specifically which bits are not set rather than an all or nothing, but I'm not sure opening that up function to this one use case is worthwhile.

I've pushed this test case up to see if your capability isn't 7 to see if that was the problem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was the problem. We have the check_extension method that is public and can be used for that, i.e.:

if kvm.check_extension(Cap::SyncRegs) { ... }

And yes we should also update the documentation examples. Thanks!

Copy link
Collaborator

@lauralt lauralt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides the comment about the failing test, looks good to me!

The coverage file might need to be updated again :(.

src/ioctls/vcpu.rs Outdated Show resolved Hide resolved
@ctfhacker ctfhacker force-pushed the sync_regs branch 5 times, most recently from bd87348 to dbc1f95 Compare February 9, 2022 12:18
vcpu.set_sync_dirty_reg(SyncReg::VcpuEvents);

// Ensure sync reg bits are available for use
assert!(kvm.check_extension(Cap::SyncRegs));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way the test is still going to fail on some machines. I propose we run this test only if we have the capability, similar to how it is done here:

if kvm.check_extension(Cap::SyncRegs) {
       let vm = kvm.create_vm().unwrap();
        // This example is based on https://lwn.net/Articles/658511/
       ...
        assert_eq!(sync_regs.regs.rax, 0x8001);
}

I just noticed there are a few places where we are going with the assert approach, but we should probably update them.

let vm = kvm.create_vm().unwrap();
let mut vcpu = vm.create_vcpu(0).unwrap();

if kvm.check_extension(Cap::SyncRegs) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the sync registers works on every machine, so I think we can run the current test without checking for the extension. My proposal was to check for it only for the other test where we call run as well. Same thing applies for the documentation tests. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah that makes sense. I agree with that. It isn't technically going to be a problem until run is called, rather than hinting ahead of time.

KVM provides the ability to synchronize general purpose registers,
system registers, and vcpu events without the need of an ioctl. This is
worthwhile for VMs that exit frequently to avoid calling `get_regs`,
`get_sregs`, or `get_vcpu_events` each exit or `set_regs`, `set_sregs`,
or `set_vcpu_events` on each entry.

Signed-off-by: Cory Duplantis <cld251@gmail.com>
Copy link
Collaborator

@lauralt lauralt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@andreeaflorescu andreeaflorescu merged commit b5b9b75 into rust-vmm:main Feb 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants