Compare commits

...

166 Commits

Author SHA1 Message Date
Rob Bradford
3eb9e61a32 rpm: Update spec file for version bump
Update the spec file for the rpm to the latest version.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-10 18:13:59 +00:00
Rob Bradford
d42b50845b build, release-notes.md: Document 0.12.0 release
Update release notes and version number for the new release.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-10 18:11:56 +00:00
Rob Bradford
06f391e022 tests: integration: Retry epoll if we receive -EINTR or -EAGAIN
On the CI we are seeing that sometimes the epoll is receiving these
errors which do not indicate a failure but that we should retry.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-10 10:11:19 -08:00
dependabot-preview[bot]
60d2469e7a build(deps): bump openssl-sys from 0.9.58 to 0.9.59
Bumps [openssl-sys](https://github.com/sfackler/rust-openssl) from 0.9.58 to 0.9.59.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-sys-v0.9.58...openssl-sys-v0.9.59)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-10 07:33:54 +00:00
Wei Liu
c4f8e4b000 main: provide a sensible error message when /dev/mshv is missing
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-09 17:28:36 +00:00
Muminul Islam
c32959261e gh: Expand clippy tests to cover the mshv feature
Since we can't test mshv and kvm at the same time, --all-features no
longer work.
We factorize all, non-hypervisor related features into a common set and
mix that with either mshv and kvm.

Co-Developed-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
ef3fad8388 hypervisor: mshv: Implement CPU state for MshvVcpu
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
2b42f6c835 hypervisor: mhsv: Implement CPU run loop
Handle CPU exits, adding instruction emulations.
Keep CPU specific data inside vmm for later use.

Co-Developed-by: Nuno Das Neves <nudasnev@microsoft.com>
Signed-off-by: Nuno Das Neves <nudasnev@microsoft.com>
Co-Developed-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Praveen Paladugu <prapal@microsoft.com>
Co-Developed-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
7acb5c6437 hypervisor, mshv: Define MshvEmulatorContext
This patch adds the definition and implementation
MshvEmulatorContext which is platform emulation for Hyper-V.

Co-Developed-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
63f356b75b hypervisor: mhsv: Define software emulated TLB
A software emulated TLB. This is mostly used by
the instruction emulator to cache gva to gpa
translations passed from the hypervisor.

Co-Developed-by: Nuno Das Neves <nudasnev@microsoft.com>
Signed-off-by: Nuno Das Neves <nudasnev@microsoft.com>
Co-Developed-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Praveen Paladugu <prapal@microsoft.com>
Co-Developed-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
fd0ef6cfb5 hypervisor: mshv: Emulate IrqFd and IOEventFd for mshv module
We don't have IrqFd and IOEventFd support in the kernel for now.
So an emulation layer is needed. In the future, we will be adding this
support in the kernel.

Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
286a23fbd4 hypervisor: mshv: Add vmmops to MshvVm struct
vmmops trait object is needed to get access some
of the upper level vmm functionalities i.e guest
memory access, IO read write etc.

Co-Developed-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
3a93487b82 hypervisor: mshv: Add Microsoft Hypervisor specific data to Mshv{Vm,Vcpu}
Adding hv_state (hyperv state) to Vm and Vcpu struct for mshv.
This state is needed to keep some kernel data(for now hypercall page)
in the vmm.

Co-Developed-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
23c46b162e hypervisor, vmm: Implement the mshv module and add mshv support in vmm
Implement hypervisor, Vm, Vcpu crate at a minimal
functionalities. Also adds the mshv feature gate,
separates out the functionalities between kvm and
mshv inside the vmm crate.

Co-Developed-by: Nuno Das Neves <nudasnev@microsoft.com>
Signed-off-by: Nuno Das Neves <nudasnev@microsoft.com>
Co-Developed-by: Praveen Paladugu <prapal@microsoft.com>
Signed-off-by: Praveen Paladugu <prapal@microsoft.com>
Co-Developed-by: Samuel Ortiz <sameo@linux.intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
f4af668d76 hypervisor, vmm: Implement MsiInterruptOps for mshv
Co-Developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
7fe5d276a3 hypervisor: mshv: Add x86_64 module
Add x86_64 specific definitions as we only plan
to support x86_64 for the moment.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
e664fa386b hypervisor: Modify toml file to add mshv-{bindings, ioctls) crates
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
9919dec1d9 hypervisor: Introduce mshv module in the hypervisor
This is the initial folder structure of the mshv module inside
the hypervisor crate. The aim of this module is to support Microsoft
Hyper-V as a supported Hypervisor.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
Muminul Islam
9ce6c3b75c hypervisor, vmm: Feature guard KVM specific code
There are some code base and function which are purely KVM specific for
now and we don't have those supports in mshv at the moment but we have plan
for the future. We are doing a feature guard with KVM. For example, KVM has
mp_state, cpu clock support,  which we don't have for mshv. In order to build
those code we are making the code base for KVM specific compilation.

Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-09 14:55:20 +01:00
dependabot-preview[bot]
47f59409db build(deps): bump anyhow from 1.0.34 to 1.0.35
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.34 to 1.0.35.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.34...1.0.35)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-08 09:01:03 +00:00
dependabot-preview[bot]
b51f112629 build(deps): bump libc from 0.2.80 to 0.2.81
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.80 to 0.2.81.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.80...0.2.81)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-08 08:13:51 +00:00
dependabot-preview[bot]
d158fa8de2 build(deps): bump linux-loader from 3cf96c5 to acf9c21
Bumps [linux-loader](https://github.com/rust-vmm/linux-loader) from `3cf96c5` to `acf9c21`.
- [Release notes](https://github.com/rust-vmm/linux-loader/releases)
- [Commits](3cf96c53ed...acf9c21591)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-08 08:13:27 +00:00
dependabot-preview[bot]
c64de38140 build(deps): bump syn from 1.0.53 to 1.0.54
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.53 to 1.0.54.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.53...1.0.54)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-07 22:10:21 +00:00
Rob Bradford
9594ecec0d build: Only pull in iced-x86 for x86_64 builds
Fixes: #2024

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-07 17:42:42 +00:00
Anatol Belski
6c0c47a530 docs/windows.md: Add explicit note on how to start WinDbg
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2020-12-07 16:56:00 +00:00
Wei Liu
6d38612f6c hypervisor: x86: move RFLAGS bits to regs.rs
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-07 14:35:35 +01:00
Wei Liu
a44d96c9cc hypervisor: emulator: switch to use vec in MockVMM
The customized hashmap macro can't be lifted to common MockVMM code.
MockVMM only needs a collection to iterate over to get initial register
states. A vector is just as good as a hashmap.

Switch to use a vector to store initial register states. This allows us
to drop the hashmap macro everywhere.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-07 14:35:35 +01:00
Wei Liu
93b7dcac12 hypervisor: emulator: emulate CMP
Unfortunately Rust stable does not yet have inline ASM support the flag
calculation will have to be implemented in software.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-07 14:35:35 +01:00
Wei Liu
dd3844a892 hypervisor: emulator: drop imm_op macro
It is not needed anymore.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-07 14:35:35 +01:00
Wei Liu
0c3ef986fa hypervisor: emulator: rewrite MOV emulation with get/set_op
Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-07 14:35:35 +01:00
Wei Liu
53c3b00fb0 hypervisor: emulator: introduce get/set_op
Provide two helpers to get and set operands. This reduces repetition.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-07 14:35:35 +01:00
Rob Bradford
03b824be0b build: Disable fast failing for build actions
nightly / beta are not required actions but if they fail then all other
builds will be cancelled and so prevent the PR from being cleared as
buildable.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-07 12:36:38 +00:00
dependabot-preview[bot]
558975aa51 build(deps): bump serde_derive from 1.0.117 to 1.0.118
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.117 to 1.0.118.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.117...v1.0.118)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-07 09:22:13 +00:00
dependabot-preview[bot]
d78b9bd287 build(deps): bump smallvec from 1.5.0 to 1.5.1
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.5.0...v1.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-07 09:08:31 +00:00
dependabot-preview[bot]
365d39ca2c build(deps): bump serde from 1.0.117 to 1.0.118
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.117 to 1.0.118.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.117...v1.0.118)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-07 08:36:52 +00:00
Muminul Islam
d79594aa3f scripts: dev_cli: Remove unnecessary spaces
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-05 00:01:41 +01:00
Muminul Islam
27b5f8d7e3 scripts: Add an option to accept hypervisor argument
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-12-05 00:01:41 +01:00
Rob Bradford
2cc312b65c build: Fix cargo fuzz build
Unfortunately it seems patch entries are ignored when obtaining
dependencies from another workspace.

Remove the problematic kvm-ioctls and kvm-bindings patch entries and use
the forked repository directly.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-04 11:38:44 +00:00
dependabot-preview[bot]
d416271457 build(deps): bump cc from 1.0.65 to 1.0.66
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.65 to 1.0.66.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.65...1.0.66)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-04 10:51:49 +00:00
Rob Bradford
9e6a023f7f vmm: Don't continue to lookup debug I/O port
When using an PIO write to 0x80 which is a special case handle that and
then return without going through the resolve.

This removes an extra warning that is reported.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-03 07:25:42 -08:00
Wei Liu
90ae4676c8 hypervisor: x86: emulator: drop unneeded curly brackets
There is no need to have a pair of curly brackets for structures without
any member.

No functional change.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-03 14:52:00 +01:00
dependabot-preview[bot]
89bc413add build(deps): bump serde_json from 1.0.59 to 1.0.60
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.59 to 1.0.60.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.59...v1.0.60)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-03 13:50:29 +00:00
Wei Liu
c352b59857 hypervisor: emulator: drop InstructionMap
It is not needed anymore.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-02 19:45:36 +01:00
Wei Liu
ab89b48143 hypervisor: emulator: use static mapping to dispatch emulation
The mapping between code and its handler is static. We can drop the
HashMap in favour of a static match expression.

This has two benefits:
1. No more memory allocation and deallocation for the HashMap.
2. Shorter look-up time.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-12-02 19:45:36 +01:00
Rob Bradford
ffaab46934 misc: Use a more relaxed memory model when possible
When a total ordering between multiple atomic variables is not required
then use Ordering::Acquire with atomic loads and Ordering::Release with
atomic stores.

This will improve performance as this does not require a memory fence
on x86_64 which Ordering::SeqCst will use.

Add a comment to the code in the vCPU handling code where it operates on
multiple atomics to explain why Ordering::SeqCst is required.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-02 19:04:30 +01:00
Rob Bradford
280d4fb245 vmm: Include device tree in vm.info API
The DeviceNode cannot be fully represented as it embeds a Rust style
enum (i.e. with data) which is instead represented by a simple
associative array.

Fixes: #1167

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-01 16:44:25 +01:00
Samuel Ortiz
15f0451c34 hypervisor: emulator: Format instructions on error paths
Formatting instructions might be costly, so we only want to do that on
the emulation error paths.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-12-01 11:13:54 +01:00
Samuel Ortiz
3fa6e17cc4 hypervisor: x86: Make imm_op available to all emulated instructions
By moving it to the instructions root module.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-12-01 11:13:54 +01:00
Wei Liu
e2c81f9ed8 hypervisor: x86: Extend the imm_op() macro
To support every kind of immediate operands.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-12-01 11:13:54 +01:00
Rob Bradford
ead8453120 build: Remove vhost_user_fs
This has been superseded by virtiofsd-rs.

Fixes: #2013

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-01 11:13:21 +01:00
Rob Bradford
c4ebe83576 rpm: Update .spec file to not reference vhost_user_fs
Fixes: #2013

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-01 11:13:21 +01:00
Rob Bradford
1908f488c3 tests: Move tests from vhost_user_fs to virtiofsd-rs
Download and build virtiofsd-rs and then use that in the integration
test suite.

Fixes: #2013

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-01 11:13:21 +01:00
Rob Bradford
593a958fe5 pci, vmm: Include VFIO devices in device tree
Fixes: #1687

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-12-01 10:49:04 +01:00
Wei Liu
ba71390d6a hypervisor: drop arc-swap dependency
It is no longer needed after 0fec32658.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-11-30 16:47:32 +00:00
Samuel Ortiz
f6892f2ede hypervisor: emulator: Add a negative instruction fetch unit test
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
251434862f hypervisor: emulator: Fix logic bug in MockVMM
emulate_first_insn() really means we want only the first instruction to
be emulated.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
1ac4c42a84 hypervisor: emulator: Add initial fetch support
When the x86 instruction decoder tells us about some missing bytes from
the instruction stream, we call into the platform fetch method and
emulate one last instruction.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
7dc92d1dac hypervisor: emulator: Decoding loop restructuring
In preparation for the instruction fetching step, we modify the decoding
loop so that we can check what the last decoding error is.

We also switch to explictly using decode_out() which removes a 32 bytes
copy compared to decode().

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
7a4edecd29 hypervisor: x86: Add an address linearization method to CpuStateManager
From a CPU state and a segment, we can translate a logical (segmented)
address into a linear one.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
d419e30df1 hypervisor: x86: Add a SegmentRegistorOps trait
In order to validate emulated memory accesses, we need to be able to get
all the segments descriptor attributes.

This is done by abstracting the SegmentRegister attributes through a
trait that each hypervisor will have to implement.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
3faffcb087 hypervisor: emulator: Add a CPU mode getter to CpuStateManager
We need to be able to build the CPU mode from its state in order to
start implementing mode related checks in the x86 emulator.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
Samuel Ortiz
b3a1f5f1be hypervisor: Start moving register definitions to the hypervisor crate
Most of arch/src/x86_64/regs.rs will eventually move unde
hypervisor/src/x86/regs.rs.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-30 13:09:19 +00:00
dependabot-preview[bot]
2bc5119730 build(deps): bump syn from 1.0.52 to 1.0.53
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.52 to 1.0.53.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.52...1.0.53)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-30 09:09:26 +00:00
dependabot-preview[bot]
5100bc94cc build(deps): bump syn from 1.0.51 to 1.0.52
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.51 to 1.0.52.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.51...1.0.52)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-28 00:28:08 +00:00
dependabot-preview[bot]
6bf9fe487b build(deps): bump pnet from 0.26.0 to 0.27.2
Bumps [pnet](https://github.com/libpnet/libpnet) from 0.26.0 to 0.27.2.
- [Release notes](https://github.com/libpnet/libpnet/releases)
- [Commits](https://github.com/libpnet/libpnet/compare/v0.26.0...v0.27.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-27 23:56:02 +00:00
Rob Bradford
d1a23d9085 docs: Add instructions for using perf
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-27 19:19:14 +01:00
Rob Bradford
05f785fa88 build: Move timeouts to integration test blocks
If we rely on timeouts at the top level we can get builds being aborted
simply because they took too long to be scheduled rather than because
the actual integration tests took too long.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-27 19:18:39 +01:00
Samuel Ortiz
a3d957ba4d hypervisor: emulator: Clean the MockVMM initialization path
Separate the standalone _init_and_run() function into 2 MockVMM methods:
new() and emulate_insn().

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-27 19:17:35 +01:00
Samuel Ortiz
5bd63efa45 hypervisor: emulator: Move MockVMM into a shared test module
The MockVMM platform will be used by other instructions emulation
implementations, but also by the emulator framework.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-27 19:17:35 +01:00
Wei Liu
66b001665f hypervisor: x86: only calculate address when necessary in MOV emulation
Only calculate the address when the operand is memory.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-11-26 16:12:23 +01:00
Wei Liu
c6aea5af5d hypervisor: x86: drop an extraneous box indirection
There is no need to put a box into another box.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-11-26 12:02:47 +00:00
Wei Liu
c8b655490e hypervisor: x86: reference PlatformEmulator in Emulator
The observation here is PlatformEmulator can be seen as the context for
emulation to take place. It should be rather easy to construct a context
that satisfies the lifetime constraints for instruction emulation.

The thread doing the emulation will have full ownership over the
context, so this removes the need to wrap PlatformEmulator in Arc and
Mutex, as well as the need for the context to be either Clone or Copy.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-11-26 12:14:40 +01:00
Rob Bradford
72741c557a actions: Add "--all --tests" to clippy in quality action
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
b2608ca285 vmm: cpu: Fix clippy issues inside test
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
74fe032827 vm-device: bus: Fix clippy issues inside tests
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
40a2e3b462 vhost_user_block: Fix clippy issue
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
abc6672f21 qcow: Fix clippy issues inside tests
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
053c9e4d73 devices: serial: Fix clippy issues inside tests
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
9ea19852b5 arch: x86_64: Fix clippy issues inside tests
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
Rob Bradford
9871ff454b arch_gen: mpspec: Fix clippy issues inside tests
Found by:  cargo clippy --all-features --all --tests

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-26 09:32:46 +01:00
dependabot-preview[bot]
e817b4f36c build(deps): bump crossbeam-utils from 0.8.0 to 0.8.1
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.0...crossbeam-utils-0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-26 08:32:02 +00:00
Rob Bradford
6f63cd00eb vmm: device_manager: Remove ActivatedBackend struct
This was used for vhost-user self spawning but no longer has any users.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-25 20:47:43 +01:00
Wei Liu
a6ad85dc40 hypervisor: x86: handle more registers in emulator
List all GPRs documented in Intel's SDM.

Signed-off-by: Wei Liu <liuwe@microsoft.com>
2020-11-25 20:18:40 +01:00
Samuel Ortiz
0f4f30dbde hypervisor: x86: Add MOV to the emulator instruction map
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
fe5401223b hypervisor: x86: Emulate MOV
And add a few unit tests based on a Mock platform.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
12b7a498d9 hypervisor: x86: Add an instruction emulator
The emulator gets a CPU state from a CpuStateManager instance, emulates
the passed instructions stream and returns the modified CPU state.

The emulator is a skeleton for now since it comes with an empty
instruction mnemonic map.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
1fc97e91a4 hypervisor: x86: Add an InstructionHandler interface
And an InstructionMap helper structure to map x86 mnemonic codes
to instruction handlers.

Any instruction emulation implementation should then boil down with
implementing InstructionHandler for any supported mnemonic.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
fc5d6c96be hypervisor: x86: Add a minimal CpuStateManager implementation
Minimal will be defined by the amount of emulated instructions.
Carrying all GPRs, all CRs, segment registers and table registers should
cover quite a few instructions.

Co-developed-by: Wei Liu <liuwe@microsoft.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
546778ebfb hypervisor: x86: Add a CpuStateManager interface
For efficiently emulating x86 instructions, we need to build and pass a
CPU state copy/reference to instruction emulation handlers. Those handlers
will typically modify the CPU state and let the caller commit those
changes back through the PlatformEmulator trait set_cpu_state method.

Hypervisors typically have internal CPU state structures, that maps back
to the correspinding kernel APIs. By implementing the CpuState trait,
instruction emulators will be able to directly work on CPU state
instances that are directly consumable by the underlying hypervisor and
its kernel APIs.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
f0360aff83 hypervisor: Architecture agnostic instruction emulation interface
In order to emulate instructions, we need a way to get access to some of
the guest resources. The PlatformEmulator interface provides guest
memory and CPU state access to emulator implementations.

Typically, an hypervisor will implement PlatformEmulator for architecture
specific instruction emulators to build their framework on top of.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Samuel Ortiz
ae96aeda65 arch: Move the gdt module to the hypervisor crate
We will need the GDT API for the hypervisor's x86 instruction
emulator implementation, it's better if the arch crate depends on the
hypervisor one rather than the other way around.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-25 17:02:11 +01:00
Rob Bradford
b5b97f7b05 vmm: When receiving a migration store the config
The configuration is stored separately to the Vm in the VMM. The failure
to store the config was preventing the VM from shutting down correctly
as Vmm::vm_delete() checks for the presence of the config.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-25 01:27:26 +01:00
Rob Bradford
9fc2613b41 vm-device: bus: Remove unwrap() when upgrading weak reference
Rather return the None to the caller to handle instead. This removes the
source of a potential panic.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-25 01:27:26 +01:00
Rob Bradford
44b5a19209 bin: ch-remote: Fix typo in error message
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-25 01:27:26 +01:00
Rob Bradford
df6b52924f vmm: Unlink created socket after source connects
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-25 01:27:26 +01:00
Rob Bradford
1ab1341775 vmm: seccomp_filters: Add KVM_GET_DIRTY_LOG to permitted calls
The live migration support added use of this ioctl but it wasn't
included in the permitted list.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-25 01:27:26 +01:00
dependabot-preview[bot]
a29f7ffcc7 build(deps): bump signal-hook from 0.2.0 to 0.2.1
Bumps [signal-hook](https://github.com/vorner/signal-hook) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-25 00:26:27 +00:00
dependabot-preview[bot]
eda39d0469 build(deps): bump rust-argon2 from 0.8.2 to 0.8.3
Bumps [rust-argon2](https://github.com/sru-systems/rust-argon2) from 0.8.2 to 0.8.3.
- [Release notes](https://github.com/sru-systems/rust-argon2/releases)
- [Changelog](https://github.com/sru-systems/rust-argon2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sru-systems/rust-argon2/compare/0.8.2...0.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-24 09:38:49 +00:00
dependabot-preview[bot]
de0c24810e build(deps): bump syn from 1.0.50 to 1.0.51
Bumps [syn](https://github.com/dtolnay/syn) from 1.0.50 to 1.0.51.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/1.0.50...1.0.51)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-24 06:01:35 +00:00
Samuel Ortiz
fadeb98c67 cargo: Bulk update
Includes updates for ssh2, cc, syn, tinyvec, backtrace micro-http and libssh2.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-23 12:25:31 +01:00
Samuel Ortiz
1fc0b94fcd cargo: Move to crates.io vm-memory 0.4.0
vm-memory 0.4.0 now contains all our fixes.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-23 10:55:13 +01:00
Samuel Ortiz
ef4c896bbf arch: aarch64: Fix rust 1.48 clippy warnings
methods called `new` usually return `Self`
See https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-20 17:09:28 +01:00
Samuel Ortiz
72bb255ff6 pci, virtio-devices: Fix rust 1.48 clippy warnings
Unnecessary closure used to substitute value for `Option::None`
See https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-20 17:09:28 +01:00
Samuel Ortiz
a406d90059 arch: Fix rust 1.48 clippy warnings
const should not be mutable types:
https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
2020-11-20 17:09:28 +01:00
dependabot-preview[bot]
0dfffee6ba build(deps): bump cc from 1.0.62 to 1.0.63
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.62 to 1.0.63.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.62...1.0.63)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-18 23:37:01 +00:00
Rob Bradford
0fec326582 hypervisor, vmm: Remove shared ownership of VmmOps
This interface is used by the vCPU thread to delegate responsibility for
handling MMIO/PIO operations and to support different approaches than a
VM exit.

During profiling I found that we were spending 13.75% of the boot CPU
uage acquiring access to the object holding the VmmOps via
ArcSwap::load_full()

    13.75%     6.02%  vcpu0            cloud-hypervisor    [.] arc_swap::ArcSwapAny<T,S>::load_full
            |
            ---arc_swap::ArcSwapAny<T,S>::load_full
               |
                --13.43%--<hypervisor::kvm::KvmVcpu as hypervisor::cpu::Vcpu>::run
                          std::sys_common::backtrace::__rust_begin_short_backtrace
                          core::ops::function::FnOnce::call_once{{vtable-shim}}
                          std::sys::unix::thread::Thread::new::thread_start

However since the object implementing VmmOps does not need to be mutable
and it is only used from the vCPU side we can change the ownership to
being a simple Arc<> that is passed in when calling create_vcpu().

This completely removes the above CPU usage from subsequent profiles.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-19 00:16:02 +01:00
dependabot-preview[bot]
6a591ca81d build(deps): bump pin-project from 1.0.1 to 1.0.2
Bumps [pin-project](https://github.com/taiki-e/pin-project) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/master/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v1.0.1...v1.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-18 22:15:58 +00:00
dependabot-preview[bot]
a525f8365f build(deps): bump unicode-normalization from 0.1.15 to 0.1.16
Bumps [unicode-normalization](https://github.com/unicode-rs/unicode-normalization) from 0.1.15 to 0.1.16.
- [Release notes](https://github.com/unicode-rs/unicode-normalization/releases)
- [Commits](https://github.com/unicode-rs/unicode-normalization/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-18 22:15:49 +00:00
Rob Bradford
f271378811 tests: Wait for reboot in test_virtio_watchdog()
Aim to reduce flaky failures of this test by waiting for the VM to come
back after a reboot.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 18:27:34 +01:00
Rob Bradford
78b9edf848 tests: Get output from firmware and Linux for test_large_vm()
As we switched to focal for this test we no longer get any output during
the boot unless serial is used over virtio-console.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 18:27:34 +01:00
Rob Bradford
d0aebfb922 tests: Increase time waited for hotplugged memory to appear
There have been a lot of flakes around tests such as
test_virtio_fs_hotplug_dax_on_w_vhost_user_fs_daemon() or
test_virtio_fs_hotplug_dax_on() which all try and hotplug memory.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 18:27:34 +01:00
Rob Bradford
f913dd6150 tests: Add integration test to force io_uring off for block
Fixes: #1561

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 11:47:54 +01:00
Rob Bradford
c622278030 config, device_manager: Add support for disabling io_uring for testing
Add config parameter to --disk called "_disable_io_uring" (the
underscore prefix indicating it is not for public consumpion.) Use this
option to disable io_uring if it would otherwise be used.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 11:47:54 +01:00
Rob Bradford
d1d0421103 main: Remove --net-backend and --block-backend from cloud-hypervisor
Remove the parameters used for self spawning from the cloud-hypervisor
binary.

See: #1925

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 11:46:32 +01:00
Rob Bradford
fb70baece0 tests: Make integration test use vhost_user_{net,block} binaries
With the removal of vhost-user self-spawning support we should migrate
the tests to use the binaries so that we can remove the functionality
from the cloud-hypervisor binary itself.

See: #1925

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-18 11:46:32 +01:00
dependabot-preview[bot]
0159ab93e1 build(deps): bump unicode-normalization from 0.1.14 to 0.1.15
Bumps [unicode-normalization](https://github.com/unicode-rs/unicode-normalization) from 0.1.14 to 0.1.15.
- [Release notes](https://github.com/unicode-rs/unicode-normalization/releases)
- [Commits](https://github.com/unicode-rs/unicode-normalization/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-18 09:01:29 +00:00
dependabot-preview[bot]
609d704168 build(deps): bump smallvec from 1.4.2 to 1.5.0
Bumps [smallvec](https://github.com/servo/rust-smallvec) from 1.4.2 to 1.5.0.
- [Release notes](https://github.com/servo/rust-smallvec/releases)
- [Commits](https://github.com/servo/rust-smallvec/compare/v1.4.2...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-17 21:56:51 +00:00
Rob Bradford
3ac9b6c404 vmm: Implement live migration
Now the VM is paused/resumed by the migration process itself.

0. The guest configuration is sent to the destination
1. Dirty page log tracking is started by start_memory_dirty_log()
2. All guest memory is sent to the destination
3. Up to 5 attempts are made to send the dirty guest memory to the
   destination...
4. ...before the VM is paused
5. One last set of dirty pages is sent to the destination
6. The guest is snapshotted and sent to the destination
7. When the migration is completed the destination unpauses the received
   VM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Rob Bradford
b34703d29f vmm: vm: Add dirty log related passthrough methods
This allows code running in the VMM to access the VM's MemoryManager's
functionality for managing the dirty log including resetting it but also
generating a table.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Rob Bradford
cf6763dfdb vmm: migration: Add missing response check
A read and check of the response was missing from when sending the
memory to the destination.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Rob Bradford
11a69450ba vm-migration, vmm: Send configuration in separate step
Prior to sending the memory the full state is not needed only the
configuration. This is sufficient to create the appropriate structures
in the guest and have the memory allocations ready for filling.

Update the protocol documentation to add a separate config step and move
the state to after the memory is transferred. As the VM is created in a
separate step to restoring it the requires a slightly different
constructor as well as saving the VM object for the subsequent commands.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Rob Bradford
c62e409827 memory_manager: Generate a MemoryRangeTable for dirty ranges
In order to do this we must extend the MemoryManager API to add the
ability to specify the tracking of the dirty pages when creating the
userspace mappings and also keep track of the userspace mappings that
have been created for RAM regions.

Currently the dirty pages are collected into ranges based on a block
level of 64 pages. The algorithm could be tweaked to create smaller
ranges but for now if any page in the block of 64 is dirty the whole
block is added to the range.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Rob Bradford
041724a7cf hypervisor: Add ability to get dirty logged pages
Return a bitmap of pages that have been dirtied (written to) since it
was last called.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Rob Bradford
8baa244ec1 hypervisor: Add control for dirty page logging
When creating a userspace mapping provide a control for enabling the
logging of dirty pages.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-17 16:57:11 +00:00
Anatol Belski
b399287430 memory_manager: Make addressable space size 64k aligned
While the addressable space size reduction of 4k in necessary due to
the Linux bug, the 64k alignment of the addressable space size is
required by Windows. This patch satisfies both.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2020-11-16 16:39:11 +00:00
Rob Bradford
c0827e01b1 build: Update arc-swap dependency to 1.0.0
Also bump the vm-memory version to one that depends on arc-swap 1.0.0
too.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-16 14:10:09 +01:00
Rob Bradford
46e736973e hypervisor: kvm: Correctly share VmmOps between Kvm{Vm,Vcpu}
Cloning the ArcSwapOption (like the ArcSwap) does not act like a
.clone() on an Arc, instead an entirely new ArcSwap is created with the
same contents. To correctly share the ArcSwap needs to be placed inside
an Arc.

See: 2433d5719b (diff-6c6d94533c44c19bd1416ef17bad1a878e63dca6e98d59181228fbe8f967c62bR6)

Due to this being wrongly used ::clone() was removed from
ArcSwap/ArcSwapOption in 1.0.0.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-16 14:10:09 +01:00
dependabot-preview[bot]
096d99664d build(deps): bump ssh2 from 0.8.2 to 0.8.3
Bumps [ssh2](https://github.com/alexcrichton/ssh2-rs) from 0.8.2 to 0.8.3.
- [Release notes](https://github.com/alexcrichton/ssh2-rs/releases)
- [Commits](https://github.com/alexcrichton/ssh2-rs/compare/0.8.2...0.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-15 16:01:54 +00:00
dependabot-preview[bot]
f7c42dc7d2 build(deps): bump vmm-sys-util from 0.6.1 to 0.7.0
Bumps [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util) from 0.6.1 to 0.7.0.
- [Release notes](https://github.com/rust-vmm/vmm-sys-util/releases)
- [Changelog](https://github.com/rust-vmm/vmm-sys-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-vmm/vmm-sys-util/compare/v0.6.1...v0.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-13 15:55:05 +00:00
dependabot-preview[bot]
37f947d614 build(deps): bump unicode-normalization from 0.1.13 to 0.1.14
Bumps [unicode-normalization](https://github.com/unicode-rs/unicode-normalization) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/unicode-rs/unicode-normalization/releases)
- [Commits](https://github.com/unicode-rs/unicode-normalization/compare/v0.1.13...v0.1.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-12 16:18:48 +00:00
dependabot-preview[bot]
47d2421e39 build(deps): bump once_cell from 1.5.1 to 1.5.2
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.5.1...v1.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-12 14:02:38 +00:00
dependabot-preview[bot]
04419065a3 build(deps): bump once_cell from 1.5.0 to 1.5.1
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.5.0...v1.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-11 11:19:21 +00:00
Rob Bradford
ca60adda70 vmm: Add support for sending and receiving migration if VM is paused
This is tested by:

Source VMM:

target/debug/cloud-hypervisor --kernel ~/src/linux/vmlinux \
--pmem file=~/workloads/focal.raw --cpus boot=1 \
--memory size=2048M \
--cmdline"root=/dev/pmem0p1 console=ttyS0" --serial tty --console off \
--api-socket=/tmp/api1 -v

Destination VMM:

target/debug/cloud-hypervisor --api-socket=/tmp/api2 -v

And the following commands:

target/debug/ch-remote --api-socket=/tmp/api1 pause
target/debug/ch-remote --api-socket=/tmp/api2 receive-migration unix:/tmp/foo &
target/debug/ch-remote --api-socket=/tmp/api1 send-migration unix:/tmp/foo
target/debug/ch-remote --api-socket=/tmp/api2 resume

The VM is then responsive on the destination VMM.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-11 11:07:24 +01:00
Rob Bradford
dfe2dadb3e vmm: memory_manager: Make the snapshot source directory an Option
This allows the code to be reused when creating the VM from a snapshot
when doing VM migration.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-11 11:07:24 +01:00
Rob Bradford
523029449e bin: ch-remote: Add support for receive/send migration commands
Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-11 11:07:24 +01:00
Rob Bradford
7ac764518c vmm: api: Implement API support for migration
Add API entry points with stub implementation for sending and receiving
a VM from one VMM to another.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-11 11:07:24 +01:00
Rob Bradford
aa98589bb4 vm-migration: Add protocol documentation and data structures
Add the documentation and basic implementation for supporting migration.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-11 11:07:24 +01:00
dependabot-preview[bot]
ca5f1d6de8 build(deps): bump once_cell from 1.4.1 to 1.5.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.4.1...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-11 08:47:02 +00:00
Julio Montes
270631922d vmm: openapi: remove omitempty json tag
Due to a known limitation in OpenAPITools/openapi-generator tool,
it's impossible to send go zero types, like false and 0 to
cloud-hypervisor because `omitempty` is added if a field is not
required.
Set cache_size, dax, num_queues and queue_size as required to remove
`omitempty` from the json tag.

fixes #1961

Signed-off-by: Julio Montes <julio.montes@intel.com>
2020-11-10 19:09:17 +01:00
Muminul Islam
465ef3f7ad scripts: dev_cli: Export volumes for windows test as well
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-11-09 14:58:26 -08:00
dependabot-preview[bot]
50e0423304 build(deps): bump futures from 0.3.7 to 0.3.8
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.7...0.3.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-09 21:50:00 +00:00
Anatol Belski
906a1eb18d doc: Add notes to compile OVMF with legacy support
Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
2020-11-09 17:41:03 +01:00
Rob Bradford
7b77f1ef90 vmm: Remove self-spawning functionality for vhost-user-{net,block}
This also removes the need to lookup up the "exe" symlink for finding
the VMM executable path.

Fixes: #1925

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-09 00:16:15 +01:00
Rob Bradford
0005d11e32 vmm: config: Require a socket when using vhost-user
With self-spawning being removed both parameters are now required.

Fixes: #1925

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-09 00:16:15 +01:00
Rob Bradford
ec84abc5c7 tests: Remove self spawning integration tests
Fixes: #1925

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-11-09 00:16:15 +01:00
dependabot-preview[bot]
2ea0d8eb91 build(deps): bump cc from 1.0.61 to 1.0.62
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.61 to 1.0.62.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.61...1.0.62)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-06 22:50:38 +00:00
dependabot-preview[bot]
6a25aaf6b9 build(deps): bump url from 2.1.1 to 2.2.0
Bumps [url](https://github.com/servo/rust-url) from 2.1.1 to 2.2.0.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.1.1...v2.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-06 09:06:10 +00:00
dependabot-preview[bot]
1b6b45bf2f build(deps): bump thiserror from 1.0.21 to 1.0.22
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.21 to 1.0.22.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.21...1.0.22)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-04 16:24:46 +00:00
dependabot-preview[bot]
ecda97f759 build(deps): bump micro_http from 40309b7 to 59ab644
Bumps [micro_http](https://github.com/firecracker-microvm/micro-http) from `40309b7` to `59ab644`.
- [Release notes](https://github.com/firecracker-microvm/micro-http/releases)
- [Commits](40309b72ee...59ab64440a)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-04 16:01:50 +00:00
Michael Zhao
5566992422 scripts: Fix the failure to fetch latest guest kernel code on AArch64
The integration script failed to obtain the new guest kernel commit when
the code folder had been existing on CI machine.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-11-04 16:00:12 +00:00
dependabot-preview[bot]
cc637f12f6 build(deps): bump aho-corasick from 0.7.14 to 0.7.15
Bumps [aho-corasick](https://github.com/BurntSushi/aho-corasick) from 0.7.14 to 0.7.15.
- [Release notes](https://github.com/BurntSushi/aho-corasick/releases)
- [Commits](https://github.com/BurntSushi/aho-corasick/compare/0.7.14...0.7.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-04 15:15:55 +00:00
dependabot-preview[bot]
592babaadd build(deps): bump ppv-lite86 from 0.2.9 to 0.2.10
Bumps [ppv-lite86](https://github.com/cryptocorrosion/cryptocorrosion) from 0.2.9 to 0.2.10.
- [Release notes](https://github.com/cryptocorrosion/cryptocorrosion/releases)
- [Commits](https://github.com/cryptocorrosion/cryptocorrosion/compare/ppv-lite86-0.2.9...ppv-lite86-0.2.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-03 08:31:08 +00:00
dependabot-preview[bot]
138a8156c8 build(deps): bump blake2b_simd from 0.5.10 to 0.5.11
Bumps [blake2b_simd](https://github.com/oconnor663/blake2_simd) from 0.5.10 to 0.5.11.
- [Release notes](https://github.com/oconnor663/blake2_simd/releases)
- [Commits](https://github.com/oconnor663/blake2_simd/compare/0.5.10...0.5.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-03 08:30:59 +00:00
dependabot-preview[bot]
7a008e66a4 build(deps): bump anyhow from 1.0.33 to 1.0.34
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.33 to 1.0.34.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.33...1.0.34)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-03 08:30:50 +00:00
Michael Zhao
164d481683 tests: Enable watchdog test case on AArch64
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-11-02 08:19:07 +00:00
Michael Zhao
52f60188ca resources: Enable watchdog in guest driver
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-11-02 08:19:07 +00:00
dependabot-preview[bot]
8cb29af712 build(deps): bump regex from 1.4.1 to 1.4.2
Bumps [regex](https://github.com/rust-lang/regex) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.4.1...1.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-02 08:17:11 +00:00
dependabot-preview[bot]
60768a84c5 build(deps): bump signal-hook-registry from 1.2.1 to 1.2.2
Bumps [signal-hook-registry](https://github.com/vorner/signal-hook) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/vorner/signal-hook/releases)
- [Changelog](https://github.com/vorner/signal-hook/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/signal-hook/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-01 09:24:50 +00:00
dependabot-preview[bot]
3266eb7cce build(deps): bump backtrace from 0.3.53 to 0.3.54
Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.53 to 0.3.54.
- [Release notes](https://github.com/rust-lang/backtrace-rs/releases)
- [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.53...0.3.54)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-31 07:42:56 +00:00
Muminul Islam
c9a28312a9 rpm: Fix vhost_user_block binary name and use latest tag
Signed-off-by: Muminul Islam <muislam@microsoft.com>
2020-10-31 07:42:39 +00:00
Michael Zhao
a278704ae0 tests: Enable reboot test case on AArch64
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-10-30 17:14:44 +00:00
Michael Zhao
093a581ee1 vmm: Implement VM rebooting on AArch64
The logic to handle AArch64 system event was: SHUTDOWN and RESET were
all treated as RESET.

Now we handle them differently:
- RESET event will trigger Vmm::vm_reboot(),
- SHUTDOWN event will trigger Vmm::vm_shutdown().

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-10-30 17:14:44 +00:00
Michael Zhao
69394c9c35 vmm: Handle hypervisor VCPU run result from Vcpu to VcpuManager
Now Vcpu::run() returns a boolean value to VcpuManager, indicating
whether the VM is going to reboot (false) or just continue (true).
Moving the handling of hypervisor VCPU run result from Vcpu to
VcpuManager gives us the flexibility to handle more scenarios like
shutting down on AArch64.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-10-30 17:14:44 +00:00
Michael Zhao
0f5e5d9e6d tests: Fix some build warnings in integration test on AArch64
Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-10-30 17:14:12 +00:00
Michael Zhao
4606f0f28a tests: Make migration test case X86 only
Set the test case test_snapshot_restore X86 only, instead of excluding
it from test command line.

The command line option was added because we used to support migration
with Virtio-MMIO, but not Virtio-PCI.

Signed-off-by: Michael Zhao <michael.zhao@arm.com>
2020-10-30 17:14:12 +00:00
Rob Bradford
cb88ceeae8 vmm: memory_manager: Move the restoration of guest memory later
Rather than filling the guest memory from a file at the point of the the
guest memory region being created instead fill from the file later. This
simplifies the region creation code but also adds flexibility for
sourcing the guest memory from a source other than an on disk file.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
2020-10-30 12:31:47 +01:00
104 changed files with 5911 additions and 9299 deletions

View File

@@ -7,6 +7,7 @@ jobs:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust:
- stable

View File

@@ -18,19 +18,28 @@ jobs:
- name: Install Rust toolchain (${{ matrix.rust }})
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: rustfmt, clippy
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
components: rustfmt, clippy
- name: Formatting (rustfmt)
run: cargo fmt -- --check
- name: Clippy (all features)
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Clippy (all features,kvm)
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,kvm" -- -D warnings
- name: Clippy (all features,mshv)
run: cargo clippy --all --all-targets --no-default-features --tests --features "common,mshv" -- -D warnings
- name: Clippy (acpi,kvm)
run: cargo clippy --all-targets --no-default-features --features "acpi,kvm" -- -D warnings
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,kvm" -- -D warnings
- name: Clippy (kvm)
run: cargo clippy --all-targets --no-default-features --features "kvm" -- -D warnings
run: cargo clippy --all --all-targets --no-default-features --tests --features "kvm" -- -D warnings
- name: Clippy (acpi,mshv)
run: cargo clippy --all --all-targets --no-default-features --tests --features "acpi,mshv" -- -D warnings
- name: Clippy (mshv)
run: cargo clippy --all --all-targets --no-default-features --tests --features "mshv" -- -D warnings

512
Cargo.lock generated
View File

@@ -9,9 +9,9 @@ dependencies = [
[[package]]
name = "addr2line"
version = "0.13.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
dependencies = [
"gimli",
]
@@ -24,9 +24,9 @@ checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "aho-corasick"
version = "0.7.14"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
@@ -42,9 +42,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
[[package]]
name = "api_client"
@@ -52,9 +52,9 @@ version = "0.1.0"
[[package]]
name = "arc-swap"
version = "0.4.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
checksum = "0688b520bcc7498f6ca8fa006e8031d353e3fd4f51bd4a50fb03cc4230b28bd2"
[[package]]
name = "arch"
@@ -111,9 +111,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.53"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
dependencies = [
"addr2line",
"cfg-if 1.0.0",
@@ -125,9 +125,9 @@ dependencies = [
[[package]]
name = "base64"
version = "0.12.3"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
@@ -143,9 +143,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2b_simd"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587"
dependencies = [
"arrayref",
"arrayvec",
@@ -176,9 +176,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cc"
version = "1.0.61"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
[[package]]
name = "cfg-if"
@@ -210,7 +210,7 @@ dependencies = [
[[package]]
name = "cloud-hypervisor"
version = "0.11.0"
version = "0.12.0"
dependencies = [
"anyhow",
"api_client",
@@ -230,8 +230,7 @@ dependencies = [
"tempdir",
"tempfile",
"thiserror",
"vhost_user_block",
"vhost_user_net",
"vm-memory",
"vmm",
"vmm-sys-util",
"wait-timeout",
@@ -264,12 +263,12 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if 0.1.10",
"cfg-if 1.0.0",
"lazy_static",
]
@@ -314,6 +313,19 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "env_logger"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e"
dependencies = [
"atty",
"humantime",
"log 0.4.11",
"regex",
"termcolor",
]
[[package]]
name = "epoll"
version = "4.3.1"
@@ -346,108 +358,22 @@ dependencies = [
"synstructure",
]
[[package]]
name = "form_urlencoded"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "futures"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
[[package]]
name = "futures-executor"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
"num_cpus",
]
[[package]]
name = "futures-io"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
[[package]]
name = "futures-macro"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
[[package]]
name = "futures-task"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
dependencies = [
"once_cell",
]
[[package]]
name = "futures-util"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "getrandom"
version = "0.1.15"
@@ -461,9 +387,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.22.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
[[package]]
name = "glob"
@@ -480,17 +406,27 @@ dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a"
[[package]]
name = "hypervisor"
version = "0.1.0"
dependencies = [
"anyhow",
"arc-swap",
"env_logger",
"epoll",
"iced-x86",
"kvm-bindings",
"kvm-ioctls",
"libc",
"linux-loader",
"log 0.4.11",
"mshv-bindings",
"mshv-ioctls",
"serde",
"serde_derive",
"serde_json",
@@ -499,6 +435,17 @@ dependencies = [
"vmm-sys-util",
]
[[package]]
name = "iced-x86"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248ce8ff0784d2b15f3c3d8b01f529be0e18aa693a2ba7415df76857967c8fc3"
dependencies = [
"lazy_static",
"rustc_version",
"static_assertions",
]
[[package]]
name = "idna"
version = "0.2.0"
@@ -522,9 +469,9 @@ dependencies = [
[[package]]
name = "ipnetwork"
version = "0.16.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e"
checksum = "02c3eaab3ac0ede60ffa41add21970a7df7d91772c03383aac6c2c3d53cc716b"
dependencies = [
"serde",
]
@@ -573,15 +520,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]]
name = "libssh2-sys"
version = "0.2.19"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca46220853ba1c512fc82826d0834d87b06bcd3c2a42241b7de72f3d2fe17056"
checksum = "df40b13fe7ea1be9b9dffa365a51273816c345fc1811478b57ed7d964fbfc4ce"
dependencies = [
"cc",
"libc",
@@ -606,7 +553,7 @@ dependencies = [
[[package]]
name = "linux-loader"
version = "0.2.0"
source = "git+https://github.com/rust-vmm/linux-loader#3cf96c53ed6bfed9c7ca7dcfdbbcd04c86b6dd09"
source = "git+https://github.com/rust-vmm/linux-loader#acf9c21591813c87dc77f667618d433f62c821af"
dependencies = [
"vm-memory",
]
@@ -653,7 +600,7 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "micro_http"
version = "0.1.0"
source = "git+https://github.com/firecracker-microvm/micro-http#40309b72ee084b0a26d60208f0872bd7c986de45"
source = "git+https://github.com/firecracker-microvm/micro-http?branch=master#59ab64440a95f7b16253fef67b5201c2ec4adaf7"
dependencies = [
"epoll",
]
@@ -668,6 +615,28 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mshv-bindings"
version = "0.1.0"
source = "git+https://github.com/cloud-hypervisor/mshv?branch=master#2ded6daf97976aa27c1aca47964672c201a7d17d"
dependencies = [
"libc",
"serde",
"serde_derive",
"vmm-sys-util",
"zerocopy",
]
[[package]]
name = "mshv-ioctls"
version = "0.1.0"
source = "git+https://github.com/cloud-hypervisor/mshv?branch=master#2ded6daf97976aa27c1aca47964672c201a7d17d"
dependencies = [
"libc",
"mshv-bindings",
"vmm-sys-util",
]
[[package]]
name = "net_gen"
version = "0.1.0"
@@ -694,33 +663,17 @@ dependencies = [
"vmm-sys-util",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.21.1"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
[[package]]
name = "once_cell"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
[[package]]
name = "openssl-sys"
version = "0.9.58"
version = "0.9.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
checksum = "de52d8eabd217311538a39bba130d7dea1f1e118010fee7a033d966845e7d5fe"
dependencies = [
"autocfg",
"cc",
@@ -784,32 +737,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.19"
@@ -818,9 +745,9 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "pnet"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62df42dcd72f6f2a658bcf38509f1027df1440ac85f1af4badbe034418302dc"
checksum = "b657d5b9a98a2c81b82549922b8b15984e49f8120cd130b11a09f81b9b55d633"
dependencies = [
"ipnetwork",
"pnet_base",
@@ -832,28 +759,28 @@ dependencies = [
[[package]]
name = "pnet_base"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7cd5f7e15220afa66b0a9a62841ea10089f39dcaa1c29752c0b22dfc03111b5"
checksum = "4e4688aa497ef62129f302a5800ebde67825f8ff129f43690ca84099f6620bed"
[[package]]
name = "pnet_datalink"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7318ae1d6e0b7fa1e49933233c9473f2b72d3d18b97e70e2716c6415dde5f915"
checksum = "59001c9c4d9d23bf2f61afaaf134a766fd6932ba2557c606b9112157053b9ac7"
dependencies = [
"ipnetwork",
"libc",
"pnet_base",
"pnet_sys",
"winapi 0.2.8",
"winapi 0.3.9",
]
[[package]]
name = "pnet_macros"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbd5c52c6e04aa720400f9c71cd0e8bcb38cd13421d5caabd9035e9efa47de9"
checksum = "d894a90dbdbe976e624453fc31b1912f658083778329442dda1cca94f76a3e76"
dependencies = [
"regex",
"syntex",
@@ -862,18 +789,18 @@ dependencies = [
[[package]]
name = "pnet_macros_support"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf9c5c0c36766d0a4da9ab268c0700771b8ec367b9463fd678109fa28463c5b"
checksum = "4b99269a458570bc06a9132254349f6543d9abc92e88b68d8de934aac9481f6c"
dependencies = [
"pnet_base",
]
[[package]]
name = "pnet_packet"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e26a864d71d0ac51a549cf40283c44ed1b8f98168545638a4730ef9f560283"
checksum = "33f8238f4eb897a55ca06510cd71afb5b5ca7b4ff2d7188f1ca855fc1710133e"
dependencies = [
"glob",
"pnet_base",
@@ -884,20 +811,19 @@ dependencies = [
[[package]]
name = "pnet_sys"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f0de0c52609f157b25d79ce24d9016ab1bbf10cde761397200d634a833872c"
checksum = "7589e4c4e7ed72a3ffdff8a65d3bea84e8c3a23e19d0a10e8f45efdf632fff15"
dependencies = [
"libc",
"winapi 0.2.8",
"ws2_32-sys",
"winapi 0.3.9",
]
[[package]]
name = "pnet_transport"
version = "0.26.0"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6712ab76534340494d849e3c51c64a6261e4b451337b7c05bd3681e384c48b10"
checksum = "326abdfd2e70e8e943bd58087b59686de170cac050a3b19c9fcc84db01690af5"
dependencies = [
"libc",
"pnet_base",
@@ -907,21 +833,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro-nested"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "proc-macro2"
@@ -1050,9 +964,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.4.1"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
dependencies = [
"aho-corasick",
"memchr",
@@ -1062,9 +976,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.20"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
[[package]]
name = "remain"
@@ -1088,9 +1002,9 @@ dependencies = [
[[package]]
name = "rust-argon2"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19"
checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb"
dependencies = [
"base64",
"blake2b_simd",
@@ -1110,6 +1024,15 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
@@ -1131,16 +1054,31 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.117"
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
[[package]]
name = "serde_derive"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2",
"quote",
@@ -1149,9 +1087,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.59"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
dependencies = [
"itoa",
"ryu",
@@ -1160,9 +1098,9 @@ dependencies = [
[[package]]
name = "signal-hook"
version = "0.1.16"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed"
checksum = "cbae917d5719a9884206132a443899e8c9056b48a156233a823417b39f34044f"
dependencies = [
"libc",
"signal-hook-registry",
@@ -1170,31 +1108,24 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035"
checksum = "ce32ea0c6c56d5eacaeb814fbed9960547021d3edd010ded1425f180536b20ab"
dependencies = [
"arc-swap",
"libc",
]
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
version = "1.4.2"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]]
name = "ssh2"
version = "0.8.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba56d741dab9a295bcb131ebfbe57f8fea2e1b7ae203e9184f5d7648213e4460"
checksum = "ed024de0a5e6944fe3080a3745e7a5649c5517ea2a6cfb16333f94f89c248985"
dependencies = [
"bitflags 1.2.1",
"libc",
@@ -1202,6 +1133,12 @@ dependencies = [
"parking_lot",
]
[[package]]
name = "static_assertions"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
[[package]]
name = "strsim"
version = "0.8.0"
@@ -1210,9 +1147,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.48"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
dependencies = [
"proc-macro2",
"quote",
@@ -1324,6 +1261,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@@ -1336,18 +1282,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42"
checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab"
checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
dependencies = [
"proc-macro2",
"quote",
@@ -1365,9 +1311,18 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "0.3.4"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "unicode-bidi"
@@ -1380,9 +1335,9 @@ dependencies = [
[[package]]
name = "unicode-normalization"
version = "0.1.13"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
dependencies = [
"tinyvec",
]
@@ -1407,10 +1362,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "url"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
@@ -1493,27 +1449,6 @@ dependencies = [
"vmm-sys-util",
]
[[package]]
name = "vhost_user_fs"
version = "0.1.0"
dependencies = [
"bitflags 1.2.1",
"clap",
"epoll",
"futures",
"libc",
"log 0.4.11",
"seccomp",
"tempdir",
"vhost",
"vhost_user_backend",
"virtio-bindings",
"virtio-devices",
"vm-memory",
"vm-virtio",
"vmm-sys-util",
]
[[package]]
name = "vhost_user_net"
version = "0.1.0"
@@ -1592,8 +1527,9 @@ dependencies = [
[[package]]
name = "vm-memory"
version = "0.3.0"
source = "git+https://github.com/cloud-hypervisor/vm-memory?branch=ch#6f2f7107f29a8f2b66b64a849aeace20783ec8f3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45b5b0a6f371f8147143b1adb95edddafc9cb9e40adaf94edb6f93a1d04b0330"
dependencies = [
"arc-swap",
"libc",
@@ -1667,9 +1603,9 @@ dependencies = [
[[package]]
name = "vmm-sys-util"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "183d25b56a61a6f518ef464ac578e790f04added34dfaab59a453d8a03cb7bd0"
checksum = "d1cdd1d72e262bbfb014de65ada24c1ac50e10a2e3b1e8ec052df188c2ee5dfa"
dependencies = [
"bitflags 1.2.1",
"libc",
@@ -1720,6 +1656,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -1727,11 +1672,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
name = "zerocopy"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
dependencies = [
"winapi 0.2.8",
"winapi-build",
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
dependencies = [
"proc-macro2",
"syn",
"synstructure",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "cloud-hypervisor"
version = "0.11.0"
version = "0.12.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
default-run = "cloud-hypervisor"
@@ -18,47 +18,43 @@ api_client = { path = "api_client" }
clap = { version = "2.33.3", features = ["wrap_help"] }
epoll = ">=4.0.1"
hypervisor = { path = "hypervisor" }
libc = "0.2.80"
libc = "0.2.81"
log = { version = "0.4.11", features = ["std"] }
option_parser = { path = "option_parser" }
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
serde_json = "1.0.59"
serde_json = "1.0.60"
thiserror = "1.0"
vhost_user_block = { path = "vhost_user_block"}
vhost_user_net = { path = "vhost_user_net"}
vmm = { path = "vmm" }
vmm-sys-util = "0.6.1"
vmm-sys-util = "0.7.0"
wait-timeout = "0.2.0"
vm-memory = "0.4.0"
[build-dependencies]
clap = { version = "2.33.3", features = ["wrap_help"] }
# List of patched crates
[patch.crates-io]
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
[patch.'https://github.com/rust-vmm/vhost']
vhost_rs = { git = "https://github.com/cloud-hypervisor/vhost", branch = "ch", package = "vhost", features = ["vhost-user-master", "vhost-user-slave"] }
[patch.'https://github.com/rust-vmm/kvm-ioctls']
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
[patch.'https://github.com/rust-vmm/kvm-bindings']
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
[dev-dependencies]
ssh2 = "0.8.2"
ssh2 = "0.9.0"
dirs = "3.0.1"
credibility = "0.1.3"
tempdir = "0.3.7"
lazy_static= "1.4.0"
tempfile = "3.1.0"
serde_json = "1.0.59"
serde_json = "1.0.60"
net_util = { path = "net_util" }
[features]
default = ["acpi", "cmos", "io_uring", "kvm"]
# Common features for all hypervisors
common = ["acpi", "cmos", "fwdebug", "io_uring"]
acpi = ["vmm/acpi"]
cmos = ["vmm/cmos"]
fwdebug = ["vmm/fwdebug"]
kvm = ["vmm/kvm"]
mshv = ["vmm/mshv"]
io_uring = ["vmm/io_uring"]
# Integration tests require a special environment to run in
@@ -80,7 +76,6 @@ members = [
"qcow",
"vhost_user_backend",
"vhost_user_block",
"vhost_user_fs",
"vhost_user_net",
"virtio-devices",
"vmm",

36
Jenkinsfile vendored
View File

@@ -26,9 +26,6 @@ pipeline{
parallel {
stage ('Worker build') {
agent { node { label 'groovy' } }
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage ('Checkout') {
steps {
@@ -46,6 +43,9 @@ pipeline{
}
}
stage ('Run integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration"
}
@@ -54,9 +54,6 @@ pipeline{
}
stage ('AArch64 worker build') {
agent { node { label 'bionic-arm64' } }
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage ('Checkout') {
steps {
@@ -69,6 +66,9 @@ pipeline{
}
}
stage ('Run integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration"
}
@@ -83,9 +83,6 @@ pipeline{
}
stage ('Worker build (musl)') {
agent { node { label 'groovy' } }
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage ('Checkout') {
steps {
@@ -98,6 +95,9 @@ pipeline{
}
}
stage ('Run integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration --libc musl"
}
@@ -106,9 +106,6 @@ pipeline{
}
stage ('Worker build SGX') {
agent { node { label 'bionic-sgx' } }
options {
timeout(time: 1, unit: 'HOURS')
}
when { branch 'master' }
stages {
stage ('Checkout') {
@@ -117,11 +114,17 @@ pipeline{
}
}
stage ('Run SGX integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-sgx"
}
}
stage ('Run SGX integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-sgx --libc musl"
}
@@ -136,9 +139,6 @@ pipeline{
}
stage ('Worker build - Windows guest') {
agent { node { label 'groovy-win' } }
options {
timeout(time: 1, unit: 'HOURS')
}
stages {
stage ('Checkout') {
steps {
@@ -161,11 +161,17 @@ pipeline{
}
}
stage ('Run Windows guest integration tests') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-windows"
}
}
stage ('Run Windows guest integration tests for musl') {
options {
timeout(time: 1, unit: 'HOURS')
}
steps {
sh "scripts/dev_cli.sh tests --integration-windows --libc musl"
}

View File

@@ -5,5 +5,5 @@ authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
vm-memory = "0.3.0"
vm-memory = "0.4.0"

View File

@@ -11,12 +11,12 @@ acpi = ["acpi_tables"]
anyhow = "1.0"
byteorder = "1.3.4"
hypervisor = { path = "../hypervisor" }
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-memory = { version = "0.3.0", features = ["backend-mmap"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap"] }
vm-migration = { path = "../vm-migration" }
acpi_tables = { path = "../acpi_tables", optional = true }
arch_gen = { path = "../arch_gen" }

View File

@@ -174,6 +174,7 @@ pub mod kvm {
}
/// Method to initialize the GIC device
#[allow(clippy::new_ret_no_self)]
fn new(vm: &Arc<dyn hypervisor::Vm>, vcpu_count: u64) -> Result<Box<dyn GICDevice>> {
let vgic_fd = Self::init_device(vm)?;

View File

@@ -43,7 +43,7 @@
//
//
use vm_memory::{GuestAddress, GuestUsize};
use vm_memory::GuestAddress;
/// Below this address will reside the GIC, above this address will reside the MMIO devices.
pub const MAPPED_IO_START: u64 = 0x0900_0000;
@@ -61,7 +61,7 @@ pub const MEM_32BIT_DEVICES_SIZE: u64 = 0x3000_0000;
/// PCI MMCONFIG space (start: after the device space at 1 GiB, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress = GuestAddress(0x4000_0000);
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
/// Start of RAM on 64 bit ARM.
pub const RAM_64BIT_START: u64 = 0x8000_0000;

View File

@@ -7,7 +7,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use vm_memory::{GuestAddress, GuestUsize};
use vm_memory::GuestAddress;
/*
@@ -81,22 +81,22 @@ pub const HIGH_RAM_START: GuestAddress = GuestAddress(0x100000);
// ** 32-bit reserved area (start: 3GiB, length: 1GiB) **
pub const MEM_32BIT_RESERVED_START: GuestAddress = GuestAddress(0xc000_0000);
pub const MEM_32BIT_RESERVED_SIZE: GuestUsize = 1024 << 20;
pub const MEM_32BIT_RESERVED_SIZE: u64 = 1024 << 20;
// == Fixed constants within the "32-bit reserved" range ==
// Sub range: 32-bit PCI devices (start: 3GiB, length: 640Mib)
pub const MEM_32BIT_DEVICES_START: GuestAddress = MEM_32BIT_RESERVED_START;
pub const MEM_32BIT_DEVICES_SIZE: GuestUsize = 640 << 20;
pub const MEM_32BIT_DEVICES_SIZE: u64 = 640 << 20;
// PCI MMCONFIG space (start: after the device space, length: 256MiB)
pub const PCI_MMCONFIG_START: GuestAddress =
GuestAddress(MEM_32BIT_DEVICES_START.0 + MEM_32BIT_DEVICES_SIZE);
pub const PCI_MMCONFIG_SIZE: GuestUsize = 256 << 20;
pub const PCI_MMCONFIG_SIZE: u64 = 256 << 20;
// IOAPIC
pub const IOAPIC_START: GuestAddress = GuestAddress(0xfec0_0000);
pub const IOAPIC_SIZE: GuestUsize = 0x20;
pub const IOAPIC_SIZE: u64 = 0x20;
// APIC
pub const APIC_START: GuestAddress = GuestAddress(0xfee0_0000);

View File

@@ -7,7 +7,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-BSD-3-Clause file.
use std::sync::Arc;
mod gdt;
pub mod interrupts;
pub mod layout;
mod mptable;
@@ -992,7 +991,7 @@ mod tests {
#[test]
fn test_system_configuration() {
let no_vcpus = 4;
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
let config_err = configure_system(
&gm,
GuestAddress(0),

View File

@@ -9,8 +9,9 @@
use std::sync::Arc;
use std::{mem, result};
use super::gdt::{gdt_entry, segment_from_gdt};
use super::BootProtocol;
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
use hypervisor::arch::x86::regs::*;
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
use layout::{
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
@@ -134,14 +135,6 @@ pub fn setup_sregs(
const BOOT_GDT_MAX: usize = 4;
const EFER_LMA: u64 = 0x400;
const EFER_LME: u64 = 0x100;
const X86_CR0_PE: u64 = 0x1;
const X86_CR0_PG: u64 = 0x80000000;
const X86_CR4_PAE: u64 = 0x20;
const X86_CR4_LA57: u64 = 0x1000;
fn write_gdt_table(table: &[u64], guest_mem: &GuestMemoryMmap) -> Result<()> {
let boot_gdt_addr = BOOT_GDT_START;
for (index, entry) in table.iter().enumerate() {
@@ -209,12 +202,12 @@ pub fn configure_segments_and_sregs(
match boot_prot {
BootProtocol::PvhBoot => {
sregs.cr0 = X86_CR0_PE;
sregs.cr0 = CR0_PE;
sregs.cr4 = 0;
}
BootProtocol::LinuxBoot => {
/* 64-bit protected mode */
sregs.cr0 |= X86_CR0_PE;
sregs.cr0 |= CR0_PE;
sregs.efer |= EFER_LME | EFER_LMA;
}
}
@@ -230,7 +223,7 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
.map_err(Error::WritePML5Address)?;
sregs.cr3 = PML5_START.raw_value();
sregs.cr4 |= X86_CR4_LA57;
sregs.cr4 |= CR4_LA57;
} else {
sregs.cr3 = PML4_START.raw_value();
}
@@ -250,8 +243,8 @@ pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) ->
.map_err(Error::WritePDEAddress)?;
}
sregs.cr4 |= X86_CR4_PAE;
sregs.cr0 |= X86_CR0_PG;
sregs.cr4 |= CR4_PAE;
sregs.cr0 |= CR0_PG;
Ok(())
}
@@ -264,7 +257,7 @@ mod tests {
use vm_memory::{GuestAddress, GuestMemoryMmap};
fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap()
GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap()
}
fn read_u64(gm: &GuestMemoryMmap, offset: GuestAddress) -> u64 {
@@ -301,7 +294,7 @@ mod tests {
assert_eq!(0, sregs.tr.base);
assert_eq!(0xffffffff, sregs.tr.limit);
assert_eq!(0, sregs.tr.avl);
assert_eq!(X86_CR0_PE, sregs.cr0);
assert_eq!(CR0_PE, sregs.cr0);
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
@@ -331,7 +324,7 @@ mod tests {
assert_eq!(0x67, sregs.tr.limit);
assert_eq!(0xb, sregs.tr.type_);
assert_eq!(0, sregs.tr.avl);
assert_eq!(X86_CR0_PE, sregs.cr0);
assert_eq!(CR0_PE, sregs.cr0);
assert_eq!(0, sregs.cr4);
}
@@ -358,7 +351,7 @@ mod tests {
} else {
assert_eq!(PML4_START.raw_value(), sregs.cr3);
}
assert_eq!(X86_CR4_PAE, sregs.cr4);
assert_eq!(X86_CR0_PG, sregs.cr0);
assert_eq!(CR4_PAE, sregs.cr4);
assert_eq!(CR0_PG, sregs.cr0);
}
}

View File

@@ -67,7 +67,7 @@ fn bindgen_test_layout_mpf_intel() {
concat!("Alignment of ", stringify!(mpf_intel))
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).signature as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).signature as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -77,7 +77,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).physptr as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).physptr as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -87,7 +87,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).length as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).length as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
@@ -97,7 +97,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).specification as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).specification as *const _ as usize },
9usize,
concat!(
"Alignment of field: ",
@@ -107,7 +107,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).checksum as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).checksum as *const _ as usize },
10usize,
concat!(
"Alignment of field: ",
@@ -117,7 +117,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).feature1 as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).feature1 as *const _ as usize },
11usize,
concat!(
"Alignment of field: ",
@@ -127,7 +127,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).feature2 as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).feature2 as *const _ as usize },
12usize,
concat!(
"Alignment of field: ",
@@ -137,7 +137,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).feature3 as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).feature3 as *const _ as usize },
13usize,
concat!(
"Alignment of field: ",
@@ -147,7 +147,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).feature4 as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).feature4 as *const _ as usize },
14usize,
concat!(
"Alignment of field: ",
@@ -157,7 +157,7 @@ fn bindgen_test_layout_mpf_intel() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpf_intel)).feature5 as *const _ as usize },
unsafe { &(*std::ptr::null::<mpf_intel>()).feature5 as *const _ as usize },
15usize,
concat!(
"Alignment of field: ",
@@ -200,7 +200,7 @@ fn bindgen_test_layout_mpc_table() {
concat!("Alignment of ", stringify!(mpc_table))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).signature as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).signature as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -210,7 +210,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).length as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).length as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -220,7 +220,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).spec as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).spec as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
@@ -230,7 +230,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).checksum as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).checksum as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
@@ -240,7 +240,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).oem as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).oem as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
@@ -250,7 +250,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).productid as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).productid as *const _ as usize },
16usize,
concat!(
"Alignment of field: ",
@@ -260,7 +260,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).oemptr as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).oemptr as *const _ as usize },
28usize,
concat!(
"Alignment of field: ",
@@ -270,7 +270,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).oemsize as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).oemsize as *const _ as usize },
32usize,
concat!(
"Alignment of field: ",
@@ -280,7 +280,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).oemcount as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).oemcount as *const _ as usize },
34usize,
concat!(
"Alignment of field: ",
@@ -290,7 +290,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).lapic as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).lapic as *const _ as usize },
36usize,
concat!(
"Alignment of field: ",
@@ -300,7 +300,7 @@ fn bindgen_test_layout_mpc_table() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_table)).reserved as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_table>()).reserved as *const _ as usize },
40usize,
concat!(
"Alignment of field: ",
@@ -339,7 +339,7 @@ fn bindgen_test_layout_mpc_cpu() {
concat!("Alignment of ", stringify!(mpc_cpu))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).type_ as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -349,7 +349,7 @@ fn bindgen_test_layout_mpc_cpu() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).apicid as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicid as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
@@ -359,7 +359,7 @@ fn bindgen_test_layout_mpc_cpu() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).apicver as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).apicver as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
@@ -369,7 +369,7 @@ fn bindgen_test_layout_mpc_cpu() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).cpuflag as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpuflag as *const _ as usize },
3usize,
concat!(
"Alignment of field: ",
@@ -379,7 +379,7 @@ fn bindgen_test_layout_mpc_cpu() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).cpufeature as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).cpufeature as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -389,7 +389,7 @@ fn bindgen_test_layout_mpc_cpu() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).featureflag as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).featureflag as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",
@@ -399,7 +399,7 @@ fn bindgen_test_layout_mpc_cpu() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_cpu)).reserved as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_cpu>()).reserved as *const _ as usize },
12usize,
concat!(
"Alignment of field: ",
@@ -434,7 +434,7 @@ fn bindgen_test_layout_mpc_bus() {
concat!("Alignment of ", stringify!(mpc_bus))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_bus)).type_ as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_bus>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -444,7 +444,7 @@ fn bindgen_test_layout_mpc_bus() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_bus)).busid as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_bus>()).busid as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
@@ -454,7 +454,7 @@ fn bindgen_test_layout_mpc_bus() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_bus)).bustype as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_bus>()).bustype as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
@@ -491,7 +491,7 @@ fn bindgen_test_layout_mpc_ioapic() {
concat!("Alignment of ", stringify!(mpc_ioapic))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_ioapic)).type_ as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_ioapic>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -501,7 +501,7 @@ fn bindgen_test_layout_mpc_ioapic() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_ioapic)).apicid as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicid as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
@@ -511,7 +511,7 @@ fn bindgen_test_layout_mpc_ioapic() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_ioapic)).apicver as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicver as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
@@ -521,7 +521,7 @@ fn bindgen_test_layout_mpc_ioapic() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_ioapic)).flags as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_ioapic>()).flags as *const _ as usize },
3usize,
concat!(
"Alignment of field: ",
@@ -531,7 +531,7 @@ fn bindgen_test_layout_mpc_ioapic() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_ioapic)).apicaddr as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_ioapic>()).apicaddr as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -570,7 +570,7 @@ fn bindgen_test_layout_mpc_intsrc() {
concat!("Alignment of ", stringify!(mpc_intsrc))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).type_ as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -580,7 +580,7 @@ fn bindgen_test_layout_mpc_intsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).irqtype as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqtype as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
@@ -590,7 +590,7 @@ fn bindgen_test_layout_mpc_intsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).irqflag as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).irqflag as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
@@ -600,7 +600,7 @@ fn bindgen_test_layout_mpc_intsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).srcbus as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbus as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -610,7 +610,7 @@ fn bindgen_test_layout_mpc_intsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).srcbusirq as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).srcbusirq as *const _ as usize },
5usize,
concat!(
"Alignment of field: ",
@@ -620,7 +620,7 @@ fn bindgen_test_layout_mpc_intsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).dstapic as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstapic as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
@@ -630,7 +630,7 @@ fn bindgen_test_layout_mpc_intsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_intsrc)).dstirq as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_intsrc>()).dstirq as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
@@ -674,7 +674,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
concat!("Alignment of ", stringify!(mpc_lintsrc))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).type_ as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).type_ as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -684,7 +684,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).irqtype as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqtype as *const _ as usize },
1usize,
concat!(
"Alignment of field: ",
@@ -694,7 +694,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).irqflag as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).irqflag as *const _ as usize },
2usize,
concat!(
"Alignment of field: ",
@@ -704,7 +704,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusid as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusid as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -714,7 +714,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).srcbusirq as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).srcbusirq as *const _ as usize },
5usize,
concat!(
"Alignment of field: ",
@@ -724,7 +724,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).destapic as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapic as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
@@ -734,7 +734,7 @@ fn bindgen_test_layout_mpc_lintsrc() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_lintsrc)).destapiclint as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_lintsrc>()).destapiclint as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
@@ -771,7 +771,7 @@ fn bindgen_test_layout_mpc_oemtable() {
concat!("Alignment of ", stringify!(mpc_oemtable))
);
assert_eq!(
unsafe { &(*(0 as *const mpc_oemtable)).signature as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_oemtable>()).signature as *const _ as usize },
0usize,
concat!(
"Alignment of field: ",
@@ -781,7 +781,7 @@ fn bindgen_test_layout_mpc_oemtable() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_oemtable)).length as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_oemtable>()).length as *const _ as usize },
4usize,
concat!(
"Alignment of field: ",
@@ -791,7 +791,7 @@ fn bindgen_test_layout_mpc_oemtable() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_oemtable)).rev as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_oemtable>()).rev as *const _ as usize },
6usize,
concat!(
"Alignment of field: ",
@@ -801,7 +801,7 @@ fn bindgen_test_layout_mpc_oemtable() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_oemtable)).checksum as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_oemtable>()).checksum as *const _ as usize },
7usize,
concat!(
"Alignment of field: ",
@@ -811,7 +811,7 @@ fn bindgen_test_layout_mpc_oemtable() {
)
);
assert_eq!(
unsafe { &(*(0 as *const mpc_oemtable)).mpc as *const _ as usize },
unsafe { &(*std::ptr::null::<mpc_oemtable>()).mpc as *const _ as usize },
8usize,
concat!(
"Alignment of field: ",

View File

@@ -10,12 +10,12 @@ io_uring = []
[dependencies]
io-uring = ">=0.4.0"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
serde = ">=1.0.27"
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1"

View File

@@ -8,14 +8,14 @@ anyhow = "1.0"
bitflags = ">=1.2.1"
byteorder = "1.3.4"
epoll = ">=4.0.1"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-device = { path = "../vm-device" }
acpi_tables = { path = "../acpi_tables", optional = true }
vm-memory = "0.3.0"
vm-memory = "0.4.0"
vm-migration = { path = "../vm-migration" }
vmm-sys-util = ">=0.3.1"

View File

@@ -394,13 +394,13 @@ mod tests {
Box::new(serial_out.clone()),
);
serial.write(0, DATA as u64, &['x' as u8, 'y' as u8]);
serial.write(0, DATA as u64, &['a' as u8]);
serial.write(0, DATA as u64, &['b' as u8]);
serial.write(0, DATA as u64, &['c' as u8]);
serial.write(0, DATA as u64, &[b'x', b'y']);
serial.write(0, DATA as u64, &[b'a']);
serial.write(0, DATA as u64, &[b'b']);
serial.write(0, DATA as u64, &[b'c']);
assert_eq!(
serial_out.buf.lock().unwrap().as_slice(),
&['a' as u8, 'b' as u8, 'c' as u8]
&[b'a', b'b', b'c']
);
}
@@ -411,16 +411,14 @@ mod tests {
let mut serial = Serial::new_out(
String::from(SERIAL_NAME),
Arc::new(Box::new(TestInterrupt::new(intr_evt.try_clone().unwrap()))),
Box::new(serial_out.clone()),
Box::new(serial_out),
);
// write 1 to the interrupt event fd, so that read doesn't block in case the event fd
// counter doesn't change (for 0 it blocks)
assert!(intr_evt.write(1).is_ok());
serial.write(0, IER as u64, &[IER_RECV_BIT]);
serial
.queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8])
.unwrap();
serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
assert_eq!(intr_evt.read().unwrap(), 2);
@@ -433,11 +431,11 @@ mod tests {
serial.read(0, LSR as u64, &mut data[..]);
assert_ne!(data[0] & LSR_DATA_BIT, 0);
serial.read(0, DATA as u64, &mut data[..]);
assert_eq!(data[0], 'a' as u8);
assert_eq!(data[0], b'a');
serial.read(0, DATA as u64, &mut data[..]);
assert_eq!(data[0], 'b' as u8);
assert_eq!(data[0], b'b');
serial.read(0, DATA as u64, &mut data[..]);
assert_eq!(data[0], 'c' as u8);
assert_eq!(data[0], b'c');
// check if reading from the largest u8 offset returns 0
serial.read(0, 0xff, &mut data[..]);
@@ -456,7 +454,7 @@ mod tests {
// counter doesn't change (for 0 it blocks)
assert!(intr_evt.write(1).is_ok());
serial.write(0, IER as u64, &[IER_THR_BIT]);
serial.write(0, DATA as u64, &['a' as u8]);
serial.write(0, DATA as u64, &[b'a']);
assert_eq!(intr_evt.read().unwrap(), 2);
let mut data = [0u8];
@@ -496,9 +494,9 @@ mod tests {
);
serial.write(0, MCR as u64, &[MCR_LOOP_BIT as u8]);
serial.write(0, DATA as u64, &['a' as u8]);
serial.write(0, DATA as u64, &['b' as u8]);
serial.write(0, DATA as u64, &['c' as u8]);
serial.write(0, DATA as u64, &[b'a']);
serial.write(0, DATA as u64, &[b'b']);
serial.write(0, DATA as u64, &[b'c']);
let mut data = [0u8];
serial.read(0, MSR as u64, &mut data[..]);
@@ -506,11 +504,11 @@ mod tests {
serial.read(0, MCR as u64, &mut data[..]);
assert_eq!(data[0], MCR_LOOP_BIT as u8);
serial.read(0, DATA as u64, &mut data[..]);
assert_eq!(data[0], 'a' as u8);
assert_eq!(data[0], b'a');
serial.read(0, DATA as u64, &mut data[..]);
assert_eq!(data[0], 'b' as u8);
assert_eq!(data[0], b'b');
serial.read(0, DATA as u64, &mut data[..]);
assert_eq!(data[0], 'c' as u8);
assert_eq!(data[0], b'c');
}
#[test]

45
docs/profiling.md Normal file
View File

@@ -0,0 +1,45 @@
# Profiling
`perf` can be used to profile the `cloud-hypervisor` binary but it is necessary to make some modifications to the the build in order to produce a binary that gives useful results.
## Building a suitable binary
Modify the `Cargo.toml` file to add `debug = 1` to the `[profile.release]` block. It should look like this:
```
[profile.release]
lto = true
debug = 1
```
This adds the symbol information to the release binary but does not otherwise affect the performance.
The binary must also be built with frame pointers included so that the call graph can be captured by the profiler.
```
$ cargo clean && RUSTFLAGS='-C force-frame-pointers=y' cargo build --release
```
## Profiling
`perf` may then be used in the usual manner:
e.g.
```
$ perf record -g target/release/cloud-hypervisor \
--kernel ~/src/linux/vmlinux \
--pmem file=~/workloads/focal.raw \
--cpus boot=1 --memory size=1G \
--cmdline "root=/dev/pmem0p1 console=ttyS0" \
--serial tty --console off \
--api-socket=/tmp/api1
```
For analysing the samples:
```
$ perf report -g
```
If profiling with a network device attached either the TAP device must be already created and configured or the profiling must be done as root so that the TAP device can be created.

View File

@@ -35,8 +35,41 @@ To make Cloud Hypervisor use UEFI boot, pass the `OVMF.fd` file path as an argum
The same firmware can be used with Cloud Hypervisor or with QEMU. This is particularly useful if using QEMU for the preparation phase.
## Building UEFI Firmware with Compatibility Support Module (CSM)
CSM is a module that allows to boot legacy operating systems using the OVMF firmware. OVMF can embed a CSM build of SeaBIOS. To build the SeaBIOS with CSM support, add `CONFIG_CSM=y` to `.config` before the build. The outcome `out/Csm16.bin` is to be moved into `OvmfPkg/Csm/Csm16/Csm16.bin` before OVMF is built. Then, the OVMF build will have to be passed the `-D CSM_ENABLE` option in order to generate a legacy aware UEFI firmware. At the current stage, all the necessary patches are included in the Cloud Hypervisor specific [SeaBIOS branch](https://github.com/cloud-hypervisor/seabios/tree/ch). Taking into account the previous instructions, the modified command sequence to compile an OVMF binary with CSM support is the following one:
```shell
sudo apt-get update
sudo apt-get install uuid-dev nasm iasl build-essential python3-distutils git
git checkout https://github.com/cloud-hypervisor/seabios -b ch
cd seabios
make menuconfig
# Enable `CONFIG_CSM` and `CONFIG_QEMU_HARDWARE`
make CONFIG_CSM=y CONFIG_QEMU_HARDWARE=y
cd ..
git clone https://github.com/cloud-hypervisor/edk2 -b ch
cd edk2
. edksetup.sh
git submodule update --init
cp ../seabios/out/Csm16.bin OvmfPkg/Csm/Csm16/
echo "ACTIVE_PLATFORM=OvmfPkg/OvmfCh.dsc" >> Conf/target.txt
echo "TARGET_ARCH=X64" >> Conf/target.txt
echo "TOOL_CHAIN_TAG=GCC5" >> Conf/target.txt
make -C ./BaseTools
build
```
Please note, that the CSM support has currently only been tested with Linux guests. There are no plans to provide legacy support for other OSes (e.g. Windows).
# Links
- [OVMF wiki](https://github.com/tianocore/tianocore.github.io/wiki/OVMF)
- [Cloud Hypervisor specific tree](https://github.com/cloud-hypervisor/edk2/tree/ch)
- [Redhat OVMF Status Report](https://access.redhat.com/sites/default/files/attachments/ovmf-whtepaper-031815.pdf)
- [SeaBIOS Build Overview](https://www.seabios.org/Build_overview#Build_as_a_UEFI_Compatibility_Support_Module_.28CSM.29)

View File

@@ -282,6 +282,17 @@ Note, this VM has the networking enabled. It is needed, because symbols and sour
Also, notice the `-serial` parameter - that's what does the magic on exposing the serial port to the guest while connecting the debugger VM with a client VM through the network. SAC/EMS needs to be disabled in the debugger VM, as otherwise the COM device might be blocked.
Hereafter, WinDbg can be started using a command below:
```cmd
set _NT_DEBUG_PORT=com1
set _NT_DEBUG_BAUD_RATE=115200
windbg -v -d -k
```
Once started, WinDbg will wait for an incoming connection which is going to be initialized by the debuggee VM started in the next section.
#### Invoke the Debuggee VM
##### Under QEMU

View File

@@ -1,4 +1,3 @@
[package]
name = "cloud-hypervisor-fuzz"
version = "0.0.0"
@@ -10,7 +9,6 @@ edition = "2018"
cargo-fuzz = true
[dependencies]
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
libc = "0.2.72"
libfuzzer-sys = "0.3"
qcow = { path = "../qcow" }
@@ -18,18 +16,11 @@ seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v
virtio-devices = { path = "../virtio-devices" }
vmm-sys-util = ">=0.3.1"
vm-virtio = { path = "../vm-virtio" }
vm-memory = "0.3.0"
vm-memory = "0.4.0"
[dependencies.cloud-hypervisor]
path = ".."
[patch.crates-io]
vm-memory = { git = "https://github.com/cloud-hypervisor/vm-memory", branch = "ch" }
[patch.'https://github.com/rust-vmm/kvm-ioctls']
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
[patch.'https://github.com/rust-vmm/kvm-bindings']
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
# Prevent this from interfering with workspaces
[workspace]
members = ["."]

View File

@@ -7,21 +7,34 @@ license = "Apache-2.0 OR BSD-3-Clause"
[features]
kvm = []
mshv = []
[dependencies]
anyhow = "1.0"
arc-swap = ">=0.4.4"
epoll = ">=4.0.1"
thiserror = "1.0"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls", branch = "master" }
kvm-bindings = { git = "https://github.com/rust-vmm/kvm-bindings", branch = "master", features = ["with-serde", "fam-wrappers"] }
kvm-ioctls = { git = "https://github.com/cloud-hypervisor/kvm-ioctls", branch = "ch" }
kvm-bindings = { git = "https://github.com/cloud-hypervisor/kvm-bindings", branch = "ch", features = ["with-serde", "fam-wrappers"] }
mshv-bindings = {git = "https://github.com/cloud-hypervisor/mshv", branch = "master", features = ["with-serde", "fam-wrappers"] }
mshv-ioctls = { git = "https://github.com/cloud-hypervisor/mshv", branch = "master" }
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }
[dependencies.linux-loader]
git = "https://github.com/rust-vmm/linux-loader"
features = ["elf", "bzimage"]
[target.'cfg(target_arch = "x86_64")'.dependencies.iced-x86]
version = "1.9.1"
default-features = false
features = ["std", "decoder", "op_code_info", "instr_info", "fast_fmt"]
[dev-dependencies]
env_logger = "0.8.2"

View File

@@ -0,0 +1,148 @@
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
extern crate thiserror;
use core::fmt::Debug;
use std::fmt::{self, Display};
use thiserror::Error;
#[derive(Clone, Copy, Error, Debug)]
pub struct Exception<T: Debug> {
vector: T,
ip: u64,
error: Option<u32>,
payload: Option<u64>,
}
impl<T: Debug> Display for Exception<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Exception {:?} at IP {:#x}", self.vector, self.ip)
}
}
#[derive(Error, Debug)]
pub enum PlatformError {
#[error("Invalid address: {0}")]
InvalidAddress(#[source] anyhow::Error),
#[error("Invalid register: {0}")]
InvalidRegister(#[source] anyhow::Error),
#[error("Invalid state: {0}")]
InvalidState(#[source] anyhow::Error),
#[error("Memory read failure: {0}")]
MemoryReadFailure(#[source] anyhow::Error),
#[error("Memory write failure: {0}")]
MemoryWriteFailure(#[source] anyhow::Error),
#[error("Get CPU state failure: {0}")]
GetCpuStateFailure(#[source] anyhow::Error),
#[error("Set CPU state failure: {0}")]
SetCpuStateFailure(#[source] anyhow::Error),
#[error("Unmapped virtual address: {0}")]
UnmappedGVA(#[source] anyhow::Error),
#[error("Unsupported CPU Mode: {0}")]
UnsupportedCpuMode(#[source] anyhow::Error),
#[error("Invalid instruction operand: {0}")]
InvalidOperand(#[source] anyhow::Error),
}
#[derive(Error, Debug)]
pub enum EmulationError<T: Debug> {
#[error("Unsupported instruction: {0}")]
UnsupportedInstruction(#[source] anyhow::Error),
#[error("Unsupported memory size: {0}")]
UnsupportedMemorySize(#[source] anyhow::Error),
#[error("Invalid operand: {0}")]
InvalidOperand(#[source] anyhow::Error),
#[error("Wrong number of operands: {0}")]
WrongNumberOperands(#[source] anyhow::Error),
#[error("Instruction Exception: {0}")]
InstructionException(Exception<T>),
#[error("Instruction fetching error: {0}")]
InstructionFetchingError(#[source] anyhow::Error),
#[error("Platform emulation error: {0}")]
PlatformEmulationError(PlatformError),
#[error(transparent)]
EmulationError(#[from] anyhow::Error),
}
/// The PlatformEmulator trait emulates a guest platform.
/// It's mostly a guest resources (memory and CPU state) getter and setter.
///
/// A CpuState is an architecture specific type, representing a CPU state.
/// The emulator and its instruction handlers modify a given CPU state and
/// eventually ask the platform to commit it back through `set_cpu_state`.
pub trait PlatformEmulator: Send + Sync {
type CpuState: Clone;
/// Read guest memory into a u8 slice.
///
/// # Arguments
///
/// * `gva` - Guest virtual address to read from.
/// * `data` - Data slice to read into.
///
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError>;
/// Write a u8 slice into guest memory.
///
/// # Arguments
///
/// * `gva` - Guest virtual address to write into.
/// * `data` - Data slice to be written.
///
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError>;
/// Get a CPU state from the guest.
///
/// # Arguments
///
/// * `cpu_id` - Logical CPU ID.
///
fn cpu_state(&self, cpu_id: usize) -> Result<Self::CpuState, PlatformError>;
/// Set a guest CPU state.
///
/// # Arguments
///
/// * `cpu_id` - Logical CPU ID.
/// * `state` - State to set the CPU into.
///
fn set_cpu_state(&self, cpu_id: usize, state: Self::CpuState) -> Result<(), PlatformError>;
/// Translate a guest virtual address into a physical one
///
/// # Arguments
///
/// * `gva` - Guest virtual address to translate.
///
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError>;
/// Fetch instruction bytes from memory.
///
/// # Arguments
///
/// * `ip` - Instruction pointer virtual address to start fetching instructions from.
///
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError>;
}
pub type EmulationResult<S, E> = std::result::Result<S, EmulationError<E>>;

View File

@@ -11,5 +11,7 @@
// Copyright © 2020, Microsoft Corporation
//
pub mod emulator;
#[cfg(target_arch = "x86_64")]
pub mod x86;

View File

@@ -0,0 +1,355 @@
//
// Copyright © 2020 Microsoft
//
// SPDX-License-Identifier: Apache-2.0
//
#![allow(non_camel_case_types)]
//
// CMP-Compare Two Operands
//
extern crate iced_x86;
use crate::arch::emulator::{EmulationError, PlatformEmulator};
use crate::arch::x86::emulator::instructions::*;
use crate::arch::x86::regs::*;
use crate::arch::x86::Exception;
// CMP affects OF, SF, ZF, AF, PF and CF
const FLAGS_MASK: u64 = CF | PF | AF | ZF | SF | OF;
// TODO: Switch to inline asm when that's stable. Executing CMP (or any arthimetic instructions)
// natively and extracting RFLAGS will be much faster and make the code simpler.
fn calc_rflags_cpazso(op0: u64, op1: u64, op_size: usize) -> u64 {
let op_bits = op_size * 8;
let msb_shift = op_bits - 1;
// CMP is the same as SUB.
let result = op0.wrapping_sub(op1);
// Carry-out vector for SUB.
let cout = (!op0 & op1) | ((!op0 ^ op1) & result);
let cf = ((cout >> msb_shift) & 0x1) << CF_SHIFT;
// PF only needs the least significant byte. XOR its higher 4 bits with its lower 4 bits then
// use the value directly.
let pf = ((0x9669 >> ((result ^ (result >> 4)) & 0xf)) & 0x1) << PF_SHIFT;
// AF cares about the lowest 4 bits (nibble). msb_shift is 3 in this case.
let af = ((cout >> 3) & 0x1) << AF_SHIFT;
let zf = if result & (!0u64 >> (63 - msb_shift)) == 0 {
1
} else {
0
} << ZF_SHIFT;
let sf = ((result >> msb_shift) & 0x1) << SF_SHIFT;
// Overflow happens when two operands have the same sign but the result has a different sign.
let of = ((((op0 ^ op1) & (op0 ^ result)) >> msb_shift) & 0x1) << OF_SHIFT;
cf | pf | af | zf | sf | of
}
macro_rules! cmp_rm_r {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
state.set_ip(insn.ip());
Ok(())
}
};
}
macro_rules! cmp_r_rm {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
let op1_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
state.set_ip(insn.ip());
Ok(())
}
};
}
macro_rules! cmp_rm_imm {
($imm:ty, $bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let op0_value = get_op(&insn, 0, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
let op1_value = get_op(&insn, 1, std::mem::size_of::<$imm>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
let cpazso = calc_rflags_cpazso(op0_value, op1_value, std::mem::size_of::<$bound>());
state.set_flags((state.flags() & !FLAGS_MASK) | cpazso);
state.set_ip(insn.ip());
Ok(())
}
};
}
pub struct Cmp_rm64_r64;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_r64 {
cmp_rm_r!(u64);
}
pub struct Cmp_rm32_r32;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_r32 {
cmp_rm_r!(u32);
}
pub struct Cmp_rm16_r16;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_r16 {
cmp_rm_r!(u16);
}
pub struct Cmp_rm8_r8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_r8 {
cmp_rm_r!(u8);
}
pub struct Cmp_r64_rm64;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r64_rm64 {
cmp_r_rm!(u64);
}
pub struct Cmp_r32_rm32;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r32_rm32 {
cmp_r_rm!(u32);
}
pub struct Cmp_r16_rm16;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r16_rm16 {
cmp_r_rm!(u16);
}
pub struct Cmp_r8_rm8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_r8_rm8 {
cmp_r_rm!(u8);
}
pub struct Cmp_AL_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AL_imm8 {
cmp_rm_imm!(u8, u8);
}
pub struct Cmp_AX_imm16;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_AX_imm16 {
cmp_rm_imm!(u16, u16);
}
pub struct Cmp_EAX_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_EAX_imm32 {
cmp_rm_imm!(u32, u32);
}
pub struct Cmp_RAX_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_RAX_imm32 {
cmp_rm_imm!(u32, u64);
}
pub struct Cmp_rm8_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm8_imm8 {
cmp_rm_imm!(u8, u8);
}
pub struct Cmp_rm16_imm16;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm16 {
cmp_rm_imm!(u16, u16);
}
pub struct Cmp_rm32_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm32 {
cmp_rm_imm!(u32, u32);
}
pub struct Cmp_rm64_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm32 {
cmp_rm_imm!(u32, u64);
}
pub struct Cmp_rm16_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm16_imm8 {
cmp_rm_imm!(u8, u16);
}
pub struct Cmp_rm32_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm32_imm8 {
cmp_rm_imm!(u8, u32);
}
pub struct Cmp_rm64_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Cmp_rm64_imm8 {
cmp_rm_imm!(u8, u64);
}
#[cfg(test)]
mod tests {
#![allow(unused_mut)]
use super::*;
use crate::arch::x86::emulator::mock_vmm::*;
#[test]
// cmp ah,al
fn test_cmp_rm8_r8_1() -> MockResult {
let rax: u64 = 0x0;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x38, 0xc4]; // cmp ah,al
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
assert_eq!(0b1000100, rflags);
Ok(())
}
#[test]
// cmp eax,100
fn test_cmp_rm32_imm8_1() -> MockResult {
let rax: u64 = 0xabcdef;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x83, 0xf8, 0x64]; // cmp eax,100
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
assert_eq!(0b100, rflags);
Ok(())
}
#[test]
// cmp eax,-1
fn test_cmp_rm32_imm8_2() -> MockResult {
let rax: u64 = 0xabcdef;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x83, 0xf8, 0xff]; // cmp eax,-1
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
assert_eq!(0b101, rflags);
Ok(())
}
#[test]
// cmp rax,rbx
fn test_cmp_rm64_r64() -> MockResult {
let rax: u64 = 0xabcdef;
let rbx: u64 = 0x1234;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x48, 0x39, 0xd8, 0x00, 0xc3]; // cmp rax,rbx + two bytes garbage
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax), (Register::RBX, rbx)], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rflags: u64 = vmm.cpu_state(cpu_id).unwrap().flags() & FLAGS_MASK;
assert_eq!(0b100, rflags);
Ok(())
}
#[test]
fn test_cmp_64() -> MockResult {
let data = [
(0xabcdef, 0x1234, 0b100),
(0x0, 0x101, 0b1001_0101),
(0x0, 0x8000_0000_0000_0000, 0b1000_1000_0101),
(0x1234abcd, 0x1234abcd, 0b100_0100),
(0x1234abcd, 0xdeadbeef, 0b1001_0101),
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b1000_0000),
(0xffff_ffff_ffff_ffff, 0x0, 0b1000_0100),
];
for d in data.iter() {
let rax = d.0;
let rbx = d.1;
let insn = [0x48, 0x39, 0xd8]; // cmp rax,rbx
let mut vmm = MockVMM::new(
0x1000,
vec![(Register::RAX, rax), (Register::RBX, rbx)],
None,
);
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
assert_eq!(d.2, rflags);
}
Ok(())
}
#[test]
fn test_cmp_32() -> MockResult {
let data = [
(0xabcdef, 0x1234, 0b100),
(0x0, 0x101, 0b1001_0101),
(0x0, 0x8000_0000_0000_0000, 0b100_0100), // Same as cmp 0,0 due to truncation
(0x1234abcd, 0x1234abcd, 0b100_0100),
(0x1234abcd, 0xdeadbeef, 0b1_0101),
(0xffff_ffff_ffff_ffff, 0xdeadbeef, 0b0), // Same as cmp 0xffffffff,0xdeadbeef
(0xffff_ffff, 0x0, 0b1000_0100),
];
for d in data.iter() {
let rax = d.0;
let rbx = d.1;
let insn = [0x39, 0xd8]; // cmp eax,ebx
let mut vmm = MockVMM::new(
0x1000,
vec![(Register::RAX, rax), (Register::RBX, rbx)],
None,
);
assert!(vmm.emulate_first_insn(0, &insn).is_ok());
let rflags: u64 = vmm.cpu_state(0).unwrap().flags() & FLAGS_MASK;
assert_eq!(d.2, rflags);
}
Ok(())
}
}

View File

@@ -0,0 +1,146 @@
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
extern crate iced_x86;
use crate::arch::emulator::{EmulationError, PlatformEmulator, PlatformError};
use crate::arch::x86::emulator::CpuStateManager;
use crate::arch::x86::Exception;
use iced_x86::*;
pub mod cmp;
pub mod mov;
fn get_op<T: CpuStateManager>(
insn: &Instruction,
op_index: u32,
op_size: usize,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<u64, PlatformError> {
if insn.op_count() < op_index + 1 {
return Err(PlatformError::InvalidOperand(anyhow!(
"Invalid operand {:?}",
op_index
)));
}
match op_size {
1 | 2 | 4 | 8 => {}
_ => {
return Err(PlatformError::InvalidOperand(anyhow!(
"Invalid operand size {:?}",
op_size
)))
}
}
let value = match insn.op_kind(op_index) {
OpKind::Register => state.read_reg(insn.op_register(op_index))?,
OpKind::Memory => {
let addr = memory_operand_address(insn, state, false)?;
let mut memory: [u8; 8] = [0; 8];
platform.read_memory(addr, &mut memory[0..op_size])?;
<u64>::from_le_bytes(memory)
}
OpKind::Immediate8 => insn.immediate8() as u64,
OpKind::Immediate8to16 => insn.immediate8to16() as u64,
OpKind::Immediate8to32 => insn.immediate8to32() as u64,
OpKind::Immediate8to64 => insn.immediate8to64() as u64,
OpKind::Immediate16 => insn.immediate16() as u64,
OpKind::Immediate32 => insn.immediate32() as u64,
OpKind::Immediate32to64 => insn.immediate32to64() as u64,
OpKind::Immediate64 => insn.immediate64() as u64,
k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))),
};
Ok(value)
}
fn set_op<T: CpuStateManager>(
insn: &Instruction,
op_index: u32,
op_size: usize,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
value: u64,
) -> Result<(), PlatformError> {
if insn.op_count() < op_index + 1 {
return Err(PlatformError::InvalidOperand(anyhow!(
"Invalid operand {:?}",
op_index
)));
}
match op_size {
1 | 2 | 4 | 8 => {}
_ => {
return Err(PlatformError::InvalidOperand(anyhow!(
"Invalid operand size {:?}",
op_size
)))
}
}
match insn.op_kind(op_index) {
OpKind::Register => state.write_reg(insn.op_register(op_index), value)?,
OpKind::Memory => {
let addr = memory_operand_address(insn, state, true)?;
platform.write_memory(addr, &value.to_le_bytes()[..op_size])?;
}
k => return Err(PlatformError::InvalidOperand(anyhow!("{:?}", k))),
};
Ok(())
}
// Returns the linear a.k.a. virtual address for a memory operand.
fn memory_operand_address<T: CpuStateManager>(
insn: &Instruction,
state: &T,
write: bool,
) -> Result<u64, PlatformError> {
let mut address: u64 = 0;
if insn.memory_base() != iced_x86::Register::None {
let base: u64 = state.read_reg(insn.memory_base())?;
address += base;
}
if insn.memory_index() != iced_x86::Register::None {
let mut index: u64 = state.read_reg(insn.memory_index())?;
index *= insn.memory_index_scale() as u64;
address += index;
}
address += insn.memory_displacement() as u64;
// Translate to a linear address.
state.linearize(insn.memory_segment(), address, write)
}
pub trait InstructionHandler<T: CpuStateManager> {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>>;
}
macro_rules! insn_format {
($insn:ident) => {{
let mut output = String::new();
let mut formatter = FastFormatter::new();
formatter
.options_mut()
.set_space_after_operand_separator(true);
formatter.format(&$insn, &mut output);
output
}};
}

View File

@@ -0,0 +1,563 @@
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
#![allow(non_camel_case_types)]
//
// MOV-Move
// SDM Volume 1, Chapter 4.3
// Copies the second operand (source operand) to the first operand (destination operand).
//
extern crate iced_x86;
use crate::arch::emulator::{EmulationError, PlatformEmulator};
use crate::arch::x86::emulator::instructions::*;
use crate::arch::x86::Exception;
macro_rules! mov_rm_r {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let src_reg_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
set_op(
&insn,
0,
std::mem::size_of::<$bound>(),
state,
platform,
src_reg_value,
)
.map_err(EmulationError::PlatformEmulationError)?;
state.set_ip(insn.ip());
Ok(())
}
};
}
macro_rules! mov_rm_imm {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let imm = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
set_op(
&insn,
0,
std::mem::size_of::<$bound>(),
state,
platform,
imm,
)
.map_err(EmulationError::PlatformEmulationError)?;
state.set_ip(insn.ip());
Ok(())
}
};
}
macro_rules! mov_r_rm {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let src_value = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
set_op(
&insn,
0,
std::mem::size_of::<$bound>(),
state,
platform,
src_value,
)
.map_err(EmulationError::PlatformEmulationError)?;
state.set_ip(insn.ip());
Ok(())
}
};
}
macro_rules! mov_r_imm {
($bound:ty) => {
fn emulate(
&self,
insn: &Instruction,
state: &mut T,
platform: &mut dyn PlatformEmulator<CpuState = T>,
) -> Result<(), EmulationError<Exception>> {
let imm = get_op(&insn, 1, std::mem::size_of::<$bound>(), state, platform)
.map_err(EmulationError::PlatformEmulationError)?;
set_op(
&insn,
0,
std::mem::size_of::<$bound>(),
state,
platform,
imm,
)
.map_err(EmulationError::PlatformEmulationError)?;
state.set_ip(insn.ip());
Ok(())
}
};
}
pub struct Mov_r8_rm8;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r8_rm8 {
mov_r_rm!(u8);
}
pub struct Mov_r8_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r8_imm8 {
mov_r_imm!(u8);
}
pub struct Mov_r16_rm16;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r16_rm16 {
mov_r_rm!(u16);
}
pub struct Mov_r16_imm16;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r16_imm16 {
mov_r_imm!(u16);
}
pub struct Mov_r32_rm32;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r32_rm32 {
mov_r_rm!(u32);
}
pub struct Mov_r32_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r32_imm32 {
mov_r_imm!(u32);
}
pub struct Mov_r64_rm64;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r64_rm64 {
mov_r_rm!(u64);
}
pub struct Mov_r64_imm64;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_r64_imm64 {
mov_r_imm!(u64);
}
pub struct Mov_rm8_imm8;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm8_imm8 {
mov_rm_imm!(u8);
}
pub struct Mov_rm8_r8;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm8_r8 {
mov_rm_r!(u8);
}
pub struct Mov_rm16_imm16;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm16_imm16 {
mov_rm_imm!(u16);
}
pub struct Mov_rm16_r16;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm16_r16 {
mov_rm_r!(u16);
}
pub struct Mov_rm32_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm32_imm32 {
mov_rm_imm!(u32);
}
pub struct Mov_rm32_r32;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm32_r32 {
mov_rm_r!(u32);
}
pub struct Mov_rm64_imm32;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_imm32 {
mov_rm_imm!(u32);
}
pub struct Mov_rm64_r64;
impl<T: CpuStateManager> InstructionHandler<T> for Mov_rm64_r64 {
mov_rm_r!(u64);
}
#[cfg(test)]
mod tests {
#![allow(unused_mut)]
use super::*;
use crate::arch::x86::emulator::mock_vmm::*;
#[test]
// mov rax,rbx
fn test_mov_r64_r64() -> MockResult {
let rbx: u64 = 0x8899aabbccddeeff;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x48, 0x89, 0xd8];
let mut vmm = MockVMM::new(ip, vec![(Register::RBX, rbx)], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rax: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, rbx);
Ok(())
}
#[test]
// mov rax,0x1122334411223344
fn test_mov_r64_imm64() -> MockResult {
let imm64: u64 = 0x1122334411223344;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x48, 0xb8, 0x44, 0x33, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11];
let mut vmm = MockVMM::new(ip, vec![], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rax: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, imm64);
Ok(())
}
#[test]
// mov rax, [rax+rax]
fn test_mov_r64_m64() -> MockResult {
let target_rax: u64 = 0x1234567812345678;
let mut rax: u64 = 0x100;
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory: [u8; 8] = target_rax.to_le_bytes();
let insn = [0x48, 0x8b, 0x04, 0x00];
let mut vmm = MockVMM::new(ip, vec![(Register::RAX, rax)], Some((rax + rax, &memory)));
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
rax = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, target_rax);
Ok(())
}
#[test]
// mov al,0x11
fn test_mov_r8_imm8() -> MockResult {
let imm8: u8 = 0x11;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0xb0, 0x11];
let mut vmm = MockVMM::new(ip, vec![], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let al = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::AL)
.unwrap();
assert_eq!(al as u8, imm8);
Ok(())
}
#[test]
// mov eax,0x11
fn test_mov_r32_imm8() -> MockResult {
let imm8: u8 = 0x11;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0xb8, 0x11, 0x00, 0x00, 0x00];
let mut vmm = MockVMM::new(ip, vec![], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let eax = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::EAX)
.unwrap();
assert_eq!(eax as u8, imm8);
Ok(())
}
#[test]
// mov rax,0x11223344
fn test_mov_r64_imm32() -> MockResult {
let imm32: u32 = 0x11223344;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x48, 0xc7, 0xc0, 0x44, 0x33, 0x22, 0x11];
let mut vmm = MockVMM::new(ip, vec![], None);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let rax: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, imm32 as u64);
Ok(())
}
#[test]
// mov byte ptr [rax],dh
fn test_mov_m8_r8() -> MockResult {
let rax: u64 = 0x100;
let dh: u8 = 0x99;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x88, 0x30];
let mut vmm = MockVMM::new(
ip,
vec![(Register::RAX, rax), (Register::DH, dh.into())],
None,
);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let mut memory: [u8; 1] = [0; 1];
vmm.read_memory(rax, &mut memory).unwrap();
assert_eq!(u8::from_le_bytes(memory), dh);
Ok(())
}
#[test]
// mov dword ptr [rax],esi
fn test_mov_m32_r32() -> MockResult {
let rax: u64 = 0x100;
let esi: u32 = 0x8899;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x89, 0x30];
let mut vmm = MockVMM::new(
ip,
vec![(Register::RAX, rax), (Register::ESI, esi.into())],
None,
);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let mut memory: [u8; 4] = [0; 4];
vmm.read_memory(rax, &mut memory).unwrap();
assert_eq!(u32::from_le_bytes(memory), esi);
Ok(())
}
#[test]
// mov dword ptr [rax+0x00000001],edi
fn test_mov_m32imm32_r32() -> MockResult {
let rax: u64 = 0x100;
let displacement: u64 = 0x1;
let edi: u32 = 0x8899;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x89, 0x3c, 0x05, 0x01, 0x00, 0x00, 0x00];
let mut vmm = MockVMM::new(
ip,
vec![(Register::RAX, rax), (Register::EDI, edi.into())],
None,
);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let mut memory: [u8; 4] = [0; 4];
vmm.read_memory(rax + displacement, &mut memory).unwrap();
assert_eq!(u32::from_le_bytes(memory), edi);
Ok(())
}
#[test]
// mov eax,dword ptr [rax+10h]
fn test_mov_r32_m32imm32() -> MockResult {
let rax: u64 = 0x100;
let displacement: u64 = 0x10;
let eax: u32 = 0xaabbccdd;
let memory: [u8; 4] = eax.to_le_bytes();
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x8b, 0x40, 0x10];
let mut vmm = MockVMM::new(
ip,
vec![(Register::RAX, rax)],
Some((rax + displacement, &memory)),
);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let new_eax = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::EAX)
.unwrap();
assert_eq!(new_eax, eax as u64);
Ok(())
}
#[test]
// mov al,byte ptr [rax+10h]
fn test_mov_r8_m32imm32() -> MockResult {
let rax: u64 = 0x100;
let displacement: u64 = 0x10;
let al: u8 = 0xaa;
let ip: u64 = 0x1000;
let cpu_id = 0;
let insn = [0x8a, 0x40, 0x10];
let memory: [u8; 1] = al.to_le_bytes();
let mut vmm = MockVMM::new(
ip,
vec![(Register::RAX, rax)],
Some((rax + displacement, &memory)),
);
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
let new_al = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::AL)
.unwrap();
assert_eq!(new_al, al as u64);
Ok(())
}
#[test]
// mov rax, 0x100
// mov rbx, qword ptr [rax+10h]
fn test_mov_r64_imm64_and_r64_m64() -> MockResult {
let target_rax: u64 = 0x1234567812345678;
let rax: u64 = 0x100;
let displacement: u64 = 0x10;
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory: [u8; 8] = target_rax.to_le_bytes();
let insn = [
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
];
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
let rbx: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RBX)
.unwrap();
assert_eq!(rbx, target_rax);
Ok(())
}
#[test]
// mov rax, 0x100
// mov rbx, qword ptr [rax+10h]
fn test_mov_r64_imm64_and_r64_m64_first_insn() -> MockResult {
let target_rax: u64 = 0x1234567812345678;
let rax: u64 = 0x100;
let displacement: u64 = 0x10;
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory: [u8; 8] = target_rax.to_le_bytes();
let insn = [
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
];
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
// Only run the first instruction.
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_ok());
assert_eq!(ip + 7 as u64, vmm.cpu_state(cpu_id).unwrap().ip());
let new_rax: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, new_rax);
Ok(())
}
#[test]
// mov rax, 0x100
// mov rbx, qword ptr [rax+10h]
// mov rax, 0x200
fn test_mov_r64_imm64_and_r64_m64_two_insns() -> MockResult {
let target_rax: u64 = 0x1234567812345678;
let rax: u64 = 0x100;
let displacement: u64 = 0x10;
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory: [u8; 8] = target_rax.to_le_bytes();
let insn = [
0x48, 0xc7, 0xc0, 0x00, 0x01, 0x00, 0x00, // mov rax, 0x100
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
0x48, 0xc7, 0xc0, 0x00, 0x02, 0x00, 0x00, // mov rax, 0x200
];
let mut vmm = MockVMM::new(ip, vec![], Some((rax + displacement, &memory)));
// Run the 2 first instructions.
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
assert_eq!(ip + 7 + 4 as u64, vmm.cpu_state(cpu_id).unwrap().ip());
let rbx: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RBX)
.unwrap();
assert_eq!(rbx, target_rax);
// Check that rax is still at 0x100
let new_rax: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, new_rax);
Ok(())
}
}

View File

@@ -0,0 +1,859 @@
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
extern crate iced_x86;
use crate::arch::emulator::{EmulationError, EmulationResult, PlatformEmulator, PlatformError};
use crate::arch::x86::emulator::instructions::*;
use crate::arch::x86::regs::*;
use crate::arch::x86::*;
use crate::arch::x86::{Exception, SegmentRegisterOps};
use crate::x86_64::{SegmentRegister, SpecialRegisters, StandardRegisters};
use anyhow::Context;
use iced_x86::*;
#[macro_use]
mod instructions;
/// x86 CPU modes
#[derive(Debug, PartialEq)]
pub enum CpuMode {
/// Real mode
Real,
/// Virtual 8086 mode
Virtual8086,
/// 16-bit protected mode
Protected16,
/// 32-bit protected mode
Protected,
/// 64-bit mode, a.k.a. long mode
Long,
}
/// CpuStateManager manages an x86 CPU state.
///
/// Instruction emulation handlers get a mutable reference to
/// a `CpuStateManager` implementation, representing the current state of the
/// CPU they have to emulate an instruction stream against. Usually those
/// handlers will modify the CPU state by modifying `CpuState` and it is up to
/// the handler caller to commit those changes back by invoking a
/// `PlatformEmulator` implementation `set_state()` method.
///
pub trait CpuStateManager: Clone {
/// Reads a CPU register.
///
/// # Arguments
///
/// * `reg` - A general purpose, control or debug register.
fn read_reg(&self, reg: Register) -> Result<u64, PlatformError>;
/// Write to a CPU register.
///
/// # Arguments
///
/// * `reg` - A general purpose, control or debug register.
/// * `val` - The value to load.
fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError>;
/// Reads a segment register.
///
/// # Arguments
///
/// * `reg` - A segment register.
fn read_segment(&self, reg: Register) -> Result<SegmentRegister, PlatformError>;
/// Write to a segment register.
///
/// # Arguments
///
/// * `reg` - A segment register.
/// * `segment_reg` - The segment register value to load.
fn write_segment(
&mut self,
reg: Register,
segment_reg: SegmentRegister,
) -> Result<(), PlatformError>;
/// Get the CPU instruction pointer.
fn ip(&self) -> u64;
/// Set the CPU instruction pointer.
///
/// # Arguments
///
/// * `ip` - The CPU instruction pointer.
fn set_ip(&mut self, ip: u64);
/// Get the CPU Extended Feature Enable Register.
fn efer(&self) -> u64;
/// Set the CPU Extended Feature Enable Register.
///
/// # Arguments
///
/// * `efer` - The CPU EFER value.
fn set_efer(&mut self, efer: u64);
/// Get the CPU flags.
fn flags(&self) -> u64;
/// Set the CPU flags.
///
/// # Arguments
///
/// * `flags` - The CPU flags
fn set_flags(&mut self, flags: u64);
/// Get the CPU mode.
fn mode(&self) -> Result<CpuMode, PlatformError>;
/// Translate a logical (segmented) address into a linear (virtual) one.
///
/// # Arguments
///
/// * `segment` - Which segment to use for linearization
/// * `logical_addr` - The logical address to be translated
fn linearize(
&self,
segment: Register,
logical_addr: u64,
write: bool,
) -> Result<u64, PlatformError> {
let segment_register = self.read_segment(segment)?;
let mode = self.mode()?;
match mode {
CpuMode::Long => {
// TODO Check that we got a canonical address.
Ok(logical_addr
.checked_add(segment_register.base)
.ok_or_else(|| {
PlatformError::InvalidAddress(anyhow!(
"Logical address {:#x} can not be linearized with segment {:#x?}",
logical_addr,
segment_register
))
})?)
}
CpuMode::Protected | CpuMode::Real => {
let segment_type = segment_register.segment_type();
// Must not write to a read-only segment.
if segment_type_ro(segment_type) && write {
return Err(PlatformError::InvalidAddress(anyhow!(
"Can not write to a read-only segment"
)));
}
let logical_addr = logical_addr & 0xffff_ffffu64;
let mut segment_limit: u32 = if segment_register.granularity() != 0 {
(segment_register.limit << 12) | 0xfff
} else {
segment_register.limit
};
// Expand-down segment
if segment_type_expand_down(segment_type) {
if logical_addr >= segment_limit.into() {
return Err(PlatformError::InvalidAddress(anyhow!(
"{:#x} is off limits {:#x} (expand down)",
logical_addr,
segment_limit
)));
}
if segment_register.db() != 0 {
segment_limit = 0xffffffff
} else {
segment_limit = 0xffff
}
}
if logical_addr > segment_limit.into() {
return Err(PlatformError::InvalidAddress(anyhow!(
"{:#x} is off limits {:#x}",
logical_addr,
segment_limit
)));
}
Ok(logical_addr + segment_register.base)
}
_ => Err(PlatformError::UnsupportedCpuMode(anyhow!("{:?}", mode))),
}
}
}
const REGISTER_MASK_64: u64 = 0xffff_ffff_ffff_ffffu64;
const REGISTER_MASK_32: u64 = 0xffff_ffffu64;
const REGISTER_MASK_16: u64 = 0xffffu64;
const REGISTER_MASK_8: u64 = 0xffu64;
macro_rules! set_reg {
($reg:expr, $mask:expr, $value:expr) => {
$reg = ($reg & $mask) | $value
};
}
#[derive(Clone, Default, Debug)]
/// A minimal, emulated CPU state.
///
/// Hypervisors needing x86 emulation can choose to either use their own
/// CPU state structures and implement the CpuStateManager interface for it,
/// or use `EmulatorCpuState`. The latter implies creating a new state
/// `EmulatorCpuState` instance for each platform `cpu_state()` call, which
/// might be less efficient.
pub struct EmulatorCpuState {
pub regs: StandardRegisters,
pub sregs: SpecialRegisters,
}
impl CpuStateManager for EmulatorCpuState {
fn read_reg(&self, reg: Register) -> Result<u64, PlatformError> {
let mut reg_value: u64 = match reg {
Register::RAX | Register::EAX | Register::AX | Register::AL | Register::AH => {
self.regs.rax
}
Register::RBX | Register::EBX | Register::BX | Register::BL | Register::BH => {
self.regs.rbx
}
Register::RCX | Register::ECX | Register::CX | Register::CL | Register::CH => {
self.regs.rcx
}
Register::RDX | Register::EDX | Register::DX | Register::DL | Register::DH => {
self.regs.rdx
}
Register::RSP | Register::ESP | Register::SP => self.regs.rsp,
Register::RBP | Register::EBP | Register::BP => self.regs.rbp,
Register::RSI | Register::ESI | Register::SI | Register::SIL => self.regs.rsi,
Register::RDI | Register::EDI | Register::DI | Register::DIL => self.regs.rdi,
Register::R8 | Register::R8D | Register::R8W | Register::R8L => self.regs.r8,
Register::R9 | Register::R9D | Register::R9W | Register::R9L => self.regs.r9,
Register::R10 | Register::R10D | Register::R10W | Register::R10L => self.regs.r10,
Register::R11 | Register::R11D | Register::R11W | Register::R11L => self.regs.r11,
Register::R12 | Register::R12D | Register::R12W | Register::R12L => self.regs.r12,
Register::R13 | Register::R13D | Register::R13W | Register::R13L => self.regs.r13,
Register::R14 | Register::R14D | Register::R14W | Register::R14L => self.regs.r14,
Register::R15 | Register::R15D | Register::R15W | Register::R15L => self.regs.r15,
Register::CR0 => self.sregs.cr0,
Register::CR2 => self.sregs.cr2,
Register::CR3 => self.sregs.cr3,
Register::CR4 => self.sregs.cr4,
Register::CR8 => self.sregs.cr8,
r => {
return Err(PlatformError::InvalidRegister(anyhow!(
"read_reg invalid GPR {:?}",
r
)))
}
};
reg_value = if reg.is_gpr64() || reg.is_cr() {
reg_value
} else if reg.is_gpr32() {
reg_value & REGISTER_MASK_32
} else if reg.is_gpr16() {
reg_value & REGISTER_MASK_16
} else if reg.is_gpr8() {
if reg == Register::AH
|| reg == Register::BH
|| reg == Register::CH
|| reg == Register::DH
{
(reg_value >> 8) & REGISTER_MASK_8
} else {
reg_value & REGISTER_MASK_8
}
} else {
return Err(PlatformError::InvalidRegister(anyhow!(
"read_reg invalid GPR {:?}",
reg
)));
};
debug!("Register read: {:#x} from {:?}", reg_value, reg);
Ok(reg_value)
}
fn write_reg(&mut self, reg: Register, val: u64) -> Result<(), PlatformError> {
debug!("Register write: {:#x} to {:?}", val, reg);
// SDM Vol 1 - 3.4.1.1
//
// 8-bit and 16-bit operands generate an 8-bit or 16-bit result.
// The upper 56 bits or 48 bits (respectively) of the destination
// general-purpose register are not modified by the operation.
let (reg_value, mask): (u64, u64) = if reg.is_gpr64() || reg.is_cr() {
(val, !REGISTER_MASK_64)
} else if reg.is_gpr32() {
(val & REGISTER_MASK_32, !REGISTER_MASK_64)
} else if reg.is_gpr16() {
(val & REGISTER_MASK_16, !REGISTER_MASK_16)
} else if reg.is_gpr8() {
if reg == Register::AH
|| reg == Register::BH
|| reg == Register::CH
|| reg == Register::DH
{
((val & REGISTER_MASK_8) << 8, !(REGISTER_MASK_8 << 8))
} else {
(val & REGISTER_MASK_8, !REGISTER_MASK_8)
}
} else {
return Err(PlatformError::InvalidRegister(anyhow!(
"write_reg invalid register {:?}",
reg
)));
};
match reg {
Register::RAX | Register::EAX | Register::AX | Register::AL | Register::AH => {
set_reg!(self.regs.rax, mask, reg_value);
}
Register::RBX | Register::EBX | Register::BX | Register::BL | Register::BH => {
set_reg!(self.regs.rbx, mask, reg_value);
}
Register::RCX | Register::ECX | Register::CX | Register::CL | Register::CH => {
set_reg!(self.regs.rcx, mask, reg_value);
}
Register::RDX | Register::EDX | Register::DX | Register::DL | Register::DH => {
set_reg!(self.regs.rdx, mask, reg_value);
}
Register::RSP | Register::ESP | Register::SP => {
set_reg!(self.regs.rsp, mask, reg_value)
}
Register::RBP | Register::EBP | Register::BP => {
set_reg!(self.regs.rbp, mask, reg_value)
}
Register::RSI | Register::ESI | Register::SI | Register::SIL => {
set_reg!(self.regs.rsi, mask, reg_value)
}
Register::RDI | Register::EDI | Register::DI | Register::DIL => {
set_reg!(self.regs.rdi, mask, reg_value)
}
Register::R8 | Register::R8D | Register::R8W | Register::R8L => {
set_reg!(self.regs.r8, mask, reg_value)
}
Register::R9 | Register::R9D | Register::R9W | Register::R9L => {
set_reg!(self.regs.r9, mask, reg_value)
}
Register::R10 | Register::R10D | Register::R10W | Register::R10L => {
set_reg!(self.regs.r10, mask, reg_value)
}
Register::R11 | Register::R11D | Register::R11W | Register::R11L => {
set_reg!(self.regs.r11, mask, reg_value)
}
Register::R12 | Register::R12D | Register::R12W | Register::R12L => {
set_reg!(self.regs.r12, mask, reg_value)
}
Register::R13 | Register::R13D | Register::R13W | Register::R13L => {
set_reg!(self.regs.r13, mask, reg_value)
}
Register::R14 | Register::R14D | Register::R14W | Register::R14L => {
set_reg!(self.regs.r14, mask, reg_value)
}
Register::R15 | Register::R15D | Register::R15W | Register::R15L => {
set_reg!(self.regs.r15, mask, reg_value)
}
Register::CR0 => set_reg!(self.sregs.cr0, mask, reg_value),
Register::CR2 => set_reg!(self.sregs.cr2, mask, reg_value),
Register::CR3 => set_reg!(self.sregs.cr3, mask, reg_value),
Register::CR4 => set_reg!(self.sregs.cr4, mask, reg_value),
Register::CR8 => set_reg!(self.sregs.cr8, mask, reg_value),
_ => {
return Err(PlatformError::InvalidRegister(anyhow!(
"write_reg invalid register {:?}",
reg
)))
}
}
Ok(())
}
fn read_segment(&self, reg: Register) -> Result<SegmentRegister, PlatformError> {
if !reg.is_segment_register() {
return Err(PlatformError::InvalidRegister(anyhow!(
"read_segment {:?} is not a segment register",
reg
)));
}
match reg {
Register::CS => Ok(self.sregs.cs),
Register::DS => Ok(self.sregs.ds),
Register::ES => Ok(self.sregs.es),
Register::FS => Ok(self.sregs.fs),
Register::GS => Ok(self.sregs.gs),
Register::SS => Ok(self.sregs.ss),
r => Err(PlatformError::InvalidRegister(anyhow!(
"read_segment invalid register {:?}",
r
))),
}
}
fn write_segment(
&mut self,
reg: Register,
segment_register: SegmentRegister,
) -> Result<(), PlatformError> {
if !reg.is_segment_register() {
return Err(PlatformError::InvalidRegister(anyhow!("{:?}", reg)));
}
match reg {
Register::CS => self.sregs.cs = segment_register,
Register::DS => self.sregs.ds = segment_register,
Register::ES => self.sregs.es = segment_register,
Register::FS => self.sregs.fs = segment_register,
Register::GS => self.sregs.gs = segment_register,
Register::SS => self.sregs.ss = segment_register,
r => return Err(PlatformError::InvalidRegister(anyhow!("{:?}", r))),
}
Ok(())
}
fn ip(&self) -> u64 {
self.regs.rip
}
fn set_ip(&mut self, ip: u64) {
self.regs.rip = ip;
}
fn efer(&self) -> u64 {
self.sregs.efer
}
fn set_efer(&mut self, efer: u64) {
self.sregs.efer = efer
}
fn flags(&self) -> u64 {
self.regs.rflags
}
fn set_flags(&mut self, flags: u64) {
self.regs.rflags = flags;
}
fn mode(&self) -> Result<CpuMode, PlatformError> {
let efer = self.efer();
let cr0 = self.read_reg(Register::CR0)?;
let mut mode = CpuMode::Real;
if (cr0 & CR0_PE) == CR0_PE {
mode = CpuMode::Protected;
}
if (efer & EFER_LMA) == EFER_LMA {
if mode != CpuMode::Protected {
return Err(PlatformError::InvalidState(anyhow!(
"Protection must be enabled in long mode"
)));
}
mode = CpuMode::Long;
}
Ok(mode)
}
}
pub struct Emulator<'a, T: CpuStateManager> {
platform: &'a mut dyn PlatformEmulator<CpuState = T>,
}
// Reduce repetition, see its invocation in get_handler().
macro_rules! gen_handler_match {
($value: ident, $( ($module:ident, $code:ident) ),* ) => {
match $value {
$(
Code::$code => Some(Box::new($module::$code)),
)*
_ => None,
}
};
}
impl<'a, T: CpuStateManager> Emulator<'a, T> {
pub fn new(platform: &mut dyn PlatformEmulator<CpuState = T>) -> Emulator<T> {
Emulator { platform }
}
fn get_handler(code: Code) -> Option<Box<dyn InstructionHandler<T>>> {
let handler: Option<Box<dyn InstructionHandler<T>>> = gen_handler_match!(
code,
// CMP
(cmp, Cmp_rm32_r32),
(cmp, Cmp_rm8_r8),
(cmp, Cmp_rm32_imm8),
(cmp, Cmp_rm64_r64),
// MOV
(mov, Mov_r8_rm8),
(mov, Mov_r8_imm8),
(mov, Mov_r16_imm16),
(mov, Mov_r16_rm16),
(mov, Mov_r32_imm32),
(mov, Mov_r32_rm32),
(mov, Mov_r64_imm64),
(mov, Mov_r64_rm64),
(mov, Mov_rm8_imm8),
(mov, Mov_rm8_r8),
(mov, Mov_rm16_imm16),
(mov, Mov_rm16_r16),
(mov, Mov_rm32_imm32),
(mov, Mov_rm32_r32),
(mov, Mov_rm64_imm32),
(mov, Mov_rm64_r64)
);
handler
}
fn emulate_insn_stream(
&mut self,
cpu_id: usize,
insn_stream: &[u8],
num_insn: Option<usize>,
) -> EmulationResult<T, Exception> {
let mut state = self
.platform
.cpu_state(cpu_id)
.map_err(EmulationError::PlatformEmulationError)?;
let mut decoder = Decoder::new(64, insn_stream, DecoderOptions::NONE);
let mut insn = Instruction::default();
let mut num_insn_emulated: usize = 0;
let mut fetched_insn_stream: [u8; 16] = [0; 16];
let mut last_decoded_ip: u64 = state.ip();
let mut stop_emulation: bool = false;
decoder.set_ip(state.ip());
while decoder.can_decode() && !stop_emulation {
decoder.decode_out(&mut insn);
if decoder.last_error() == DecoderError::NoMoreBytes {
// The decoder is missing some bytes to decode the current
// instruction, for example because the instruction stream
// crosses a page boundary.
// We fetch 16 more bytes from the instruction segment,
// decode and emulate the failing instruction and terminate
// the emulation loop.
debug!(
"Fetching {} bytes from {:#x}",
fetched_insn_stream.len(),
last_decoded_ip
);
// fetched_insn_stream is 16 bytes long, enough to contain
// any complete x86 instruction.
self.platform
.fetch(last_decoded_ip, &mut fetched_insn_stream)
.map_err(EmulationError::PlatformEmulationError)?;
debug!("Fetched {:x?}", fetched_insn_stream);
// Once we have the new stream, we must create a new decoder
// and emulate one last instruction from the last decoded IP.
decoder = Decoder::new(64, &fetched_insn_stream, DecoderOptions::NONE);
decoder.decode_out(&mut insn);
if decoder.last_error() != DecoderError::None {
return Err(EmulationError::InstructionFetchingError(anyhow!(
"{:#x?}",
insn_format!(insn)
)));
}
stop_emulation = true;
}
// Emulate the decoded instruction
Emulator::get_handler(insn.code())
.ok_or_else(|| {
EmulationError::UnsupportedInstruction(anyhow!(
"{:#x?} {:?} {:?}",
insn_format!(insn),
insn.mnemonic(),
insn.code()
))
})?
.emulate(&insn, &mut state, self.platform)
.context(anyhow!("Failed to emulate {:#x?}", insn_format!(insn)))?;
last_decoded_ip = decoder.ip();
num_insn_emulated += 1;
if let Some(num_insn) = num_insn {
if num_insn_emulated >= num_insn {
// Exit the decoding loop, do not decode the next instruction.
stop_emulation = true;
}
}
}
state.set_ip(decoder.ip());
Ok(state)
}
/// Emulate all instructions from the instructions stream.
pub fn emulate(&mut self, cpu_id: usize, insn_stream: &[u8]) -> EmulationResult<T, Exception> {
self.emulate_insn_stream(cpu_id, insn_stream, None)
}
/// Only emulate the first instruction from the stream.
///
/// This is useful for cases where we get readahead instruction stream
/// but implicitly must only emulate the first instruction, and then return
/// to the guest.
pub fn emulate_first_insn(
&mut self,
cpu_id: usize,
insn_stream: &[u8],
) -> EmulationResult<T, Exception> {
self.emulate_insn_stream(cpu_id, insn_stream, Some(1))
}
}
#[cfg(test)]
mod mock_vmm {
#![allow(unused_mut)]
extern crate env_logger;
use super::*;
use crate::arch::emulator::{EmulationError, PlatformEmulator};
use crate::arch::x86::emulator::{Emulator, EmulatorCpuState as CpuState};
use crate::arch::x86::gdt::{gdt_entry, segment_from_gdt};
use crate::arch::x86::Exception;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct MockVMM {
memory: Vec<u8>,
state: Arc<Mutex<CpuState>>,
}
unsafe impl Sync for MockVMM {}
pub type MockResult = Result<(), EmulationError<Exception>>;
impl MockVMM {
pub fn new(ip: u64, regs: Vec<(Register, u64)>, memory: Option<(u64, &[u8])>) -> MockVMM {
let _ = env_logger::try_init();
let cs_reg = segment_from_gdt(gdt_entry(0xc09b, 0, 0xffffffff), 1);
let ds_reg = segment_from_gdt(gdt_entry(0xc093, 0, 0xffffffff), 2);
let mut initial_state = CpuState::default();
initial_state.set_ip(ip);
initial_state.write_segment(Register::CS, cs_reg).unwrap();
initial_state.write_segment(Register::DS, ds_reg).unwrap();
for (reg, value) in regs {
initial_state.write_reg(reg, value).unwrap();
}
let mut vmm = MockVMM {
memory: vec![0; 8192],
state: Arc::new(Mutex::new(initial_state)),
};
if let Some(mem) = memory {
vmm.write_memory(mem.0, &mem.1).unwrap();
}
vmm
}
pub fn emulate_insn(
&mut self,
cpu_id: usize,
insn: &[u8],
num_insn: Option<usize>,
) -> MockResult {
let ip = self.cpu_state(cpu_id).unwrap().ip();
let mut emulator = Emulator::new(self);
let new_state = emulator.emulate_insn_stream(cpu_id, &insn, num_insn)?;
if num_insn.is_none() {
assert_eq!(ip + insn.len() as u64, new_state.ip());
}
self.set_cpu_state(cpu_id, new_state).unwrap();
Ok(())
}
pub fn emulate_first_insn(&mut self, cpu_id: usize, insn: &[u8]) -> MockResult {
self.emulate_insn(cpu_id, insn, Some(1))
}
}
impl PlatformEmulator for MockVMM {
type CpuState = CpuState;
fn read_memory(&self, gva: u64, data: &mut [u8]) -> Result<(), PlatformError> {
debug!(
"Memory read {} bytes from [{:#x} -> {:#x}]",
data.len(),
gva,
gva
);
data.copy_from_slice(&self.memory[gva as usize..gva as usize + data.len()]);
Ok(())
}
fn write_memory(&mut self, gva: u64, data: &[u8]) -> Result<(), PlatformError> {
debug!(
"Memory write {} bytes at [{:#x} -> {:#x}]",
data.len(),
gva,
gva
);
self.memory[gva as usize..gva as usize + data.len()].copy_from_slice(data);
Ok(())
}
fn cpu_state(&self, _cpu_id: usize) -> Result<CpuState, PlatformError> {
Ok(self.state.lock().unwrap().clone())
}
fn set_cpu_state(
&self,
_cpu_id: usize,
state: Self::CpuState,
) -> Result<(), PlatformError> {
*self.state.lock().unwrap() = state;
Ok(())
}
fn gva_to_gpa(&self, gva: u64) -> Result<u64, PlatformError> {
Ok(gva)
}
fn fetch(&self, ip: u64, instruction_bytes: &mut [u8]) -> Result<(), PlatformError> {
let rip = self
.state
.lock()
.unwrap()
.linearize(Register::CS, ip, false)?;
self.read_memory(rip, instruction_bytes)
}
}
}
#[cfg(test)]
mod tests {
#![allow(unused_mut)]
use super::*;
use crate::arch::x86::emulator::mock_vmm::*;
#[test]
// Emulate truncated instruction stream, which should cause a fetch.
//
// mov rax, 0x1000
// Test with a first instruction truncated.
fn test_fetch_first_instruction() -> MockResult {
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory = [
// Code at IP
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
// Padding
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
// Data at IP + 0x10 (0x1234567812345678 in LE)
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
];
let insn = [
// First instruction is truncated
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
];
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
let rax: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RAX)
.unwrap();
assert_eq!(rax, ip);
Ok(())
}
#[test]
// Emulate truncated instruction stream, which should cause a fetch.
//
// mov rax, 0x1000
// mov rbx, qword ptr [rax+10h]
// Test with a 2nd instruction truncated.
fn test_fetch_second_instruction() -> MockResult {
let target_rax: u64 = 0x1234567812345678;
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory = [
// Code at IP
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
0x48, 0x8b, 0x58, 0x10, // mov rbx, qword ptr [rax+10h]
// Padding
0x00, 0x00, 0x00, 0x00, 0x00, // Padding is all zeroes
// Data at IP + 0x10 (0x1234567812345678 in LE)
0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
];
let insn = [
0x48, 0xc7, 0xc0, 0x00, 0x10, 0x00, 0x00, // mov rax, 0x1000
0x48, 0x8b, // Truncated mov rbx, qword ptr [rax+10h] -- missing [0x58, 0x10]
];
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
assert!(vmm.emulate_insn(cpu_id, &insn, Some(2)).is_ok());
let rbx: u64 = vmm
.cpu_state(cpu_id)
.unwrap()
.read_reg(Register::RBX)
.unwrap();
assert_eq!(rbx, target_rax);
Ok(())
}
#[test]
// Emulate truncated instruction stream, which should cause a fetch.
//
// mov rax, 0x1000
// Test with a first instruction truncated and a bad fetched instruction.
// Verify that the instruction emulation returns an error.
fn test_fetch_bad_insn() -> MockResult {
let ip: u64 = 0x1000;
let cpu_id = 0;
let memory = [
// Code at IP
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
];
let insn = [
// First instruction is truncated
0x48, 0xc7, 0xc0, 0x00, // mov rax, 0x1000 -- Missing bytes: 0x00, 0x10, 0x00, 0x00,
];
let mut vmm = MockVMM::new(ip, vec![], Some((ip, &memory)));
assert!(vmm.emulate_first_insn(cpu_id, &insn).is_err());
Ok(())
}
}

View File

@@ -8,7 +8,7 @@
// found in the LICENSE-BSD-3-Clause file.
// For GDT details see arch/x86/include/asm/segment.h
use hypervisor::x86_64::SegmentRegister;
use crate::x86_64::SegmentRegister;
/// Constructor for a conventional segment GDT (or LDT) entry. Derived from the kernel's segment.h.
pub fn gdt_entry(flags: u16, base: u32, limit: u32) -> u64 {

View File

@@ -11,6 +11,8 @@
// Copyright © 2020, Microsoft Corporation
//
pub mod gdt;
#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
@@ -28,9 +30,95 @@
)]
pub mod msr_index;
pub mod emulator;
// MTRR constants
pub const MTRR_ENABLE: u64 = 0x800; // IA32_MTRR_DEF_TYPE MSR: E (MTRRs enabled) flag, bit 11
pub const MTRR_MEM_TYPE_WB: u64 = 0x6;
// IOAPIC pins
pub const NUM_IOAPIC_PINS: usize = 24;
// X86 Exceptions
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum Exception {
DE = 1, // Divide Error
DB = 2, // Debug Exception
BP = 3, // Breakpoint
OF = 4, // Overflow
BR = 5, // BOUND Range Exceeded
UD = 6, // Invalid/Undefined Opcode
NM = 7, // No Math Coprocessor
DF = 8, // Double Fault
TS = 10, // Invalid TSS
NP = 11, // Segment Not Present
SS = 12, // Stack Segment Fault
GP = 13, // General Protection
PF = 14, // Page Fault
MF = 16, // Math Fault
AC = 17, // Alignment Check
MC = 18, // Machine Check
XM = 19, // SIMD Floating-Point Exception
VE = 20, // Virtualization Exception
CP = 21, // Control Protection Exception
}
pub mod regs;
// Abstracted segment register ops.
// Each x86 hypervisor should implement those.
pub trait SegmentRegisterOps {
// Segment type
fn segment_type(&self) -> u8;
fn set_segment_type(&mut self, val: u8);
// Descriptor Privilege Level (DPL)
fn dpl(&self) -> u8;
fn set_dpl(&mut self, val: u8);
// Granularity
fn granularity(&self) -> u8;
fn set_granularity(&mut self, val: u8);
// Memory Presence
fn present(&self) -> u8;
fn set_present(&mut self, val: u8);
// Long mode
fn long(&self) -> u8;
fn set_long(&mut self, val: u8);
// Available for system use (AVL)
fn avl(&self) -> u8;
fn set_avl(&mut self, val: u8);
// Descriptor type (System or code/data)
fn desc_type(&self) -> u8;
fn set_desc_type(&mut self, val: u8);
// D/B
fn db(&self) -> u8;
fn set_db(&mut self, val: u8);
}
// Code segment
pub const CODE_SEGMENT_TYPE: u8 = 0x8;
// Read/Write or Read/Exec segment
pub const RWRX_SEGMENT_TYPE: u8 = 0x2;
// Expand down segment
pub const EXPAND_DOWN_SEGMENT_TYPE: u8 = 0x4;
pub fn segment_type_code(t: u8) -> bool {
t & CODE_SEGMENT_TYPE != 0
}
pub fn segment_type_ro(t: u8) -> bool {
t & !RWRX_SEGMENT_TYPE == 0
}
pub fn segment_type_expand_down(t: u8) -> bool {
!segment_type_code(t) && (t & EXPAND_DOWN_SEGMENT_TYPE != 0)
}

View File

@@ -0,0 +1,32 @@
//
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
// EFER (technically not a register) bits
pub const EFER_LMA: u64 = 0x400;
pub const EFER_LME: u64 = 0x100;
// CR0 bits
pub const CR0_PE: u64 = 0x1;
pub const CR0_PG: u64 = 0x80000000;
// CR4 bits
pub const CR4_PAE: u64 = 0x20;
pub const CR4_LA57: u64 = 0x1000;
// RFlags bits
pub const CF_SHIFT: usize = 0;
pub const PF_SHIFT: usize = 2;
pub const AF_SHIFT: usize = 4;
pub const ZF_SHIFT: usize = 6;
pub const SF_SHIFT: usize = 7;
pub const OF_SHIFT: usize = 11;
pub const CF: u64 = 1 << CF_SHIFT;
pub const PF: u64 = 1 << PF_SHIFT;
pub const AF: u64 = 1 << AF_SHIFT;
pub const ZF: u64 = 1 << ZF_SHIFT;
pub const SF: u64 = 1 << SF_SHIFT;
pub const OF: u64 = 1 << OF_SHIFT;

View File

@@ -12,13 +12,17 @@
use crate::aarch64::VcpuInit;
#[cfg(target_arch = "aarch64")]
use crate::aarch64::{RegList, Register, StandardRegisters};
use crate::{CpuState, MpState};
#[cfg(target_arch = "x86_64")]
use crate::x86_64::{CpuId, LapicState};
#[cfg(target_arch = "x86_64")]
use crate::x86_64::{
CpuId, ExtendedControlRegisters, FpuState, LapicState, MsrEntries, SpecialRegisters,
StandardRegisters, VcpuEvents, Xsave,
ExtendedControlRegisters, FpuState, MsrEntries, SpecialRegisters, StandardRegisters, VcpuEvents,
};
use crate::CpuState;
#[cfg(feature = "kvm")]
use crate::MpState;
#[cfg(target_arch = "x86_64")]
use crate::Xsave;
use thiserror::Error;
#[derive(Error, Debug)]
@@ -151,6 +155,20 @@ pub enum HypervisorCpuError {
#[error("Failed to notify guest its clock was paused: {0}")]
NotifyGuestClockPaused(#[source] anyhow::Error),
///
/// Setting debug register error
///
#[error("Failed to set debug registers: {0}")]
SetDebugRegs(#[source] anyhow::Error),
///
/// Getting debug register error
///
#[error("Failed to get debug registers: {0}")]
GetDebugRegs(#[source] anyhow::Error),
///
/// Write to Guest Mem
///
#[error("Failed to write to Guest Mem at: {0}")]
GuestMemWrite(#[source] anyhow::Error),
/// Enabling HyperV SynIC error
///
#[error("Failed to enable HyperV SynIC")]
@@ -194,6 +212,7 @@ pub enum VmExit<'a> {
MmioWrite(u64 /* address */, &'a [u8]),
Ignore,
Reset,
Shutdown,
Hyperv,
}
@@ -271,10 +290,12 @@ pub trait Vcpu: Send + Sync {
/// Setup the model-specific registers (MSR) for this vCPU.
///
fn set_msrs(&self, msrs: &MsrEntries) -> Result<usize>;
#[cfg(feature = "kvm")]
///
/// Returns the vcpu's current "multiprocessing state".
///
fn get_mp_state(&self) -> Result<MpState>;
#[cfg(feature = "kvm")]
///
/// Sets the vcpu's current "multiprocessing state".
///
@@ -311,7 +332,7 @@ pub trait Vcpu: Send + Sync {
/// of the vcpu.
///
fn set_vcpu_events(&self, events: &VcpuEvents) -> Result<()>;
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
///
/// Let the guest know that it has been paused, which prevents from
/// potential soft lockups when being resumed.

View File

@@ -9,8 +9,10 @@
//
use crate::vm::Vm;
#[cfg(target_arch = "x86_64")]
use crate::x86_64::{CpuId, MsrList};
use crate::x86_64::CpuId;
#[cfg(target_arch = "x86_64")]
use crate::x86_64::MsrList;
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
use kvm_ioctls::Cap;
use std::sync::Arc;
@@ -20,6 +22,11 @@ use thiserror::Error;
///
///
pub enum HypervisorError {
///
/// hypervisor creation error
///
#[error("Failed to create the hypervisor: {0}")]
HypervisorCreate(#[source] anyhow::Error),
///
/// Vm creation failure
///
@@ -78,19 +85,22 @@ pub trait Hypervisor: Send + Sync {
/// Return a hypervisor-agnostic Vm trait object
///
fn create_vm(&self) -> Result<Arc<dyn Vm>>;
#[cfg(feature = "kvm")]
///
/// Returns the size of the memory mapping required to use the vcpu's structures
///
fn get_vcpu_mmap_size(&self) -> Result<usize>;
#[cfg(feature = "kvm")]
///
/// Gets the recommended maximum number of VCPUs per VM.
///
fn get_max_vcpus(&self) -> Result<usize>;
#[cfg(feature = "kvm")]
///
/// Gets the recommended number of VCPUs per VM.
///
fn get_nr_vcpus(&self) -> Result<usize>;
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
///
/// Checks if a particular `Cap` is available.
///
@@ -100,6 +110,7 @@ pub trait Hypervisor: Send + Sync {
/// Get the supported CpuID
///
fn get_cpuid(&self) -> Result<CpuId>;
#[cfg(not(feature = "mshv"))]
///
/// Check particular extensions if any
///

View File

@@ -19,7 +19,6 @@ use crate::hypervisor;
use crate::vm::{self, VmmOps};
#[cfg(target_arch = "aarch64")]
use crate::{arm64_core_reg_id, offset__of};
use arc_swap::ArcSwapOption;
use kvm_ioctls::{NoDatamatch, VcpuFd, VmFd};
use serde_derive::{Deserialize, Serialize};
use std::os::unix::io::{AsRawFd, RawFd};
@@ -70,7 +69,8 @@ use std::mem;
pub use kvm_bindings;
pub use kvm_bindings::{
kvm_create_device, kvm_device_type_KVM_DEV_TYPE_VFIO, kvm_irq_routing, kvm_irq_routing_entry,
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_READONLY, KVM_MSI_VALID_DEVID,
kvm_userspace_memory_region, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MEM_READONLY,
KVM_MSI_VALID_DEVID,
};
pub use kvm_ioctls;
pub use kvm_ioctls::{Cap, Kvm};
@@ -96,7 +96,6 @@ pub struct KvmVm {
#[cfg(target_arch = "x86_64")]
msrs: MsrEntries,
state: KvmVmState,
vmmops: ArcSwapOption<Box<dyn vm::VmmOps>>,
}
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
@@ -176,7 +175,11 @@ impl vm::Vm for KvmVm {
///
/// Creates a VcpuFd object from a vcpu RawFd.
///
fn create_vcpu(&self, id: u8) -> vm::Result<Arc<dyn cpu::Vcpu>> {
fn create_vcpu(
&self,
id: u8,
vmmops: Option<Arc<Box<dyn VmmOps>>>,
) -> vm::Result<Arc<dyn cpu::Vcpu>> {
let vc = self
.fd
.create_vcpu(id)
@@ -185,7 +188,7 @@ impl vm::Vm for KvmVm {
fd: vc,
#[cfg(target_arch = "x86_64")]
msrs: self.msrs.clone(),
vmmops: self.vmmops.clone(),
vmmops,
#[cfg(target_arch = "x86_64")]
hyperv_synic: AtomicBool::new(false),
};
@@ -255,13 +258,19 @@ impl vm::Vm for KvmVm {
memory_size: u64,
userspace_addr: u64,
readonly: bool,
log_dirty_pages: bool,
) -> MemoryRegion {
MemoryRegion {
slot,
guest_phys_addr,
memory_size,
userspace_addr,
flags: if readonly { KVM_MEM_READONLY } else { 0 },
flags: if readonly { KVM_MEM_READONLY } else { 0 }
| if log_dirty_pages {
KVM_MEM_LOG_DIRTY_PAGES
} else {
0
},
}
}
///
@@ -356,11 +365,12 @@ impl vm::Vm for KvmVm {
}
///
/// Set the VmmOps interface
/// Get dirty pages bitmap (one bit per page)
///
fn set_vmmops(&self, vmmops: Box<dyn VmmOps>) -> vm::Result<()> {
self.vmmops.store(Some(Arc::new(vmmops)));
Ok(())
fn get_dirty_log(&self, slot: u32, memory_size: u64) -> vm::Result<Vec<u64>> {
self.fd
.get_dirty_log(slot, memory_size as usize)
.map_err(|e| vm::HypervisorVmError::GetDirtyLog(e.into()))
}
}
/// Wrapper over KVM system ioctls.
@@ -439,7 +449,6 @@ impl hypervisor::Hypervisor for KvmHypervisor {
fd: vm_fd,
msrs,
state: VmState {},
vmmops: ArcSwapOption::from(None),
}))
}
@@ -448,7 +457,6 @@ impl hypervisor::Hypervisor for KvmHypervisor {
Ok(Arc::new(KvmVm {
fd: vm_fd,
state: VmState {},
vmmops: ArcSwapOption::from(None),
}))
}
}
@@ -509,7 +517,7 @@ pub struct KvmVcpu {
fd: VcpuFd,
#[cfg(target_arch = "x86_64")]
msrs: MsrEntries,
vmmops: ArcSwapOption<Box<dyn vm::VmmOps>>,
vmmops: Option<Arc<Box<dyn vm::VmmOps>>>,
#[cfg(target_arch = "x86_64")]
hyperv_synic: AtomicBool,
}
@@ -520,7 +528,7 @@ pub struct KvmVcpu {
/// let kvm = hypervisor::kvm::KvmHypervisor::new().unwrap();
/// let hypervisor: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hypervisor.create_vm().expect("new VM fd creation failed");
/// let vcpu = vm.create_vcpu(0).unwrap();
/// let vcpu = vm.create_vcpu(0, None).unwrap();
/// vcpu.get/set().unwrap()
///
impl cpu::Vcpu for KvmVcpu {
@@ -594,7 +602,7 @@ impl cpu::Vcpu for KvmVcpu {
fn enable_hyperv_synic(&self) -> cpu::Result<()> {
// Update the information about Hyper-V SynIC being enabled and
// emulated as it will influence later which MSRs should be saved.
self.hyperv_synic.store(true, Ordering::SeqCst);
self.hyperv_synic.store(true, Ordering::Release);
let mut cap: kvm_enable_cap = Default::default();
cap.cap = KVM_CAP_HYPERV_SYNIC;
@@ -708,7 +716,7 @@ impl cpu::Vcpu for KvmVcpu {
Ok(run) => match run {
#[cfg(target_arch = "x86_64")]
VcpuExit::IoIn(addr, data) => {
if let Some(vmmops) = self.vmmops.load_full() {
if let Some(vmmops) = &self.vmmops {
return vmmops
.pio_read(addr.into(), data)
.map(|_| cpu::VmExit::Ignore)
@@ -719,7 +727,7 @@ impl cpu::Vcpu for KvmVcpu {
}
#[cfg(target_arch = "x86_64")]
VcpuExit::IoOut(addr, data) => {
if let Some(vmmops) = self.vmmops.load_full() {
if let Some(vmmops) = &self.vmmops {
return vmmops
.pio_write(addr.into(), data)
.map(|_| cpu::VmExit::Ignore)
@@ -738,10 +746,10 @@ impl cpu::Vcpu for KvmVcpu {
use kvm_bindings::{KVM_SYSTEM_EVENT_RESET, KVM_SYSTEM_EVENT_SHUTDOWN};
// On Aarch64, when the VM is shutdown, run() returns
// VcpuExit::SystemEvent with reason KVM_SYSTEM_EVENT_SHUTDOWN
if event_type == KVM_SYSTEM_EVENT_SHUTDOWN
|| event_type == KVM_SYSTEM_EVENT_RESET
{
if event_type == KVM_SYSTEM_EVENT_RESET {
Ok(cpu::VmExit::Reset)
} else if event_type == KVM_SYSTEM_EVENT_SHUTDOWN {
Ok(cpu::VmExit::Shutdown)
} else {
Err(cpu::HypervisorCpuError::RunVcpu(anyhow!(
"Unexpected system event with type 0x{:x}, flags 0x{:x}",
@@ -752,7 +760,7 @@ impl cpu::Vcpu for KvmVcpu {
}
VcpuExit::MmioRead(addr, data) => {
if let Some(vmmops) = self.vmmops.load_full() {
if let Some(vmmops) = &self.vmmops {
return vmmops
.mmio_read(addr, data)
.map(|_| cpu::VmExit::Ignore)
@@ -762,7 +770,7 @@ impl cpu::Vcpu for KvmVcpu {
Ok(cpu::VmExit::MmioRead(addr, data))
}
VcpuExit::MmioWrite(addr, data) => {
if let Some(vmmops) = self.vmmops.load_full() {
if let Some(vmmops) = &self.vmmops {
return vmmops
.mmio_write(addr, data)
.map(|_| cpu::VmExit::Ignore)
@@ -1121,7 +1129,7 @@ impl cpu::Vcpu for KvmVcpu {
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hv.create_vm().expect("new VM fd creation failed");
/// vm.enable_split_irq().unwrap();
/// let vcpu = vm.create_vcpu(0).unwrap();
/// let vcpu = vm.create_vcpu(0, None).unwrap();
/// let state = vcpu.state().unwrap();
/// ```
fn state(&self) -> cpu::Result<CpuState> {
@@ -1143,7 +1151,7 @@ impl cpu::Vcpu for KvmVcpu {
// Save extra MSRs if the Hyper-V synthetic interrupt controller is
// emulated.
if self.hyperv_synic.load(Ordering::SeqCst) {
if self.hyperv_synic.load(Ordering::Acquire) {
let hyperv_synic_msrs = vec![
0x40000020, 0x40000021, 0x40000080, 0x40000081, 0x40000082, 0x40000083, 0x40000084,
0x40000090, 0x40000091, 0x40000092, 0x40000093, 0x40000094, 0x40000095, 0x40000096,
@@ -1267,7 +1275,7 @@ impl cpu::Vcpu for KvmVcpu {
/// let hv: Arc<dyn hypervisor::Hypervisor> = Arc::new(kvm);
/// let vm = hv.create_vm().expect("new VM fd creation failed");
/// vm.enable_split_irq().unwrap();
/// let vcpu = vm.create_vcpu(0).unwrap();
/// let vcpu = vm.create_vcpu(0, None).unwrap();
/// let state = vcpu.state().unwrap();
/// vcpu.set_state(&state).unwrap();
/// ```

View File

@@ -10,7 +10,7 @@
use vm_memory::GuestAddress;
use crate::arch::x86::{msr_index, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
use crate::arch::x86::{msr_index, SegmentRegisterOps, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
use crate::kvm::{Cap, Kvm, KvmError, KvmResult};
use serde_derive::{Deserialize, Serialize};
@@ -28,6 +28,71 @@ pub use {
kvm_bindings::KVM_CPUID_FLAG_SIGNIFCANT_INDEX as CPUID_FLAG_VALID_INDEX,
};
impl SegmentRegisterOps for SegmentRegister {
fn segment_type(&self) -> u8 {
self.type_
}
fn set_segment_type(&mut self, val: u8) {
self.type_ = val;
}
fn dpl(&self) -> u8 {
self.dpl
}
fn set_dpl(&mut self, val: u8) {
self.dpl = val;
}
fn present(&self) -> u8 {
self.present
}
fn set_present(&mut self, val: u8) {
self.present = val;
}
fn long(&self) -> u8 {
self.l
}
fn set_long(&mut self, val: u8) {
self.l = val;
}
fn avl(&self) -> u8 {
self.avl
}
fn set_avl(&mut self, val: u8) {
self.avl = val;
}
fn desc_type(&self) -> u8 {
self.s
}
fn set_desc_type(&mut self, val: u8) {
self.s = val;
}
fn granularity(&self) -> u8 {
self.g
}
fn set_granularity(&mut self, val: u8) {
self.g = val;
}
fn db(&self) -> u8 {
self.db
}
fn set_db(&mut self, val: u8) {
self.db = val;
}
}
pub const KVM_TSS_ADDRESS: GuestAddress = GuestAddress(0xfffb_d000);
macro_rules! msr {

View File

@@ -28,9 +28,14 @@ extern crate serde_derive;
extern crate serde_json;
extern crate thiserror;
#[cfg(feature = "kvm")]
/// KVM implementation module
pub mod kvm;
/// Microsoft Hypervisor implementation module
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
pub mod mshv;
/// Hypevisor related module
pub mod hypervisor;
@@ -49,7 +54,10 @@ mod device;
pub use crate::hypervisor::{Hypervisor, HypervisorError};
pub use cpu::{HypervisorCpuError, Vcpu, VmExit};
pub use device::{Device, HypervisorDeviceError};
#[cfg(feature = "kvm")]
pub use kvm::*;
#[cfg(all(feature = "mshv", target_arch = "x86_64"))]
pub use mshv::*;
pub use vm::{DataMatch, HypervisorVmError, Vm};
use std::sync::Arc;
@@ -58,5 +66,8 @@ pub fn new() -> std::result::Result<Arc<dyn Hypervisor>, HypervisorError> {
#[cfg(feature = "kvm")]
let hv = kvm::KvmHypervisor::new()?;
#[cfg(feature = "mshv")]
let hv = mshv::MshvHypervisor::new()?;
Ok(Arc::new(hv))
}

1082
hypervisor/src/mshv/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
//
// Copyright © 2020, Microsoft Corporation
//
// Copyright 2018-2019 CrowdStrike, Inc.
//
//
use crate::arch::x86::{msr_index, SegmentRegisterOps, MTRR_ENABLE, MTRR_MEM_TYPE_WB};
use serde_derive::{Deserialize, Serialize};
///
/// Export generically-named wrappers of mshv_bindings for Unix-based platforms
///
pub use {
mshv_bindings::mshv_user_mem_region as MemoryRegion, mshv_bindings::msr_entry as MsrEntry,
mshv_bindings::CpuId, mshv_bindings::DebugRegisters,
mshv_bindings::FloatingPointUnit as FpuState, mshv_bindings::LapicState,
mshv_bindings::MsrList, mshv_bindings::Msrs as MsrEntries, mshv_bindings::Msrs,
mshv_bindings::SegmentRegister, mshv_bindings::SpecialRegisters,
mshv_bindings::StandardRegisters, mshv_bindings::VcpuEvents, mshv_bindings::XSave as Xsave,
mshv_bindings::Xcrs as ExtendedControlRegisters,
};
#[derive(Clone, Serialize, Deserialize)]
pub struct VcpuMshvState {
pub msrs: MsrEntries,
pub vcpu_events: VcpuEvents,
pub regs: StandardRegisters,
pub sregs: SpecialRegisters,
pub fpu: FpuState,
pub xcrs: ExtendedControlRegisters,
pub lapic: LapicState,
pub dbg: DebugRegisters,
pub xsave: Xsave,
}
pub struct CreateDevice {}
pub struct DeviceAttr {}
pub struct IrqRouting {}
pub enum VcpuExit {}
pub struct MpState {}
#[derive(Eq, PartialEq, Hash, Clone, Debug, Copy)]
pub enum IoEventAddress {
/// Representation of an programmable I/O address.
Pio(u64),
/// Representation of an memory mapped I/O address.
Mmio(u64),
}
macro_rules! msr {
($msr:expr) => {
MsrEntry {
index: $msr,
data: 0x0,
..Default::default()
}
};
}
macro_rules! msr_data {
($msr:expr, $data:expr) => {
MsrEntry {
index: $msr,
data: $data,
..Default::default()
}
};
}
impl SegmentRegisterOps for SegmentRegister {
fn segment_type(&self) -> u8 {
self.type_
}
fn set_segment_type(&mut self, val: u8) {
self.type_ = val;
}
fn dpl(&self) -> u8 {
self.dpl
}
fn set_dpl(&mut self, val: u8) {
self.dpl = val;
}
fn present(&self) -> u8 {
self.present
}
fn set_present(&mut self, val: u8) {
self.present = val;
}
fn long(&self) -> u8 {
self.l
}
fn set_long(&mut self, val: u8) {
self.l = val;
}
fn avl(&self) -> u8 {
self.avl
}
fn set_avl(&mut self, val: u8) {
self.avl = val;
}
fn desc_type(&self) -> u8 {
self.s
}
fn set_desc_type(&mut self, val: u8) {
self.s = val;
}
fn granularity(&self) -> u8 {
self.g
}
fn set_granularity(&mut self, val: u8) {
self.g = val;
}
fn db(&self) -> u8 {
self.db
}
fn set_db(&mut self, val: u8) {
self.db = val;
}
}
pub fn boot_msr_entries() -> MsrEntries {
MsrEntries::from_entries(&[
msr!(msr_index::MSR_IA32_SYSENTER_CS),
msr!(msr_index::MSR_IA32_SYSENTER_ESP),
msr!(msr_index::MSR_IA32_SYSENTER_EIP),
msr!(msr_index::MSR_STAR),
msr!(msr_index::MSR_CSTAR),
msr!(msr_index::MSR_LSTAR),
msr!(msr_index::MSR_KERNEL_GS_BASE),
msr!(msr_index::MSR_SYSCALL_MASK),
msr!(msr_index::MSR_IA32_TSC),
])
}

View File

@@ -12,10 +12,16 @@
use crate::aarch64::VcpuInit;
use crate::cpu::Vcpu;
use crate::device::Device;
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
use crate::ClockData;
#[cfg(feature = "kvm")]
use crate::CreateDevice;
#[cfg(feature = "mshv")]
use crate::HvState as VmState;
#[cfg(feature = "kvm")]
use crate::KvmVmState as VmState;
use crate::{CreateDevice, IoEventAddress, IrqRoutingEntry, MemoryRegion};
use crate::{IoEventAddress, IrqRoutingEntry, MemoryRegion};
#[cfg(feature = "kvm")]
use kvm_ioctls::Cap;
use std::sync::Arc;
use thiserror::Error;
@@ -24,6 +30,7 @@ use vmm_sys_util::eventfd::EventFd;
///
/// I/O events data matches (32 or 64 bits).
///
#[derive(Debug)]
pub enum DataMatch {
DataMatch32(u32),
DataMatch64(u64),
@@ -117,7 +124,6 @@ pub enum HypervisorVmError {
///
#[error("Failed to create passthrough device: {0}")]
CreatePassthroughDevice(#[source] anyhow::Error),
///
/// Write to Guest memory
///
#[error("Failed to write to guest memory: {0}")]
@@ -147,6 +153,16 @@ pub enum HypervisorVmError {
///
#[error("Failed to write to IO Bus: {0}")]
IoBusWrite(#[source] anyhow::Error),
///
/// Get dirty log error
///
#[error("Failed to get dirty log: {0}")]
GetDirtyLog(#[source] anyhow::Error),
///
/// Assert virtual interrupt error
///
#[error("Failed to assert virtual Interrupt: {0}")]
AsserttVirtualInterrupt(#[source] anyhow::Error),
}
///
/// Result type for returning from a function
@@ -169,7 +185,7 @@ pub trait Vm: Send + Sync {
/// Unregister an event that will, when signaled, trigger the `gsi` IRQ.
fn unregister_irqfd(&self, fd: &EventFd, gsi: u32) -> Result<()>;
/// Creates a new KVM vCPU file descriptor and maps the memory corresponding
fn create_vcpu(&self, id: u8) -> Result<Arc<dyn Vcpu>>;
fn create_vcpu(&self, id: u8, vmmops: Option<Arc<Box<dyn VmmOps>>>) -> Result<Arc<dyn Vcpu>>;
/// Registers an event to be signaled whenever a certain address is written to.
fn register_ioevent(
&self,
@@ -189,9 +205,11 @@ pub trait Vm: Send + Sync {
memory_size: u64,
userspace_addr: u64,
readonly: bool,
log_dirty_pages: bool,
) -> MemoryRegion;
/// Creates/modifies a guest physical memory slot.
fn set_user_memory_region(&self, user_memory_region: MemoryRegion) -> Result<()>;
#[cfg(feature = "kvm")]
/// Creates an emulated device in the kernel.
fn create_device(&self, device: &mut CreateDevice) -> Result<Arc<dyn Device>>;
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
@@ -201,11 +219,12 @@ pub trait Vm: Send + Sync {
#[cfg(target_arch = "x86_64")]
fn enable_split_irq(&self) -> Result<()>;
/// Retrieve guest clock.
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
fn get_clock(&self) -> Result<ClockData>;
/// Set guest clock.
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
fn set_clock(&self, data: &ClockData) -> Result<()>;
#[cfg(feature = "kvm")]
/// Checks if a particular `Cap` is available.
fn check_extension(&self, c: Cap) -> bool;
/// Create a device that is used for passthrough
@@ -214,8 +233,8 @@ pub trait Vm: Send + Sync {
fn state(&self) -> Result<VmState>;
/// Set the VM state
fn set_state(&self, state: VmState) -> Result<()>;
/// Set VmmOps interface
fn set_vmmops(&self, vmmops: Box<dyn VmmOps>) -> Result<()>;
/// Get dirty pages bitmap
fn get_dirty_log(&self, slot: u32, memory_size: u64) -> Result<Vec<u64>>;
}
pub trait VmmOps: Send + Sync {

View File

@@ -5,17 +5,17 @@ authors = ["The Chromium OS Authors"]
[dependencies]
epoll = ">=4.0.1"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
net_gen = { path = "../net_gen" }
rand = "0.7.3"
serde = "1.0.117"
serde = "1.0.118"
virtio-bindings = "0.1.0"
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1"
[dev-dependencies]
lazy_static = "1.3.0"
pnet = "0.26.0"
serde_json = "1.0.59"
pnet = "0.27.2"
serde_json = "1.0.60"

View File

@@ -10,14 +10,14 @@ byteorder = "1.3.4"
hypervisor = { path = "../hypervisor" }
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
vmm-sys-util = ">=0.3.1"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = "0.3.0"
vm-memory = "0.4.0"
vm-migration = { path = "../vm-migration" }
[dependencies.vfio-bindings]

View File

@@ -570,7 +570,7 @@ impl PciConfiguration {
let end_addr = config
.addr
.checked_add(config.size - 1)
.ok_or_else(|| Error::BarAddressInvalid(config.addr, config.size))?;
.ok_or(Error::BarAddressInvalid(config.addr, config.size))?;
match config.region_type {
PciBarRegionType::Memory32BitRegion | PciBarRegionType::IORegion => {
if end_addr > u64::from(u32::max_value()) {
@@ -642,7 +642,7 @@ impl PciConfiguration {
let end_addr = config
.addr
.checked_add(config.size - 1)
.ok_or_else(|| Error::RomBarAddressInvalid(config.addr, config.size))?;
.ok_or(Error::RomBarAddressInvalid(config.addr, config.size))?;
if end_addr > u64::from(u32::max_value()) {
return Err(Error::RomBarAddressInvalid(config.addr, config.size));
@@ -698,7 +698,7 @@ impl PciConfiguration {
};
let end_offset = cap_offset
.checked_add(total_len)
.ok_or_else(|| Error::CapabilitySpaceFull(total_len))?;
.ok_or(Error::CapabilitySpaceFull(total_len))?;
if end_offset > CAPABILITY_MAX_OFFSET {
return Err(Error::CapabilitySpaceFull(total_len));
}

View File

@@ -221,9 +221,9 @@ impl Interrupt {
}
#[derive(Copy, Clone)]
struct MmioRegion {
start: GuestAddress,
length: GuestUsize,
pub struct MmioRegion {
pub start: GuestAddress,
pub length: GuestUsize,
type_: PciBarRegionType,
index: u32,
mem_slot: Option<u32>,
@@ -561,6 +561,7 @@ impl VfioPciDevice {
mmap_size as u64,
host_addr as u64,
false,
false,
);
vm.set_user_memory_region(mem_region)
@@ -590,6 +591,7 @@ impl VfioPciDevice {
0,
host_addr as u64,
false,
false,
);
if let Err(e) = self.vm.set_user_memory_region(r) {
@@ -613,6 +615,10 @@ impl VfioPciDevice {
.extend_dma_map(new_region)
.map_err(VfioPciError::UpdateMemory)
}
pub fn mmio_regions(&self) -> Vec<MmioRegion> {
self.mmio_regions.clone()
}
}
impl Drop for VfioPciDevice {
@@ -748,7 +754,7 @@ impl PciDevice for VfioPciDevice {
// The address needs to be 4 bytes aligned.
bar_addr = allocator
.allocate_io_addresses(None, region_size, Some(0x4))
.ok_or_else(|| PciDeviceError::IoAllocationFailed(region_size))?;
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
}
#[cfg(target_arch = "aarch64")]
unimplemented!()
@@ -795,11 +801,11 @@ impl PciDevice for VfioPciDevice {
if is_64bit_bar {
bar_addr = allocator
.allocate_mmio_addresses(None, region_size, Some(bar_alignment))
.ok_or_else(|| PciDeviceError::IoAllocationFailed(region_size))?;
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
} else {
bar_addr = allocator
.allocate_mmio_hole_addresses(None, region_size, Some(bar_alignment))
.ok_or_else(|| PciDeviceError::IoAllocationFailed(region_size))?;
.ok_or(PciDeviceError::IoAllocationFailed(region_size))?;
}
}
@@ -1012,6 +1018,7 @@ impl PciDevice for VfioPciDevice {
0,
host_addr as u64,
false,
false,
);
self.vm
@@ -1025,6 +1032,7 @@ impl PciDevice for VfioPciDevice {
mmap_size as u64,
host_addr as u64,
false,
false,
);
self.vm

View File

@@ -10,7 +10,7 @@ path = "src/qcow.rs"
[dependencies]
byteorder = "1.3.4"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
remain = "0.2.2"
vmm-sys-util = ">=0.3.1"

View File

@@ -1868,7 +1868,7 @@ mod tests {
#[test]
fn test_header_crazy_file_size_rejected() {
let mut header = valid_header_v3();
&mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Failed to create file.");
});
@@ -1917,7 +1917,7 @@ mod tests {
#[test]
fn test_header_huge_num_refcounts() {
let mut header = valid_header_v3();
&mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
});
@@ -1926,7 +1926,7 @@ mod tests {
#[test]
fn test_header_huge_refcount_offset() {
let mut header = valid_header_v3();
&mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
with_basic_file(&header, |disk_file: RawFile| {
QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
});
@@ -1936,11 +1936,11 @@ mod tests {
fn write_read_start() {
with_basic_file(&valid_header_v3(), |disk_file: RawFile| {
let mut q = QcowFile::from(disk_file).unwrap();
q.write(b"test first bytes")
q.write_all(b"test first bytes")
.expect("Failed to write test string.");
let mut buf = [0u8; 4];
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
q.read(&mut buf).expect("Failed to read.");
q.read_exact(&mut buf).expect("Failed to read.");
assert_eq!(&buf, b"test");
});
}
@@ -1951,10 +1951,10 @@ mod tests {
let mut q = QcowFile::from(disk_file).unwrap();
let b = [0x55u8; 0x1000];
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
q.write(&b).expect("Failed to write test string.");
q.write_all(&b).expect("Failed to write test string.");
let mut buf = [0u8; 4];
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
q.read(&mut buf).expect("Failed to read.");
q.read_exact(&mut buf).expect("Failed to read.");
assert_eq!(buf[0], 0x55);
});
}
@@ -1966,7 +1966,7 @@ mod tests {
// Write some test data.
let b = [0x55u8; 0x1000];
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
q.write(&b).expect("Failed to write test string.");
q.write_all(&b).expect("Failed to write test string.");
// Overwrite the test data with zeroes.
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
let nwritten = q.write_zeroes(0x200).expect("Failed to write zeroes.");
@@ -1974,7 +1974,7 @@ mod tests {
// Verify that the correct part of the data was zeroed out.
let mut buf = [0u8; 0x1000];
q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
q.read(&mut buf).expect("Failed to read.");
q.read_exact(&mut buf).expect("Failed to read.");
assert_eq!(buf[0], 0);
assert_eq!(buf[0x1FF], 0);
assert_eq!(buf[0x200], 0x55);
@@ -1992,7 +1992,7 @@ mod tests {
// Write some test data.
let b = [0x55u8; CHUNK_SIZE];
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
q.write(&b).expect("Failed to write test string.");
q.write_all(&b).expect("Failed to write test string.");
// Overwrite the full cluster with zeroes.
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
let nwritten = q.write_zeroes(CHUNK_SIZE).expect("Failed to write zeroes.");
@@ -2000,7 +2000,7 @@ mod tests {
// Verify that the data was zeroed out.
let mut buf = [0u8; CHUNK_SIZE];
q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
q.read(&mut buf).expect("Failed to read.");
q.read_exact(&mut buf).expect("Failed to read.");
assert_eq!(buf[0], 0);
assert_eq!(buf[CHUNK_SIZE - 1], 0);
});
@@ -2024,7 +2024,7 @@ mod tests {
let mut q = QcowFile::from(disk_file).unwrap();
let mut b = [5u8; 16];
q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
q.read(&mut b).expect("Failed to read.");
q.read_exact(&mut b).expect("Failed to read.");
assert_eq!(0, b[0]);
assert_eq!(0, b[15]);
});
@@ -2397,7 +2397,7 @@ mod tests {
for xfer in &xfers {
q.seek(SeekFrom::Start(xfer.addr)).expect("Failed to seek.");
if xfer.write {
q.write(&b).expect("Failed to write.");
q.write_all(&b).expect("Failed to write.");
} else {
let read_count: usize = q.read(&mut b).expect("Failed to read.");
assert_eq!(read_count, BUF_SIZE);

View File

@@ -1,3 +1,9 @@
- [v0.12.0](#v0120)
- [ARM64 enhancements](#arm64-enhancements)
- [Removal of `vhost-user-net` and `vhost-user-block` self spawning](#removal-of-vhost-user-net-and-vhost-user-block-self-spawning)
- [Migration of `vhost-user-fs` backend](#migration-of-vhost-user-fs-backend)
- [Enhanced "info" API](#enhanced-info-api)
- [Contributors](#contributors)
- [v0.11.0](#v0110)
- [`io_uring` support by default for `virtio-block`](#io_uring-support-by-default-for-virtio-block)
- [Windows Guest Support](#windows-guest-support)
@@ -10,14 +16,14 @@
- [New `--balloon` Parameter Added](#new---balloon-parameter-added)
- [Experimental `virtio-watchdog` Support](#experimental-virtio-watchdog-support)
- [Notable Bug Fixes](#notable-bug-fixes)
- [Contributors](#contributors)
- [Contributors](#contributors-1)
- [v0.10.0](#v0100)
- [`virtio-block` Support for Multiple Descriptors](#virtio-block-support-for-multiple-descriptors)
- [Memory Zones](#memory-zones)
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements)
- [Preliminary KVM HyperV Emulation Control](#preliminary-kvm-hyperv-emulation-control)
- [Notable Bug Fixes](#notable-bug-fixes-1)
- [Contributors](#contributors-1)
- [Contributors](#contributors-2)
- [v0.9.0](#v090)
- [`io_uring` Based Block Device Support](#io_uring-based-block-device-support)
- [Block and Network Device Statistics](#block-and-network-device-statistics)
@@ -31,7 +37,7 @@
- [Intel SGX Support](#intel-sgx-support)
- [`Seccomp` Sandbox Improvements](#seccomp-sandbox-improvements-1)
- [Notable Bug Fixes](#notable-bug-fixes-2)
- [Contributors](#contributors-2)
- [Contributors](#contributors-3)
- [v0.8.0](#v080)
- [Experimental Snapshot and Restore Support](#experimental-snapshot-and-restore-support)
- [Experimental ARM64 Support](#experimental-arm64-support)
@@ -40,7 +46,7 @@
- [`vhost_user_fs` Improvements](#vhost_user_fs-improvements)
- [Notable Bug Fixes](#notable-bug-fixes-3)
- [Command Line and API Changes](#command-line-and-api-changes)
- [Contributors](#contributors-3)
- [Contributors](#contributors-4)
- [v0.7.0](#v070)
- [Block, Network, Persistent Memory (PMEM), VirtioFS and Vsock hotplug](#block-network-persistent-memory-pmem-virtiofs-and-vsock-hotplug)
- [Alternative `libc` Support](#alternative-libc-support)
@@ -50,14 +56,14 @@
- [`Seccomp` Sandboxing](#seccomp-sandboxing)
- [Updated Distribution Support](#updated-distribution-support)
- [Command Line and API Changes](#command-line-and-api-changes-1)
- [Contributors](#contributors-4)
- [Contributors](#contributors-5)
- [v0.6.0](#v060)
- [Directly Assigned Devices Hotplug](#directly-assigned-devices-hotplug)
- [Shared Filesystem Improvements](#shared-filesystem-improvements)
- [Block and Networking IO Self Offloading](#block-and-networking-io-self-offloading)
- [Command Line Interface](#command-line-interface)
- [PVH Boot](#pvh-boot)
- [Contributors](#contributors-5)
- [Contributors](#contributors-6)
- [v0.5.1](#v051)
- [v0.5.0](#v050)
- [Virtual Machine Dynamic Resizing](#virtual-machine-dynamic-resizing)
@@ -65,7 +71,7 @@
- [New Interrupt Management Framework](#new-interrupt-management-framework)
- [Development Tools](#development-tools)
- [Kata Containers Integration](#kata-containers-integration)
- [Contributors](#contributors-6)
- [Contributors](#contributors-7)
- [v0.4.0](#v040)
- [Dynamic virtual CPUs addition](#dynamic-virtual-cpus-addition)
- [Programmatic firmware tables generation](#programmatic-firmware-tables-generation)
@@ -74,7 +80,7 @@
- [Userspace IOAPIC by default](#userspace-ioapic-by-default)
- [PCI BAR reprogramming](#pci-bar-reprogramming)
- [New `cloud-hypervisor` organization](#new-cloud-hypervisor-organization)
- [Contributors](#contributors-7)
- [Contributors](#contributors-8)
- [v0.3.0](#v030)
- [Block device offloading](#block-device-offloading)
- [Network device backend](#network-device-backend)
@@ -101,6 +107,47 @@
- [Unit testing](#unit-testing)
- [Integration tests parallelization](#integration-tests-parallelization)
# v0.12.0
This release has been tracked through the [0.12.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/15).
Highlights for `cloud-hypervisor` version 0.12.0 include:
### ARM64 enhancements
The use of `--watchdog` is now fully supported as is the ability to reboot the
VM from within the guest when running Cloud Hypervisor on an ARM64 system.
### Removal of `vhost-user-net` and `vhost-user-block` self spawning
In order to use `vhost-user-net` or `vhost-user-block` backends the user is now
responsible for starting the backend and providing the socket for the VMM to
use. This functionality was deprecated in the last release and how now been
removed.
### Migration of `vhost-user-fs` backend
The `vhost-user-fs` backend is no longer included in Cloud Hypervisor and it is
instead hosted in [it's own
repository](https://gitlab.com/virtio-fs/virtiofsd-rs)
### Enhanced "info" API
The `vm.info` HTTP API endpoint has been extended to include the details of the
devices used by the VM including any VFIO devices used.
### Contributors
Many thanks to everyone who has contributed to our 0.12.0 release:
* Anatol Belski <anbelski@linux.microsoft.com>
* Julio Montes <julio.montes@intel.com>
* Michael Zhao <michael.zhao@arm.com>
* Muminul Islam <muislam@microsoft.com>
* Rob Bradford <robert.bradford@intel.com>
* Samuel Ortiz <sameo@linux.intel.com>
* Wei Liu <liuwe@microsoft.com>
# v0.11.0
This release has been tracked through the [0.11.0 project](https://github.com/cloud-hypervisor/cloud-hypervisor/projects/14).

View File

@@ -1543,7 +1543,39 @@ CONFIG_THERMAL_GOV_USER_SPACE=y
# CONFIG_CPU_THERMAL is not set
# CONFIG_THERMAL_EMULATION is not set
# CONFIG_THERMAL_MMIO is not set
# CONFIG_WATCHDOG is not set
CONFIG_WATCHDOG=y
CONFIG_WATCHDOG_CORE=y
# CONFIG_WATCHDOG_NOWAYOUT is not set
CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED=y
CONFIG_WATCHDOG_OPEN_TIMEOUT=0
# CONFIG_WATCHDOG_SYSFS is not set
#
# Watchdog Pretimeout Governors
#
# CONFIG_WATCHDOG_PRETIMEOUT_GOV is not set
#
# Watchdog Device Drivers
#
# CONFIG_SOFT_WATCHDOG is not set
# CONFIG_WDAT_WDT is not set
# CONFIG_XILINX_WATCHDOG is not set
# CONFIG_ARM_SP805_WATCHDOG is not set
# CONFIG_ARM_SBSA_WATCHDOG is not set
# CONFIG_CADENCE_WATCHDOG is not set
# CONFIG_DW_WATCHDOG is not set
# CONFIG_MAX63XX_WATCHDOG is not set
# CONFIG_ARM_SMC_WATCHDOG is not set
# CONFIG_ALIM7101_WDT is not set
# CONFIG_I6300ESB_WDT is not set
CONFIG_VIRTIO_WDT=y
#
# PCI-based Watchdog Cards
#
# CONFIG_PCIPCWATCHDOG is not set
# CONFIG_WDTPCI is not set
CONFIG_SSB_POSSIBLE=y
# CONFIG_SSB is not set
CONFIG_BCMA_POSSIBLE=y

View File

@@ -1,6 +1,6 @@
Name: cloud-hypervisor
Summary: Cloud Hypervisor is an open source Virtual Machine Monitor (VMM) that runs on top of KVM.
Version: 0.8.0
Version: 0.12.0
Release: 0%{?dist}
License: ASL 2.0 or BSD-3-clause
Group: Applications/System
@@ -36,14 +36,12 @@ install -D -m755 ./target/x86_64-unknown-linux-gnu/release/cloud-hypervisor %{b
install -D -m755 ./target/x86_64-unknown-linux-gnu/release/ch-remote %{buildroot}%{_bindir}
install -d %{buildroot}%{_libdir}
install -d %{buildroot}%{_libdir}/cloud-hypervisor
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_blk %{buildroot}%{_libdir}/cloud-hypervisor
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_fs %{buildroot}%{_libdir}/cloud-hypervisor
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_block %{buildroot}%{_libdir}/cloud-hypervisor
install -D -m755 target/x86_64-unknown-linux-gnu/release/vhost_user_net %{buildroot}%{_libdir}/cloud-hypervisor
install -d %{buildroot}%{_libdir}/cloud-hypervisor/static
install -D -m755 target/x86_64-unknown-linux-musl/release/cloud-hypervisor %{buildroot}%{_libdir}/cloud-hypervisor/static
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_blk %{buildroot}%{_libdir}/cloud-hypervisor/static
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_fs %{buildroot}%{_libdir}/cloud-hypervisor/static
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_block %{buildroot}%{_libdir}/cloud-hypervisor/static
install -D -m755 target/x86_64-unknown-linux-musl/release/vhost_user_net %{buildroot}%{_libdir}/cloud-hypervisor/static
install -D -m755 target/x86_64-unknown-linux-musl/release/ch-remote %{buildroot}%{_libdir}/cloud-hypervisor/static
@@ -87,8 +85,6 @@ setcap cap_net_admin+ep %{_bindir}/cloud-hypervisor
setcap cap_net_admin+ep %{_libdir}/cloud-hypervisor/vhost_user_net
setcap cap_net_admin+ep %{_libdir}/cloud-hypervisor/static/cloud-hypervisor
setcap cap_net_admin+ep %{_libdir}/cloud-hypervisor/static/vhost_user_net
setcap cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_setgid,cap_setuid,cap_mknod,cap_setfcap,cap_sys_admin+epi %{_libdir}/cloud-hypervisor/vhost_user_fs
setcap cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_setgid,cap_setuid,cap_mknod,cap_setfcap,cap_sys_admin+epi %{_libdir}/cloud-hypervisor/static/vhost_user_fs
%changelog

View File

@@ -175,6 +175,7 @@ cmd_help() {
echo " --release Build the release binaries."
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
echo " --volumes Hash separated volumes to be exported. Example --volumes /mnt:/mnt#/myvol:/myvol"
echo " --hypervisor Underlying hypervisor. Options kvm, mshv"
echo ""
echo " tests [--unit|--cargo|--all] [--libc musl|gnu] [-- [<cargo test args>]]"
echo " Run the Cloud Hypervisor tests."
@@ -185,6 +186,7 @@ cmd_help() {
echo " --integration-windows Run the Windows guest integration tests."
echo " --libc Select the C library Cloud Hypervisor will be built against. Default is gnu"
echo " --volumes Hash separated volumes to be exported. Example --volumes /mnt:/mnt#/myvol:/myvol"
echo " --hypervisor Underlying hypervisor. Options kvm, mshv"
echo " --all Run all tests."
echo ""
echo " build-container [--type]"
@@ -205,12 +207,14 @@ cmd_help() {
cmd_build() {
build="debug"
libc="gnu"
hypervisor="kvm"
features_build=""
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--debug") { build="debug"; } ;;
"--release") { build="release"; } ;;
"-h"|"--help") { cmd_help; exit 1; } ;;
"--debug") { build="debug"; } ;;
"--release") { build="release"; } ;;
"--libc")
shift
[[ "$1" =~ ^(musl|gnu)$ ]] || \
@@ -221,7 +225,11 @@ cmd_build() {
shift
arg_vols="$1"
;;
"--") { shift; break; } ;;
"--hypervisor")
shift
hypervisor="$1"
;;
"--") { shift; break; } ;;
*)
die "Unknown build argument: $1. Please use --help for help."
;;
@@ -229,6 +237,9 @@ cmd_build() {
shift
done
process_volumes_args
if [[ "$hypervisor" != "kvm" ]]; then
die "Hypervisor value must be kvm"
fi
target="$(uname -m)-unknown-linux-${libc}"
@@ -251,7 +262,7 @@ cmd_build() {
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
--env RUSTFLAGS="$rustflags" \
"$CTR_IMAGE" \
cargo build --all \
cargo build --all $features_build \
--target-dir "$CTR_CLH_CARGO_TARGET" \
"${cargo_args[@]}" && say "Binaries placed under $CLH_CARGO_TARGET/$target/$build"
}
@@ -278,15 +289,15 @@ cmd_tests() {
integration_windows=false
libc="gnu"
arg_vols=""
hypervisor="kvm"
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--unit") { unit=true; } ;;
"--cargo") { cargo=true; } ;;
"--integration") { integration=true; } ;;
"--integration-sgx") { integration_sgx=true; } ;;
"--integration-windows") { integration_windows=true; } ;;
"-h"|"--help") { cmd_help; exit 1; } ;;
"--unit") { unit=true; } ;;
"--cargo") { cargo=true; } ;;
"--integration") { integration=true; } ;;
"--integration-sgx") { integration_sgx=true; } ;;
"--integration-windows") { integration_windows=true; } ;;
"--libc")
shift
[[ "$1" =~ ^(musl|gnu)$ ]] || \
@@ -297,14 +308,22 @@ cmd_tests() {
shift
arg_vols="$1"
;;
"--all") { cargo=true; unit=true; integration=true; } ;;
"--") { shift; break; } ;;
"--hypervisor")
shift
hypervisor="$1"
;;
"--all") { cargo=true; unit=true; integration=true; } ;;
"--") { shift; break; } ;;
*)
die "Unknown tests argument: $1. Please use --help for help."
;;
esac
shift
done
if [[ "$hypervisor" != "kvm" ]]; then
die "Hypervisor value must be kvm"
fi
set -- "$@" '--hypervisor' $hypervisor
process_volumes_args
target="$(uname -m)-unknown-linux-${libc}"
@@ -315,7 +334,7 @@ cmd_tests() {
cflags="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
fi
if [ "$unit" = true ] ; then
if [[ "$unit" = true && $hypervisor = "kvm" ]] ; then
say "Running unit tests for $target..."
$DOCKER_RUNTIME run \
--workdir "$CTR_CLH_ROOT_DIR" \
@@ -338,7 +357,7 @@ cmd_tests() {
--rm \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
"$CTR_IMAGE" \
./scripts/run_cargo_tests.sh || fix_dir_perms $? || exit $?
./scripts/run_cargo_tests.sh "$@" || fix_dir_perms $? || exit $?
fi
if [ "$integration" = true ] ; then
@@ -390,7 +409,7 @@ cmd_tests() {
--net="$CTR_CLH_NET" \
--mount type=tmpfs,destination=/tmp \
--volume /dev:/dev \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" \
--volume "$CLH_ROOT_DIR:$CTR_CLH_ROOT_DIR" $exported_volumes \
--volume "$CLH_INTEGRATION_WORKLOADS:$CTR_CLH_INTEGRATION_WORKLOADS" \
--env USER="root" \
--env CH_LIBC="${libc}" \
@@ -405,9 +424,9 @@ cmd_build-container() {
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"-h"|"--help") { cmd_help; exit 1; } ;;
"--dev") { container_type="dev"; } ;;
"--") { shift; break; } ;;
"--") { shift; break; } ;;
*)
die "Unknown build-container argument: $1. Please use --help for help."
;;
@@ -457,8 +476,8 @@ cmd_shell() {
#
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) { cmd_help; exit 1; } ;;
-y|--unattended) { OPT_UNATTENDED=true; } ;;
-h|--help) { cmd_help; exit 1; } ;;
-y|--unattended) { OPT_UNATTENDED=true; } ;;
-*)
die "Unknown arg: $1. Please use \`$0 help\` for help."
;;

View File

@@ -3,6 +3,9 @@ set -e
set -x
source $HOME/.cargo/env
source $(dirname "$0")/test-util.sh
process_common_args "$@"
# Install cargo components
time rustup component add clippy

View File

@@ -2,6 +2,7 @@
set -x
source $HOME/.cargo/env
source $(dirname "$0")/test-util.sh
export BUILD_TARGET=${BUILD_TARGET-aarch64-unknown-linux-gnu}
@@ -106,19 +107,25 @@ update_workloads() {
}
SRCDIR=$PWD
if [ ! -d "$LINUX_CUSTOM_DIR" ]; then
pushd $WORKLOADS_DIR
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b "virtio-fs-virtio-iommu-5.8-rc4" $LINUX_CUSTOM_DIR
cp $SRCDIR/resources/linux-config-aarch64 $LINUX_CUSTOM_DIR/.config
popd
else
LINUX_CUSTOM_BRANCH="virtio-fs-virtio-iommu-5.8-rc4"
# Check whether the local HEAD commit same as the remote HEAD or not. Remove the folder if they are different.
if [ -d "$LINUX_CUSTOM_DIR" ]; then
pushd $LINUX_CUSTOM_DIR
git fetch
git checkout -f "virtio-fs-virtio-iommu-5.8-rc4"
cp $SRCDIR/resources/linux-config-aarch64 $LINUX_CUSTOM_DIR/.config
LINUX_CUSTOM_LOCAL_HEAD=$(git rev-parse HEAD)
LINUX_CUSTOM_REMOTE_HEAD=$(git rev-parse remotes/origin/$LINUX_CUSTOM_BRANCH)
popd
if [ "$LINUX_CUSTOM_LOCAL_HEAD" != "$LINUX_CUSTOM_REMOTE_HEAD" ]; then
rm -rf "$LINUX_CUSTOM_DIR"
fi
fi
if [ ! -d "$LINUX_CUSTOM_DIR" ]; then
time git clone --depth 1 "https://github.com/cloud-hypervisor/linux.git" -b $LINUX_CUSTOM_BRANCH $LINUX_CUSTOM_DIR
fi
cp $SRCDIR/resources/linux-config-aarch64 $LINUX_CUSTOM_DIR/.config
build_custom_linux_kernel
VIRTIOFSD="$WORKLOADS_DIR/virtiofsd"
@@ -136,6 +143,19 @@ update_workloads() {
popd
fi
VIRTIOFSD_RS="$WORKLOADS_DIR/virtiofsd-rs"
VIRTIOFSD_RS_DIR="virtiofsd_rs_build"
if [ ! -f "$VIRTIOFSD_RS" ]; then
pushd $WORKLOADS_DIR
git clone --depth 1 "https://gitlab.com/virtio-fs/virtiofsd-rs.git" $VIRTIOFSD_RS_DIR
pushd $VIRTIOFSD_RS_DIR
time cargo build --release
cp target/release/virtiofsd-rs $VIRTIOFSD_RS || exit 1
popd
rm -rf $VIRTIOFSD_RS_DIR
popd
fi
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
MNT_DIR="mount_image"
if [ ! -f "$BLK_IMAGE" ]; then
@@ -158,6 +178,10 @@ update_workloads() {
fi
}
process_common_args "$@"
features_build="--no-default-features --features kvm "
features_test="--no-default-features --features integration_tests,kvm"
# lock the workloads folder to avoid parallel updating by different containers
(
echo "try to lock $WORKLOADS_DIR folder and update"
@@ -185,7 +209,7 @@ TARGET_CC="musl-gcc"
CFLAGS="-I /usr/include/aarch64-linux-musl/ -idirafter /usr/include/"
fi
cargo build --all --release --no-default-features --features kvm --target $BUILD_TARGET
cargo build --all --release $features_build --target $BUILD_TARGET
strip target/$BUILD_TARGET/release/cloud-hypervisor
strip target/$BUILD_TARGET/release/vhost_user_net
strip target/$BUILD_TARGET/release/ch-remote
@@ -197,7 +221,7 @@ sudo bash -c "echo 10 > /sys/kernel/mm/ksm/sleep_millisecs"
sudo bash -c "echo 1 > /sys/kernel/mm/ksm/run"
export RUST_BACKTRACE=1
time cargo test --no-default-features --features "integration_tests,kvm" "tests::parallel::$@" -- --skip test_snapshot_restore
time cargo test $features_test "tests::parallel::"
RES=$?
# Tear vhost_user_net test network down

View File

@@ -2,6 +2,12 @@
set -x
source $HOME/.cargo/env
source $(dirname "$0")/test-util.sh
process_common_args "$@"
# For now these values are deafult for kvm
features_build=""
features_test="--features integration_tests"
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
CFLAGS=""
@@ -11,12 +17,12 @@ TARGET_CC="musl-gcc"
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
fi
cargo build --all --release --target $BUILD_TARGET
cargo build --all --release $features_build --target $BUILD_TARGET
strip target/$BUILD_TARGET/release/cloud-hypervisor
export RUST_BACKTRACE=1
time cargo test --features "integration_tests" "tests::sgx::$@"
time cargo test $features_test "tests::sgx::"
RES=$?
exit $RES

View File

@@ -2,6 +2,12 @@
set -x
source $HOME/.cargo/env
source $(dirname "$0")/test-util.sh
process_common_args "$@"
# For now these values are deafult for kvm
features_build=""
features_test="--features integration_tests"
BUILD_TARGET="$(uname -m)-unknown-linux-${CH_LIBC}"
CFLAGS=""
@@ -11,14 +17,14 @@ TARGET_CC="musl-gcc"
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
fi
cargo build --all --release --target $BUILD_TARGET
cargo build --all --release $features_build --target $BUILD_TARGET
strip target/$BUILD_TARGET/release/cloud-hypervisor
export RUST_BACKTRACE=1
# Only run with 1 thread to avoid tests interfering with one another because
# Windows has a static IP configured
time cargo test --features "integration_tests" "tests::windows::$@" -- --test-threads=1
time cargo test $features_test "tests::windows::" -- --test-threads=1
RES=$?
exit $RES

View File

@@ -2,12 +2,20 @@
set -x
source $HOME/.cargo/env
source $(dirname "$0")/test-util.sh
export BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
WORKLOADS_DIR="$HOME/workloads"
mkdir -p "$WORKLOADS_DIR"
process_common_args "$@"
# For now these values are deafult for kvm
features_build=""
features_test="--features integration_tests"
cp scripts/sha1sums-x86_64 $WORKLOADS_DIR
FW_URL=$(curl --silent https://api.github.com/repos/cloud-hypervisor/rust-hypervisor-firmware/releases/latest | grep "browser_download_url" | grep -o 'https://.*[^ "]')
@@ -137,6 +145,20 @@ if [ ! -f "$VIRTIOFSD" ]; then
popd
fi
VIRTIOFSD_RS="$WORKLOADS_DIR/virtiofsd-rs"
VIRTIOFSD_RS_DIR="virtiofsd_rs_build"
if [ ! -f "$VIRTIOFSD_RS" ]; then
pushd $WORKLOADS_DIR
git clone --depth 1 "https://gitlab.com/virtio-fs/virtiofsd-rs.git" $VIRTIOFSD_RS_DIR
pushd $VIRTIOFSD_RS_DIR
time cargo build --release
cp target/release/virtiofsd-rs $VIRTIOFSD_RS || exit 1
popd
rm -rf $VIRTIOFSD_RS_DIR
popd
fi
BLK_IMAGE="$WORKLOADS_DIR/blk.img"
MNT_DIR="mount_image"
if [ ! -f "$BLK_IMAGE" ]; then
@@ -201,7 +223,7 @@ TARGET_CC="musl-gcc"
CFLAGS="-I /usr/include/x86_64-linux-musl/ -idirafter /usr/include/"
fi
cargo build --all --release --target $BUILD_TARGET
cargo build --all --release $features_build --target $BUILD_TARGET
strip target/$BUILD_TARGET/release/cloud-hypervisor
strip target/$BUILD_TARGET/release/vhost_user_net
strip target/$BUILD_TARGET/release/ch-remote
@@ -221,14 +243,14 @@ echo 4096 | sudo tee /proc/sys/vm/nr_hugepages
sudo chmod a+rwX /dev/hugepages
export RUST_BACKTRACE=1
time cargo test --features "integration_tests" "tests::parallel::$@"
time cargo test $features_test "tests::parallel::"
RES=$?
# Run some tests in sequence since the result could be affected by other tests
# running in parallel.
if [ $RES -eq 0 ]; then
export RUST_BACKTRACE=1
time cargo test --features "integration_tests" "tests::sequential::$@" -- --test-threads=1
time cargo test $features_test "tests::sequential::" -- --test-threads=1
RES=$?
fi

View File

@@ -1,9 +1,12 @@
#!/bin/bash
source $HOME/.cargo/env
source $(dirname "$0")/test-util.sh
process_common_args "$@"
BUILD_TARGET=${BUILD_TARGET-x86_64-unknown-linux-gnu}
cargo_args=("$@")
cargo_args=("")
[ $(uname -m) = "aarch64" ] && cargo_args+=("--no-default-features")
[ $(uname -m) = "aarch64" ] && cargo_args+=("--features kvm")

34
scripts/test-util.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
hypervisor="kvm"
cmd_help() {
echo ""
echo "Cloud Hypervisor $(basename $0)"
echo "Usage: $(basename $0) [<args>]"
echo ""
echo "Available arguments:"
echo ""
echo " --hypervisor Underlying hypervisor. Options kvm, mshv"
echo ""
echo " --help Display this help message."
echo ""
}
process_common_args() {
while [ $# -gt 0 ]; do
case "$1" in
"-h"|"--help") { cmd_help; exit 1; } ;;
"--hypervisor")
shift
hypervisor="$1"
;;
*)
# We only care about hypervisor , do nothing for other arguments
;;
esac
shift
done
if [[ "$hypervisor" != "kvm" ]]; then
echo "Hypervisor value must be kvm"
exit 1
fi
}

View File

@@ -38,7 +38,7 @@ impl fmt::Display for Error {
use Error::*;
match self {
ApiClient(e) => e.fmt(f),
Connect(e) => write!(f, "Error openning HTTP socket: {}", e),
Connect(e) => write!(f, "Error opening HTTP socket: {}", e),
InvalidCPUCount(e) => write!(f, "Error parsing CPU count: {}", e),
InvalidMemorySize(e) => write!(f, "Error parsing memory size: {:?}", e),
InvalidBalloonSize(e) => write!(f, "Error parsing balloon size: {:?}", e),
@@ -230,6 +230,32 @@ fn restore_api_command(socket: &mut UnixStream, config: &str) -> Result<(), Erro
.map_err(Error::ApiClient)
}
fn receive_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
let receive_migration_data = vmm::api::VmReceiveMigrationData {
receiver_url: url.to_owned(),
};
simple_api_command(
socket,
"PUT",
"receive-migration",
Some(&serde_json::to_string(&receive_migration_data).unwrap()),
)
.map_err(Error::ApiClient)
}
fn send_migration_api_command(socket: &mut UnixStream, url: &str) -> Result<(), Error> {
let send_migration_data = vmm::api::VmSendMigrationData {
destination_url: url.to_owned(),
};
simple_api_command(
socket,
"PUT",
"send-migration",
Some(&serde_json::to_string(&send_migration_data).unwrap()),
)
.map_err(Error::ApiClient)
}
fn do_command(matches: &ArgMatches) -> Result<(), Error> {
let mut socket =
UnixStream::connect(matches.value_of("api-socket").unwrap()).map_err(Error::Connect)?;
@@ -341,6 +367,22 @@ fn do_command(matches: &ArgMatches) -> Result<(), Error> {
.value_of("restore_config")
.unwrap(),
),
Some("send-migration") => send_migration_api_command(
&mut socket,
matches
.subcommand_matches("send-migration")
.unwrap()
.value_of("send_migration_config")
.unwrap(),
),
Some("receive-migration") => receive_migration_api_command(
&mut socket,
matches
.subcommand_matches("receive-migration")
.unwrap()
.value_of("receive_migration_config")
.unwrap(),
),
Some(c) => simple_api_command(&mut socket, "PUT", c, None).map_err(Error::ApiClient),
None => unreachable!(),
}
@@ -484,6 +526,24 @@ fn main() {
.index(1)
.help(vmm::config::RestoreConfig::SYNTAX),
),
)
.subcommand(
SubCommand::with_name("send-migration")
.about("Initiate a VM migration")
.arg(
Arg::with_name("send_migration_config")
.index(1)
.help("<destination_url>"),
),
)
.subcommand(
SubCommand::with_name("receive-migration")
.about("Receive a VM migration")
.arg(
Arg::with_name("receive_migration_config")
.index(1)
.help("<receiver_url>"),
),
);
let matches = app.get_matches();

View File

@@ -18,8 +18,6 @@ use std::env;
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use thiserror::Error;
use vhost_user_block::start_block_backend;
use vhost_user_net::start_net_backend;
use vmm::config;
use vmm_sys_util::eventfd::EventFd;
@@ -27,7 +25,14 @@ use vmm_sys_util::eventfd::EventFd;
enum Error {
#[error("Failed to create API EventFd: {0}")]
CreateAPIEventFd(#[source] std::io::Error),
#[error("Failed to open hypervisor interface (is /dev/kvm available?): {0}")]
#[cfg_attr(
feature = "kvm",
error("Failed to open hypervisor interface (is /dev/kvm available?): {0}")
)]
#[cfg_attr(
feature = "mshv",
error("Failed to open hypervisor interface (is /dev/mshv available?): {0}")
)]
CreateHypervisor(#[source] hypervisor::HypervisorError),
#[error("Failed to start the VMM thread: {0}")]
StartVMMThread(#[source] vmm::Error),
@@ -307,22 +312,6 @@ fn create_app<'a, 'b>(
.min_values(1)
.group("vmm-config"),
)
.arg(
Arg::with_name("net-backend")
.long("net-backend")
.help(vhost_user_net::SYNTAX)
.takes_value(true)
.conflicts_with_all(&["block-backend", "kernel"])
.min_values(1),
)
.arg(
Arg::with_name("block-backend")
.long("block-backend")
.help(vhost_user_block::SYNTAX)
.takes_value(true)
.conflicts_with_all(&["net-backend", "kernel"])
.min_values(1),
)
.arg(
Arg::with_name("seccomp")
.long("seccomp")
@@ -471,23 +460,17 @@ fn main() {
.map(|()| log::set_max_level(log_level))
.expect("Expected to be able to setup logger");
if let Some(backend_command) = cmd_arguments.value_of("net-backend") {
start_net_backend(backend_command);
} else if let Some(backend_command) = cmd_arguments.value_of("block-backend") {
start_block_backend(backend_command);
} else {
let api_socket_path = cmd_arguments
.value_of("api-socket")
.expect("Missing argument: api-socket")
.to_string();
let api_socket_path = cmd_arguments
.value_of("api-socket")
.expect("Missing argument: api-socket")
.to_string();
if let Err(e) = start_vmm(cmd_arguments, &api_socket_path) {
eprintln!("{}", e);
std::fs::remove_file(api_socket_path).ok();
std::process::exit(1);
}
if let Err(e) = start_vmm(cmd_arguments, &api_socket_path) {
eprintln!("{}", e);
std::fs::remove_file(api_socket_path).ok();
std::process::exit(1);
}
std::fs::remove_file(api_socket_path).ok();
}
#[cfg(test)]
@@ -812,14 +795,12 @@ mod unit_tests {
"shared=true",
"--disk",
"vhost_user=true,socket=/tmp/sock1",
"path=/path/to/disk/2",
],
r#"{
"kernel": {"path": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"disks": [
{"vhost_user":true, "vhost_socket":"/tmp/sock1"},
{"path": "/path/to/disk/2"}
{"vhost_user":true, "vhost_socket":"/tmp/sock1"}
]
}"#,
true,
@@ -833,14 +814,12 @@ mod unit_tests {
"shared=true",
"--disk",
"vhost_user=true,socket=/tmp/sock1",
"path=/path/to/disk/2",
],
r#"{
"kernel": {"path": "/path/to/kernel"},
"memory" : { "shared": true, "size": 536870912 },
"disks": [
{"vhost_user":true, "vhost_socket":"/tmp/sock1"},
{"path": "/path/to/disk/2"}
{"vhost_user":true, "vhost_socket":"/tmp/sock1"}
]
}"#,
true,

View File

@@ -31,6 +31,7 @@ mod tests {
use std::thread;
use tempdir::TempDir;
use tempfile::NamedTempFile;
#[cfg_attr(target_arch = "aarch64", allow(unused_imports))]
use wait_timeout::ChildExt;
lazy_static! {
@@ -348,16 +349,23 @@ mod tests {
(child, virtiofsd_socket_path)
}
fn prepare_vhost_user_fs_daemon(
fn prepare_virtofsd_rs_daemon(
tmp_dir: &TempDir,
shared_dir: &str,
_cache: &str,
) -> (std::process::Child, String) {
let mut workload_path = dirs::home_dir().unwrap();
workload_path.push("workloads");
let mut virtiofsd_path = workload_path;
virtiofsd_path.push("virtiofsd-rs");
let virtiofsd_path = String::from(virtiofsd_path.to_str().unwrap());
let virtiofsd_socket_path =
String::from(tmp_dir.path().join("virtiofs.sock").to_str().unwrap());
// Start the daemon
let child = Command::new(clh_command("vhost_user_fs"))
let child = Command::new(virtiofsd_path.as_str())
.args(&["--shared-dir", shared_dir])
.args(&["--socket", virtiofsd_socket_path.as_str()])
.spawn()
@@ -385,7 +393,7 @@ mod tests {
let vubd_socket_path = String::from(tmp_dir.path().join("vub.sock").to_str().unwrap());
// Start the daemon
let child = Command::new(clh_command("cloud-hypervisor"))
let child = Command::new(clh_command("vhost_user_block"))
.args(&[
"--block-backend",
format!(
@@ -460,7 +468,7 @@ mod tests {
)
};
let child = Command::new(clh_command("cloud-hypervisor"))
let child = Command::new(clh_command("vhost_user_net"))
.args(&["--net-backend", net_params.as_str()])
.spawn()
.unwrap();
@@ -856,10 +864,20 @@ mod tests {
)
.expect("Cannot add 'tcp_listener' event to epoll");
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); 1];
let num_events = epoll::wait(epoll_fd, timeout * 1000 as i32, &mut events[..])
.map_err(Error::EpollWait)?;
if num_events == 0 {
return Err(Error::EpollWaitTimeout);
loop {
let num_events =
match epoll::wait(epoll_fd, timeout * 1000 as i32, &mut events[..]) {
Ok(num_events) => Ok(num_events),
Err(e) => match e.raw_os_error() {
Some(libc::EAGAIN) | Some(libc::EINTR) => continue,
_ => Err(e),
},
}
.map_err(Error::EpollWait)?;
if num_events == 0 {
return Err(Error::EpollWaitTimeout);
}
break;
}
match listener.accept() {
@@ -1289,7 +1307,6 @@ mod tests {
tap: Option<&str>,
num_queues: usize,
prepare_vhost_user_net_daemon: Option<&PrepareNetDaemon>,
self_spawned: bool,
generate_host_mac: bool,
) {
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
@@ -1307,20 +1324,7 @@ mod tests {
None
};
let (net_params, daemon_child) = if self_spawned {
(
format!(
"vhost_user=true,mac={},ip={},mask=255.255.255.0,num_queues={},queue_size=1024{}",
guest.network.guest_mac, guest.network.host_ip, num_queues,
if let Some(host_mac) =host_mac {
format!(",host_mac={}", host_mac)
} else {
"".to_owned()
}
),
None,
)
} else {
let (net_params, daemon_child) = {
let prepare_daemon = prepare_vhost_user_net_daemon.unwrap();
// Start the daemon
let (daemon_child, vunet_socket_path) =
@@ -1456,7 +1460,6 @@ mod tests {
readonly: bool,
direct: bool,
prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>,
self_spawned: bool,
) {
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
let guest = Guest::new(&mut focal);
@@ -1467,19 +1470,7 @@ mod tests {
let kernel_path = direct_kernel_boot_path().unwrap();
let (blk_params, daemon_child) = if self_spawned {
let mut blk_file_path = workload_path;
blk_file_path.push("blk.img");
let blk_file_path = String::from(blk_file_path.to_str().unwrap());
(
format!(
"vhost_user=true,path={},num_queues={},queue_size=128",
blk_file_path, num_queues,
),
None,
)
} else {
let (blk_params, daemon_child) = {
let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap();
// Start the daemon
let (daemon_child, vubd_socket_path) =
@@ -1629,7 +1620,6 @@ mod tests {
readonly: bool,
direct: bool,
prepare_vhost_user_blk_daemon: Option<&PrepareBlkDaemon>,
self_spawned: bool,
) {
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
let guest = Guest::new(&mut focal);
@@ -1644,15 +1634,7 @@ mod tests {
.disk(DiskType::RawOperatingSystem)
.unwrap();
let (blk_boot_params, daemon_child) = if self_spawned {
(
format!(
"vhost_user=true,path={},num_queues={},queue_size=128",
disk_path, num_queues,
),
None,
)
} else {
let (blk_boot_params, daemon_child) = {
let prepare_daemon = prepare_vhost_user_blk_daemon.unwrap();
// Start the daemon
let (daemon_child, vubd_socket_path) = prepare_daemon(
@@ -1697,28 +1679,6 @@ mod tests {
// Just check the VM booted correctly.
assert_eq!(guest.get_cpu_count().unwrap_or_default(), num_queues as u32);
assert!(guest.get_total_memory().unwrap_or_default() > 480_000);
if self_spawned {
let reboot_count = guest
.ssh_command("sudo journalctl | grep -c -- \"-- Reboot --\"")
.unwrap_or_default()
.trim()
.parse::<u32>()
.unwrap_or(1);
assert_eq!(reboot_count, 0);
guest.ssh_command("sudo reboot").unwrap_or_default();
guest.wait_vm_boot(None).unwrap();
let reboot_count = guest
.ssh_command("sudo journalctl | grep -c -- \"-- Reboot --\"")
.unwrap_or_default()
.trim()
.parse::<u32>()
.unwrap_or_default();
assert_eq!(reboot_count, 1);
}
});
let _ = child.kill();
let output = child.wait_with_output().unwrap();
@@ -1855,7 +1815,7 @@ mod tests {
let desired_ram = 1024 << 20;
resize_command(&api_socket, None, Some(desired_ram), None);
thread::sleep(std::time::Duration::new(10, 0));
thread::sleep(std::time::Duration::new(30, 0));
assert!(guest.get_total_memory().unwrap_or_default() > 960_000);
// After the resize, check again that file1 exists and its
@@ -2468,6 +2428,8 @@ mod tests {
cmd.args(&["--cpus", "boot=48"])
.args(&["--memory", "size=5120M"])
.args(&["--kernel", guest.fw_path.as_str()])
.args(&["--serial", "tty"])
.args(&["--console", "off"])
.capture_output()
.default_disks()
.default_net();
@@ -2910,8 +2872,7 @@ mod tests {
handle_child_output(r, &output);
}
#[test]
fn test_virtio_blk() {
fn _test_virtio_block(disable_io_uring: bool) {
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
let guest = Guest::new(&mut focal);
@@ -2941,8 +2902,9 @@ mod tests {
)
.as_str(),
format!(
"path={},readonly=on,direct=on,num_queues=4",
blk_file_path.to_str().unwrap()
"path={},readonly=on,direct=on,num_queues=4,_disable_io_uring={}",
blk_file_path.to_str().unwrap(),
disable_io_uring
)
.as_str(),
])
@@ -2994,9 +2956,19 @@ mod tests {
handle_child_output(r, &output);
}
#[test]
fn test_virtio_block() {
_test_virtio_block(false)
}
#[test]
fn test_virtio_block_disable_io_uring() {
_test_virtio_block(false)
}
#[test]
fn test_vhost_user_net_default() {
test_vhost_user_net(None, 2, Some(&prepare_vhost_user_net_daemon), false, false)
test_vhost_user_net(None, 2, Some(&prepare_vhost_user_net_daemon), false)
}
#[test]
@@ -3006,7 +2978,6 @@ mod tests {
2,
Some(&prepare_vhost_user_net_daemon),
false,
false,
)
}
@@ -3017,13 +2988,12 @@ mod tests {
2,
Some(&prepare_vhost_user_net_daemon),
false,
false,
)
}
#[test]
fn test_vhost_user_net_multiple_queues() {
test_vhost_user_net(None, 4, Some(&prepare_vhost_user_net_daemon), false, false)
test_vhost_user_net(None, 4, Some(&prepare_vhost_user_net_daemon), false)
}
#[test]
@@ -3033,60 +3003,37 @@ mod tests {
4,
Some(&prepare_vhost_user_net_daemon),
false,
false,
)
}
#[test]
fn test_vhost_user_net_self_spawning() {
test_vhost_user_net(None, 4, None, true, false)
}
#[test]
fn test_vhost_user_net_self_spawning_host_mac() {
test_vhost_user_net(None, 2, None, true, true)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_vhost_user_net_host_mac() {
test_vhost_user_net(None, 2, None, true, true)
test_vhost_user_net(None, 2, Some(&prepare_vhost_user_net_daemon), true)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_vhost_user_blk_default() {
test_vhost_user_blk(2, false, false, Some(&prepare_vubd), false)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_vhost_user_blk_self_spawning() {
test_vhost_user_blk(1, false, false, None, true)
test_vhost_user_blk(2, false, false, Some(&prepare_vubd))
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_vhost_user_blk_readonly() {
test_vhost_user_blk(1, true, false, Some(&prepare_vubd), false)
test_vhost_user_blk(1, true, false, Some(&prepare_vubd))
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_vhost_user_blk_direct() {
test_vhost_user_blk(1, false, true, Some(&prepare_vubd), false)
test_vhost_user_blk(1, false, true, Some(&prepare_vubd))
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_boot_from_vhost_user_blk_default() {
test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd), false)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_boot_from_vhost_user_blk_self_spawning() {
test_boot_from_vhost_user_blk(1, false, false, None, true)
test_boot_from_vhost_user_blk(1, false, false, Some(&prepare_vubd))
}
#[test]
@@ -3150,24 +3097,24 @@ mod tests {
}
#[test]
fn test_virtio_fs_dax_on_default_cache_size_w_vhost_user_fs_daemon() {
test_virtio_fs(true, None, "none", &prepare_vhost_user_fs_daemon, false)
fn test_virtio_fs_dax_on_default_cache_size_w_virtiofsd_rs_daemon() {
test_virtio_fs(true, None, "none", &prepare_virtofsd_rs_daemon, false)
}
#[test]
fn test_virtio_fs_dax_on_cache_size_1_gib_w_vhost_user_fs_daemon() {
fn test_virtio_fs_dax_on_cache_size_1_gib_w_virtiofsd_rs_daemon() {
test_virtio_fs(
true,
Some(0x4000_0000),
"none",
&prepare_vhost_user_fs_daemon,
&prepare_virtofsd_rs_daemon,
false,
)
}
#[test]
fn test_virtio_fs_dax_off_w_vhost_user_fs_daemon() {
test_virtio_fs(false, None, "none", &prepare_vhost_user_fs_daemon, false)
fn test_virtio_fs_dax_off_w_virtiofsd_rs_daemon() {
test_virtio_fs(false, None, "none", &prepare_virtofsd_rs_daemon, false)
}
#[test]
@@ -3184,14 +3131,14 @@ mod tests {
#[test]
#[cfg(target_arch = "x86_64")]
fn test_virtio_fs_hotplug_dax_on_w_vhost_user_fs_daemon() {
test_virtio_fs(true, None, "none", &prepare_vhost_user_fs_daemon, true)
fn test_virtio_fs_hotplug_dax_on_w_virtiofsd_rs_daemon() {
test_virtio_fs(true, None, "none", &prepare_virtofsd_rs_daemon, true)
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_virtio_fs_hotplug_dax_off_w_vhost_user_fs_daemon() {
test_virtio_fs(false, None, "none", &prepare_vhost_user_fs_daemon, true)
fn test_virtio_fs_hotplug_dax_off_w_virtiofsd_rs_daemon() {
test_virtio_fs(false, None, "none", &prepare_virtofsd_rs_daemon, true)
}
#[test]
@@ -3913,7 +3860,6 @@ mod tests {
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_reboot() {
let mut bionic = UbuntuDiskConfig::new(BIONIC_IMAGE_NAME.to_string());
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
@@ -3926,16 +3872,18 @@ mod tests {
.for_each(|disk_config| {
let guest = Guest::new(*disk_config);
let mut child = GuestCommand::new(&guest)
.args(&["--cpus", "boot=1"])
let mut cmd = GuestCommand::new(&guest);
cmd.args(&["--cpus", "boot=1"])
.args(&["--memory", "size=512M"])
.args(&["--kernel", guest.fw_path.as_str()])
.default_raw_disks()
.default_net()
.args(&["--serial", "tty", "--console", "off"])
.capture_output()
.spawn()
.unwrap();
.capture_output();
#[cfg(target_arch = "aarch64")]
cmd.args(&["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]);
let mut child = cmd.spawn().unwrap();
let r = std::panic::catch_unwind(|| {
guest.wait_vm_boot(Some(120)).unwrap();
@@ -5247,6 +5195,7 @@ mod tests {
// through each ssh command. There's no need to perform a dedicated test to
// verify the migration went well for virtio-net.
#[test]
#[cfg(target_arch = "x86_64")]
fn test_snapshot_restore() {
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
let guest = Guest::new(&mut focal);
@@ -5254,10 +5203,7 @@ mod tests {
workload_path.push("workloads");
let mut kernel_path = workload_path;
#[cfg(target_arch = "x86_64")]
kernel_path.push("bzImage");
#[cfg(target_arch = "aarch64")]
kernel_path.push("Image");
let api_socket = temp_api_path(&guest.tmp_dir);
@@ -5459,7 +5405,6 @@ mod tests {
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_watchdog() {
let mut focal = UbuntuDiskConfig::new(FOCAL_IMAGE_NAME.to_string());
let guest = Guest::new(&mut focal);
@@ -5539,7 +5484,7 @@ mod tests {
guest.ssh_command("screen -dmS reboot sh -c \"sleep 5; echo s | tee /proc/sysrq-trigger; echo c | sudo tee /proc/sysrq-trigger\"").unwrap();
// Allow some time for the watchdog to trigger (max 30s) and reboot to happen
thread::sleep(std::time::Duration::new(50, 0));
guest.wait_vm_boot(Some(50)).unwrap();
// Check that watchdog triggered reboot
let boot_count = guest
@@ -5550,19 +5495,22 @@ mod tests {
.unwrap_or_default();
assert_eq!(boot_count, 3);
// Now pause the VM and remain offline for 30s
assert!(remote_command(&api_socket, "pause", None));
thread::sleep(std::time::Duration::new(30, 0));
assert!(remote_command(&api_socket, "resume", None));
#[cfg(target_arch = "x86_64")]
{
// Now pause the VM and remain offline for 30s
assert!(remote_command(&api_socket, "pause", None));
thread::sleep(std::time::Duration::new(30, 0));
assert!(remote_command(&api_socket, "resume", None));
// Check no reboot
let boot_count = guest
.ssh_command("sudo journalctl --list-boots | wc -l")
.unwrap_or_default()
.trim()
.parse::<u32>()
.unwrap_or_default();
assert_eq!(boot_count, 3);
// Check no reboot
let boot_count = guest
.ssh_command("sudo journalctl --list-boots | wc -l")
.unwrap_or_default()
.trim()
.parse::<u32>()
.unwrap_or_default();
assert_eq!(boot_count, 3);
}
});
let _ = child.kill();
@@ -5581,10 +5529,10 @@ mod tests {
}
}
#[cfg(target_arch = "x86_64")]
mod windows {
use crate::tests::*;
#[cfg(target_arch = "x86_64")]
fn windows_auth() -> PasswordAuth {
PasswordAuth {
username: String::from("administrator"),
@@ -5593,7 +5541,6 @@ mod tests {
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_windows_guest() {
let mut workload_path = dirs::home_dir().unwrap();
workload_path.push("workloads");
@@ -5605,7 +5552,7 @@ mod tests {
osdisk_path.push("windows-server-2019.raw");
let mut child = Command::new(clh_command("cloud-hypervisor"))
.args(&["--cpus", "boot=2,kvm_hyperv=on,max_phys_bits=39"])
.args(&["--cpus", "boot=2,kvm_hyperv=on"])
.args(&["--memory", "size=4G"])
.args(&["--kernel", ovmf_path.to_str().unwrap()])
.args(&["--disk", &format!("path={}", osdisk_path.to_str().unwrap())])
@@ -5645,7 +5592,6 @@ mod tests {
}
#[test]
#[cfg(target_arch = "x86_64")]
fn test_windows_guest_snapshot_restore() {
let tmp_dir = TempDir::new("ch").unwrap();
let mut workload_path = dirs::home_dir().unwrap();
@@ -5661,7 +5607,7 @@ mod tests {
let mut child = Command::new(clh_command("cloud-hypervisor"))
.args(&["--api-socket", &api_socket])
.args(&["--cpus", "boot=2,kvm_hyperv=on,max_phys_bits=39"])
.args(&["--cpus", "boot=2,kvm_hyperv=on"])
.args(&["--memory", "size=4G"])
.args(&["--kernel", ovmf_path.to_str().unwrap()])
.args(&["--disk", &format!("path={}", osdisk_path.to_str().unwrap())])
@@ -5740,11 +5686,11 @@ mod tests {
}
}
#[cfg(target_arch = "x86_64")]
mod sgx {
use crate::tests::*;
#[test]
#[cfg(target_arch = "x86_64")]
fn test_sgx() {
let mut focal = UbuntuDiskConfig::new(FOCAL_SGX_IMAGE_NAME.to_string());
let guest = Guest::new(&mut focal);

View File

@@ -9,10 +9,10 @@ default = []
[dependencies]
epoll = ">=4.0.1"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
virtio-bindings = "0.1.0"
vm-memory = "0.3.0"
vm-memory = "0.4.0"
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1"
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }

View File

@@ -8,12 +8,12 @@ edition = "2018"
block_util = { path = "../block_util" }
clap = { version = "2.33.3", features=["wrap_help"] }
epoll = ">=4.0.1"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
option_parser = { path = "../option_parser" }
qcow = { path = "../qcow" }
vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
virtio-bindings = "0.1.0"
vm-memory = "0.3.0"
vm-memory = "0.4.0"
vmm-sys-util = ">=0.3.1"

View File

@@ -132,7 +132,7 @@ impl VhostUserBlkThread {
match Request::parse(&head, mem) {
Ok(mut request) => {
debug!("element is a valid request");
request.set_writeback(self.writeback.load(Ordering::SeqCst));
request.set_writeback(self.writeback.load(Ordering::Acquire));
let status = match request.execute(
&mut self.disk_image.lock().unwrap().deref_mut(),
self.disk_nsectors,
@@ -273,7 +273,7 @@ impl VhostUserBlkBackend {
"writethrough"
}
);
self.writeback.store(writeback, Ordering::SeqCst);
self.writeback.store(writeback, Ordering::Release);
}
}
@@ -455,7 +455,7 @@ impl VhostUserBlkBackendConfig {
let poll_queue = parser
.convert::<Toggle>("poll_queue")
.map_err(Error::FailedConfigParse)?
.unwrap_or_else(|| Toggle(true))
.unwrap_or(Toggle(true))
.0;
let queue_size = parser
.convert("queue_size")

View File

@@ -1,23 +0,0 @@
[package]
name = "vhost_user_fs"
version = "0.1.0"
authors = ["The Cloud Hypervisor Authors"]
edition = "2018"
[dependencies]
bitflags = "1.1.0"
clap = { version = "2.33.3", features=["wrap_help"] }
epoll = ">=4.0.1"
futures = { version = "0.3.7", features = ["thread-pool"] }
libc = "0.2.80"
log = "0.4.11"
# Match the version in vmm
seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v0.22.0" }
tempdir = "0.3.7"
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
virtio-devices = { path = "../virtio-devices" }
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
vhost_user_backend = { path = "../vhost_user_backend"}
vm-memory = "0.3.0"
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = "0.6.1"

File diff suppressed because it is too large Load Diff

View File

@@ -1,409 +0,0 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs::File;
use std::io::{Error, ErrorKind, Result};
use std::os::unix::io::AsRawFd;
use vm_memory::VolatileSlice;
use libc::{
c_int, c_void, off64_t, pread64, preadv64, pwrite64, pwritev64, read, readv, size_t, write,
writev,
};
/// A trait for setting the size of a file.
/// This is equivalent to File's `set_len` method, but
/// wrapped in a trait so that it can be implemented for
/// other types.
pub trait FileSetLen {
// Set the size of this file.
// This is the moral equivalent of `ftruncate()`.
fn set_len(&self, _len: u64) -> Result<()>;
}
impl FileSetLen for File {
fn set_len(&self, len: u64) -> Result<()> {
File::set_len(self, len)
}
}
/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
pub trait FileReadWriteVolatile {
/// Read bytes from this file into the given slice, returning the number of bytes read on
/// success.
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
/// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
/// buffer in order, with the final buffer written to possibly being only partially filled. This
/// method must behave as a single call to `read_volatile` with the buffers concatenated would.
/// The default implementation calls `read_volatile` with either the first nonempty buffer
/// provided, or returns `Ok(0)` if none exists.
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
bufs.iter()
.find(|b| !b.is_empty())
.map(|&b| self.read_volatile(b))
.unwrap_or(Ok(0))
}
/// Reads bytes from this into the given slice until all bytes in the slice are written, or an
/// error is returned.
fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
while !slice.is_empty() {
let bytes_read = self.read_volatile(slice)?;
if bytes_read == 0 {
return Err(Error::from(ErrorKind::UnexpectedEof));
}
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
// a panic.
slice = slice.offset(bytes_read).unwrap();
}
Ok(())
}
/// Write bytes from the slice to the given file, returning the number of bytes written on
/// success.
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
/// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
/// each buffer in order, with the final buffer read from possibly being only partially
/// consumed. This method must behave as a call to `write_volatile` with the buffers
/// concatenated would. The default implementation calls `write_volatile` with either the first
/// nonempty buffer provided, or returns `Ok(0)` if none exists.
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
bufs.iter()
.find(|b| !b.is_empty())
.map(|&b| self.write_volatile(b))
.unwrap_or(Ok(0))
}
/// Write bytes from the slice to the given file until all the bytes from the slice have been
/// written, or an error is returned.
fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
while !slice.is_empty() {
let bytes_written = self.write_volatile(slice)?;
if bytes_written == 0 {
return Err(Error::from(ErrorKind::WriteZero));
}
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
// a panic.
slice = slice.offset(bytes_written).unwrap();
}
Ok(())
}
}
impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
(**self).read_volatile(slice)
}
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
(**self).read_vectored_volatile(bufs)
}
fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
(**self).read_exact_volatile(slice)
}
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
(**self).write_volatile(slice)
}
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
(**self).write_vectored_volatile(bufs)
}
fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
(**self).write_all_volatile(slice)
}
}
/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
pub trait FileReadWriteAtVolatile {
/// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
/// read on success.
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
/// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
/// buffer in order, with the final buffer written to possibly being only partially filled. This
/// method must behave as a single call to `read_at_volatile` with the buffers concatenated
/// would. The default implementation calls `read_at_volatile` with either the first nonempty
/// buffer provided, or returns `Ok(0)` if none exists.
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
if let Some(&slice) = bufs.first() {
self.read_at_volatile(slice, offset)
} else {
Ok(0)
}
}
/// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
/// read, or an error is returned.
fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
while !slice.is_empty() {
match self.read_at_volatile(slice, offset) {
Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
Ok(n) => {
slice = slice.offset(n).unwrap();
offset = offset.checked_add(n as u64).unwrap();
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
/// Writes bytes from this file at `offset` into the given slice, returning the number of bytes
/// written on success.
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
/// Like `write_at_at_volatile`, except that it writes from a slice of buffers. Data is copied
/// from each buffer in order, with the final buffer read from possibly being only partially
/// consumed. This method must behave as a call to `write_at_volatile` with the buffers
/// concatenated would. The default implementation calls `write_at_volatile` with either the
/// first nonempty buffer provided, or returns `Ok(0)` if none exists.
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
if let Some(&slice) = bufs.first() {
self.write_at_volatile(slice, offset)
} else {
Ok(0)
}
}
/// Writes bytes from this file at `offset` into the given slice until all bytes in the slice
/// are written, or an error is returned.
fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
while !slice.is_empty() {
match self.write_at_volatile(slice, offset) {
Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
Ok(n) => {
slice = slice.offset(n).unwrap();
offset = offset.checked_add(n as u64).unwrap();
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
(**self).read_at_volatile(slice, offset)
}
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
(**self).read_vectored_at_volatile(bufs, offset)
}
fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
(**self).read_exact_at_volatile(slice, offset)
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
(**self).write_at_volatile(slice, offset)
}
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
(**self).write_vectored_at_volatile(bufs, offset)
}
fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
(**self).write_all_at_volatile(slice, offset)
}
}
macro_rules! volatile_impl {
($ty:ty) => {
impl FileReadWriteVolatile for $ty {
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret =
unsafe { read(self.as_raw_fd(), slice.as_ptr() as *mut c_void, slice.len()) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe { readv(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
write(
self.as_raw_fd(),
slice.as_ptr() as *const c_void,
slice.len(),
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe { writev(self.as_raw_fd(), &iovecs[0], iovecs.len() as c_int) };
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
}
impl FileReadWriteAtVolatile for $ty {
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
pread64(
self.as_raw_fd(),
slice.as_ptr() as *mut c_void,
slice.len(),
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn read_vectored_at_volatile(
&mut self,
bufs: &[VolatileSlice],
offset: u64,
) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
preadv64(
self.as_raw_fd(),
&iovecs[0],
iovecs.len() as c_int,
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
pwrite64(
self.as_raw_fd(),
slice.as_ptr() as *const c_void,
slice.len(),
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
fn write_vectored_at_volatile(
&mut self,
bufs: &[VolatileSlice],
offset: u64,
) -> Result<usize> {
let iovecs: Vec<libc::iovec> = bufs
.iter()
.map(|s| libc::iovec {
iov_base: s.as_ptr() as *mut c_void,
iov_len: s.len() as size_t,
})
.collect();
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
pwritev64(
self.as_raw_fd(),
&iovecs[0],
iovecs.len() as c_int,
offset as off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(Error::last_os_error())
}
}
}
};
}
volatile_impl!(File);

File diff suppressed because it is too large Load Diff

View File

@@ -1,62 +0,0 @@
use crate::fuse;
use std::io;
use std::os::unix::io::RawFd;
use vhost_rs::vhost_user::message::{
VhostUserFSSlaveMsg, VhostUserFSSlaveMsgFlags, VHOST_USER_FS_SLAVE_ENTRIES,
};
use vhost_rs::vhost_user::{SlaveFsCacheReq, VhostUserMasterReqHandler};
/// Trait for virtio-fs cache requests operations. This is mainly used to hide
/// vhost-user details from virtio-fs's fuse part.
pub trait FsCacheReqHandler: Send + Sync + 'static {
/// Setup a dedicated mapping so that guest can access file data in DAX style.
fn map(
&mut self,
foffset: u64,
moffset: u64,
len: u64,
flags: u64,
fd: RawFd,
) -> io::Result<()>;
/// Remove those mappings that provide the access to file data.
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()>;
}
impl FsCacheReqHandler for SlaveFsCacheReq {
fn map(
&mut self,
foffset: u64,
moffset: u64,
len: u64,
flags: u64,
fd: RawFd,
) -> io::Result<()> {
let mut msg: VhostUserFSSlaveMsg = Default::default();
msg.fd_offset[0] = foffset;
msg.cache_offset[0] = moffset;
msg.len[0] = len;
msg.flags[0] = if (flags & fuse::SetupmappingFlags::WRITE.bits()) != 0 {
VhostUserFSSlaveMsgFlags::MAP_W | VhostUserFSSlaveMsgFlags::MAP_R
} else {
VhostUserFSSlaveMsgFlags::MAP_R
};
self.fs_slave_map(&msg, fd)?;
Ok(())
}
fn unmap(&mut self, requests: Vec<fuse::RemovemappingOne>) -> io::Result<()> {
for chunk in requests.chunks(VHOST_USER_FS_SLAVE_ENTRIES) {
let mut msg: VhostUserFSSlaveMsg = Default::default();
for (ind, req) in chunk.iter().enumerate() {
msg.len[ind] = req.len;
msg.cache_offset[ind] = req.moffset;
}
self.fs_slave_unmap(&msg)?;
}
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +0,0 @@
// Copyright © 2019 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
#[macro_use]
extern crate log;
pub mod descriptor_utils;
pub mod file_traits;
pub mod filesystem;
pub mod fs_cache_req_handler;
pub mod fuse;
pub mod multikey;
pub mod passthrough;
pub mod sandbox;
pub mod seccomp;
pub mod server;
use std::ffi::FromBytesWithNulError;
use std::{error, fmt, io};
#[derive(Debug)]
pub enum Error {
/// Failed to decode protocol messages.
DecodeMessage(io::Error),
/// Failed to encode protocol messages.
EncodeMessage(io::Error),
/// One or more parameters are missing.
MissingParameter,
/// A C string parameter is invalid.
InvalidCString(FromBytesWithNulError),
/// The `len` field of the header is too small.
InvalidHeaderLength,
/// The `size` field of the `SetxattrIn` message does not match the length
/// of the decoded value.
InvalidXattrSize((u32, usize)),
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match self {
DecodeMessage(err) => write!(f, "failed to decode fuse message: {}", err),
EncodeMessage(err) => write!(f, "failed to encode fuse message: {}", err),
MissingParameter => write!(f, "one or more parameters are missing"),
InvalidHeaderLength => write!(f, "the `len` field of the header is too small"),
InvalidCString(err) => write!(f, "a c string parameter is invalid: {}", err),
InvalidXattrSize((size, len)) => write!(
f,
"The `size` field of the `SetxattrIn` message does not match the length of the\
decoded value: size = {}, value.len() = {}",
size, len
),
}
}
}
pub type Result<T> = ::std::result::Result<T, Error>;

View File

@@ -1,423 +0,0 @@
// Copyright 2019 Intel Corporation. All Rights Reserved.
//
// SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause)
#[macro_use(crate_version, crate_authors)]
extern crate clap;
extern crate log;
extern crate vhost_rs;
extern crate vhost_user_backend;
extern crate virtio_devices;
use clap::{App, Arg};
use futures::executor::{ThreadPool, ThreadPoolBuilder};
use libc::EFD_NONBLOCK;
use log::*;
use seccomp::SeccompAction;
use std::num::Wrapping;
use std::sync::{Arc, Mutex, RwLock};
use std::{convert, error, fmt, io, process};
use vhost_rs::vhost_user::message::*;
use vhost_rs::vhost_user::{Listener, SlaveFsCacheReq};
use vhost_user_backend::{VhostUserBackend, VhostUserDaemon, Vring};
use vhost_user_fs::descriptor_utils::Error as VufDescriptorError;
use vhost_user_fs::descriptor_utils::{Reader, Writer};
use vhost_user_fs::filesystem::FileSystem;
use vhost_user_fs::passthrough::{self, PassthroughFs};
use vhost_user_fs::sandbox::Sandbox;
use vhost_user_fs::seccomp::enable_seccomp;
use vhost_user_fs::server::Server;
use vhost_user_fs::Error as VhostUserFsError;
use virtio_bindings::bindings::virtio_net::*;
use virtio_bindings::bindings::virtio_ring::{
VIRTIO_RING_F_EVENT_IDX, VIRTIO_RING_F_INDIRECT_DESC,
};
use vm_memory::{GuestAddressSpace, GuestMemoryAtomic, GuestMemoryMmap};
use vm_virtio::queue::DescriptorChain;
use vmm_sys_util::eventfd::EventFd;
const QUEUE_SIZE: usize = 1024;
const NUM_QUEUES: usize = 2;
const THREAD_POOL_SIZE: usize = 64;
// The guest queued an available buffer for the high priority queue.
const HIPRIO_QUEUE_EVENT: u16 = 0;
// The guest queued an available buffer for the request queue.
const REQ_QUEUE_EVENT: u16 = 1;
// The device has been dropped.
const KILL_EVENT: u16 = 2;
type Result<T> = std::result::Result<T, Error>;
type VhostUserBackendResult<T> = std::result::Result<T, std::io::Error>;
#[derive(Debug)]
enum Error {
/// Failed to create kill eventfd.
CreateKillEventFd(io::Error),
/// Failed to create thread pool.
CreateThreadPool(io::Error),
/// Failed to handle event other than input event.
HandleEventNotEpollIn,
/// Failed to handle unknown event.
HandleEventUnknownEvent,
/// No memory configured.
NoMemoryConfigured,
/// Processing queue failed.
ProcessQueue(VhostUserFsError),
/// Creating a queue reader failed.
QueueReader(VufDescriptorError),
/// Creating a queue writer failed.
QueueWriter(VufDescriptorError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_fs_error: {:?}", self)
}
}
impl error::Error for Error {}
impl convert::From<Error> for io::Error {
fn from(e: Error) -> Self {
io::Error::new(io::ErrorKind::Other, e)
}
}
struct VhostUserFsThread<F: FileSystem + Send + Sync + 'static> {
mem: Option<GuestMemoryAtomic<GuestMemoryMmap>>,
kill_evt: EventFd,
server: Arc<Server<F>>,
// handle request from slave to master
vu_req: Option<SlaveFsCacheReq>,
event_idx: bool,
pool: ThreadPool,
}
impl<F: FileSystem + Send + Sync + 'static> Clone for VhostUserFsThread<F> {
fn clone(&self) -> Self {
VhostUserFsThread {
mem: self.mem.clone(),
kill_evt: self.kill_evt.try_clone().unwrap(),
server: self.server.clone(),
vu_req: self.vu_req.clone(),
event_idx: self.event_idx,
pool: self.pool.clone(),
}
}
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsThread<F> {
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
Ok(VhostUserFsThread {
mem: None,
kill_evt: EventFd::new(EFD_NONBLOCK).map_err(Error::CreateKillEventFd)?,
server: Arc::new(Server::new(fs)),
vu_req: None,
event_idx: false,
pool: ThreadPoolBuilder::new()
.pool_size(thread_pool_size)
.create()
.map_err(Error::CreateThreadPool)?,
})
}
fn process_queue(&mut self, vring_lock: Arc<RwLock<Vring>>) -> Result<bool> {
let mut used_any = false;
let (atomic_mem, mem) = match &self.mem {
Some(m) => (m, m.memory()),
None => return Err(Error::NoMemoryConfigured),
};
let mut vring = vring_lock.write().unwrap();
while let Some(avail_desc) = vring.mut_queue().iter(&mem).next() {
used_any = true;
// Prepare a set of objects that can be moved to the worker thread.
let desc_head = avail_desc.get_head();
let atomic_mem = atomic_mem.clone();
let server = self.server.clone();
let mut vu_req = self.vu_req.clone();
let event_idx = self.event_idx;
let vring_lock = vring_lock.clone();
self.pool.spawn_ok(async move {
let mem = atomic_mem.memory();
let desc = DescriptorChain::new_from_head(&mem, desc_head).unwrap();
let head_index = desc.index;
let reader = Reader::new(&mem, desc.clone())
.map_err(Error::QueueReader)
.unwrap();
let writer = Writer::new(&mem, desc.clone())
.map_err(Error::QueueWriter)
.unwrap();
server
.handle_message(reader, writer, vu_req.as_mut())
.map_err(Error::ProcessQueue)
.unwrap();
let mut vring = vring_lock.write().unwrap();
if event_idx {
let queue = vring.mut_queue();
if let Some(used_idx) = queue.add_used(&mem, head_index, 0) {
if queue.needs_notification(&mem, Wrapping(used_idx)) {
vring.signal_used_queue().unwrap();
}
}
} else {
vring.mut_queue().add_used(&mem, head_index, 0);
vring.signal_used_queue().unwrap();
}
});
}
Ok(used_any)
}
}
struct VhostUserFsBackend<F: FileSystem + Send + Sync + 'static> {
thread: Mutex<VhostUserFsThread<F>>,
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserFsBackend<F> {
fn new(fs: F, thread_pool_size: usize) -> Result<Self> {
let thread = Mutex::new(VhostUserFsThread::new(fs, thread_pool_size)?);
Ok(VhostUserFsBackend { thread })
}
}
impl<F: FileSystem + Send + Sync + 'static> VhostUserBackend for VhostUserFsBackend<F> {
fn num_queues(&self) -> usize {
NUM_QUEUES
}
fn max_queue_size(&self) -> usize {
QUEUE_SIZE
}
fn features(&self) -> u64 {
1 << VIRTIO_F_VERSION_1
| 1 << VIRTIO_RING_F_INDIRECT_DESC
| 1 << VIRTIO_RING_F_EVENT_IDX
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::SLAVE_REQ
}
fn set_event_idx(&mut self, enabled: bool) {
self.thread.lock().unwrap().event_idx = enabled;
}
fn update_memory(&mut self, mem: GuestMemoryMmap) -> VhostUserBackendResult<()> {
self.thread.lock().unwrap().mem = Some(GuestMemoryAtomic::new(mem));
Ok(())
}
fn handle_event(
&self,
device_event: u16,
evset: epoll::Events,
vrings: &[Arc<RwLock<Vring>>],
_thread_id: usize,
) -> VhostUserBackendResult<bool> {
if evset != epoll::Events::EPOLLIN {
return Err(Error::HandleEventNotEpollIn.into());
}
let mut thread = self.thread.lock().unwrap();
let mem = match &thread.mem {
Some(m) => m.memory(),
None => return Err(Error::NoMemoryConfigured.into()),
};
let vring_lock = match device_event {
HIPRIO_QUEUE_EVENT => {
debug!("HIPRIO_QUEUE_EVENT");
vrings[0].clone()
}
REQ_QUEUE_EVENT => {
debug!("QUEUE_EVENT");
vrings[1].clone()
}
_ => return Err(Error::HandleEventUnknownEvent.into()),
};
if thread.event_idx {
// vm-virtio's Queue implementation only checks avail_index
// once, so to properly support EVENT_IDX we need to keep
// calling process_queue() until it stops finding new
// requests on the queue.
loop {
{
let mut vring = vring_lock.write().unwrap();
vring.mut_queue().update_avail_event(&mem);
}
if !thread.process_queue(vring_lock.clone())? {
break;
}
}
} else {
// Without EVENT_IDX, a single call is enough.
thread.process_queue(vring_lock)?;
}
Ok(false)
}
fn exit_event(&self, _thread_index: usize) -> Option<(EventFd, Option<u16>)> {
Some((
self.thread.lock().unwrap().kill_evt.try_clone().unwrap(),
Some(KILL_EVENT),
))
}
fn set_slave_req_fd(&mut self, vu_req: SlaveFsCacheReq) {
self.thread.lock().unwrap().vu_req = Some(vu_req);
}
}
fn main() {
let cmd_arguments = App::new("vhost-user-fs backend")
.version(crate_version!())
.author(crate_authors!())
.about("Launch a vhost-user-fs backend.")
.arg(
Arg::with_name("shared-dir")
.long("shared-dir")
.help("Shared directory path")
.takes_value(true)
.min_values(1),
)
.arg(
Arg::with_name("sock")
.long("sock")
.help("vhost-user socket path (deprecated)")
.takes_value(true)
.min_values(1),
)
.arg(
Arg::with_name("socket")
.long("socket")
.help("vhost-user socket path")
.takes_value(true)
.min_values(1),
)
.arg(
Arg::with_name("thread-pool-size")
.long("thread-pool-size")
.help("thread pool size (default 64)")
.takes_value(true)
.min_values(1),
)
.arg(
Arg::with_name("disable-xattr")
.long("disable-xattr")
.help("Disable support for extended attributes"),
)
.arg(
Arg::with_name("disable-sandbox")
.long("disable-sandbox")
.help("Don't set up a sandbox for the daemon"),
)
.arg(
Arg::with_name("seccomp")
.long("seccomp")
.help("Disable/debug seccomp security")
.possible_values(&["kill", "log", "trap", "none"])
.default_value("kill"),
)
.get_matches();
// Retrieve arguments
let shared_dir = cmd_arguments
.value_of("shared-dir")
.expect("Failed to retrieve shared directory path");
let socket = match cmd_arguments.value_of("socket") {
Some(path) => path,
None => {
println!("warning: use of deprecated parameter '--sock': Please use the '--socket' option instead.");
cmd_arguments
.value_of("sock")
.expect("Failed to retrieve vhost-user socket path")
}
};
let thread_pool_size: usize = match cmd_arguments.value_of("thread-pool-size") {
Some(size) => size.parse().expect("Invalid argument for thread-pool-size"),
None => THREAD_POOL_SIZE,
};
let xattr: bool = !cmd_arguments.is_present("disable-xattr");
let create_sandbox: bool = !cmd_arguments.is_present("disable-sandbox");
let seccomp_mode: SeccompAction = match cmd_arguments.value_of("seccomp").unwrap() {
"none" => SeccompAction::Allow, // i.e. no seccomp
"kill" => SeccompAction::Kill,
"log" => SeccompAction::Log,
"trap" => SeccompAction::Trap,
_ => unreachable!(), // We told Arg possible_values
};
let listener = Listener::new(socket, true).unwrap();
let fs_cfg = if create_sandbox {
let mut sandbox = Sandbox::new(shared_dir.to_string());
match sandbox.enter().unwrap() {
Some(child_pid) => {
unsafe { libc::waitpid(child_pid, std::ptr::null_mut(), 0) };
return;
}
None => passthrough::Config {
root_dir: "/".to_string(),
xattr,
proc_sfd_rawfd: sandbox.get_proc_self_fd(),
..Default::default()
},
}
} else {
passthrough::Config {
root_dir: shared_dir.to_string(),
xattr,
..Default::default()
}
};
// Must happen before we start the thread pool
if seccomp_mode != SeccompAction::Allow {
enable_seccomp(seccomp_mode).unwrap();
};
let fs = PassthroughFs::new(fs_cfg).unwrap();
let fs_backend = Arc::new(RwLock::new(
VhostUserFsBackend::new(fs, thread_pool_size).unwrap(),
));
let mut daemon =
VhostUserDaemon::new(String::from("vhost-user-fs-backend"), fs_backend.clone()).unwrap();
if let Err(e) = daemon.start(listener) {
error!("Failed to start daemon: {:?}", e);
process::exit(1);
}
if let Err(e) = daemon.wait() {
error!("Waiting for daemon failed: {:?}", e);
}
let kill_evt = fs_backend
.read()
.unwrap()
.thread
.lock()
.unwrap()
.kill_evt
.try_clone()
.unwrap();
if let Err(e) = kill_evt.write(1) {
error!("Error shutting down worker thread: {:?}", e)
}
}

View File

@@ -1,274 +0,0 @@
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::borrow::Borrow;
use std::collections::BTreeMap;
/// A BTreeMap that supports 2 types of keys per value. All the usual restrictions and warnings for
/// `std::collections::BTreeMap` also apply to this struct. Additionally, there is a 1:1
/// relationship between the 2 key types. In other words, for each `K1` in the map, there is exactly
/// one `K2` in the map and vice versa.
#[derive(Default)]
pub struct MultikeyBTreeMap<K1, K2, V>
where
K1: Ord,
K2: Ord,
{
// We need to keep a copy of the second key in the main map so that we can remove entries using
// just the main key. Otherwise we would require the caller to provide both keys when calling
// `remove`.
main: BTreeMap<K1, (K2, V)>,
alt: BTreeMap<K2, K1>,
}
impl<K1, K2, V> MultikeyBTreeMap<K1, K2, V>
where
K1: Clone + Ord,
K2: Clone + Ord,
{
/// Create a new empty MultikeyBTreeMap.
pub fn new() -> Self {
MultikeyBTreeMap {
main: BTreeMap::default(),
alt: BTreeMap::default(),
}
}
/// Returns a reference to the value corresponding to the key.
///
/// The key may be any borrowed form of `K1``, but the ordering on the borrowed form must match
/// the ordering on `K1`.
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K1: Borrow<Q>,
Q: Ord + ?Sized,
{
self.main.get(key).map(|(_, v)| v)
}
/// Returns a reference to the value corresponding to the alternate key.
///
/// The key may be any borrowed form of the `K2``, but the ordering on the borrowed form must
/// match the ordering on `K2`.
///
/// Note that this method performs 2 lookups: one to get the main key and another to get the
/// value associated with that key. For best performance callers should prefer the `get` method
/// over this method whenever possible as `get` only needs to perform one lookup.
pub fn get_alt<Q2>(&self, key: &Q2) -> Option<&V>
where
K2: Borrow<Q2>,
Q2: Ord + ?Sized,
{
if let Some(k) = self.alt.get(key) {
self.get(k)
} else {
None
}
}
/// Inserts a new entry into the map with the given keys and value.
///
/// Returns `None` if the map did not have an entry with `k1` or `k2` present. If exactly one
/// key was present, then the value associated with that key is updated, the other key is
/// removed, and the old value is returned. If **both** keys were present then the value
/// associated with the main key is updated, the value associated with the alternate key is
/// removed, and the old value associated with the main key is returned.
pub fn insert(&mut self, k1: K1, k2: K2, v: V) -> Option<V> {
let oldval = if let Some(oldkey) = self.alt.insert(k2.clone(), k1.clone()) {
self.main.remove(&oldkey)
} else {
None
};
self.main
.insert(k1, (k2.clone(), v))
.or(oldval)
.map(|(oldk2, v)| {
if oldk2 != k2 {
self.alt.remove(&oldk2);
}
v
})
}
/// Remove a key from the map, returning the value associated with that key if it was previously
/// in the map.
///
/// The key may be any borrowed form of `K1``, but the ordering on the borrowed form must match
/// the ordering on `K1`.
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K1: Borrow<Q>,
Q: Ord + ?Sized,
{
self.main.remove(key).map(|(k2, v)| {
self.alt.remove(&k2);
v
})
}
/// Clears the map, removing all values.
pub fn clear(&mut self) {
self.alt.clear();
self.main.clear()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn get() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val);
}
#[test]
fn update_main_key() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k1 = 0x3add_f8f8_c7c5_df5e;
let val2 = 0x7389_f8a7;
assert_eq!(
m.insert(new_k1, k2, val2)
.expect("failed to update main key"),
val
);
assert!(m.get(&k1).is_none());
assert_eq!(*m.get(&new_k1).expect("failed to look up main key"), val2);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val2);
}
#[test]
fn update_alt_key() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k2 = 0x6825_a60b_61ac_b333;
let val2 = 0xbb14_8f2c;
assert_eq!(
m.insert(k1, new_k2, val2)
.expect("failed to update alt key"),
val
);
assert!(m.get_alt(&k2).is_none());
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val2);
assert_eq!(
*m.get_alt(&new_k2).expect("failed to look up alt key"),
val2
);
}
#[test]
fn update_value() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let val2 = 0xe42d_79ba;
assert_eq!(
m.insert(k1, k2, val2).expect("failed to update alt key"),
val
);
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val2);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val2);
}
#[test]
fn update_both_keys_main() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k1 = 0xc980_587a_24b3_ae30;
let new_k2 = 0x2773_c5ee_8239_45a2;
let val2 = 0x31f4_33f9;
assert!(m.insert(new_k1, new_k2, val2).is_none());
let val3 = 0x8da1_9cf7;
assert_eq!(
m.insert(k1, new_k2, val3)
.expect("failed to update main key"),
val
);
// Both new_k1 and k2 should now be gone from the map.
assert!(m.get(&new_k1).is_none());
assert!(m.get_alt(&k2).is_none());
assert_eq!(*m.get(&k1).expect("failed to look up main key"), val3);
assert_eq!(
*m.get_alt(&new_k2).expect("failed to look up alt key"),
val3
);
}
#[test]
fn update_both_keys_alt() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
let new_k1 = 0xc980_587a_24b3_ae30;
let new_k2 = 0x2773_c5ee_8239_45a2;
let val2 = 0x31f4_33f9;
assert!(m.insert(new_k1, new_k2, val2).is_none());
let val3 = 0x8da1_9cf7;
assert_eq!(
m.insert(new_k1, k2, val3)
.expect("failed to update main key"),
val2
);
// Both k1 and new_k2 should now be gone from the map.
assert!(m.get(&k1).is_none());
assert!(m.get_alt(&new_k2).is_none());
assert_eq!(*m.get(&new_k1).expect("failed to look up main key"), val3);
assert_eq!(*m.get_alt(&k2).expect("failed to look up alt key"), val3);
}
#[test]
fn remove() {
let mut m = MultikeyBTreeMap::<u64, i64, u32>::new();
let k1 = 0xc6c8_f5e0_b13e_ed40;
let k2 = 0x1a04_ce4b_8329_14fe;
let val = 0xf4e3_c360;
assert!(m.insert(k1, k2, val).is_none());
assert_eq!(m.remove(&k1).expect("failed to remove entry"), val);
assert!(m.get(&k1).is_none());
assert!(m.get_alt(&k2).is_none());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,319 +0,0 @@
// Copyright 2020 Red Hat, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::ffi::CString;
use std::os::unix::io::RawFd;
use std::{fmt, fs, io};
use tempdir::TempDir;
#[derive(Debug)]
pub enum Error {
/// Failed to bind mount `/proc/self/fd` into a temporary directory.
BindMountProcSelfFd(io::Error),
/// Failed to bind mount shared directory.
BindMountSharedDir(io::Error),
/// Failed to change to the old root directory.
ChdirOldRoot(io::Error),
/// Failed to change to the new root directory.
ChdirNewRoot(io::Error),
/// Failed to clean the properties of the mount point.
CleanMount(io::Error),
/// Failed to create a temporary directory.
CreateTempDir(io::Error),
/// Call to libc::fork returned an error.
Fork(io::Error),
/// Error bind-mounting a directory.
MountBind(io::Error),
/// Failed to mount old root.
MountOldRoot(io::Error),
/// Error mounting proc.
MountProc(io::Error),
/// Failed to mount new root.
MountNewRoot(io::Error),
/// Error mounting target directory.
MountTarget(io::Error),
/// Failed to open new root.
OpenNewRoot(io::Error),
/// Failed to open old root.
OpenOldRoot(io::Error),
/// Failed to open `/proc/self/fd`.
OpenProcSelfFd(io::Error),
/// Error switching root directory.
PivotRoot(io::Error),
/// Failed to remove temporary directory.
RmdirTempDir(io::Error),
/// Failed to lazily unmount old root.
UmountOldRoot(io::Error),
/// Failed to lazily unmount temporary directory.
UmountTempDir(io::Error),
/// Call to libc::unshare returned an error.
Unshare(io::Error),
/// Failed to read from procfs.
ReadProc(io::Error),
/// Failed to parse `/proc/sys/fs/nr_open`.
InvalidNrOpen(std::num::ParseIntError),
/// Failed to set rlimit.
SetRlimit(io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_fs_sandbox_error: {:?}", self)
}
}
/// A helper for creating a sandbox for isolating the service.
pub struct Sandbox {
/// The directory that is going to be shared with the VM. The sandbox will be constructed on top
/// of this directory.
shared_dir: String,
/// A file descriptor for `/proc/self/fd` obtained from the sandboxed context.
proc_self_fd: Option<RawFd>,
}
impl Sandbox {
pub fn new(shared_dir: String) -> Self {
Sandbox {
shared_dir,
proc_self_fd: None,
}
}
// Make `self.shared_dir` our root directory, and get an isolated file descriptor for
// `/proc/self/fd`.
//
// This is based on virtiofsd's setup_namespaces() and setup_mounts(), and it's very similar to
// the strategy used in containers. Consists on a careful sequence of mounts and bind-mounts to
// ensure it's not possible to escape the sandbox through `self.shared_dir` nor the file
// descriptor obtained for `/proc/self/fd`.
//
// It's ugly, but it's the only way until Linux implements a proper containerization API.
fn setup_mounts(&mut self) -> Result<(), Error> {
// Ensure our mount changes don't affect the parent mount namespace.
let c_root_dir = CString::new("/").unwrap();
let ret = unsafe {
libc::mount(
std::ptr::null(),
c_root_dir.as_ptr(),
std::ptr::null(),
libc::MS_SLAVE | libc::MS_REC,
std::ptr::null(),
)
};
if ret != 0 {
return Err(Error::CleanMount(std::io::Error::last_os_error()));
}
// Mount `/proc` in this context.
let c_proc_dir = CString::new("/proc").unwrap();
let c_proc_fs = CString::new("proc").unwrap();
let ret = unsafe {
libc::mount(
c_proc_fs.as_ptr(),
c_proc_dir.as_ptr(),
c_proc_fs.as_ptr(),
libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID | libc::MS_RELATIME,
std::ptr::null(),
)
};
if ret != 0 {
return Err(Error::MountProc(std::io::Error::last_os_error()));
}
// Bind-mount `/proc/self/fd` info a temporary directory, preventing access to ancestor
// directories.
let c_proc_self_fd = CString::new("/proc/self/fd").unwrap();
let tmp_dir = TempDir::new("vhostuserfs-")
.map_err(|_| Error::CreateTempDir(std::io::Error::last_os_error()))?;
let c_tmp_dir = CString::new(tmp_dir.into_path().to_str().unwrap()).unwrap();
let ret = unsafe {
libc::mount(
c_proc_self_fd.as_ptr(),
c_tmp_dir.as_ptr(),
std::ptr::null(),
libc::MS_BIND,
std::ptr::null(),
)
};
if ret < 0 {
return Err(Error::BindMountProcSelfFd(std::io::Error::last_os_error()));
}
// Obtain a file descriptor for `/proc/self/fd` through the bind-mounted temporary directory.
let proc_self_fd = unsafe { libc::open(c_tmp_dir.as_ptr(), libc::O_PATH) };
if proc_self_fd < 0 {
return Err(Error::OpenProcSelfFd(std::io::Error::last_os_error()));
}
self.proc_self_fd = Some(proc_self_fd);
// Now that we have a file descriptor for `/proc/self/fd`, we no longer need the bind-mount.
// Unmount it and remove the temporary directory.
let ret = unsafe { libc::umount2(c_tmp_dir.as_ptr(), libc::MNT_DETACH) };
if ret < 0 {
return Err(Error::UmountTempDir(std::io::Error::last_os_error()));
}
let ret = unsafe { libc::rmdir(c_tmp_dir.as_ptr()) };
if ret < 0 {
return Err(Error::RmdirTempDir(std::io::Error::last_os_error()));
}
// Bind-mount `self.shared_dir` on itself so we can use as new root on `pivot_root` syscall.
let c_shared_dir = CString::new(self.shared_dir.clone()).unwrap();
let ret = unsafe {
libc::mount(
c_shared_dir.as_ptr(),
c_shared_dir.as_ptr(),
std::ptr::null(),
libc::MS_BIND | libc::MS_REC,
std::ptr::null(),
)
};
if ret < 0 {
return Err(Error::BindMountSharedDir(std::io::Error::last_os_error()));
}
// Get a file descriptor to our old root so we can reference it after switching root.
let oldroot_fd = unsafe {
libc::open(
c_root_dir.as_ptr(),
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if oldroot_fd < 0 {
return Err(Error::OpenOldRoot(std::io::Error::last_os_error()));
}
// Get a file descriptor to the new root so we can reference it after switching root.
let newroot_fd = unsafe {
libc::open(
c_shared_dir.as_ptr(),
libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
)
};
if newroot_fd < 0 {
return Err(Error::OpenNewRoot(std::io::Error::last_os_error()));
}
// Change to new root directory to prepare for `pivot_root` syscall.
let ret = unsafe { libc::fchdir(newroot_fd) };
if ret < 0 {
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
}
// Call to `pivot_root` using `.` as both new and old root.
let c_current_dir = CString::new(".").unwrap();
let ret = unsafe {
libc::syscall(
libc::SYS_pivot_root,
c_current_dir.as_ptr(),
c_current_dir.as_ptr(),
)
};
if ret < 0 {
return Err(Error::PivotRoot(std::io::Error::last_os_error()));
}
// Change to old root directory to prepare for cleaning up and unmounting it.
let ret = unsafe { libc::fchdir(oldroot_fd) };
if ret < 0 {
return Err(Error::ChdirOldRoot(std::io::Error::last_os_error()));
}
// Clean up old root to avoid mount namespace propagation.
let c_empty = CString::new("").unwrap();
let ret = unsafe {
libc::mount(
c_empty.as_ptr(),
c_current_dir.as_ptr(),
c_empty.as_ptr(),
libc::MS_SLAVE | libc::MS_REC,
std::ptr::null(),
)
};
if ret != 0 {
return Err(Error::CleanMount(std::io::Error::last_os_error()));
}
// Lazily unmount old root.
let ret = unsafe { libc::umount2(c_current_dir.as_ptr(), libc::MNT_DETACH) };
if ret < 0 {
return Err(Error::UmountOldRoot(std::io::Error::last_os_error()));
}
// Change to new root.
let ret = unsafe { libc::fchdir(newroot_fd) };
if ret < 0 {
return Err(Error::ChdirNewRoot(std::io::Error::last_os_error()));
}
// We no longer need these file descriptors, so close them.
unsafe { libc::close(newroot_fd) };
unsafe { libc::close(oldroot_fd) };
Ok(())
}
/// Sets the limit of open files to the max possible.
fn setup_nofile_rlimit(&self) -> Result<(), Error> {
// /proc/sys/fs/nr_open is a sysctl file that shows the maximum number
// of file-handles a process can allocate.
let path = "/proc/sys/fs/nr_open";
let max_str = fs::read_to_string(path).map_err(Error::ReadProc)?;
let max = max_str.trim().parse().map_err(Error::InvalidNrOpen)?;
let limit = libc::rlimit {
rlim_cur: max,
rlim_max: max,
};
let ret = unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &limit) };
if ret < 0 {
Err(Error::SetRlimit(std::io::Error::last_os_error()))
} else {
Ok(())
}
}
/// Set up sandbox, fork and jump into it.
///
/// On success, the returned value will be the PID of the child for the parent and `None` for
/// the child itself, with the latter running isolated in `self.shared_dir`.
pub fn enter(&mut self) -> Result<Option<i32>, Error> {
let uid = unsafe { libc::geteuid() };
let flags = if uid == 0 {
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET
} else {
// If running as an unprivileged user, rely on user_namespaces(7) for isolation. The
// main limitation of this strategy is that only the current uid/gid are mapped into
// the new namespace, so most operations on permissions will fail.
libc::CLONE_NEWPID | libc::CLONE_NEWNS | libc::CLONE_NEWNET | libc::CLONE_NEWUSER
};
let ret = unsafe { libc::unshare(flags) };
if ret != 0 {
return Err(Error::Unshare(std::io::Error::last_os_error()));
}
let child = unsafe { libc::fork() };
match child {
0 => {
// This is the child. Request to receive SIGTERM on parent's death.
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
self.setup_nofile_rlimit()?;
self.setup_mounts()?;
Ok(None)
}
x if x > 0 => {
// This is the parent.
Ok(Some(child))
}
_ => Err(Error::Fork(std::io::Error::last_os_error())),
}
}
pub fn get_proc_self_fd(&self) -> Option<RawFd> {
self.proc_self_fd
}
}

View File

@@ -1,141 +0,0 @@
// Copyright 2020 Red Hat, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use seccomp::{allow_syscall, BpfProgram, SeccompAction, SeccompFilter};
use std::convert::TryInto;
use std::{convert, fmt};
#[derive(Debug)]
pub enum Error {
/// Cannot create seccomp filter
CreateSeccompFilter(seccomp::SeccompError),
/// Cannot apply seccomp filter
ApplySeccompFilter(seccomp::Error),
}
impl convert::From<seccomp::Error> for Error {
fn from(e: seccomp::Error) -> Self {
Error::ApplySeccompFilter(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "vhost_user_fs_seccomp_error: {:?}", self)
}
}
fn vuf_filter(action: SeccompAction) -> Result<SeccompFilter, Error> {
Ok(SeccompFilter::new(
vec![
allow_syscall(libc::SYS_accept4),
allow_syscall(libc::SYS_brk),
allow_syscall(libc::SYS_capget), // For CAP_FSETID
allow_syscall(libc::SYS_capset),
allow_syscall(libc::SYS_clock_gettime),
allow_syscall(libc::SYS_clone),
allow_syscall(libc::SYS_close),
allow_syscall(libc::SYS_copy_file_range),
allow_syscall(libc::SYS_dup),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_epoll_create),
allow_syscall(libc::SYS_epoll_create1),
allow_syscall(libc::SYS_epoll_ctl),
allow_syscall(libc::SYS_epoll_pwait),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_epoll_wait),
allow_syscall(libc::SYS_eventfd2),
allow_syscall(libc::SYS_exit),
allow_syscall(libc::SYS_exit_group),
allow_syscall(libc::SYS_fallocate),
allow_syscall(libc::SYS_fchdir),
allow_syscall(libc::SYS_fchmod),
allow_syscall(libc::SYS_fchmodat),
allow_syscall(libc::SYS_fchownat),
allow_syscall(libc::SYS_fcntl),
allow_syscall(libc::SYS_fdatasync),
allow_syscall(libc::SYS_fgetxattr),
allow_syscall(libc::SYS_flistxattr),
allow_syscall(libc::SYS_flock),
allow_syscall(libc::SYS_fremovexattr),
allow_syscall(libc::SYS_fsetxattr),
allow_syscall(libc::SYS_fstat),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_fstatfs),
allow_syscall(libc::SYS_fsync),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_ftruncate),
allow_syscall(libc::SYS_futex),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_getdents),
allow_syscall(libc::SYS_getdents64),
allow_syscall(libc::SYS_getegid),
allow_syscall(libc::SYS_geteuid),
allow_syscall(libc::SYS_getpid),
allow_syscall(libc::SYS_gettid),
allow_syscall(libc::SYS_gettimeofday),
allow_syscall(libc::SYS_getxattr),
allow_syscall(libc::SYS_linkat),
allow_syscall(libc::SYS_listxattr),
allow_syscall(libc::SYS_lseek),
allow_syscall(libc::SYS_madvise),
allow_syscall(libc::SYS_mkdirat),
allow_syscall(libc::SYS_mknodat),
allow_syscall(libc::SYS_mmap),
allow_syscall(libc::SYS_mprotect),
allow_syscall(libc::SYS_mremap),
allow_syscall(libc::SYS_munmap),
allow_syscall(libc::SYS_newfstatat),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_open),
allow_syscall(libc::SYS_openat),
allow_syscall(libc::SYS_prctl), // TODO restrict to just PR_SET_NAME?
allow_syscall(libc::SYS_preadv),
allow_syscall(libc::SYS_pread64),
allow_syscall(libc::SYS_pwritev),
allow_syscall(libc::SYS_pwrite64),
allow_syscall(libc::SYS_read),
allow_syscall(libc::SYS_readlinkat),
allow_syscall(libc::SYS_recvmsg),
allow_syscall(libc::SYS_renameat),
allow_syscall(libc::SYS_renameat2),
allow_syscall(libc::SYS_removexattr),
allow_syscall(libc::SYS_rt_sigaction),
allow_syscall(libc::SYS_rt_sigprocmask),
allow_syscall(libc::SYS_rt_sigreturn),
allow_syscall(libc::SYS_sched_getaffinity), // used by thread_pool
allow_syscall(libc::SYS_sendmsg),
allow_syscall(libc::SYS_setresgid),
allow_syscall(libc::SYS_setresuid),
//allow_syscall(libc::SYS_setresgid32), Needed on some platforms,
//allow_syscall(libc::SYS_setresuid32), Needed on some platforms
allow_syscall(libc::SYS_set_robust_list),
allow_syscall(libc::SYS_setxattr),
allow_syscall(libc::SYS_sigaltstack),
allow_syscall(libc::SYS_statx),
allow_syscall(libc::SYS_symlinkat),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_time), // Rarely needed, except on static builds
allow_syscall(libc::SYS_tgkill),
allow_syscall(libc::SYS_umask),
#[cfg(target_arch = "x86_64")]
allow_syscall(libc::SYS_unlink),
allow_syscall(libc::SYS_unlinkat),
allow_syscall(libc::SYS_unshare),
allow_syscall(libc::SYS_utimensat),
allow_syscall(libc::SYS_write),
allow_syscall(libc::SYS_writev),
]
.into_iter()
.collect(),
action,
)?)
}
pub fn enable_seccomp(action: SeccompAction) -> Result<(), Error> {
let scfilter = vuf_filter(action)?;
let bpfprog: BpfProgram = scfilter.try_into()?;
SeccompFilter::apply(bpfprog).map_err(Error::ApplySeccompFilter)
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,12 @@ edition = "2018"
[dependencies]
clap = { version = "2.33.3", features=["wrap_help"] }
epoll = ">=4.0.1"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
net_util = { path = "../net_util" }
option_parser = { path = "../option_parser" }
vhost_user_backend = { path = "../vhost_user_backend" }
vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", package = "vhost", features = ["vhost-user-slave"] }
virtio-bindings = "0.1.0"
vm-memory = "0.3.0"
vm-memory = "0.4.0"
vmm-sys-util = ">=0.3.1"

View File

@@ -10,12 +10,12 @@ io_uring = ["block_util/io_uring"]
[dependencies]
anyhow = "1.0"
arc-swap = ">=0.4.4"
arc-swap = ">=1.0.0"
block_util = { path = "../block_util" }
byteorder = "1.3.4"
epoll = ">=4.0.1"
io-uring = ">=0.4.0"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
net_gen = { path = "../net_gen" }
net_util = { path = "../net_util" }
@@ -30,7 +30,7 @@ vhost_rs = { git = "https://github.com/rust-vmm/vhost", branch = "master", packa
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
vm-migration = { path = "../vm-migration" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = ">=0.3.1"

View File

@@ -98,7 +98,7 @@ struct VirtioBalloonResizeReceiver {
impl VirtioBalloonResizeReceiver {
fn get_size(&self) -> u64 {
self.size.load(Ordering::SeqCst)
self.size.load(Ordering::Acquire)
}
fn send(&self, r: Result<(), Error>) -> Result<(), mpsc::SendError<Result<(), Error>>> {
@@ -134,7 +134,7 @@ impl VirtioBalloonResize {
}
pub fn work(&self, size: u64) -> Result<(), Error> {
self.size.store(size, Ordering::SeqCst);
self.size.store(size, Ordering::Release);
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
self.rx.recv().map_err(Error::MpscRecvFail)?
}

View File

@@ -108,7 +108,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
let len;
match Request::parse(&avail_desc, &mem) {
Ok(mut request) => {
request.set_writeback(self.writeback.load(Ordering::SeqCst));
request.set_writeback(self.writeback.load(Ordering::Acquire));
let mut disk_image_locked = self.disk_image.lock().unwrap();
let mut disk_image = disk_image_locked.deref_mut();
@@ -387,7 +387,7 @@ impl<T: DiskFile> Block<T> {
"writethrough"
}
);
self.writeback.store(writeback, Ordering::SeqCst);
self.writeback.store(writeback, Ordering::Release);
}
}

View File

@@ -116,7 +116,7 @@ impl BlockIoUringEpollHandler {
for avail_desc in queue.iter(&mem) {
let mut request = Request::parse(&avail_desc, &mem).map_err(Error::RequestParsing)?;
request.set_writeback(self.writeback.load(Ordering::SeqCst));
request.set_writeback(self.writeback.load(Ordering::Acquire));
if request
.execute_io_uring(
&mem,
@@ -428,7 +428,7 @@ impl BlockIoUring {
"writethrough"
}
);
self.writeback.store(writeback, Ordering::SeqCst);
self.writeback.store(writeback, Ordering::Release);
}
}

View File

@@ -270,7 +270,7 @@ impl ConsoleInput {
pub fn update_console_size(&self, cols: u16, rows: u16) {
if self
.acked_features
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::SeqCst)
.fetch_and(1u64 << VIRTIO_CONSOLE_F_SIZE, Ordering::AcqRel)
!= 0
{
self.config.lock().unwrap().update_console_size(cols, rows);

View File

@@ -284,7 +284,7 @@ impl Resize {
pub fn work(&self, size: u64) -> Result<(), Error> {
if let Some(rx) = &self.rx {
self.size.store(size, Ordering::SeqCst);
self.size.store(size, Ordering::Release);
self.evt.write(1).map_err(Error::EventFdWriteFail)?;
rx.recv().map_err(Error::MpscRecvFail)?
} else {
@@ -293,7 +293,7 @@ impl Resize {
}
fn get_size(&self) -> u64 {
self.size.load(Ordering::SeqCst)
self.size.load(Ordering::Acquire)
}
fn send(&self, r: Result<(), Error>) -> Result<(), mpsc::SendError<Result<(), Error>>> {

View File

@@ -64,7 +64,7 @@ impl VirtioPciCommonConfig {
device_feature_select: self.device_feature_select,
driver_feature_select: self.driver_feature_select,
queue_select: self.queue_select,
msix_config: self.msix_config.load(Ordering::SeqCst),
msix_config: self.msix_config.load(Ordering::Acquire),
}
}
@@ -74,7 +74,7 @@ impl VirtioPciCommonConfig {
self.device_feature_select = state.device_feature_select;
self.driver_feature_select = state.driver_feature_select;
self.queue_select = state.queue_select;
self.msix_config.store(state.msix_config, Ordering::SeqCst);
self.msix_config.store(state.msix_config, Ordering::Release);
}
pub fn read(
@@ -153,7 +153,7 @@ impl VirtioPciCommonConfig {
fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 {
debug!("read_common_config_word: offset 0x{:x}", offset);
match offset {
0x10 => self.msix_config.load(Ordering::SeqCst),
0x10 => self.msix_config.load(Ordering::Acquire),
0x12 => queues.len() as u16, // num_queues
0x16 => self.queue_select,
0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
@@ -176,7 +176,7 @@ impl VirtioPciCommonConfig {
fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
debug!("write_common_config_word: offset 0x{:x}", offset);
match offset {
0x10 => self.msix_config.store(value, Ordering::SeqCst),
0x10 => self.msix_config.store(value, Ordering::Release),
0x16 => self.queue_select = value,
0x18 => self.with_queue_mut(queues, |q| q.size = value),
0x1a => self.with_queue_mut(queues, |q| q.vector = value),

View File

@@ -448,7 +448,7 @@ impl VirtioPciDevice {
fn state(&self) -> VirtioPciDeviceState {
VirtioPciDeviceState {
device_activated: self.device_activated,
interrupt_status: self.interrupt_status.load(Ordering::SeqCst),
interrupt_status: self.interrupt_status.load(Ordering::Acquire),
queues: self.queues.clone(),
}
}
@@ -456,7 +456,7 @@ impl VirtioPciDevice {
fn set_state(&mut self, state: &VirtioPciDeviceState) -> std::result::Result<(), Error> {
self.device_activated = state.device_activated;
self.interrupt_status
.store(state.interrupt_status, Ordering::SeqCst);
.store(state.interrupt_status, Ordering::Release);
// Update virtqueues indexes for both available and used rings.
if let Some(mem) = self.memory.as_ref() {
@@ -685,7 +685,7 @@ impl VirtioInterrupt for VirtioInterruptMsix {
queue: Option<&Queue>,
) -> std::result::Result<(), std::io::Error> {
let vector = match int_type {
VirtioInterruptType::Config => self.config_vector.load(Ordering::SeqCst),
VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire),
VirtioInterruptType::Queue => {
if let Some(q) = queue {
q.vector
@@ -717,7 +717,7 @@ impl VirtioInterrupt for VirtioInterruptMsix {
fn notifier(&self, int_type: &VirtioInterruptType, queue: Option<&Queue>) -> Option<&EventFd> {
let vector = match int_type {
VirtioInterruptType::Config => self.config_vector.load(Ordering::SeqCst),
VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire),
VirtioInterruptType::Queue => {
if let Some(q) = queue {
q.vector
@@ -891,7 +891,7 @@ impl PciDevice for VirtioPciDevice {
o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
if let Some(v) = data.get_mut(0) {
// Reading this register resets it to 0.
*v = self.interrupt_status.swap(0, Ordering::SeqCst) as u8;
*v = self.interrupt_status.swap(0, Ordering::AcqRel) as u8;
}
}
o if DEVICE_CONFIG_BAR_OFFSET <= o
@@ -936,7 +936,7 @@ impl PciDevice for VirtioPciDevice {
o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
if let Some(v) = data.get(0) {
self.interrupt_status
.fetch_and(!(*v as usize), Ordering::SeqCst);
.fetch_and(!(*v as usize), Ordering::AcqRel);
}
}
o if DEVICE_CONFIG_BAR_OFFSET <= o

View File

@@ -77,15 +77,15 @@ pub fn setup_vhost_user_vring(
queue.desc_table,
actual_size * std::mem::size_of::<Descriptor>(),
)
.ok_or_else(|| Error::DescriptorTableAddress)? as u64,
.ok_or(Error::DescriptorTableAddress)? as u64,
// The used ring is {flags: u16; idx: u16; virtq_used_elem [{id: u16, len: u16}; actual_size]},
// i.e. 4 + (4 + 4) * actual_size.
used_ring_addr: get_host_address_range(mem, queue.used_ring, 4 + actual_size * 8)
.ok_or_else(|| Error::UsedAddress)? as u64,
.ok_or(Error::UsedAddress)? as u64,
// The used ring is {flags: u16; idx: u16; elem [u16; actual_size]},
// i.e. 4 + (2) * actual_size.
avail_ring_addr: get_host_address_range(mem, queue.avail_ring, 4 + actual_size * 2)
.ok_or_else(|| Error::AvailAddress)? as u64,
.ok_or(Error::AvailAddress)? as u64,
log_addr: None,
};

View File

@@ -118,7 +118,7 @@ impl VsockPacket {
let mut pkt = Self {
hdr: get_host_address_range(head.mem, head.addr, VSOCK_PKT_HDR_SIZE)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
.ok_or(VsockError::GuestMemory)? as *mut u8,
buf: None,
buf_size: 0,
};
@@ -151,7 +151,7 @@ impl VsockPacket {
pkt.buf_size = buf_desc.len as usize;
pkt.buf = Some(
get_host_address_range(buf_desc.mem, buf_desc.addr, pkt.buf_size)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
.ok_or(VsockError::GuestMemory)? as *mut u8,
);
Ok(pkt)
@@ -183,10 +183,10 @@ impl VsockPacket {
Ok(Self {
hdr: get_host_address_range(head.mem, head.addr, VSOCK_PKT_HDR_SIZE)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
.ok_or(VsockError::GuestMemory)? as *mut u8,
buf: Some(
get_host_address_range(buf_desc.mem, buf_desc.addr, buf_size)
.ok_or_else(|| VsockError::GuestMemory)? as *mut u8,
.ok_or(VsockError::GuestMemory)? as *mut u8,
),
buf_size,
})

View File

@@ -5,6 +5,6 @@ authors = ["The Chromium OS Authors"]
edition = "2018"
[dependencies]
libc = "0.2.80"
vm-memory = "0.3.0"
libc = "0.2.81"
vm-memory = "0.4.0"
arch = { path = "../arch" }

View File

@@ -10,6 +10,6 @@ thiserror = "1.0"
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
vm-memory = { version = "0.3.0", features = ["backend-mmap"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap"] }
vmm-sys-util = ">=0.3.1"

View File

@@ -112,7 +112,7 @@ impl Bus {
.range(..=BusRange { base: addr, len: 1 })
.rev()
.next()?;
Some((*range, dev.upgrade().unwrap().clone()))
dev.upgrade().map(|d| (*range, d.clone()))
}
#[allow(clippy::type_complexity)]
@@ -282,10 +282,11 @@ mod tests {
assert!(bus.insert(dummy.clone(), 0x0, 0x20).is_err());
assert!(bus.insert(dummy.clone(), 0x20, 0x05).is_ok());
assert!(bus.insert(dummy.clone(), 0x25, 0x05).is_ok());
assert!(bus.insert(dummy.clone(), 0x0, 0x10).is_ok());
assert!(bus.insert(dummy, 0x0, 0x10).is_ok());
}
#[test]
#[allow(clippy::redundant_clone)]
fn bus_read_write() {
let bus = Bus::new();
let dummy = Arc::new(Mutex::new(DummyDevice));
@@ -297,12 +298,13 @@ mod tests {
assert!(bus.read(0x16, &mut [0, 0, 0, 0]).is_ok());
assert!(bus.write(0x16, &[0, 0, 0, 0]).is_ok());
assert!(bus.read(0x20, &mut [0, 0, 0, 0]).is_err());
assert!(bus.write(0x20, &mut [0, 0, 0, 0]).is_err());
assert!(bus.write(0x20, &[0, 0, 0, 0]).is_err());
assert!(bus.read(0x06, &mut [0, 0, 0, 0]).is_err());
assert!(bus.write(0x06, &mut [0, 0, 0, 0]).is_err());
assert!(bus.write(0x06, &[0, 0, 0, 0]).is_err());
}
#[test]
#[allow(clippy::redundant_clone)]
fn bus_read_write_values() {
let bus = Bus::new();
let dummy = Arc::new(Mutex::new(ConstantDevice));
@@ -318,6 +320,7 @@ mod tests {
}
#[test]
#[allow(clippy::redundant_clone)]
fn busrange_cmp() {
let range = BusRange { base: 0x10, len: 2 };
assert_eq!(range, BusRange { base: 0x10, len: 3 });
@@ -332,7 +335,7 @@ mod tests {
let mut data = [1, 2, 3, 4];
let device = Arc::new(Mutex::new(DummyDevice));
assert!(bus.insert(device.clone(), 0x10, 0x10).is_ok());
assert!(bus.write(0x10, &mut data).is_ok());
assert!(bus.write(0x10, &data).is_ok());
assert!(bus.read(0x10, &mut data).is_ok());
assert_eq!(data, [1, 2, 3, 4]);
}

View File

@@ -10,6 +10,8 @@ extern crate serde_derive;
use thiserror::Error;
pub mod protocol;
#[derive(Error, Debug)]
pub enum MigratableError {
#[error("Failed to pause migratable component: {0}")]
@@ -29,6 +31,9 @@ pub enum MigratableError {
#[error("Failed to receive migratable component snapshot: {0}")]
MigrateReceive(#[source] anyhow::Error),
#[error("Socket error: {0}")]
MigrateSocket(#[source] std::io::Error),
}
/// A Pausable component can be paused and resumed.

View File

@@ -0,0 +1,239 @@
// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::MigratableError;
// Migration protocol
// 1: Source establishes communication with destination (file socket or TCP connection.)
// (The establishment is out of scope.)
// 2: Source -> Dest : send "start command"
// 3: Dest -> Source : sends "ok response" when read to accept state data
// 4: Source -> Dest : sends "config command" followed by config data, length
// in command is length of config data
// 5: Dest -> Source : sends "ok response" when ready to accept memory data
// 6: Source -> Dest : send "memory command" followed by table of u64 pairs (GPA, size)
// followed by the memory described in those pairs.
// !! length is size of table i.e. 16 * number of ranges !!
// 7: Dest -> Source : sends "ok response" when ready to accept more memory data
// 8..(n-4): Repeat steps 6 and 7 until source has no more memory to send
// (n-3): Source -> Dest : sends "state command" followed by state data, length
// in command is length of config data
// (n-2): Dest -> Source : sends "ok response"
// (n-1): Source -> Dest : send "complete command"
// n: Dest -> Source: sends "ok response"
// The destination can at any time send an "error response" to cancel
// The source can at any time send an "abandon request" to cancel
use std::io::{Read, Write};
#[repr(u16)]
#[derive(Copy, Clone)]
pub enum Command {
Invalid,
Start,
Config,
State,
Memory,
Complete,
Abandon,
}
impl Default for Command {
fn default() -> Self {
Self::Invalid
}
}
trait AsBytes {
fn as_bytes<T: Sized>(p: &T) -> &[u8] {
unsafe {
std::slice::from_raw_parts((p as *const T) as *const u8, std::mem::size_of::<T>())
}
}
fn as_mut_bytes<T: Sized>(p: &mut T) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut((p as *mut T) as *mut u8, std::mem::size_of::<T>())
}
}
}
#[repr(C)]
#[derive(Default)]
pub struct Request {
command: Command,
padding: [u8; 6],
length: u64, // Length of payload for command excluding the Request struct
}
impl Request {
pub fn new(command: Command, length: u64) -> Self {
Self {
command,
length,
..Default::default()
}
}
pub fn start() -> Self {
Self::new(Command::Start, 0)
}
pub fn state(length: u64) -> Self {
Self::new(Command::State, length)
}
pub fn config(length: u64) -> Self {
Self::new(Command::Config, length)
}
pub fn memory(length: u64) -> Self {
Self::new(Command::Memory, length)
}
pub fn complete() -> Self {
Self::new(Command::Complete, 0)
}
pub fn abandon() -> Self {
Self::new(Command::Abandon, 0)
}
pub fn command(&self) -> Command {
self.command
}
pub fn length(&self) -> u64 {
self.length
}
pub fn read_from(fd: &mut dyn Read) -> Result<Request, MigratableError> {
let mut request = Request::default();
fd.read_exact(Self::as_mut_bytes(&mut request))
.map_err(MigratableError::MigrateSocket)?;
Ok(request)
}
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
fd.write_all(Self::as_bytes(self))
.map_err(MigratableError::MigrateSocket)
}
}
impl AsBytes for Request {}
#[repr(u16)]
#[derive(Copy, Clone, PartialEq)]
pub enum Status {
Invalid,
Ok,
Error,
}
impl Default for Status {
fn default() -> Self {
Self::Invalid
}
}
#[repr(C)]
#[derive(Default)]
pub struct Response {
status: Status,
padding: [u8; 6],
length: u64, // Length of payload for command excluding the Response struct
}
impl Response {
pub fn new(status: Status, length: u64) -> Self {
Self {
status,
length,
..Default::default()
}
}
pub fn ok() -> Self {
Self::new(Status::Ok, 0)
}
pub fn error() -> Self {
Self::new(Status::Error, 0)
}
pub fn status(&self) -> Status {
self.status
}
pub fn read_from(fd: &mut dyn Read) -> Result<Response, MigratableError> {
let mut response = Response::default();
fd.read_exact(Self::as_mut_bytes(&mut response))
.map_err(MigratableError::MigrateSocket)?;
Ok(response)
}
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
fd.write_all(Self::as_bytes(self))
.map_err(MigratableError::MigrateSocket)
}
}
impl AsBytes for Response {}
#[repr(C)]
pub struct MemoryRange {
pub gpa: u64,
pub length: u64,
}
#[derive(Default)]
pub struct MemoryRangeTable {
data: Vec<MemoryRange>,
}
impl MemoryRangeTable {
pub fn regions(&self) -> &[MemoryRange] {
&self.data
}
pub fn push(&mut self, range: MemoryRange) {
self.data.push(range)
}
pub fn read_from(fd: &mut dyn Read, length: u64) -> Result<MemoryRangeTable, MigratableError> {
assert!(length as usize % std::mem::size_of::<MemoryRange>() == 0);
let mut data = Vec::with_capacity(length as usize / (std::mem::size_of::<MemoryRange>()));
unsafe {
data.set_len(length as usize / (std::mem::size_of::<MemoryRange>()));
}
fd.read_exact(unsafe {
std::slice::from_raw_parts_mut(
data.as_ptr() as *mut MemoryRange as *mut u8,
length as usize,
)
})
.map_err(MigratableError::MigrateSocket)?;
Ok(Self { data })
}
pub fn length(&self) -> u64 {
(std::mem::size_of::<MemoryRange>() * self.data.len()) as u64
}
pub fn write_to(&self, fd: &mut dyn Write) -> Result<(), MigratableError> {
fd.write_all(unsafe {
std::slice::from_raw_parts(
self.data.as_ptr() as *const MemoryRange as *const u8,
self.length() as usize,
)
})
.map_err(MigratableError::MigrateSocket)
}
}

View File

@@ -13,4 +13,4 @@ serde = ">=1.0.27"
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
virtio-bindings = { version = "0.1", features = ["virtio-v5_0_0"]}
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }

View File

@@ -10,12 +10,13 @@ acpi = ["acpi_tables","devices/acpi", "arch/acpi"]
cmos = ["devices/cmos"]
fwdebug = ["devices/fwdebug"]
kvm = ["hypervisor/kvm"]
mshv = ["hypervisor/mshv"]
io_uring = ["virtio-devices/io_uring"]
[dependencies]
acpi_tables = { path = "../acpi_tables", optional = true }
anyhow = "1.0"
arc-swap = ">=0.4.4"
arc-swap = ">=1.0.0"
arch = { path = "../arch" }
bitflags = ">=1.2.1"
block_util = { path = "../block_util" }
@@ -24,7 +25,7 @@ devices = { path = "../devices" }
epoll = ">=4.0.1"
hypervisor = { path = "../hypervisor" }
lazy_static = "1.4.0"
libc = "0.2.80"
libc = "0.2.81"
log = "0.4.11"
micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "master" }
net_util = { path = "../net_util" }
@@ -35,15 +36,15 @@ seccomp = { git = "https://github.com/firecracker-microvm/firecracker", tag = "v
serde = {version = ">=1.0.27", features = ["rc"] }
serde_derive = ">=1.0.27"
serde_json = ">=1.0.9"
signal-hook = "0.1.16"
signal-hook = "0.2.1"
tempfile = "3.1.0"
thiserror = "1.0"
url = "2.1.1"
url = "2.2.0"
vfio-ioctls = { git = "https://github.com/cloud-hypervisor/vfio-ioctls", branch = "ch" }
virtio-devices = { path = "../virtio-devices" }
vm-allocator = { path = "../vm-allocator" }
vm-device = { path = "../vm-device" }
vm-memory = { version = "0.3.0", features = ["backend-mmap", "backend-atomic"] }
vm-memory = { version = "0.4.0", features = ["backend-mmap", "backend-atomic"] }
vm-migration = { path = "../vm-migration" }
vm-virtio = { path = "../vm-virtio" }
vmm-sys-util = { version = ">=0.5.0", features = ["with-serde"] }

View File

@@ -100,6 +100,12 @@ pub enum HttpError {
/// Could not get counters from VM
VmCounters(ApiError),
/// Error setting up migration received
VmReceiveMigration(ApiError),
/// Error setting up migration sender
VmSendMigration(ApiError),
}
impl From<serde_json::Error> for HttpError {
@@ -205,11 +211,13 @@ lazy_static! {
r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
r.routes.insert(endpoint!("/vm.pause"), Box::new(VmActionHandler::new(VmAction::Pause)));
r.routes.insert(endpoint!("/vm.reboot"), Box::new(VmActionHandler::new(VmAction::Reboot)));
r.routes.insert(endpoint!("/vm.receive-migration"), Box::new(VmActionHandler::new(VmAction::ReceiveMigration(Arc::default()))));
r.routes.insert(endpoint!("/vm.remove-device"), Box::new(VmActionHandler::new(VmAction::RemoveDevice(Arc::default()))));
r.routes.insert(endpoint!("/vm.resize"), Box::new(VmActionHandler::new(VmAction::Resize(Arc::default()))));
r.routes.insert(endpoint!("/vm.resize-zone"), Box::new(VmActionHandler::new(VmAction::ResizeZone(Arc::default()))));
r.routes.insert(endpoint!("/vm.restore"), Box::new(VmActionHandler::new(VmAction::Restore(Arc::default()))));
r.routes.insert(endpoint!("/vm.resume"), Box::new(VmActionHandler::new(VmAction::Resume)));
r.routes.insert(endpoint!("/vm.send-migration"), Box::new(VmActionHandler::new(VmAction::SendMigration(Arc::default()))));
r.routes.insert(endpoint!("/vm.shutdown"), Box::new(VmActionHandler::new(VmAction::Shutdown)));
r.routes.insert(endpoint!("/vm.snapshot"), Box::new(VmActionHandler::new(VmAction::Snapshot(Arc::default()))));
r.routes.insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));

View File

@@ -6,9 +6,9 @@
use crate::api::http::{error_response, EndpointHandler, HttpError};
use crate::api::{
vm_add_device, vm_add_disk, vm_add_fs, vm_add_net, vm_add_pmem, vm_add_vsock, vm_boot,
vm_counters, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_remove_device, vm_resize,
vm_resize_zone, vm_restore, vm_resume, vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown,
ApiRequest, VmAction, VmConfig,
vm_counters, vm_create, vm_delete, vm_info, vm_pause, vm_reboot, vm_receive_migration,
vm_remove_device, vm_resize, vm_resize_zone, vm_restore, vm_resume, vm_send_migration,
vm_shutdown, vm_snapshot, vmm_ping, vmm_shutdown, ApiRequest, VmAction, VmConfig,
};
use micro_http::{Body, Method, Request, Response, StatusCode, Version};
use std::sync::mpsc::Sender;
@@ -153,6 +153,20 @@ impl EndpointHandler for VmActionHandler {
)
.map_err(HttpError::VmSnapshot),
ReceiveMigration(_) => vm_receive_migration(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
)
.map_err(HttpError::VmReceiveMigration),
SendMigration(_) => vm_send_migration(
api_notifier,
api_sender,
Arc::new(serde_json::from_slice(body.raw())?),
)
.map_err(HttpError::VmSendMigration),
_ => Err(HttpError::BadRequest),
}
} else {

View File

@@ -39,11 +39,13 @@ pub mod http_endpoint;
use crate::config::{
DeviceConfig, DiskConfig, FsConfig, NetConfig, PmemConfig, RestoreConfig, VmConfig, VsockConfig,
};
use crate::device_tree::DeviceTree;
use crate::vm::{Error as VmError, VmState};
use micro_http::Body;
use std::io;
use std::sync::mpsc::{channel, RecvError, SendError, Sender};
use std::sync::{Arc, Mutex};
use vm_migration::MigratableError;
use vmm_sys_util::eventfd::EventFd;
/// API errors are sent back from the VMM API server through the ApiResponse.
@@ -138,6 +140,12 @@ pub enum ApiError {
/// The vsock device could not be added to the VM.
VmAddVsock(VmError),
/// Error starting migration receiever
VmReceiveMigration(MigratableError),
/// Error starting migration sender
VmSendMigration(MigratableError),
}
pub type ApiResult<T> = std::result::Result<T, ApiError>;
@@ -146,6 +154,7 @@ pub struct VmInfo {
pub config: Arc<Mutex<VmConfig>>,
pub state: VmState,
pub memory_actual_size: u64,
pub device_tree: Option<Arc<Mutex<DeviceTree>>>,
}
#[derive(Clone, Deserialize, Serialize)]
@@ -177,6 +186,18 @@ pub struct VmSnapshotConfig {
pub destination_url: String,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct VmReceiveMigrationData {
/// URL for the reception of migration state
pub receiver_url: String,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct VmSendMigrationData {
/// URL to migrate the VM to
pub destination_url: String,
}
pub enum ApiResponsePayload {
/// No data is sent on the channel.
Empty,
@@ -275,6 +296,12 @@ pub enum ApiRequest {
/// Restore from a VM snapshot
VmRestore(Arc<RestoreConfig>, Sender<ApiResponse>),
/// Incoming migration
VmReceiveMigration(Arc<VmReceiveMigrationData>, Sender<ApiResponse>),
/// Outgoing migration
VmSendMigration(Arc<VmSendMigrationData>, Sender<ApiResponse>),
}
pub fn vm_create(
@@ -352,6 +379,12 @@ pub enum VmAction {
/// Snapshot VM
Snapshot(Arc<VmSnapshotConfig>),
/// Incoming migration
ReceiveMigration(Arc<VmReceiveMigrationData>),
/// Outgoing migration
SendMigration(Arc<VmSendMigrationData>),
}
fn vm_action(
@@ -381,6 +414,8 @@ fn vm_action(
ResizeZone(v) => ApiRequest::VmResizeZone(v, response_sender),
Restore(v) => ApiRequest::VmRestore(v, response_sender),
Snapshot(v) => ApiRequest::VmSnapshot(v, response_sender),
ReceiveMigration(v) => ApiRequest::VmReceiveMigration(v, response_sender),
SendMigration(v) => ApiRequest::VmSendMigration(v, response_sender),
};
// Send the VM request.
@@ -424,6 +459,22 @@ pub fn vm_counters(api_evt: EventFd, api_sender: Sender<ApiRequest>) -> ApiResul
vm_action(api_evt, api_sender, VmAction::Counters)
}
pub fn vm_receive_migration(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<VmReceiveMigrationData>,
) -> ApiResult<Option<Body>> {
vm_action(api_evt, api_sender, VmAction::ReceiveMigration(data))
}
pub fn vm_send_migration(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,
data: Arc<VmSendMigrationData>,
) -> ApiResult<Option<Body>> {
vm_action(api_evt, api_sender, VmAction::SendMigration(data))
}
pub fn vm_snapshot(
api_evt: EventFd,
api_sender: Sender<ApiRequest>,

View File

@@ -363,8 +363,30 @@ components:
memory_actual_size:
type: integer
format: int64
device_tree:
type: object
additionalProperties:
$ref: '#/components/schemas/DeviceNode'
description: Virtual Machine information
DeviceNode:
type: object
properties:
id:
type: string
resources:
type: array
items:
# Rust enum type (with data) which can't be better represented here
type: object
children:
type: array
items:
type: string
pci_bdf:
type: integer
format: int32
VmCounters:
type: object
additionalProperties:
@@ -654,8 +676,12 @@ components:
FsConfig:
required:
- tag
- cache_size
- dax
- num_queues
- queue_size
- socket
- tag
type: object
properties:
tag:

View File

@@ -95,6 +95,8 @@ pub enum ValidationError {
DiskSocketAndPath,
/// Using vhost user requires shared memory
VhostUserRequiresSharedMemory,
/// No socket provided for vhost_use
VhostUserMissingSocket,
/// Trying to use IOMMU without PCI
IommuUnsupported,
/// Trying to use VFIO without PCI
@@ -121,6 +123,7 @@ impl fmt::Display for ValidationError {
VhostUserRequiresSharedMemory => {
write!(f, "Using vhost-user requires using shared memory")
}
VhostUserMissingSocket => write!(f, "No socket provided when using vhost-user"),
IommuUnsupported => write!(f, "Using an IOMMU without PCI support is unsupported"),
VfioUnsupported => write!(f, "Using VFIO without PCI support is unsupported"),
CpuTopologyZeroPart => write!(f, "No part of the CPU topology can be zero"),
@@ -619,6 +622,9 @@ pub struct DiskConfig {
pub poll_queue: bool,
#[serde(default)]
pub id: Option<String>,
// For testing use only. Not exposed in API.
#[serde(default)]
pub disable_io_uring: bool,
}
fn default_diskconfig_num_queues() -> usize {
@@ -646,6 +652,7 @@ impl Default for DiskConfig {
vhost_socket: None,
poll_queue: default_diskconfig_poll_queue(),
id: None,
disable_io_uring: false,
}
}
}
@@ -668,7 +675,8 @@ impl DiskConfig {
.add("vhost_user")
.add("socket")
.add("poll_queue")
.add("id");
.add("id")
.add("_disable_io_uring");
parser.parse(disk).map_err(Error::ParseDisk)?;
let path = parser.get("path").map(PathBuf::from);
@@ -707,6 +715,11 @@ impl DiskConfig {
.unwrap_or_else(|| Toggle(default_diskconfig_poll_queue()))
.0;
let id = parser.get("id");
let disable_io_uring = parser
.convert::<Toggle>("_disable_io_uring")
.map_err(Error::ParseDisk)?
.unwrap_or(Toggle(false))
.0;
if parser.is_set("poll_queue") && !vhost_user {
warn!("poll_queue parameter currently only has effect when used vhost_user=true");
@@ -723,6 +736,7 @@ impl DiskConfig {
vhost_user,
poll_queue,
id,
disable_io_uring,
})
}
}
@@ -1453,6 +1467,9 @@ impl VmConfig {
if disk.vhost_user && !self.memory.shared {
return Err(ValidationError::VhostUserRequiresSharedMemory);
}
if disk.vhost_user && disk.vhost_socket.is_none() {
return Err(ValidationError::VhostUserMissingSocket);
}
}
}
@@ -1788,9 +1805,9 @@ mod tests {
}
);
assert_eq!(
DiskConfig::parse("path=/path/to_file,vhost_user=true")?,
DiskConfig::parse("vhost_user=true,socket=/tmp/sock")?,
DiskConfig {
path: Some(PathBuf::from("/path/to_file")),
vhost_socket: Some(String::from("/tmp/sock")),
vhost_user: true,
..Default::default()
}
@@ -2264,9 +2281,18 @@ mod tests {
}]);
assert!(invalid_config.validate().is_err());
let mut invalid_config = valid_config.clone();
invalid_config.disks = Some(vec![DiskConfig {
vhost_user: true,
vhost_socket: Some("/path/to/sock".to_owned()),
..Default::default()
}]);
assert!(invalid_config.validate().is_err());
let mut still_valid_config = valid_config.clone();
still_valid_config.disks = Some(vec![DiskConfig {
vhost_user: true,
vhost_socket: Some("/path/to/sock".to_owned()),
..Default::default()
}]);
still_valid_config.memory.shared = true;
@@ -2282,6 +2308,7 @@ mod tests {
let mut still_valid_config = valid_config.clone();
still_valid_config.net = Some(vec![NetConfig {
vhost_user: true,
vhost_socket: Some("/path/to/sock".to_owned()),
..Default::default()
}]);
still_valid_config.memory.shared = true;
@@ -2293,7 +2320,7 @@ mod tests {
}]);
assert!(invalid_config.validate().is_err());
let mut still_valid_config = valid_config.clone();
let mut still_valid_config = valid_config;
invalid_config.fs = Some(vec![FsConfig {
..Default::default()
}]);

View File

@@ -34,10 +34,9 @@ use devices::interrupt_controller::InterruptController;
use hypervisor::kvm::kvm_bindings;
#[cfg(target_arch = "x86_64")]
use hypervisor::CpuId;
use hypervisor::{CpuState, VmExit};
use seccomp::{SeccompAction, SeccompFilter};
use hypervisor::{vm::VmmOps, CpuState, HypervisorCpuError, VmExit};
use libc::{c_void, siginfo_t};
use seccomp::{SeccompAction, SeccompFilter};
use std::os::unix::thread::JoinHandleExt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier, Mutex};
@@ -213,8 +212,6 @@ pub struct Vcpu {
// The hypervisor abstracted CPU.
vcpu: Arc<dyn hypervisor::Vcpu>,
id: u8,
#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
interrupt_controller: Option<Arc<Mutex<dyn InterruptController>>>,
#[cfg(target_arch = "aarch64")]
mpidr: u64,
saved_state: Option<CpuState>,
@@ -227,19 +224,19 @@ impl Vcpu {
///
/// * `id` - Represents the CPU number between [0, max vcpus).
/// * `vm` - The virtual machine this vcpu will get attached to.
/// * `vmmops` - Optional object for exit handling.
pub fn new(
id: u8,
vm: &Arc<dyn hypervisor::Vm>,
interrupt_controller: Option<Arc<Mutex<dyn InterruptController>>>,
vmmops: Option<Arc<Box<dyn VmmOps>>>,
) -> Result<Arc<Mutex<Self>>> {
let vcpu = vm
.create_vcpu(id)
.create_vcpu(id, vmmops)
.map_err(|e| Error::VcpuCreate(e.into()))?;
// Initially the cpuid per vCPU is the one supported by this VM.
Ok(Arc::new(Mutex::new(Vcpu {
vcpu,
id,
interrupt_controller,
#[cfg(target_arch = "aarch64")]
mpidr: 0,
saved_state: None,
@@ -323,28 +320,8 @@ impl Vcpu {
///
/// Note that the state of the VCPU and associated VM must be setup first for this to do
/// anything useful.
pub fn run(&self) -> Result<bool> {
match self.vcpu.run() {
Ok(run) => match run {
#[cfg(target_arch = "x86_64")]
VmExit::IoapicEoi(vector) => {
if let Some(interrupt_controller) = &self.interrupt_controller {
interrupt_controller
.lock()
.unwrap()
.end_of_interrupt(vector);
}
Ok(true)
}
VmExit::Ignore => Ok(true),
VmExit::Reset => Ok(false),
// No need to handle anything from a KVM HyperV exit
VmExit::Hyperv => Ok(true),
_ => Err(Error::UnexpectedVmExit),
},
Err(e) => Err(Error::VcpuRun(e.into())),
}
pub fn run(&self) -> std::result::Result<VmExit, HypervisorCpuError> {
self.vcpu.run()
}
}
@@ -425,12 +402,14 @@ pub struct CpuManager {
vm: Arc<dyn hypervisor::Vm>,
vcpus_kill_signalled: Arc<AtomicBool>,
vcpus_pause_signalled: Arc<AtomicBool>,
exit_evt: EventFd,
#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
reset_evt: EventFd,
vcpu_states: Vec<VcpuState>,
selected_cpu: u8,
vcpus: Vec<Arc<Mutex<Vcpu>>>,
seccomp_action: SeccompAction,
vmmops: Arc<Box<dyn VmmOps>>,
}
const CPU_ENABLE_FLAG: usize = 0;
@@ -550,14 +529,17 @@ impl VcpuState {
impl CpuManager {
#[allow(unused_variables)]
#[allow(clippy::too_many_arguments)]
pub fn new(
config: &CpusConfig,
device_manager: &Arc<Mutex<DeviceManager>>,
memory_manager: &Arc<Mutex<MemoryManager>>,
vm: Arc<dyn hypervisor::Vm>,
exit_evt: EventFd,
reset_evt: EventFd,
hypervisor: Arc<dyn hypervisor::Hypervisor>,
seccomp_action: SeccompAction,
vmmops: Arc<Box<dyn VmmOps>>,
) -> Result<Arc<Mutex<CpuManager>>> {
let guest_memory = memory_manager.lock().unwrap().guest_memory();
let mut vcpu_states = Vec::with_capacity(usize::from(config.max_vcpus));
@@ -584,10 +566,12 @@ impl CpuManager {
vcpus_kill_signalled: Arc::new(AtomicBool::new(false)),
vcpus_pause_signalled: Arc::new(AtomicBool::new(false)),
vcpu_states,
exit_evt,
reset_evt,
selected_cpu: 0,
vcpus: Vec::with_capacity(usize::from(config.max_vcpus)),
seccomp_action,
vmmops,
}));
#[cfg(target_arch = "x86_64")]
@@ -679,13 +663,7 @@ impl CpuManager {
) -> Result<Arc<Mutex<Vcpu>>> {
info!("Creating vCPU: cpu_id = {}", cpu_id);
let interrupt_controller = if let Some(interrupt_controller) = &self.interrupt_controller {
Some(interrupt_controller.clone())
} else {
None
};
let vcpu = Vcpu::new(cpu_id, &self.vm, interrupt_controller)?;
let vcpu = Vcpu::new(cpu_id, &self.vm, Some(self.vmmops.clone()))?;
if let Some(snapshot) = snapshot {
// AArch64 vCPUs should be initialized after created.
@@ -756,6 +734,7 @@ impl CpuManager {
) -> Result<()> {
let cpu_id = vcpu.lock().unwrap().id;
let reset_evt = self.reset_evt.try_clone().unwrap();
let exit_evt = self.exit_evt.try_clone().unwrap();
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
@@ -770,6 +749,14 @@ impl CpuManager {
let vcpu_seccomp_filter = get_seccomp_filter(&self.seccomp_action, Thread::Vcpu)
.map_err(Error::CreateSeccompFilter)?;
#[cfg(target_arch = "x86_64")]
let interrupt_controller_clone =
if let Some(interrupt_controller) = &self.interrupt_controller {
Some(interrupt_controller.clone())
} else {
None
};
let handle = Some(
thread::Builder::new()
.name(format!("vcpu{}", cpu_id))
@@ -798,6 +785,11 @@ impl CpuManager {
// We enter a loop because park() could spuriously
// return. We will then park() again unless the
// pause boolean has been toggled.
// Need to use Ordering::SeqCst as we have multiple
// loads and stores to different atomics and we need
// to see them in a consistent order in all threads
if vcpu_pause_signalled.load(Ordering::SeqCst) {
vcpu_run_interrupted.store(true, Ordering::SeqCst);
while vcpu_pause_signalled.load(Ordering::SeqCst) {
@@ -816,14 +808,39 @@ impl CpuManager {
// vcpu.run() returns false on a triple-fault so trigger a reset
match vcpu.lock().unwrap().run() {
Ok(run) => match run {
#[cfg(target_arch = "x86_64")]
VmExit::IoapicEoi(vector) => {
if let Some(interrupt_controller) = &interrupt_controller_clone
{
interrupt_controller
.lock()
.unwrap()
.end_of_interrupt(vector);
}
}
VmExit::Ignore => {}
VmExit::Hyperv => {}
VmExit::Reset => {
debug!("VmExit::Reset");
vcpu_run_interrupted.store(true, Ordering::SeqCst);
reset_evt.write(1).unwrap();
break;
}
VmExit::Shutdown => {
debug!("VmExit::Shutdown");
vcpu_run_interrupted.store(true, Ordering::SeqCst);
exit_evt.write(1).unwrap();
break;
}
_ => {
error!("VCPU generated error: {:?}", Error::UnexpectedVmExit);
break;
}
},
Err(e) => {
error!("VCPU generated error: {:?}", e);
break;
}
Ok(true) => {}
Ok(false) => {
vcpu_run_interrupted.store(true, Ordering::SeqCst);
reset_evt.write(1).unwrap();
error!("VCPU generated error: {:?}", Error::VcpuRun(e.into()));
break;
}
}
@@ -1343,7 +1360,7 @@ impl Pausable for CpuManager {
for vcpu in self.vcpus.iter() {
let mut vcpu = vcpu.lock().unwrap();
vcpu.pause()?;
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
if !self.config.kvm_hyperv {
vcpu.vcpu.notify_guest_clock_paused().map_err(|e| {
MigratableError::Pause(anyhow!(
@@ -1407,7 +1424,7 @@ impl Snapshottable for CpuManager {
impl Transportable for CpuManager {}
impl Migratable for CpuManager {}
#[cfg(target_arch = "x86_64")]
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
#[cfg(test)]
mod tests {
@@ -1424,7 +1441,7 @@ mod tests {
assert!(hv.check_capability(hypervisor::kvm::Cap::Irqchip));
// Calling get_lapic will fail if there is no irqchip before hand.
assert!(vm.create_irq_chip().is_ok());
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let klapic_before: LapicState = vcpu.get_lapic().unwrap();
// Compute the value that is expected to represent LVT0 and LVT1.
@@ -1447,7 +1464,7 @@ mod tests {
fn test_setup_fpu() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().expect("new VM fd creation failed");
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
setup_fpu(&vcpu).unwrap();
let expected_fpu: FpuState = FpuState {
@@ -1472,7 +1489,7 @@ mod tests {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().expect("new VM fd creation failed");
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
setup_msrs(&vcpu).unwrap();
// This test will check against the last MSR entry configured (the tenth one).
@@ -1498,7 +1515,7 @@ mod tests {
fn test_setup_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().expect("new VM fd creation failed");
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let expected_regs: StandardRegisters = StandardRegisters {
rflags: 0x0000000000000002u64,
@@ -1526,10 +1543,10 @@ mod tests {
fn test_setup_sregs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().expect("new VM fd creation failed");
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut expected_sregs: SpecialRegisters = vcpu.get_sregs().unwrap();
let gm = GuestMemoryMmap::from_ranges(&vec![(GuestAddress(0), 0x10000)]).unwrap();
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
configure_segments_and_sregs(&gm, &mut expected_sregs, BootProtocol::LinuxBoot).unwrap();
setup_page_tables(&gm, &mut expected_sregs).unwrap();
@@ -1557,7 +1574,7 @@ mod tests {
fn test_setup_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut regions = Vec::new();
regions.push((
GuestAddress(layout::RAM_64BIT_START),
@@ -1580,7 +1597,7 @@ mod tests {
fn test_read_mpidr() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
vm.get_preferred_target(&mut kvi).unwrap();
@@ -1604,7 +1621,7 @@ mod tests {
fn test_save_restore_core_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
vm.get_preferred_target(&mut kvi).unwrap();
@@ -1640,7 +1657,7 @@ mod tests {
fn test_save_restore_system_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
vm.get_preferred_target(&mut kvi).unwrap();
@@ -1681,7 +1698,7 @@ mod tests {
fn test_get_set_mpstate() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut kvi: kvm_vcpu_init = kvm_vcpu_init::default();
vm.get_preferred_target(&mut kvi).unwrap();

View File

@@ -15,6 +15,8 @@ use crate::config::{DiskConfig, FsConfig, NetConfig, PmemConfig, VmConfig, Vsock
use crate::device_tree::{DeviceNode, DeviceTree};
#[cfg(feature = "kvm")]
use crate::interrupt::kvm::KvmMsiInterruptManager as MsiInterruptManager;
#[cfg(feature = "mshv")]
use crate::interrupt::mshv::MshvMsiInterruptManager as MsiInterruptManager;
use crate::interrupt::LegacyUserspaceInterruptManager;
use crate::memory_manager::{Error as MemoryManagerError, MemoryManager};
#[cfg(feature = "acpi")]
@@ -43,10 +45,12 @@ use devices::{
interrupt_controller, interrupt_controller::InterruptController, legacy::Serial,
HotPlugNotificationFlags,
};
use hypervisor::kvm_ioctls;
#[cfg(feature = "kvm")]
use hypervisor::kvm_ioctls::*;
#[cfg(target_arch = "aarch64")]
use hypervisor::CpuState;
#[cfg(feature = "mshv")]
use hypervisor::IoEventAddress;
use libc::TIOCGWINSZ;
use libc::{MAP_NORESERVE, MAP_PRIVATE, MAP_SHARED, O_TMPFILE, PROT_READ, PROT_WRITE};
use pci::{
@@ -63,10 +67,8 @@ use std::num::Wrapping;
use std::os::unix::fs::OpenOptionsExt;
#[cfg(feature = "kvm")]
use std::os::unix::io::FromRawFd;
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, Mutex};
use tempfile::NamedTempFile;
#[cfg(feature = "kvm")]
use vfio_ioctls::{VfioContainer, VfioDevice, VfioDmaMapping};
use virtio_devices::transport::VirtioPciDevice;
@@ -183,7 +185,7 @@ pub enum DeviceManagerError {
AllocateIrq,
/// Cannot configure the IRQ.
Irq(kvm_ioctls::Error),
Irq(vmm_sys_util::errno::Error),
/// Cannot allocate PCI BARs
AllocateBars(pci::PciDeviceError),
@@ -614,6 +616,7 @@ impl DeviceRelocation for AddressManager {
0,
shm_regions.host_addr,
false,
false,
);
self.vm.set_user_memory_region(mem_region).map_err(|e| {
@@ -630,6 +633,7 @@ impl DeviceRelocation for AddressManager {
shm_regions.len,
shm_regions.host_addr,
false,
false,
);
self.vm.set_user_memory_region(mem_region).map_err(|e| {
@@ -656,17 +660,6 @@ impl DeviceRelocation for AddressManager {
}
}
struct ActivatedBackend {
_socket_file: tempfile::NamedTempFile,
child: std::process::Child,
}
impl Drop for ActivatedBackend {
fn drop(&mut self) {
self.child.wait().ok();
}
}
#[derive(Serialize, Deserialize)]
struct DeviceManagerState {
device_tree: DeviceTree,
@@ -733,12 +726,6 @@ pub struct DeviceManager {
// which prevents cyclic dependencies.
bus_devices: Vec<Arc<Mutex<dyn BusDevice>>>,
// The path to the VMM for self spawning
vmm_path: PathBuf,
// Backends that have been spawned
vhost_user_backends: Vec<ActivatedBackend>,
// Counter to keep track of the consumed device IDs.
device_id_cnt: Wrapping<usize>,
@@ -799,7 +786,6 @@ impl DeviceManager {
memory_manager: Arc<Mutex<MemoryManager>>,
_exit_evt: &EventFd,
reset_evt: &EventFd,
vmm_path: PathBuf,
seccomp_action: SeccompAction,
#[cfg(feature = "acpi")] numa_nodes: NumaNodes,
) -> DeviceManagerResult<Arc<Mutex<Self>>> {
@@ -839,8 +825,6 @@ impl DeviceManager {
memory_manager,
virtio_devices: Vec::new(),
bus_devices: Vec::new(),
vmm_path,
vhost_user_backends: Vec::new(),
device_id_cnt: Wrapping(0),
pci_bus: None,
msi_interrupt_manager,
@@ -1580,39 +1564,6 @@ impl DeviceManager {
Ok(devices)
}
/// Launch block backend
fn start_block_backend(&mut self, disk_cfg: &DiskConfig) -> DeviceManagerResult<String> {
let _socket_file = NamedTempFile::new().map_err(DeviceManagerError::CreateSocketFile)?;
let socket = _socket_file.path().to_str().unwrap().to_owned();
let child = std::process::Command::new(&self.vmm_path)
.args(&[
"--block-backend",
&format!(
"path={},socket={},num_queues={},queue_size={}",
disk_cfg
.path
.as_ref()
.ok_or(DeviceManagerError::NoDiskPath)?
.to_str()
.unwrap(),
&socket,
disk_cfg.num_queues,
disk_cfg.queue_size
),
])
.spawn()
.map_err(DeviceManagerError::SpawnBlockBackend)?;
// The ActivatedBackend::drop() will automatically reap the child
self.vhost_user_backends.push(ActivatedBackend {
child,
_socket_file,
});
Ok(socket)
}
fn make_virtio_block_device(
&mut self,
disk_cfg: &mut DiskConfig,
@@ -1626,14 +1577,9 @@ impl DeviceManager {
};
if disk_cfg.vhost_user {
let socket = if let Some(socket) = disk_cfg.vhost_socket.clone() {
socket
} else {
warn!("Self-spawning of vhost-user block backend is deprecated and will be removed in a future release.");
self.start_block_backend(disk_cfg)?
};
let socket = disk_cfg.vhost_socket.as_ref().unwrap().clone();
let vu_cfg = VhostUserConfig {
socket: socket.clone(),
socket,
num_queues: disk_cfg.num_queues,
queue_size: disk_cfg.queue_size,
};
@@ -1645,11 +1591,6 @@ impl DeviceManager {
) {
Ok(vub_device) => vub_device,
Err(e) => {
for vub in self.vhost_user_backends.iter_mut() {
if vub._socket_file.path().to_str().unwrap() == socket {
let _ = vub.child.kill();
}
}
return Err(DeviceManagerError::CreateVhostUserBlk(e));
}
},
@@ -1694,7 +1635,7 @@ impl DeviceManager {
ImageType::Raw => {
// Use asynchronous backend relying on io_uring if the
// syscalls are supported.
if block_io_uring_is_supported() {
if block_io_uring_is_supported() && !disk_cfg.disable_io_uring {
let dev = Arc::new(Mutex::new(
virtio_devices::BlockIoUring::new(
id.clone(),
@@ -1797,40 +1738,6 @@ impl DeviceManager {
Ok(devices)
}
/// Launch network backend
fn start_net_backend(&mut self, net_cfg: &NetConfig) -> DeviceManagerResult<String> {
let _socket_file = NamedTempFile::new().map_err(DeviceManagerError::CreateSocketFile)?;
let socket = _socket_file.path().to_str().unwrap().to_owned();
let child = std::process::Command::new(&self.vmm_path)
.args(&[
"--net-backend",
&format!(
"ip={},mask={},socket={},num_queues={},queue_size={}{}",
net_cfg.ip,
net_cfg.mask,
&socket,
net_cfg.num_queues,
net_cfg.queue_size,
if let Some(mac) = net_cfg.host_mac {
format!(",host_mac={:}", mac)
} else {
"".to_owned()
}
),
])
.spawn()
.map_err(DeviceManagerError::SpawnNetBackend)?;
// The ActivatedBackend::drop() will automatically reap the child
self.vhost_user_backends.push(ActivatedBackend {
child,
_socket_file,
});
Ok(socket)
}
fn make_virtio_net_device(
&mut self,
net_cfg: &mut NetConfig,
@@ -1844,14 +1751,9 @@ impl DeviceManager {
};
if net_cfg.vhost_user {
let socket = if let Some(socket) = net_cfg.vhost_socket.clone() {
socket
} else {
warn!("Self-spawning of vhost-user net backend is deprecated and will be removed in a future release.");
self.start_net_backend(net_cfg)?
};
let socket = net_cfg.vhost_socket.as_ref().unwrap().clone();
let vu_cfg = VhostUserConfig {
socket: socket.clone(),
socket,
num_queues: net_cfg.num_queues,
queue_size: net_cfg.queue_size,
};
@@ -1864,11 +1766,6 @@ impl DeviceManager {
) {
Ok(vun_device) => vun_device,
Err(e) => {
for vun in self.vhost_user_backends.iter_mut() {
if vun._socket_file.path().to_str().unwrap() == socket {
let _ = vun.child.kill();
}
}
return Err(DeviceManagerError::CreateVhostUserNet(e));
}
},
@@ -2086,7 +1983,9 @@ impl DeviceManager {
.memory_manager
.lock()
.unwrap()
.create_userspace_mapping(cache_base, cache_size, host_addr, false, false)
.create_userspace_mapping(
cache_base, cache_size, host_addr, false, false, false,
)
.map_err(DeviceManagerError::MemoryManager)?;
let mut region_list = Vec::new();
@@ -2277,6 +2176,7 @@ impl DeviceManager {
host_addr,
pmem_cfg.mergeable,
false,
false,
)
.map_err(DeviceManagerError::MemoryManager)?;
@@ -2643,6 +2543,15 @@ impl DeviceManager {
})
.map_err(DeviceManagerError::VfioMapRegion)?;
let mut node = device_node!(vfio_name);
for region in vfio_pci_device.mmio_regions() {
node.resources.push(Resource::MmioAddressRange {
base: region.start.0,
size: region.length as u64,
});
}
let vfio_pci_device = Arc::new(Mutex::new(vfio_pci_device));
self.add_pci_device(
@@ -2653,6 +2562,10 @@ impl DeviceManager {
pci_device_bdf,
vfio_name.clone(),
)?;
self.device_tree
.lock()
.unwrap()
.insert(vfio_name.clone(), node);
Ok((pci_device_bdf, vfio_name))
}
@@ -3225,6 +3138,10 @@ impl DeviceManager {
0
}
pub fn device_tree(&self) -> Arc<Mutex<DeviceTree>> {
self.device_tree.clone()
}
}
#[cfg(feature = "acpi")]

View File

@@ -39,7 +39,7 @@ impl InterruptRoute {
}
pub fn enable(&self, vm: &Arc<dyn hypervisor::Vm>) -> Result<()> {
if !self.registered.load(Ordering::SeqCst) {
if !self.registered.load(Ordering::Acquire) {
vm.register_irqfd(&self.irq_fd, self.gsi).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
@@ -48,14 +48,14 @@ impl InterruptRoute {
})?;
// Update internals to track the irq_fd as "registered".
self.registered.store(true, Ordering::SeqCst);
self.registered.store(true, Ordering::Release);
}
Ok(())
}
pub fn disable(&self, vm: &Arc<dyn hypervisor::Vm>) -> Result<()> {
if self.registered.load(Ordering::SeqCst) {
if self.registered.load(Ordering::Acquire) {
vm.unregister_irqfd(&self.irq_fd, self.gsi).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
@@ -64,7 +64,7 @@ impl InterruptRoute {
})?;
// Update internals to track the irq_fd as "unregistered".
self.registered.store(false, Ordering::SeqCst);
self.registered.store(false, Ordering::Release);
}
Ok(())
@@ -325,6 +325,7 @@ where
}
}
#[cfg(feature = "kvm")]
pub mod kvm {
use super::*;
use hypervisor::kvm::KVM_MSI_VALID_DEVID;
@@ -395,6 +396,69 @@ pub mod kvm {
}
}
#[cfg(feature = "mshv")]
pub mod mshv {
use super::*;
use hypervisor::mshv::*;
type MshvMsiInterruptGroup = MsiInterruptGroup<MshvIrqRoutingEntry>;
type MshvRoutingEntry = RoutingEntry<MshvIrqRoutingEntry>;
pub type MshvMsiInterruptManager = MsiInterruptManager<MshvIrqRoutingEntry>;
impl RoutingEntryExt for MshvRoutingEntry {
fn make_entry(
_vm: &Arc<dyn hypervisor::Vm>,
gsi: u32,
config: &InterruptSourceConfig,
) -> Result<Box<Self>> {
if let InterruptSourceConfig::MsiIrq(cfg) = &config {
let route = MshvIrqRoutingEntry {
gsi,
route: MshvIrqRouting::Msi(MshvIrqRoutingMsi {
address_lo: cfg.low_addr,
address_hi: cfg.high_addr,
data: cfg.data,
}),
};
let entry = MshvRoutingEntry {
route,
masked: false,
};
return Ok(Box::new(entry));
}
Err(io::Error::new(
io::ErrorKind::Other,
"Interrupt config type not supported",
))
}
}
impl MsiInterruptGroupOps<MshvIrqRoutingEntry> for MshvMsiInterruptGroup {
fn set_gsi_routes(
&self,
routes: &HashMap<u32, RoutingEntry<MshvIrqRoutingEntry>>,
) -> Result<()> {
let mut entry_vec: Vec<MshvIrqRoutingEntry> = Vec::new();
for (_, entry) in routes.iter() {
if entry.masked {
continue;
}
entry_vec.push(entry.route);
}
self.vm.set_gsi_routing(&entry_vec).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed setting GSI routing: {}", e),
)
})
}
}
}
#[cfg(target_arch = "aarch64")]
#[cfg(test)]
mod tests {
@@ -415,7 +479,7 @@ mod tests {
fn test_get_set_dist_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let _ = vm.create_vcpu(0).unwrap();
let _ = vm.create_vcpu(0, None).unwrap();
let gic = create_gic(&vm, 1).expect("Cannot create gic");
let res = get_dist_regs(gic.device());
@@ -431,7 +495,7 @@ mod tests {
fn test_get_set_redist_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let _ = vm.create_vcpu(0).unwrap();
let _ = vm.create_vcpu(0, None).unwrap();
let gic = create_gic(&vm, 1).expect("Cannot create gic");
let mut gicr_typer = Vec::new();
@@ -449,7 +513,7 @@ mod tests {
fn test_get_set_icc_regs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let _ = vm.create_vcpu(0).unwrap();
let _ = vm.create_vcpu(0, None).unwrap();
let gic = create_gic(&vm, 1).expect("Cannot create gic");
let mut gicr_typer = Vec::new();
@@ -467,7 +531,7 @@ mod tests {
fn test_save_pending_tables() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().unwrap();
let _ = vm.create_vcpu(0).unwrap();
let _ = vm.create_vcpu(0, None).unwrap();
let gic = create_gic(&vm, 1).expect("Cannot create gic");
assert!(save_pending_tables(gic.device()).is_ok());

Some files were not shown because too many files have changed in this diff Show More