vendor: Add vendored dependencies

We use cargo vendor to generate a .cargo/config file and the vendor
directory. Vendoring allows us to lock our dependencies and to modify
them easily from the top level Cargo.toml.

We vendor all dependencies, including the crates.io ones, which allows
for network isolated builds.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
Samuel Ortiz
2019-06-03 16:40:09 +02:00
parent e3f7bc2e9d
commit d5f5648b37
4062 changed files with 442206 additions and 2 deletions
@@ -0,0 +1,155 @@
steps:
- label: "build-gnu-x86"
commands:
- cargo build --release
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "build-gnu-x86-mmap"
commands:
- cargo build --release --features=backend-mmap
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "build-gnu-arm-mmap"
commands:
- cargo build --release --features=backend-mmap
retry:
automatic: false
agents:
platform: arm.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "build-musl-arm-mmap"
commands:
- cargo build --release --features=backend-mmap --target aarch64-unknown-linux-musl
retry:
automatic: false
agents:
platform: arm.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "style"
command: cargo fmt --all -- --check
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "unittests-gnu-x86"
commands:
- cargo test --all-features
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "unittests-musl-x86"
commands:
- cargo test --all-features --target x86_64-unknown-linux-musl
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "unittests-gnu-arm"
commands:
- cargo test --all-features
retry:
automatic: false
agents:
platform: arm.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "unittests-musl-arm"
commands:
- cargo test --all-features --target aarch64-unknown-linux-musl
retry:
automatic: false
agents:
platform: arm.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "clippy-x86"
commands:
- cargo clippy --all
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "check-warnings-x86"
commands:
- RUSTFLAGS="-D warnings" cargo check --all-targets
retry:
automatic: false
agents:
platform: x86_64.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
- label: "check-warnings-arm"
commands:
- RUSTFLAGS="-D warnings" cargo check --all-targets
retry:
automatic: false
agents:
platform: arm.metal
os: linux
plugins:
- docker#v3.0.1:
image: "fandree/rust-vmm-dev"
always-pull: true
@@ -0,0 +1,79 @@
steps:
- label: "build-msvc-x86"
commands:
- cargo build --release
retry:
automatic: true
agents:
platform: x86_64
os: windows
plugins:
- petrutlucian94/docker#v3.1.1:
image: "lpetrut/rust_win_buildtools"
always-pull: true
- label: "build-msvc-x86-mmap"
commands:
- cargo build --release --features=backend-mmap
retry:
automatic: true
agents:
platform: x86_64
os: windows
plugins:
- petrutlucian94/docker#v3.1.1:
image: "lpetrut/rust_win_buildtools"
always-pull: true
- label: "style"
command: cargo fmt --all -- --check
retry:
automatic: true
agents:
platform: x86_64
os: windows
plugins:
- petrutlucian94/docker#v3.1.1:
image: "lpetrut/rust_win_buildtools"
always-pull: true
- label: "unittests-msvc-x86"
commands:
- cargo test --all-features
retry:
automatic: true
agents:
platform: x86_64
os: windows
plugins:
- petrutlucian94/docker#v3.1.1:
image: "lpetrut/rust_win_buildtools"
always-pull: true
- label: "clippy-x86"
commands:
- cargo clippy --all
retry:
automatic: true
agents:
platform: x86_64
os: windows
plugins:
- petrutlucian94/docker#v3.1.1:
image: "lpetrut/rust_win_buildtools"
always-pull: true
- label: "check-warnings-x86"
commands:
- cargo check --all-targets
retry:
automatic: true
agents:
platform: x86_64
os: windows
plugins:
- petrutlucian94/docker#v3.1.1:
image: "lpetrut/rust_win_buildtools"
always-pull: true
environment:
- "RUSTFLAGS=-D warnings"
@@ -0,0 +1 @@
{"files":{".buildkite/pipeline.linux.yml":"a792da4b923a91eb9a590da2d057f89b37b86ebcf483c344813a1abd5957854d",".buildkite/pipeline.windows.yml":"15e21819ef7321c79181ae7f04aafc0c3ceff709a800edd8994f15a9454d6405",".cargo/config":"c2f1c2fd93436e068cfb14eef3dff8a79d25d1f03c11baf6acbcfbdc9fd3a465","Cargo.toml":"459c6688ce88cac6767971e8053b03a14e05aec157a927134c34d5497c84cf68","DESIGN.md":"aa60ac0a1d59179c253c7be0e496f956344cd0bf41f01f86c9a28575ea433785","LICENSE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","README.md":"2f4aa7c6dbb257a9345d17cec07b0731cae37dc216d25b41272061861bf984b8","THIRD-PARTY":"a6d3ebd1c2f37d4fd83d0676621f695fc0cc2d8c6e646cdbb831b46e0650c208","TODO.md":"c844f03be6631843e90d13b3410df031b07ee16db4a3c7cbda7e89557e9be46b","src/address.rs":"dbc3fa642269bb6f9fb5e176dddc9fcedbe024949f32c43577f52ef04fe0cf09","src/bytes.rs":"2d0c58f53e40f51b5177a234a5062f700ea153c779661c8c34f5d9340f24dd05","src/endian.rs":"948548db28334bceeefe72b09560a700d62993372bb89f46e39990038690f516","src/guest_memory.rs":"2be506fe58244f51279c473a7d0d8e1fc41ef638e9c16cc9e6f071bcbecb3b9b","src/lib.rs":"f65c3f36632da8df7b165578bfd8db010af11c1736fd9855614435e76dd3c390","src/mmap.rs":"9f449c7dac3830128bc1875ca0cc7e7407534eeb7cc1e1db0b0d118aba8de07b","src/mmap_unix.rs":"9a1d71bf1bb7952c25e3796f992953390b6db5a32ef40f2dda1275f866dd9ff0","src/mmap_windows.rs":"bd0091dd90eddede03739ab89648a38f513310437dafdf1f449603bb15a7a2ed","src/volatile_memory.rs":"06bdb496a75f3a190b7092daffce01158acdf73654badd72e697b674eedc4fac"},"package":null}
+2
View File
@@ -0,0 +1,2 @@
[target.aarch64-unknown-linux-musl]
rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc"]
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "vm-memory"
version = "0.1.0"
authors = ["Liu Jiang <gerry@linux.alibaba.com>"]
repository = "https://github.com/rust-vmm/vm-memory"
license = "Apache-2.0"
[features]
default = []
backend-mmap = []
[dependencies]
libc = ">=0.2.39"
[dev-dependencies]
matches = ">=0"
tempfile = ">=3.0.2"
+79
View File
@@ -0,0 +1,79 @@
## Objectives
For a typical hypervisor, there are seveval components, such as boot loader, virtual device drivers, virtio backend drivers and vhost drivers etc, that need to access VM's physical memory. The `vm-memory` crate aims to provide a set of stable traits to decouple VM memory consumers from VM memory providers. Based on these traits, VM memory consumers could access VM's physical memory without knowing the implementation details of the VM memory provider. Thus hypervisor components, such as boot loader, virtual device drivers, virtio backend drivers and vhost drivers etc, could be shared and reused by multiple hypervisors.
## API Principles
- Define consumer side interfaces to access VM's physical memory.
- Do not define provider side interfaces to supply VM physical memory.
The `vm-memory` crate focuses on defining consumer side interfaces to access VM's physical memory, and it dosen't define the way how the underline VM memory provider is implemented. For light-wieght hypervisors like crosvm and firecracker, they may make some assumptions about the structure of VM's physical memory and implement a light-weight backend to access VM's physical memory. For hypervisors like qemu, a high performance and full functionality backend may be implemented with less assumptions.
## Architecture
Th `vm-memory` is derived from two upstream projects:
- [crosvm project](https://chromium.googlesource.com/chromiumos/platform/crosvm/) commit 186eb8b0db644892e8ffba8344efe3492bb2b823
- [firecracker project](https://firecracker-microvm.github.io/) commit 80128ea61b305a27df1f751d70415b04b503eae7
To be hypervisor neutral, the high level abstraction has been heavily refactored. The new `vm-memory` crate could be divided into four logic parts as:
### Abstraction of Generic Address Space
Build generic abstractions to describe and access an address space as below:
- AddressValue: Stores the raw value of an address. Typically u32, u64 or usize is used to store the raw value. But pointers, such as \*u8, can't be used because it doesn't implement the Add and Sub traits.
- Address: Encapsulates an AddressValue object and defines methods to access it.
- Bytes: Common trait for volatile access to memory. The `Bytes` trait can be parameterized with newtypes that represent addresses, in order to enforce that addresses are used with the right "kind" of volatile memory.
- VolatileMemory: Basic implementation of volatile access to memory, implements `Bytes<usize>`.
To make the abstraction as generic as possible, all of above core traits only define methods to access the address space, and they never define methods to manage (create, delete, insert, remove etc) address spaces. By this way, the address space consumers (virtio device drivers, vhost-user drivers and boot loaders etc) may be decoupled from the address space provider (typically a hypervisor).
### Specialization for Virtual Machine Physical Address Space
The generic address space crates are specialized to access VM's physical memory with following traits:
- GuestAddress: represents a guest physical address (GPA). On ARM64, a 32-bit hypervisor may be used to support a 64-bit VM. For simplicity, u64 is used to store the the raw value no matter if it is a 32-bit or 64-bit virtual machine.
- GuestMemoryRegion: used to represent a continuous region of VM's physical memory.
- GuestMemory: used to represent a collection of GuestMemoryRegion objects. The main responsibilities of the GuestMemory trait are:
- hide the detail of accessing VM's physical address (for example complex hierarchical structures).
- map a request address to a GuestMemoryRegion object and relay the request to it.
- handle cases where an access request spanning two or more GuestMemoryRegion objects.
The VM memory consumers, such as virtio device drivers, vhost drivers and boot loaders etc, should only rely on traits and structs defined here to access VM's physical memory.
### A Sample and Default Backend Implementation Based on mmap()
Provide a default and sample implementation of the GuestMemory trait by mmapping VM's physical memory into current process. Three data structures are defined here:
- MmapRegion: mmap a continous range of VM's physical memory into current and provide methods to access the mmapped memory.
- GuestRegionMmap: a wrapper structure to map VM's physical address into (mmap\_region, offset) tuple.
- GuestMemoryMmap: manage a collection of GuestRegionMmap objects for a VM.
One of the main responsibilities of the GuestMemoryMmap object is to handle the use cases where an access request crosses the memory region boundary. This scenario may be triggered when memory hotplug is supported. So there's a tradeoff between functionality and code complexity:
- use following pattern for simplicity which fails when the request crosses region boundary. It's current default behavior in the crosvm and firecracker project.
```rust
let guest_memory_mmap: GuestMemoryMmap = ...
let addr: GuestAddress = ...
let buf = &mut [0u8; 5];
let result = guest_memory_mmap.find_region(addr).unwrap().write(buf, addr);
```
- use following pattern for functionality to support request crossing region boundary:
```rust
let guest_memory_mmap: GuestMemoryMmap = ...
let addr: GuestAddress = ...
let buf = &mut [0u8; 5];
let result = guest_memory_mmap.write(buf, addr);
```
### Utilities and Helpers
Following utility and helper traits/macros are imported from the [crosvm project](https://chromium.googlesource.com/chromiumos/platform/crosvm/) with minor changes:
- ByteValued (originally `DataInit`): Types for which it is safe to initialize from raw data. A type `T` is `ByteValued` if and only if it can be initialized by reading its contents from a byte array. This is generally true for all plain-old-data structs. It is notably not true for any type that includes a reference.
- {Le,Be}\_{16,32,64}: Explicit endian types useful for embedding in structs or reinterpreting data.
## Relationships between Traits, Structs and new Types
Traits:
- Address inherits AddressValue
- GuestMemoryRegion inherits Bytes<MemoryRegionAddress, E = Error> (must be implemented)
- GuestMemory implements Bytes<GuestAddress> (generic implementation)
New Types:
- GuestAddress: Address\<u64\>
- MemoryRegionAddress: Address\<u64\>
Structs:
- MmapRegion implements VolatileMemory
- GuestRegionMmap implements Bytes<MemoryRegionAddress> + GuestMemoryRegion
- GuestMemoryMmap implements GuestMemory
- VolatileSlice: Bytes<usize, E = volatile_memory::Error> + VolatileMemory
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+47
View File
@@ -0,0 +1,47 @@
# vm-memory
A library to access virtual machine's physical memory.
For a typical hypervisor, there are seveval components, such as boot loader, virtual device drivers, virtio backend drivers and vhost drivers etc, need to access VM's physical memory. The `vm-memory` crate provides a set of traits to decouple VM memory consumers from VM memory providers. Based on these traits, VM memory consumers could access VM's physical memory without knowing the implementation details of the VM memory provider. Thus hypervisor components based on these traits could be shared and reused by multiple hypervisors.
## Platform Support
- Arch: x86, AMD64, ARM64
- OS: Linux/Unix/Windows
## Usage
First, add the following to your `Cargo.toml`:
```toml
vm-memory = "0.1"
```
Next, add this to your crate root:
```rust
extern crate vm_memory;
```
## Example
- Create VM physical memory objects in hypervisor specific ways. Use the default GuestMemoryMmap as an example:
```
fn provide_mem_to_virt_dev() {
let gm = GuestMemoryMmap::new(&[(GuestAddress(0), 0x1000), (GuestAddress(0x1000), 0x1000)]).unwrap();
virt_device_io(&gm);
}
```
- Consumers access VM's physical memory
```
fn virt_device_io<T: GuestMemory>(mem: &T) {
let sample_buf = &[1, 2, 3, 4, 5];
assert_eq!(mem.write(sample_buf, GuestAddress(0xffc)).unwrap(), 5);
let buf = &mut [0u8; 5];
assert_eq!(mem.read(buf, GuestAddress(0xffc)).unwrap(), 5);
assert_eq!(buf, sample_buf);
}
```
## Documentations & References
- [Design of The `vm-memory` Crate](DESIGN.md)
- [TODO List](TODO.md)
- [The rust-vmm Project](https://github.com/rust-vmm/)
## License
This project is licensed under
- Apache License, Version 2.0, (LICENSE or http://www.apache.org/licenses/LICENSE-2.0)
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+3
View File
@@ -0,0 +1,3 @@
### TODO List
- Abstraction layer to seperate VM memory management from VM memory accessor.
- Help needed to refine documentation and usage examples.
+170
View File
@@ -0,0 +1,170 @@
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
//! Traits to represent an address within an address space.
//!
//! Two traits are defined to present an address within an address space:
//! - [AddressValue](trait.AddressValue.html): stores the raw value of an address. Typically u32,
//! u64 or usize is used to store the raw value. But pointers, such as *u8, can't be used because
//! it doesn't implement the Add and Sub traits.
//! - [Address](trait.Address.html): encapsulates an AddressValue object and defines methods to
//! access and manipulate it.
use std::cmp::{Eq, Ord, PartialEq, PartialOrd};
use std::ops::{Add, BitAnd, BitOr, Sub};
/// Simple helper trait used to store a raw address value.
pub trait AddressValue {
/// Type of the address raw value.
type V: Copy
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Add<Output = Self::V>
+ Sub<Output = Self::V>
+ BitAnd<Output = Self::V>
+ BitOr<Output = Self::V>;
}
/// Trait to represent an address within an address space.
///
/// To simplify the design and implementation, assume the same raw data type (AddressValue::V)
/// could be used to store address, size and offset for the address space. Thus the Address trait
/// could be used to manage address, size and offset. On the other hand, type aliases may be
/// defined to improve code readability.
///
/// One design rule is applied to the Address trait that operators (+, -, &, | etc) are not
/// supported and it forces clients to explicitly invoke corresponding methods. But there are
/// always exceptions:
/// Address (BitAnd|BitOr) AddressValue are supported.
pub trait Address:
AddressValue
+ Sized
+ Default
+ Copy
+ Eq
+ PartialEq
+ Ord
+ PartialOrd
+ BitAnd<<Self as AddressValue>::V, Output = Self>
+ BitOr<<Self as AddressValue>::V, Output = Self>
{
/// Create an address from a raw address value.
fn new(addr: Self::V) -> Self;
/// Get the raw value of the address.
fn raw_value(&self) -> Self::V;
/// Returns the bitwise and of the address with the given mask.
fn mask(&self, mask: Self::V) -> Self::V {
self.raw_value() & mask
}
/// Returns the offset from this address to the given base address and None if there is
/// underflow.
fn checked_offset_from(&self, base: Self) -> Option<Self::V>;
/// Returns the offset from this address to the given base address.
/// Only use this when `base` is guaranteed not to overflow.
fn unchecked_offset_from(&self, base: Self) -> Self::V {
self.raw_value() - base.raw_value()
}
/// Returns the result of the add or None if there is overflow.
fn checked_add(&self, other: Self::V) -> Option<Self>;
/// Returns the result of the add and a flag identifying whether there was overflow
fn overflowing_add(&self, other: Self::V) -> (Self, bool);
/// Returns the result of the base address + the size.
/// Only use this when `offset` is guaranteed not to overflow.
fn unchecked_add(&self, offset: Self::V) -> Self;
/// Returns the result of the subtraction or None if there is underflow.
fn checked_sub(&self, other: Self::V) -> Option<Self>;
/// Returns the result of the subtraction and a flag identifying whether there was overflow
fn overflowing_sub(&self, other: Self::V) -> (Self, bool);
/// Returns the result of the subtraction.
/// Only use this when `other` is guaranteed not to underflow.
fn unchecked_sub(&self, other: Self::V) -> Self;
}
macro_rules! impl_address_ops {
($T:ident, $V:ty) => {
impl AddressValue for $T {
type V = $V;
}
impl Address for $T {
fn new(value: $V) -> $T {
$T(value)
}
fn raw_value(&self) -> $V {
self.0
}
fn checked_offset_from(&self, base: $T) -> Option<$V> {
self.0.checked_sub(base.0)
}
fn checked_add(&self, other: $V) -> Option<$T> {
self.0.checked_add(other).map($T)
}
fn overflowing_add(&self, other: $V) -> ($T, bool) {
let (t, ovf) = self.0.overflowing_add(other);
($T(t), ovf)
}
fn unchecked_add(&self, offset: $V) -> $T {
$T(self.0 + offset)
}
fn checked_sub(&self, other: $V) -> Option<$T> {
self.0.checked_sub(other).map($T)
}
fn overflowing_sub(&self, other: $V) -> ($T, bool) {
let (t, ovf) = self.0.overflowing_sub(other);
($T(t), ovf)
}
fn unchecked_sub(&self, other: $V) -> $T {
$T(self.0 - other)
}
}
impl Default for $T {
fn default() -> $T {
Self::new(0 as $V)
}
}
impl BitAnd<$V> for $T {
type Output = $T;
fn bitand(self, other: $V) -> $T {
$T(self.0 & other)
}
}
impl BitOr<$V> for $T {
type Output = $T;
fn bitor(self, other: $V) -> $T {
$T(self.0 | other)
}
}
};
}
+285
View File
@@ -0,0 +1,285 @@
// Portions Copyright 2019 Red Hat, Inc.
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
//! Define the ByteValued trait to mark that it is safe to instantiate the struct with random data.
use std::io::{Read, Write};
use std::mem::size_of;
use std::result::Result;
use std::slice::{from_raw_parts, from_raw_parts_mut};
/// Types for which it is safe to initialize from raw data.
///
/// A type `T` is `ByteValued` if and only if it can be initialized by reading its contents from a
/// byte array. This is generally true for all plain-old-data structs. It is notably not true for
/// any type that includes a reference.
///
/// Implementing this trait guarantees that it is safe to instantiate the struct with random data.
pub unsafe trait ByteValued: Copy + Default + Send + Sync {
/// Converts a slice of raw data into a reference of `Self`.
///
/// The value of `data` is not copied. Instead a reference is made from the given slice. The
/// value of `Self` will depend on the representation of the type in memory, and may change in
/// an unstable fashion.
///
/// This will return `None` if the length of data does not match the size of `Self`, or if the
/// data is not aligned for the type of `Self`.
fn from_slice(data: &[u8]) -> Option<&Self> {
// Early out to avoid an unneeded `align_to` call.
if data.len() != size_of::<Self>() {
return None;
}
// Safe because the ByteValued trait asserts any data is valid for this type, and we ensured
// the size of the pointer's buffer is the correct size. The `align_to` method ensures that
// we don't have any unaligned references. This aliases a pointer, but because the pointer
// is from a const slice reference, there are no mutable aliases. Finally, the reference
// returned can not outlive data because they have equal implicit lifetime constraints.
match unsafe { data.align_to::<Self>() } {
([], [mid], []) => Some(mid),
_ => None,
}
}
/// Converts a mutable slice of raw data into a mutable reference of `Self`.
///
/// Because `Self` is made from a reference to the mutable slice`, mutations to the returned
/// reference are immediately reflected in `data`. The value of the returned `Self` will depend
/// on the representation of the type in memory, and may change in an unstable fashion.
///
/// This will return `None` if the length of data does not match the size of `Self`, or if the
/// data is not aligned for the type of `Self`.
fn from_mut_slice(data: &mut [u8]) -> Option<&mut Self> {
// Early out to avoid an unneeded `align_to_mut` call.
if data.len() != size_of::<Self>() {
return None;
}
// Safe because the ByteValued trait asserts any data is valid for this type, and we ensured
// the size of the pointer's buffer is the correct size. The `align_to` method ensures that
// we don't have any unaligned references. This aliases a pointer, but because the pointer
// is from a mut slice reference, we borrow the passed in mutable reference. Finally, the
// reference returned can not outlive data because they have equal implicit lifetime
// constraints.
match unsafe { data.align_to_mut::<Self>() } {
([], [mid], []) => Some(mid),
_ => None,
}
}
/// Converts a reference to `self` into a slice of bytes.
///
/// The value of `self` is not copied. Instead, the slice is made from a reference to `self`.
/// The value of bytes in the returned slice will depend on the representation of the type in
/// memory, and may change in an unstable fashion.
fn as_slice(&self) -> &[u8] {
// Safe because the entire size of self is accessible as bytes because the trait guarantees
// it. The lifetime of the returned slice is the same as the passed reference, so that no
// dangling pointers will result from this pointer alias.
unsafe { from_raw_parts(self as *const Self as *const u8, size_of::<Self>()) }
}
/// Converts a mutable reference to `self` into a mutable slice of bytes.
///
/// Because the slice is made from a reference to `self`, mutations to the returned slice are
/// immediately reflected in `self`. The value of bytes in the returned slice will depend on
/// the representation of the type in memory, and may change in an unstable fashion.
fn as_mut_slice(&mut self) -> &mut [u8] {
// Safe because the entire size of self is accessible as bytes because the trait guarantees
// it. The trait also guarantees that any combination of bytes is valid for this type, so
// modifying them in the form of a byte slice is valid. The lifetime of the returned slice
// is the same as the passed reference, so that no dangling pointers will result from this
// pointer alias. Although this does alias a mutable pointer, we do so by exclusively
// borrowing the given mutable reference.
unsafe { from_raw_parts_mut(self as *mut Self as *mut u8, size_of::<Self>()) }
}
}
/// A container to host a range of bytes and access its content.
///
/// Candidates which may implement this trait include:
/// - anonymous memory areas
/// - mmapped memory areas
/// - data files
/// - a proxy to access memory on remote
pub trait Bytes<A> {
/// Associated error codes
type E;
/// Writes a slice into the container at the specified address.
/// Returns the number of bytes written. The number of bytes written can
/// be less than the length of the slice if there isn't enough room in the
/// container.
fn write(&self, buf: &[u8], addr: A) -> Result<usize, Self::E>;
/// Reads to a slice from the container at the specified address.
/// Returns the number of bytes read. The number of bytes read can be less than the length
/// of the slice if there isn't enough room within the container.
fn read(&self, buf: &mut [u8], addr: A) -> Result<usize, Self::E>;
/// Writes the entire contents of a slice into the container at the specified address.
///
/// Returns an error if there isn't enough room within the container to complete the entire
/// write. Part of the data may have been written nevertheless.
fn write_slice(&self, buf: &[u8], addr: A) -> Result<(), Self::E>;
/// Reads from the container at the specified address to fill the entire buffer.
///
/// Returns an error if there isn't enough room within the container to fill the entire buffer.
/// Part of the buffer may have been filled nevertheless.
fn read_slice(&self, buf: &mut [u8], addr: A) -> Result<(), Self::E>;
/// Writes an object into the container at the specified address.
/// Returns Ok(()) if the object fits, or Err if it extends past the end.
fn write_obj<T: ByteValued>(&self, val: T, addr: A) -> Result<(), Self::E> {
self.write_slice(val.as_slice(), addr)
}
/// Reads an object from the container at the given address.
/// Reading from a volatile area isn't strictly safe as it could change mid-read.
/// However, as long as the type T is plain old data and can handle random initialization,
/// everything will be OK.
fn read_obj<T: ByteValued>(&self, addr: A) -> Result<T, Self::E> {
let mut result: T = Default::default();
self.read_slice(result.as_mut_slice(), addr).map(|_| result)
}
/// Writes data from a readable object like a File and writes it into the container.
///
/// # Arguments
/// * `addr` - Begin writing at this address.
/// * `src` - Copy from `src` into the container.
/// * `count` - Copy `count` bytes from `src` into the container.
fn read_from<F>(&self, addr: A, src: &mut F, count: usize) -> Result<usize, Self::E>
where
F: Read;
/// Writes data from a readable object like a File and writes it into the container.
///
/// # Arguments
/// * `addr` - Begin writing at this address.
/// * `src` - Copy from `src` into the container.
/// * `count` - Copy `count` bytes from `src` into the container.
fn read_exact_from<F>(&self, addr: A, src: &mut F, count: usize) -> Result<(), Self::E>
where
F: Read;
/// Reads data from the container to a writable object.
///
/// # Arguments
/// * `addr` - Begin reading from this addr.
/// * `dst` - Copy from the container to `dst`.
/// * `count` - Copy `count` bytes from the container to `dst`.
fn write_to<F>(&self, addr: A, dst: &mut F, count: usize) -> Result<usize, Self::E>
where
F: Write;
/// Reads data from the container to a writable object.
///
/// # Arguments
/// * `addr` - Begin reading from this addr.
/// * `dst` - Copy from the container to `dst`.
/// * `count` - Copy `count` bytes from the container to `dst`.
fn write_all_to<F>(&self, addr: A, dst: &mut F, count: usize) -> Result<(), Self::E>
where
F: Write;
}
// All intrinsic types and arrays of intrinsic types are ByteValued. They are just numbers.
macro_rules! array_data_init {
($T:ty, $($N:expr)+) => {
$(
unsafe impl ByteValued for [$T; $N] {}
)+
}
}
macro_rules! data_init_type {
($T:ty) => {
unsafe impl ByteValued for $T {}
array_data_init! {
$T,
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32
}
};
}
data_init_type!(u8);
data_init_type!(u16);
data_init_type!(u32);
data_init_type!(u64);
data_init_type!(usize);
data_init_type!(i8);
data_init_type!(i16);
data_init_type!(i32);
data_init_type!(i64);
data_init_type!(isize);
#[cfg(test)]
mod tests {
use std::fmt::Debug;
use std::mem::{align_of, size_of};
use ByteValued;
fn from_slice_alignment<T>()
where
T: ByteValued + PartialEq + Debug + Default,
{
let mut v = [0u8; 32];
let pre_len = {
let (pre, _, _) = unsafe { v.align_to::<T>() };
pre.len()
};
{
let aligned_v = &mut v[pre_len..pre_len + size_of::<T>()];
{
let from_aligned = T::from_slice(aligned_v);
let val: T = Default::default();
assert_eq!(from_aligned, Some(&val));
}
{
let from_aligned_mut = T::from_mut_slice(aligned_v);
let mut val: T = Default::default();
assert_eq!(from_aligned_mut, Some(&mut val));
}
}
for i in 1..size_of::<T>() {
let begin = pre_len + i;
let end = begin + size_of::<T>();
let unaligned_v = &mut v[begin..end];
{
let from_unaligned = T::from_slice(unaligned_v);
if align_of::<T>() != 1 {
assert_eq!(from_unaligned, None);
}
}
{
let from_unaligned_mut = T::from_mut_slice(unaligned_v);
if align_of::<T>() != 1 {
assert_eq!(from_unaligned_mut, None);
}
}
}
}
#[test]
fn test_slice_alignment() {
from_slice_alignment::<u8>();
from_slice_alignment::<u16>();
from_slice_alignment::<u32>();
from_slice_alignment::<u64>();
from_slice_alignment::<usize>();
from_slice_alignment::<i8>();
from_slice_alignment::<i16>();
from_slice_alignment::<i32>();
from_slice_alignment::<i64>();
from_slice_alignment::<isize>();
}
}
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2017 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.
//! Explicit endian types useful for embedding in structs or reinterpreting data.
//!
//! Each endian type is guaarnteed to have the same size and alignment as a regular unsigned
//! primitive of the equal size.
//!
//! # Examples
//!
//! ```
//! # use vm_memory::*;
//! let b: Be32 = From::from(3);
//! let l: Le32 = From::from(3);
//!
//! assert_eq!(b.to_native(), 3);
//! assert_eq!(l.to_native(), 3);
//! assert!(b == 3);
//! assert!(l == 3);
//!
//! let b_trans: u32 = unsafe { std::mem::transmute(b) };
//! let l_trans: u32 = unsafe { std::mem::transmute(l) };
//!
//! #[cfg(target_endian = "little")]
//! assert_eq!(l_trans, 3);
//! #[cfg(target_endian = "big")]
//! assert_eq!(b_trans, 3);
//!
//! assert_ne!(b_trans, l_trans);
//! ```
use std::mem::{align_of, size_of};
use bytes::ByteValued;
macro_rules! const_assert {
($condition:expr) => {
let _ = [(); 0 - !$condition as usize];
};
}
macro_rules! endian_type {
($old_type:ident, $new_type:ident, $to_new:ident, $from_new:ident) => {
/// An unsigned integer type of with an explicit endianness.
///
/// See module level documentation for examples.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub struct $new_type($old_type);
impl $new_type {
fn _assert() {
const_assert!(align_of::<$new_type>() == align_of::<$old_type>());
const_assert!(size_of::<$new_type>() == size_of::<$old_type>());
}
/// Converts `self` to the native endianness.
pub fn to_native(self) -> $old_type {
$old_type::$from_new(self.0)
}
}
unsafe impl ByteValued for $new_type {}
impl PartialEq<$old_type> for $new_type {
fn eq(&self, other: &$old_type) -> bool {
self.0 == $old_type::$to_new(*other)
}
}
impl PartialEq<$new_type> for $old_type {
fn eq(&self, other: &$new_type) -> bool {
$old_type::$to_new(other.0) == *self
}
}
impl Into<$old_type> for $new_type {
fn into(self) -> $old_type {
$old_type::$from_new(self.0)
}
}
impl From<$old_type> for $new_type {
fn from(v: $old_type) -> $new_type {
$new_type($old_type::$to_new(v))
}
}
};
}
endian_type!(u16, Le16, to_le, from_le);
endian_type!(u32, Le32, to_le, from_le);
endian_type!(u64, Le64, to_le, from_le);
endian_type!(usize, LeSize, to_le, from_le);
endian_type!(u16, Be16, to_be, from_be);
endian_type!(u32, Be32, to_be, from_be);
endian_type!(u64, Be64, to_be, from_be);
endian_type!(usize, BeSize, to_be, from_be);
#[cfg(test)]
mod tests {
use super::*;
use std::convert::From;
use std::mem::transmute;
#[cfg(target_endian = "little")]
const NATIVE_LITTLE: bool = true;
#[cfg(target_endian = "big")]
const NATIVE_LITTLE: bool = false;
const NATIVE_BIG: bool = !NATIVE_LITTLE;
macro_rules! endian_test {
($old_type:ty, $new_type:ty, $test_name:ident, $native:expr) => {
mod $test_name {
use super::*;
#[allow(overflowing_literals)]
#[test]
fn equality() {
let v = 0x0123_4567_89AB_CDEF as $old_type;
let endian_v: $new_type = From::from(v);
let endian_into: $old_type = endian_v.into();
let endian_transmute: $old_type = unsafe { transmute(endian_v) };
if $native {
assert_eq!(endian_v, endian_transmute);
} else {
assert_eq!(endian_v, endian_transmute.swap_bytes());
}
assert_eq!(v, endian_into);
assert!(v == endian_v);
assert!(endian_v == v);
}
}
};
}
endian_test!(u16, Le16, test_le16, NATIVE_LITTLE);
endian_test!(u32, Le32, test_le32, NATIVE_LITTLE);
endian_test!(u64, Le64, test_le64, NATIVE_LITTLE);
endian_test!(usize, LeSize, test_le_size, NATIVE_LITTLE);
endian_test!(u16, Be16, test_be16, NATIVE_BIG);
endian_test!(u32, Be32, test_be32, NATIVE_BIG);
endian_test!(u64, Be64, test_be64, NATIVE_BIG);
endian_test!(usize, BeSize, test_be_size, NATIVE_BIG);
}
@@ -0,0 +1,515 @@
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
//! Traits to track and access guest's physical memory.
//!
//! To make the abstraction as generic as possible, all the core traits declared here only define
//! methods to access guest's memory, and never define methods to manage (create, delete, insert,
//! remove etc) guest's memory. By this way, the guest memory consumers (virtio device drivers,
//! vhost drivers and boot loaders etc) may be decoupled from the guest memory provider (typically
//! a hypervisor).
//!
//! Traits and Structs
//! - [GuestAddress](struct.GuestAddress.html): represents a guest physical address (GPA).
//! - [MemoryRegionAddress](struct.MemoryRegionAddress.html): represents an offset inside a region.
//! - [GuestMemoryRegion](trait.GuestMemoryRegion.html): represent a continuous region of guest's
//! physical memory.
//! - [GuestMemory](trait.GuestMemroy.html): represent a collection of GuestMemoryRegion objects.
//! The main responsibilities of the GuestMemory trait are:
//! - hide the detail of accessing guest's physical address.
//! - map a request address to a GuestMemoryRegion object and relay the request to it.
//! - handle cases where an access request spanning two or more GuestMemoryRegion objects.
use std::convert::From;
use std::fmt::{self, Display};
use std::io::{self, Read, Write};
use std::ops::{BitAnd, BitOr};
use address::{Address, AddressValue};
use bytes::Bytes;
use volatile_memory;
static MAX_ACCESS_CHUNK: usize = 4096;
/// Errors associated with handling guest memory accesses.
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Error {
/// Failure in finding a guest address in any memory regions mapped by this guest.
InvalidGuestAddress(GuestAddress),
/// Couldn't read/write from the given source.
IOError(io::Error),
/// Incomplete read or write
PartialBuffer { expected: usize, completed: usize },
/// Requested backend address is out of range.
InvalidBackendAddress,
}
impl From<volatile_memory::Error> for Error {
fn from(e: volatile_memory::Error) -> Self {
match e {
volatile_memory::Error::OutOfBounds { .. } => Error::InvalidBackendAddress,
volatile_memory::Error::Overflow { .. } => Error::InvalidBackendAddress,
volatile_memory::Error::IOError(e) => Error::IOError(e),
volatile_memory::Error::PartialBuffer {
expected,
completed,
} => Error::PartialBuffer {
expected,
completed,
},
}
}
}
/// Result of guest memory operations
pub type Result<T> = std::result::Result<T, Error>;
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Guest memory error: ")?;
match self {
Error::InvalidGuestAddress(addr) => {
write!(f, "invalid guest address {}", addr.raw_value())
}
Error::IOError(error) => write!(f, "{}", error),
Error::PartialBuffer {
expected,
completed,
} => write!(
f,
"only used {} bytes in {} long buffer",
completed, expected,
),
Error::InvalidBackendAddress => write!(f, "invalid backend address"),
}
}
}
/// Represents a guest physical address (GPA).
///
/// Notes:
/// - On ARM64, a 32-bit hypervisor may be used to support a 64-bit guest. For simplicity,
/// u64 is used to store the the raw value no matter if the guest a 32-bit or 64-bit virtual
/// machine.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct GuestAddress(pub u64);
impl_address_ops!(GuestAddress, u64);
/// Represents an offset inside a region.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct MemoryRegionAddress(pub u64);
impl_address_ops!(MemoryRegionAddress, u64);
/// Type of the raw value stored in a GuestAddress object.
pub type GuestUsize = <GuestAddress as AddressValue>::V;
/// Represents a continuous region of guest physical memory.
#[allow(clippy::len_without_is_empty)]
pub trait GuestMemoryRegion: Bytes<MemoryRegionAddress, E = Error> {
/// Get the size of the region.
fn len(&self) -> GuestUsize;
/// Get minimum (inclusive) address managed by the region.
fn start_addr(&self) -> GuestAddress;
/// Get maximum (inclusive) address managed by the region.
fn end_addr(&self) -> GuestAddress {
// unchecked_add is safe as the region bounds were checked when it was created.
self.start_addr().unchecked_add(self.len() - 1)
}
/// Returns the given address if it is within the memory range accessible
/// through this region.
fn check_address(&self, addr: MemoryRegionAddress) -> Option<MemoryRegionAddress> {
if self.address_in_range(addr) {
Some(addr)
} else {
None
}
}
/// Returns true if the given address is within the memory range accessible
/// through this region.
fn address_in_range(&self, addr: MemoryRegionAddress) -> bool {
addr.raw_value() < self.len()
}
/// Returns the address plus the offset if it is in range.
fn checked_offset(
&self,
base: MemoryRegionAddress,
offset: usize,
) -> Option<MemoryRegionAddress> {
base.checked_add(offset as u64)
.and_then(|addr| self.check_address(addr))
}
/// Convert an absolute address into an address space (GuestMemory)
/// to a relative address within this region, or return an error if
/// it is out of bounds.
fn to_region_addr(&self, addr: GuestAddress) -> Option<MemoryRegionAddress> {
addr.checked_offset_from(self.start_addr())
.and_then(|offset| self.check_address(MemoryRegionAddress(offset)))
}
/// Return a slice corresponding to the data in the region; unsafe because of
/// possible aliasing. Return None if the region does not support slice-based
/// access.
unsafe fn as_slice(&self) -> Option<&[u8]> {
None
}
/// Return a mutable slice corresponding to the data in the region; unsafe because of
/// possible aliasing. Return None if the region does not support slice-based
/// access.
unsafe fn as_mut_slice(&self) -> Option<&mut [u8]> {
None
}
}
/// Represents a container for a collection of GuestMemoryRegion objects.
///
/// The main responsibilities of the GuestMemory trait are:
/// - hide the detail of accessing guest's physical address.
/// - map a request address to a GuestMemoryRegion object and relay the request to it.
/// - handle cases where an access request spanning two or more GuestMemoryRegion objects.
///
/// Note: all regions in a GuestMemory object must not intersect with each other.
pub trait GuestMemory {
/// Type of objects hosted by the address space.
type R: GuestMemoryRegion;
/// Returns the number of regions in the collection.
fn num_regions(&self) -> usize;
/// Return the region containing the specified address or None.
fn find_region(&self, addr: GuestAddress) -> Option<&Self::R>;
/// Perform the specified action on each region.
/// It only walks children of current region and do not step into sub regions.
fn with_regions<F, E>(&self, cb: F) -> std::result::Result<(), E>
where
F: Fn(usize, &Self::R) -> std::result::Result<(), E>;
/// Perform the specified action on each region mutably.
/// It only walks children of current region and do not step into sub regions.
fn with_regions_mut<F, E>(&self, cb: F) -> std::result::Result<(), E>
where
F: FnMut(usize, &Self::R) -> std::result::Result<(), E>;
/// Applies two functions, specified as callbacks, on the inner memory regions.
///
/// # Arguments
/// * `init` - Starting value of the accumulator for the `foldf` function.
/// * `mapf` - "Map" function, applied to all the inner memory regions. It returns an array of
/// the same size as the memory regions array, containing the function's results
/// for each region.
/// * `foldf` - "Fold" function, applied to the array returned by `mapf`. It acts as an
/// operator, applying itself to the `init` value and to each subsequent elemnent
/// in the array returned by `mapf`.
///
/// # Examples
///
/// * Compute the total size of all memory mappings in KB by iterating over the memory regions
/// and dividing their sizes to 1024, then summing up the values in an accumulator.
///
/// ```
/// # #[cfg(feature = "backend-mmap")]
/// # fn test_map_fold() -> Result<(), ()> {
/// # use vm_memory::{GuestAddress, GuestMemory, GuestMemoryRegion, mmap::GuestMemoryMmap};
/// let start_addr1 = GuestAddress(0x0);
/// let start_addr2 = GuestAddress(0x400);
/// let mem = GuestMemoryMmap::new(&vec![(start_addr1, 1024), (start_addr2, 2048)]).unwrap();
/// let total_size = mem.map_and_fold(
/// 0,
/// |(_, region)| region.len() / 1024,
/// |acc, size| acc + size
/// );
/// println!("Total memory size = {} KB", total_size);
/// Ok(())
/// # }
/// ```
fn map_and_fold<F, G, T>(&self, init: T, mapf: F, foldf: G) -> T
where
F: Fn((usize, &Self::R)) -> T,
G: Fn(T, T) -> T;
/// Get maximum (inclusive) address managed by the region.
fn end_addr(&self) -> GuestAddress {
self.map_and_fold(
GuestAddress(0),
|(_, region)| region.end_addr(),
std::cmp::max,
)
}
/// Convert an absolute address into an address space (GuestMemory)
/// to a relative address within this region, or return None if
/// it is out of bounds.
fn to_region_addr(&self, addr: GuestAddress) -> Option<(&Self::R, MemoryRegionAddress)> {
self.find_region(addr)
.map(|r| (r, r.to_region_addr(addr).unwrap()))
}
/// Returns true if the given address is within the memory range available to the guest.
fn address_in_range(&self, addr: GuestAddress) -> bool {
self.find_region(addr).is_some()
}
/// Returns the given address if it is within the memory range available to the guest.
fn check_address(&self, addr: GuestAddress) -> Option<GuestAddress> {
self.find_region(addr).map(|_| addr)
}
/// Returns the address plus the offset if it is in range.
fn checked_offset(&self, base: GuestAddress, offset: usize) -> Option<GuestAddress> {
base.checked_add(offset as u64)
.and_then(|addr| self.check_address(addr))
}
/// Invoke callback `f` to handle data in the address range [addr, addr + count).
///
/// The address range [addr, addr + count) may span more than one GuestMemoryRegion objects, or
/// even has holes within it. So try_access() invokes the callback 'f' for each GuestMemoryRegion
/// object involved and returns:
/// - error code returned by the callback 'f'
/// - size of data already handled when encountering the first hole
/// - size of data already handled when the whole range has been handled
fn try_access<F>(&self, count: usize, addr: GuestAddress, mut f: F) -> Result<usize>
where
F: FnMut(usize, usize, MemoryRegionAddress, &Self::R) -> Result<usize>,
{
let mut cur = addr;
let mut total = 0;
while let Some(region) = self.find_region(cur) {
let start = region.to_region_addr(cur).unwrap();
let cap = region.len() as usize;
let len = std::cmp::min(cap, count - total);
match f(total, len, start, region) {
// no more data
Ok(0) => break,
// made some progress
Ok(len) => {
total += len;
if total == count {
break;
}
cur = match cur.overflowing_add(len as GuestUsize) {
(GuestAddress(0), _) => GuestAddress(0),
(result, false) => result,
(_, true) => panic!("guest address overflow"),
}
}
// error happened
e => return e,
}
}
if total == 0 {
Err(Error::InvalidGuestAddress(addr))
} else {
Ok(total)
}
}
}
impl<T: GuestMemory> Bytes<GuestAddress> for T {
type E = Error;
fn write(&self, buf: &[u8], addr: GuestAddress) -> Result<usize> {
self.try_access(
buf.len(),
addr,
|offset, _count, caddr, region| -> Result<usize> {
region.write(&buf[offset as usize..], caddr)
},
)
}
fn read(&self, buf: &mut [u8], addr: GuestAddress) -> Result<usize> {
self.try_access(
buf.len(),
addr,
|offset, _count, caddr, region| -> Result<usize> {
region.read(&mut buf[offset as usize..], caddr)
},
)
}
fn write_slice(&self, buf: &[u8], addr: GuestAddress) -> Result<()> {
let res = self.write(buf, addr)?;
if res != buf.len() {
return Err(Error::PartialBuffer {
expected: buf.len(),
completed: res,
});
}
Ok(())
}
fn read_slice(&self, buf: &mut [u8], addr: GuestAddress) -> Result<()> {
let res = self.read(buf, addr)?;
if res != buf.len() {
return Err(Error::PartialBuffer {
expected: buf.len(),
completed: res,
});
}
Ok(())
}
fn read_from<F>(&self, addr: GuestAddress, src: &mut F, count: usize) -> Result<usize>
where
F: Read,
{
self.try_access(count, addr, |offset, len, caddr, region| -> Result<usize> {
// Check if something bad happened before doing unsafe things.
assert!(offset < count);
if let Some(dst) = unsafe { region.as_mut_slice() } {
// This is safe cause `start` and `len` are within the `region`.
let start = caddr.raw_value() as usize;
let end = start + len;
src.read_exact(&mut dst[start..end])
.map_err(Error::IOError)?;
Ok(len)
} else {
let len = std::cmp::min(len, MAX_ACCESS_CHUNK);
let mut buf = vec![0u8; len].into_boxed_slice();
let bytes_read = src.read(&mut buf[..]).map_err(Error::IOError)?;
let bytes_written = region.write(&buf[0..bytes_read], caddr)?;
assert_eq!(bytes_written, bytes_read);
Ok(len)
}
})
}
fn read_exact_from<F>(&self, addr: GuestAddress, src: &mut F, count: usize) -> Result<()>
where
F: Read,
{
let res = self.read_from(addr, src, count)?;
if res != count {
return Err(Error::PartialBuffer {
expected: count,
completed: res,
});
}
Ok(())
}
fn write_to<F>(&self, addr: GuestAddress, dst: &mut F, count: usize) -> Result<usize>
where
F: Write,
{
self.try_access(count, addr, |offset, len, caddr, region| -> Result<usize> {
// Check if something bad happened before doing unsafe things.
assert!(offset < count);
if let Some(src) = unsafe { region.as_slice() } {
// This is safe cause `start` and `len` are within the `region`.
let start = caddr.raw_value() as usize;
let end = start + len;
// It is safe to read from volatile memory. Accessing the guest
// memory as a slice is OK because nothing assumes another thread
// won't change what is loaded.
let bytes_written = dst.write(&src[start..end]).map_err(Error::IOError)?;
Ok(bytes_written)
} else {
let len = std::cmp::min(len, MAX_ACCESS_CHUNK);
let mut buf = vec![0u8; len].into_boxed_slice();
let bytes_read = region.read(&mut buf, caddr)?;
assert_eq!(bytes_read, len);
let bytes_written = dst.write(&buf).map_err(Error::IOError)?;
Ok(bytes_written)
}
})
}
fn write_all_to<F>(&self, addr: GuestAddress, dst: &mut F, count: usize) -> Result<()>
where
F: Write,
{
let res = self.write_to(addr, dst, count)?;
if res != count {
return Err(Error::PartialBuffer {
expected: count,
completed: res,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn offset_from() {
let base = GuestAddress(0x100);
let addr = GuestAddress(0x150);
assert_eq!(addr.unchecked_offset_from(base), 0x50u64);
assert_eq!(addr.checked_offset_from(base), Some(0x50u64));
assert_eq!(base.checked_offset_from(addr), None);
}
#[test]
fn equals() {
let a = GuestAddress(0x300);
let b = GuestAddress(0x300);
let c = GuestAddress(0x301);
assert_eq!(a, GuestAddress(a.raw_value()));
assert_eq!(a, b);
assert_eq!(b, a);
assert_ne!(a, c);
assert_ne!(c, a);
}
#[test]
#[allow(clippy::eq_op)]
fn cmp() {
let a = GuestAddress(0x300);
let b = GuestAddress(0x301);
assert!(a < b);
assert!(b > a);
assert!(!(a < a));
}
#[test]
fn mask() {
let a = GuestAddress(0x5050);
assert_eq!(GuestAddress(0x5000), a & 0xff00u64);
assert_eq!(0x5000, a.mask(0xff00u64));
assert_eq!(GuestAddress(0x5055), a | 0x0005u64);
}
#[test]
fn add_sub() {
let a = GuestAddress(0x50);
let b = GuestAddress(0x60);
assert_eq!(Some(GuestAddress(0xb0)), a.checked_add(0x60));
assert_eq!(0x10, b.unchecked_offset_from(a));
}
#[test]
fn checked_add_overflow() {
let a = GuestAddress(0xffff_ffff_ffff_ff55);
assert_eq!(Some(GuestAddress(0xffff_ffff_ffff_ff57)), a.checked_add(2));
assert!(a.checked_add(0xf0).is_none());
}
#[test]
fn checked_sub_underflow() {
let a = GuestAddress(0xff);
assert_eq!(Some(GuestAddress(0x0f)), a.checked_sub(0xf0));
assert!(a.checked_sub(0xffff).is_none());
}
}
+57
View File
@@ -0,0 +1,57 @@
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
//! Traits for allocating, handling and interacting with the VM's physical memory.
//!
//! For a typical hypervisor, there are seveval components, such as boot loader, virtual device
//! drivers, virtio backend drivers and vhost drivers etc, that need to access VM's physical memory.
//! This crate aims to provide a set of stable traits to decouple VM memory consumers from VM
//! memory providers. Based on these traits, VM memory consumers could access VM's physical memory
//! without knowing the implementation details of the VM memory provider. Thus hypervisor
//! components, such as boot loader, virtual device drivers, virtio backend drivers and vhost
//! drivers etc, could be shared and reused by multiple hypervisors.
#![deny(missing_docs)]
extern crate libc;
#[cfg(test)]
#[macro_use]
extern crate matches;
#[macro_use]
pub mod address;
pub use address::{Address, AddressValue};
pub mod bytes;
pub use bytes::{ByteValued, Bytes};
pub mod endian;
pub use endian::{Be16, Be32, Be64, BeSize, Le16, Le32, Le64, LeSize};
pub mod guest_memory;
pub use guest_memory::{
Error as GuestMemoryError, GuestAddress, GuestMemory, GuestMemoryRegion, GuestUsize,
MemoryRegionAddress, Result as GuestMemoryResult,
};
#[cfg(all(feature = "backend-mmap", unix))]
mod mmap_unix;
#[cfg(all(feature = "backend-mmap", windows))]
mod mmap_windows;
#[cfg(feature = "backend-mmap")]
pub mod mmap;
#[cfg(feature = "backend-mmap")]
pub use mmap::{GuestMemoryMmap, GuestRegionMmap, MmapError, MmapRegion};
pub mod volatile_memory;
pub use volatile_memory::{
Error as VolatileMemoryError, Result as VolatileMemoryResult, VolatileMemory, VolatileRef,
VolatileSlice,
};
+681
View File
@@ -0,0 +1,681 @@
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
//! A default implementation of the GuestMemory trait by mmap()-ing guest's memory into the current
//! process.
//!
//! The main structs to access guest's memory are:
//! - [MmapRegion](struct.MmapRegion.html): mmap a continuous region of guest's memory into the
//! current process
//! - [GuestRegionMmap](struct.GuestRegionMmap.html): tracks a mapping of memory in the current
//! process and the corresponding base address. It relays guest memory access requests to the
//! underline [MmapRegion](struct.MmapRegion.html) object.
//! - [GuestMemoryMmap](struct.GuestMemoryMmap.html): provides methods to access a collection of
//! GuestRegionMmap objects.
use std::io::{self, Read, Write};
use std::ops::Deref;
use std::sync::Arc;
use address::Address;
use guest_memory::*;
use volatile_memory::VolatileMemory;
use Bytes;
#[cfg(unix)]
pub use mmap_unix::MmapRegion;
#[cfg(windows)]
pub use mmap_windows::MmapRegion;
// For MmapRegion
pub(crate) trait AsSlice {
unsafe fn as_slice(&self) -> &[u8];
#[allow(clippy::mut_from_ref)]
unsafe fn as_mut_slice(&self) -> &mut [u8];
}
/// Errors that can happen when creating a memory map
#[derive(Debug)]
pub enum MmapError {
/// Syscall returned the given error.
SystemCallFailed(io::Error),
/// No memory region found.
NoMemoryRegion,
/// Some of the memory regions intersect with each other.
MemoryRegionOverlap,
}
/// Tracks a mapping of memory in the current process and the corresponding base address
/// in the guest's memory space.
#[derive(Debug)]
pub struct GuestRegionMmap {
mapping: MmapRegion,
guest_base: GuestAddress,
}
impl GuestRegionMmap {
/// Create a new memory-mapped memory region for guest's physical memory.
/// Note: caller needs to ensure that (mapping.len() + guest_base) doesn't wrapping around.
pub fn new(mapping: MmapRegion, guest_base: GuestAddress) -> Self {
GuestRegionMmap {
mapping,
guest_base,
}
}
/// Convert an absolute address into an address space (GuestMemory)
/// to a host pointer, or return None if it is out of bounds.
pub fn get_host_address(&self, addr: MemoryRegionAddress) -> Option<*mut u8> {
// Not sure why wrapping_offset is not unsafe. Anyway this
// is safe because we've just range-checked addr using check_address.
self.check_address(addr)
.map(|addr| self.as_ptr().wrapping_offset(addr.raw_value() as isize))
}
}
impl Deref for GuestRegionMmap {
type Target = MmapRegion;
fn deref(&self) -> &MmapRegion {
&self.mapping
}
}
impl Bytes<MemoryRegionAddress> for GuestRegionMmap {
type E = Error;
/// # Examples
/// * Write a slice at guest address 0x1200.
///
/// ```
/// # use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
/// # let start_addr = GuestAddress(0x1000);
/// # let mut gm = GuestMemoryMmap::new(&vec![(start_addr, 0x400)]).unwrap();
/// let res = gm.write(&[1,2,3,4,5], GuestAddress(0x1200)).unwrap();
/// assert_eq!(5, res);
/// ```
fn write(&self, buf: &[u8], addr: MemoryRegionAddress) -> Result<usize> {
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.write(buf, maddr)
.map_err(Into::into)
}
/// # Examples
/// * Read a slice of length 16 at guestaddress 0x1200.
///
/// ```
/// # use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
/// # let start_addr = GuestAddress(0x1000);
/// # let mut gm = GuestMemoryMmap::new(&vec![(start_addr, 0x400)]).unwrap();
/// let buf = &mut [0u8; 16];
/// let res = gm.read(buf, GuestAddress(0x1200)).unwrap();
/// assert_eq!(16, res);
/// ```
fn read(&self, buf: &mut [u8], addr: MemoryRegionAddress) -> Result<usize> {
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.read(buf, maddr)
.map_err(Into::into)
}
fn write_slice(&self, buf: &[u8], addr: MemoryRegionAddress) -> Result<()> {
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.write_slice(buf, maddr)
.map_err(Into::into)
}
fn read_slice(&self, buf: &mut [u8], addr: MemoryRegionAddress) -> Result<()> {
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.read_slice(buf, maddr)
.map_err(Into::into)
}
/// # Examples
///
/// * Read bytes from /dev/urandom
///
/// ```
/// # use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
/// # use std::fs::File;
/// # use std::path::Path;
/// # let start_addr = GuestAddress(0x1000);
/// # let gm = GuestMemoryMmap::new(&vec![(start_addr, 0x400)]).unwrap();
/// let mut file = if cfg!(unix) {
/// File::open(Path::new("/dev/urandom")).unwrap()
/// } else {
/// File::open(Path::new("c:\\Windows\\system32\\ntoskrnl.exe")).unwrap()
/// };
/// let addr = GuestAddress(0x1010);
/// gm.read_from(addr, &mut file, 128).unwrap();
/// let read_addr = addr.checked_add(8).unwrap();
/// let _: u32 = gm.read_obj(read_addr).unwrap();
/// ```
fn read_from<F>(&self, addr: MemoryRegionAddress, src: &mut F, count: usize) -> Result<usize>
where
F: Read,
{
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.read_from::<F>(maddr, src, count)
.map_err(Into::into)
}
/// # Examples
///
/// * Read bytes from /dev/urandom
///
/// ```
/// # extern crate tempfile;
/// # use self::tempfile::tempfile;
/// # use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
/// # use std::fs::File;
/// # use std::path::Path;
/// # let start_addr = GuestAddress(0x1000);
/// # let gm = GuestMemoryMmap::new(&vec![(start_addr, 0x400)]).unwrap();
/// let mut file = if cfg!(unix) {
/// File::open(Path::new("/dev/urandom")).unwrap()
/// } else {
/// File::open(Path::new("c:\\Windows\\system32\\ntoskrnl.exe")).unwrap()
/// };
/// let addr = GuestAddress(0x1010);
/// gm.read_exact_from(addr, &mut file, 128).unwrap();
/// let read_addr = addr.checked_add(8).unwrap();
/// let _: u32 = gm.read_obj(read_addr).unwrap();
/// ```
fn read_exact_from<F>(&self, addr: MemoryRegionAddress, src: &mut F, count: usize) -> Result<()>
where
F: Read,
{
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.read_exact_from::<F>(maddr, src, count)
.map_err(Into::into)
}
/// Writes data from the region to a writable object.
///
/// # Examples
///
/// * Write 128 bytes to a temp file
///
/// ```
/// # extern crate tempfile;
/// # use self::tempfile::tempfile;
/// # use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
/// # use std::fs::OpenOptions;
/// # let start_addr = GuestAddress(0x1000);
/// # let gm = GuestMemoryMmap::new(&vec![(start_addr, 0x400)]).unwrap();
/// let mut file = tempfile().unwrap();
/// let mut mem = [0u8; 1024];
/// gm.write_to(start_addr, &mut file, 128).unwrap();
/// ```
fn write_to<F>(&self, addr: MemoryRegionAddress, dst: &mut F, count: usize) -> Result<usize>
where
F: Write,
{
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.write_to::<F>(maddr, dst, count)
.map_err(Into::into)
}
/// Writes data from the region to a writable object.
///
/// # Examples
///
/// * Write 128 bytes to a temp file
///
/// ```
/// # extern crate tempfile;
/// # use self::tempfile::tempfile;
/// # use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryMmap};
/// # use std::fs::OpenOptions;
/// # let start_addr = GuestAddress(0x1000);
/// # let gm = GuestMemoryMmap::new(&vec![(start_addr, 0x400)]).unwrap();
/// let mut file = tempfile().unwrap();
/// let mut mem = [0u8; 1024];
/// gm.write_all_to(start_addr, &mut file, 128).unwrap();
/// ```
fn write_all_to<F>(&self, addr: MemoryRegionAddress, dst: &mut F, count: usize) -> Result<()>
where
F: Write,
{
let maddr = addr.raw_value() as usize;
self.as_volatile_slice()
.write_all_to::<F>(maddr, dst, count)
.map_err(Into::into)
}
}
impl GuestMemoryRegion for GuestRegionMmap {
fn len(&self) -> GuestUsize {
self.mapping.len() as GuestUsize
}
fn start_addr(&self) -> GuestAddress {
self.guest_base
}
unsafe fn as_slice(&self) -> Option<&[u8]> {
Some(self.mapping.as_slice())
}
unsafe fn as_mut_slice(&self) -> Option<&mut [u8]> {
Some(self.mapping.as_mut_slice())
}
}
/// Tracks memory regions allocated/mapped for the guest in the current process.
#[derive(Clone, Debug)]
pub struct GuestMemoryMmap {
regions: Arc<Vec<GuestRegionMmap>>,
}
impl GuestMemoryMmap {
/// Creates a container and allocates anonymous memory for guest memory regions.
/// Valid memory regions are specified as a Vec of (Address, Size) tuples sorted by Address.
pub fn new(ranges: &[(GuestAddress, usize)]) -> std::result::Result<Self, MmapError> {
if ranges.is_empty() {
return Err(MmapError::NoMemoryRegion);
}
let mut regions = Vec::<GuestRegionMmap>::new();
for range in ranges.iter() {
if let Some(last) = regions.last() {
if last
.guest_base
.checked_add(last.mapping.len() as GuestUsize)
.map_or(true, |a| a > range.0)
{
return Err(MmapError::MemoryRegionOverlap);
}
}
let mapping = MmapRegion::new(range.1).map_err(MmapError::SystemCallFailed)?;
regions.push(GuestRegionMmap {
mapping,
guest_base: range.0,
});
}
Ok(Self {
regions: Arc::new(regions),
})
}
/// Creates a container and adds an existing set of mappings to it.
pub fn from_regions(ranges: Vec<GuestRegionMmap>) -> std::result::Result<Self, MmapError> {
if ranges.is_empty() {
return Err(MmapError::NoMemoryRegion);
}
for rangei in 1..ranges.len() {
let range = &ranges[rangei];
let last = &ranges[rangei - 1];
if last
.guest_base
.checked_add(last.mapping.len() as GuestUsize)
.map_or(true, |a| a > range.start_addr())
{
return Err(MmapError::MemoryRegionOverlap);
}
}
Ok(Self {
regions: Arc::new(ranges),
})
}
/// Convert an absolute address into an address space (GuestMemory)
/// to a host pointer, or return None if it is out of bounds.
pub fn get_host_address(&self, addr: GuestAddress) -> Option<*mut u8> {
self.to_region_addr(addr)
.and_then(|(r, addr)| r.get_host_address(addr))
}
}
impl GuestMemory for GuestMemoryMmap {
type R = GuestRegionMmap;
fn num_regions(&self) -> usize {
self.regions.len()
}
fn find_region(&self, addr: GuestAddress) -> Option<&GuestRegionMmap> {
for region in self.regions.iter() {
if addr >= region.start_addr() && addr <= region.end_addr() {
return Some(region);
}
}
None
}
fn with_regions<F, E>(&self, cb: F) -> std::result::Result<(), E>
where
F: Fn(usize, &Self::R) -> std::result::Result<(), E>,
{
for (index, region) in self.regions.iter().enumerate() {
cb(index, region)?;
}
Ok(())
}
fn with_regions_mut<F, E>(&self, mut cb: F) -> std::result::Result<(), E>
where
F: FnMut(usize, &Self::R) -> std::result::Result<(), E>,
{
for (index, region) in self.regions.iter().enumerate() {
cb(index, region)?;
}
Ok(())
}
fn map_and_fold<F, G, T>(&self, init: T, mapf: F, foldf: G) -> T
where
F: Fn((usize, &Self::R)) -> T,
G: Fn(T, T) -> T,
{
self.regions.iter().enumerate().map(mapf).fold(init, foldf)
}
}
#[cfg(test)]
mod tests {
extern crate tempfile;
use self::tempfile::tempfile;
use super::*;
use std::fs::File;
use std::mem;
use std::path::Path;
use Bytes;
#[test]
fn basic_map() {
let m = MmapRegion::new(1024).unwrap();
assert_eq!(1024, m.len());
}
#[test]
fn map_invalid_size() {
let e = MmapRegion::new(0).unwrap_err();
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
}
#[test]
fn slice_addr() {
let m = MmapRegion::new(5).unwrap();
let s = m.get_slice(2, 3).unwrap();
assert_eq!(s.as_ptr(), unsafe { m.as_ptr().offset(2) });
}
#[test]
fn mapped_file_read() {
let mut f = tempfile().unwrap();
let sample_buf = &[1, 2, 3, 4, 5];
assert!(f.write_all(sample_buf).is_ok());
let mem_map = MmapRegion::from_fd(&f, sample_buf.len(), 0).unwrap();
let buf = &mut [0u8; 16];
assert_eq!(
mem_map.as_volatile_slice().read(buf, 0).unwrap(),
sample_buf.len()
);
assert_eq!(buf[0..sample_buf.len()], sample_buf[..]);
}
#[test]
fn test_regions() {
// No regions provided should return error.
assert_eq!(
format!("{:?}", GuestMemoryMmap::new(&[]).err().unwrap()),
format!("{:?}", MmapError::NoMemoryRegion)
);
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x800);
let guest_mem =
GuestMemoryMmap::new(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert_eq!(guest_mem.num_regions(), 2);
assert_eq!(guest_mem.end_addr(), GuestAddress(0xbff));
assert!(guest_mem.find_region(GuestAddress(0x200)).is_some());
assert!(guest_mem.find_region(GuestAddress(0x600)).is_none());
assert!(guest_mem.find_region(GuestAddress(0xa00)).is_some());
assert!(guest_mem.find_region(GuestAddress(0xc00)).is_none());
}
#[test]
fn test_address_in_range() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x800);
let guest_mem =
GuestMemoryMmap::new(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert!(guest_mem.address_in_range(GuestAddress(0x200)));
assert!(!guest_mem.address_in_range(GuestAddress(0x600)));
assert!(guest_mem.address_in_range(GuestAddress(0xa00)));
assert!(!guest_mem.address_in_range(GuestAddress(0xc00)));
}
#[test]
fn test_check_address() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x800);
let guest_mem =
GuestMemoryMmap::new(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert_eq!(
guest_mem.check_address(GuestAddress(0x200)),
Some(GuestAddress(0x200))
);
assert_eq!(guest_mem.check_address(GuestAddress(0x600)), None);
assert_eq!(
guest_mem.check_address(GuestAddress(0xa00)),
Some(GuestAddress(0xa00))
);
assert_eq!(guest_mem.check_address(GuestAddress(0xc00)), None);
}
#[test]
fn test_to_region_addr() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x800);
let guest_mem =
GuestMemoryMmap::new(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert!(guest_mem.to_region_addr(GuestAddress(0x600)).is_none());
let (r0, addr0) = guest_mem.to_region_addr(GuestAddress(0x800)).unwrap();
let (r1, addr1) = guest_mem.to_region_addr(GuestAddress(0xa00)).unwrap();
assert!(r0.as_ptr() == r1.as_ptr());
assert_eq!(addr0, MemoryRegionAddress(0));
assert_eq!(addr1, MemoryRegionAddress(0x200));
}
#[test]
fn test_get_host_address() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x800);
let guest_mem =
GuestMemoryMmap::new(&[(start_addr1, 0x400), (start_addr2, 0x400)]).unwrap();
assert!(guest_mem.get_host_address(GuestAddress(0x600)).is_none());
let ptr0 = guest_mem.get_host_address(GuestAddress(0x800)).unwrap();
let ptr1 = guest_mem.get_host_address(GuestAddress(0xa00)).unwrap();
assert_eq!(
ptr0,
guest_mem.find_region(GuestAddress(0x800)).unwrap().as_ptr()
);
assert_eq!(unsafe { ptr0.offset(0x200) }, ptr1);
}
#[test]
fn test_deref() {
let start_addr = GuestAddress(0x0);
let guest_mem = GuestMemoryMmap::new(&[(start_addr, 0x400)]).unwrap();
let sample_buf = &[1, 2, 3, 4, 5];
assert_eq!(guest_mem.write(sample_buf, start_addr).unwrap(), 5);
let slice = guest_mem
.find_region(GuestAddress(0))
.unwrap()
.as_volatile_slice();
let buf = &mut [0, 0, 0, 0, 0];
assert_eq!(slice.read(buf, 0).unwrap(), 5);
assert_eq!(buf, sample_buf);
}
#[test]
fn mapped_file_regions() {
let mut f = tempfile().unwrap();
let empty_buf = &[0; 16384];
assert!(f.write_all(empty_buf).is_ok());
let mem_map = MmapRegion::from_fd(&f, empty_buf.len(), 0).unwrap();
let guest_reg = GuestRegionMmap::new(mem_map, GuestAddress(0x8000));
let mut region_vec = Vec::new();
region_vec.push(guest_reg);
let guest_mem = GuestMemoryMmap::from_regions(region_vec).unwrap();
assert_eq!(guest_mem.num_regions(), 1);
assert!(guest_mem.find_region(GuestAddress(0)).is_none());
assert!(guest_mem.find_region(GuestAddress(0x8000)).is_some());
}
#[test]
fn overlap_memory() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let res = GuestMemoryMmap::new(&[(start_addr1, 0x2000), (start_addr2, 0x2000)]);
assert_eq!(
format!("{:?}", res.err().unwrap()),
format!("{:?}", MmapError::MemoryRegionOverlap)
);
}
#[test]
fn test_read_u64() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let bad_addr = GuestAddress(0x2001);
let bad_addr2 = GuestAddress(0x1ffc);
let max_addr = GuestAddress(0x2000);
let gm = GuestMemoryMmap::new(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let val1: u64 = 0xaa55_aa55_aa55_aa55;
let val2: u64 = 0x55aa_55aa_55aa_55aa;
assert_eq!(
format!("{:?}", gm.write_obj(val1, bad_addr).err().unwrap()),
format!("InvalidGuestAddress({:?})", bad_addr,)
);
assert_eq!(
format!("{:?}", gm.write_obj(val1, bad_addr2).err().unwrap()),
format!(
"PartialBuffer {{ expected: {:?}, completed: {:?} }}",
mem::size_of::<u64>(),
max_addr.checked_offset_from(bad_addr2).unwrap()
)
);
gm.write_obj(val1, GuestAddress(0x500)).unwrap();
gm.write_obj(val2, GuestAddress(0x1000 + 32)).unwrap();
let num1: u64 = gm.read_obj(GuestAddress(0x500)).unwrap();
let num2: u64 = gm.read_obj(GuestAddress(0x1000 + 32)).unwrap();
assert_eq!(val1, num1);
assert_eq!(val2, num2);
}
#[test]
fn write_and_read() {
let mut start_addr = GuestAddress(0x1000);
let gm = GuestMemoryMmap::new(&[(start_addr, 0x400)]).unwrap();
let sample_buf = &[1, 2, 3, 4, 5];
assert_eq!(gm.write(sample_buf, start_addr).unwrap(), 5);
let buf = &mut [0u8; 5];
assert_eq!(gm.read(buf, start_addr).unwrap(), 5);
assert_eq!(buf, sample_buf);
start_addr = GuestAddress(0x13ff);
assert_eq!(gm.write(sample_buf, start_addr).unwrap(), 1);
assert_eq!(gm.read(buf, start_addr).unwrap(), 1);
assert_eq!(buf[0], sample_buf[0]);
}
#[test]
fn read_to_and_write_from_mem() {
let gm = GuestMemoryMmap::new(&[(GuestAddress(0x1000), 0x400)]).unwrap();
let addr = GuestAddress(0x1010);
let mut file = if cfg!(unix) {
File::open(Path::new("/dev/zero")).unwrap()
} else {
File::open(Path::new("c:\\Windows\\system32\\ntoskrnl.exe")).unwrap()
};
gm.write_obj(!0u32, addr).unwrap();
gm.read_exact_from(addr, &mut file, mem::size_of::<u32>())
.unwrap();
let value: u32 = gm.read_obj(addr).unwrap();
if cfg!(unix) {
assert_eq!(value, 0);
} else {
assert_eq!(value, 0x0090_5a4d);
}
let mut sink = Vec::new();
gm.write_all_to(addr, &mut sink, mem::size_of::<u32>())
.unwrap();
if cfg!(unix) {
assert_eq!(sink, vec![0; mem::size_of::<u32>()]);
} else {
assert_eq!(sink, vec![0x4d, 0x5a, 0x90, 0x00]);
};
}
#[test]
fn create_vec_with_regions() {
let region_size = 0x400;
let regions = vec![
(GuestAddress(0x0), region_size),
(GuestAddress(0x1000), region_size),
];
let mut iterated_regions = Vec::new();
let gm = GuestMemoryMmap::new(&regions).unwrap();
let res: Result<()> = gm.with_regions(|_, region| {
assert_eq!(region.len(), region_size as GuestUsize);
Ok(())
});
assert!(res.is_ok());
let res: Result<()> = gm.with_regions_mut(|_, region| {
iterated_regions.push((region.start_addr(), region.len() as usize));
Ok(())
});
assert!(res.is_ok());
assert_eq!(regions, iterated_regions);
assert_eq!(gm.clone().regions[0].guest_base, regions[0].0);
assert_eq!(gm.clone().regions[1].guest_base, regions[1].0);
}
#[test]
fn test_access_cross_boundary() {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
let gm = GuestMemoryMmap::new(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap();
let sample_buf = &[1, 2, 3, 4, 5];
assert_eq!(gm.write(sample_buf, GuestAddress(0xffc)).unwrap(), 5);
let buf = &mut [0u8; 5];
assert_eq!(gm.read(buf, GuestAddress(0xffc)).unwrap(), 5);
assert_eq!(buf, sample_buf);
}
}
+168
View File
@@ -0,0 +1,168 @@
// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Portions Copyright 2017 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 THIRD-PARTY file.
//! A default Unix implementation of the GuestMemory trait by mmap()-ing guest's memory into
//! the current process.
//!
//! The main structs to access guest's memory are:
//! - [MmapRegion](struct.MmapRegion.html): mmap a continuous region of guest's memory into the
//! current process
//! - [GuestRegionMmap](struct.GuestRegionMmap.html): tracks a mapping of memory in the current
//! process and the corresponding base address. It relays guest memory access requests to the
//! underline [MmapRegion](struct.MmapRegion.html) object.
//! - [GuestMemoryMmap](struct.GuestMemoryMmap.html): provides methods to access a collection of
//! GuestRegionMmap objects.
use libc;
use std::io;
use std::ptr::null_mut;
use mmap::AsSlice;
use volatile_memory::{self, compute_offset, VolatileMemory, VolatileSlice};
use std::os::unix::io::AsRawFd;
/// A backend driver to access guest's physical memory by mmapping guest's memory into the current
/// process.
/// For a combination of 32-bit hypervisor and 64-bit virtual machine, only partial of guest's
/// physical memory may be mapped into current process due to limited process virtual address
/// space size.
#[derive(Debug)]
pub struct MmapRegion {
addr: *mut u8,
size: usize,
}
// Send and Sync aren't automatically inherited for the raw address pointer.
// Accessing that pointer is only done through the stateless interface which
// allows the object to be shared by multiple threads without a decrease in
// safety.
unsafe impl Send for MmapRegion {}
unsafe impl Sync for MmapRegion {}
impl MmapRegion {
/// Creates an anonymous shared mapping of `size` bytes.
///
/// # Arguments
/// * `size` - Size of memory region in bytes.
pub fn new(size: usize) -> io::Result<Self> {
// This is safe because we are creating an anonymous mapping in a place not already used by
// any other area in this process.
let addr = unsafe {
libc::mmap(
null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANONYMOUS | libc::MAP_SHARED | libc::MAP_NORESERVE,
-1,
0,
)
};
if addr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
Ok(Self {
addr: addr as *mut u8,
size,
})
}
/// Maps the `size` bytes starting at `offset` bytes of the given `fd`.
///
/// # Arguments
/// * `fd` - File descriptor to mmap from.
/// * `size` - Size of memory region in bytes.
/// * `offset` - Offset in bytes from the beginning of `fd` to start the mmap.
pub fn from_fd(fd: &AsRawFd, size: usize, offset: libc::off_t) -> io::Result<Self> {
// This is safe because we are creating a mapping in a place not already used by any other
// area in this process.
let addr = unsafe {
libc::mmap(
null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd.as_raw_fd(),
offset,
)
};
if addr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
Ok(Self {
addr: addr as *mut u8,
size,
})
}
/// Returns a pointer to the beginning of the memory region. Should only be
/// used for passing this region to ioctls for setting guest memory.
pub fn as_ptr(&self) -> *mut u8 {
self.addr
}
}
impl AsSlice for MmapRegion {
// Returns the region as a slice
// used to do crap
unsafe fn as_slice(&self) -> &[u8] {
// This is safe because we mapped the area at addr ourselves, so this slice will not
// overflow. However, it is possible to alias.
std::slice::from_raw_parts(self.addr, self.size)
}
// safe because it's expected interior mutability
#[allow(clippy::mut_from_ref)]
unsafe fn as_mut_slice(&self) -> &mut [u8] {
// This is safe because we mapped the area at addr ourselves, so this slice will not
// overflow. However, it is possible to alias.
std::slice::from_raw_parts_mut(self.addr, self.size)
}
}
impl VolatileMemory for MmapRegion {
fn len(&self) -> usize {
self.size
}
fn get_slice(&self, offset: usize, count: usize) -> volatile_memory::Result<VolatileSlice> {
let end = compute_offset(offset, count)?;
if end > self.size {
return Err(volatile_memory::Error::OutOfBounds { addr: end });
}
// Safe because we checked that offset + count was within our range and we only ever hand
// out volatile accessors.
Ok(unsafe { VolatileSlice::new((self.addr as usize + offset) as *mut _, count) })
}
}
impl Drop for MmapRegion {
fn drop(&mut self) {
// This is safe because we mmap the area at addr ourselves, and nobody
// else is holding a reference to it.
unsafe {
libc::munmap(self.addr as *mut libc::c_void, self.size);
}
}
}
#[cfg(test)]
mod tests {
use mmap_unix::MmapRegion;
use std::os::unix::io::FromRawFd;
#[test]
fn map_invalid_fd() {
let fd = unsafe { std::fs::File::from_raw_fd(-1) };
let e = MmapRegion::from_fd(&fd, 1024, 0).unwrap_err();
assert_eq!(e.raw_os_error(), Some(libc::EBADF));
}
}
@@ -0,0 +1,225 @@
// Copyright (C) 2019 CrowdStrike, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
//! A default Windows implementation of the GuestMemory trait using VirtualAlloc() and MapViewOfFile().
//!
//! The main structs to access guest's memory are:
//! - [MmapRegion](struct.MmapRegion.html): mmap a continuous region of guest's memory into the
//! current process
//! - [GuestRegionMmap](struct.GuestRegionMmap.html): tracks a mapping of memory in the current
//! process and the corresponding base address. It relays guest memory access requests to the
//! underline [MmapRegion](struct.MmapRegion.html) object.
//! - [GuestMemoryMmap](struct.GuestMemoryMmap.html): provides methods to access a collection of
//! GuestRegionMmap objects.
use libc;
use std::io;
use std::ptr::null_mut;
use mmap::AsSlice;
use volatile_memory::{self, compute_offset, VolatileMemory, VolatileSlice};
use libc::{c_void, size_t};
use std;
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::ptr::null;
#[allow(non_snake_case)]
#[link(name = "kernel32")]
extern "stdcall" {
pub fn VirtualAlloc(
lpAddress: *mut c_void,
dwSize: size_t,
flAllocationType: u32,
flProtect: u32,
) -> *mut c_void;
pub fn VirtualFree(lpAddress: *mut c_void, dwSize: size_t, dwFreeType: u32) -> u32;
pub fn CreateFileMappingA(
hFile: RawHandle, // HANDLE
lpFileMappingAttributes: *const c_void, // LPSECURITY_ATTRIBUTES
flProtect: u32, // DWORD
dwMaximumSizeHigh: u32, // DWORD
dwMaximumSizeLow: u32, // DWORD
lpName: *const u8, // LPCSTR
) -> RawHandle; // HANDLE
pub fn MapViewOfFile(
hFileMappingObject: RawHandle,
dwDesiredAccess: u32,
dwFileOffsetHigh: u32,
dwFileOffsetLow: u32,
dwNumberOfBytesToMap: size_t,
) -> *mut c_void;
pub fn CloseHandle(hObject: RawHandle) -> u32; // BOOL
}
const MM_HIGHEST_VAD_ADDRESS: u64 = 0x000007FFFFFDFFFF;
const MEM_COMMIT: u32 = 0x00001000;
const MEM_RELEASE: u32 = 0x00008000;
const FILE_MAP_ALL_ACCESS: u32 = 0xf001f;
const PAGE_READWRITE: u32 = 0x04;
pub const MAP_FAILED: *mut c_void = 0 as *mut c_void;
pub const INVALID_HANDLE_VALUE: RawHandle = (-1isize) as RawHandle;
#[allow(dead_code)]
pub const ERROR_INVALID_PARAMETER: i32 = 87;
/// A backend driver to access guest's physical memory by mmapping guest's memory into the current
/// process.
/// For a combination of 32-bit hypervisor and 64-bit virtual machine, only partial of guest's
/// physical memory may be mapped into current process due to limited process virtual address
/// space size.
#[derive(Debug)]
pub struct MmapRegion {
addr: *mut u8,
size: usize,
}
// Send and Sync aren't automatically inherited for the raw address pointer.
// Accessing that pointer is only done through the stateless interface which
// allows the object to be shared by multiple threads without a decrease in
// safety.
unsafe impl Send for MmapRegion {}
unsafe impl Sync for MmapRegion {}
impl MmapRegion {
/// Creates an anonymous shared mapping of `size` bytes.
///
/// # Arguments
/// * `size` - Size of memory region in bytes.
pub fn new(size: usize) -> io::Result<Self> {
if (size == 0) || (size > MM_HIGHEST_VAD_ADDRESS as usize) {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
// This is safe because we are creating an anonymous mapping in a place not already used by
// any other area in this process.
let addr = unsafe { VirtualAlloc(0 as *mut c_void, size, MEM_COMMIT, PAGE_READWRITE) };
if addr == MAP_FAILED {
return Err(io::Error::last_os_error());
}
Ok(Self {
addr: addr as *mut u8,
size,
})
}
/// Maps the `size` bytes starting at `offset` bytes of the given `fd`.
///
/// # Arguments
/// * `file` - Raw handle to a file to map into the address space.
/// * `size` - Size of memory region in bytes.
/// * `offset` - Offset in bytes from the beginning of `file` to start the mapping.
pub fn from_fd(file: &AsRawHandle, size: usize, offset: libc::off_t) -> io::Result<Self> {
let handle = file.as_raw_handle();
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::from_raw_os_error(libc::EBADF));
}
let mapping = unsafe {
CreateFileMappingA(
handle,
null(),
PAGE_READWRITE,
(size >> 32) as u32,
size as u32,
null(),
)
};
if mapping == 0 as RawHandle {
return Err(io::Error::last_os_error());
}
// This is safe because we are creating a mapping in a place not already used by any other
// area in this process.
let addr = unsafe {
MapViewOfFile(
mapping,
FILE_MAP_ALL_ACCESS,
(offset as u64 >> 32) as u32,
offset as u32,
size,
)
};
unsafe {
CloseHandle(mapping);
}
if addr == null_mut() {
return Err(io::Error::last_os_error());
}
Ok(Self {
addr: addr as *mut u8,
size,
})
}
/// Returns a pointer to the beginning of the memory region. Should only be
/// used for passing this region to ioctls for setting guest memory.
pub fn as_ptr(&self) -> *mut u8 {
self.addr
}
}
impl AsSlice for MmapRegion {
// Returns the region as a slice
// used to do crap
unsafe fn as_slice(&self) -> &[u8] {
// This is safe because we mapped the area at addr ourselves, so this slice will not
// overflow. However, it is possible to alias.
std::slice::from_raw_parts(self.addr, self.size)
}
// safe because it's expected interior mutability
#[allow(clippy::mut_from_ref)]
unsafe fn as_mut_slice(&self) -> &mut [u8] {
// This is safe because we mapped the area at addr ourselves, so this slice will not
// overflow. However, it is possible to alias.
std::slice::from_raw_parts_mut(self.addr, self.size)
}
}
impl VolatileMemory for MmapRegion {
fn len(&self) -> usize {
self.size
}
fn get_slice(&self, offset: usize, count: usize) -> volatile_memory::Result<VolatileSlice> {
let end = compute_offset(offset, count)?;
if end > self.size {
return Err(volatile_memory::Error::OutOfBounds { addr: end });
}
// Safe because we checked that offset + count was within our range and we only ever hand
// out volatile accessors.
Ok(unsafe { VolatileSlice::new((self.addr as usize + offset) as *mut _, count) })
}
}
impl Drop for MmapRegion {
fn drop(&mut self) {
// This is safe because we mmap the area at addr ourselves, and nobody
// else is holding a reference to it.
unsafe {
VirtualFree(self.addr as *mut libc::c_void, self.size, MEM_RELEASE);
}
}
}
#[cfg(test)]
mod tests {
use mmap_windows::{MmapRegion, INVALID_HANDLE_VALUE};
use std::os::windows::io::FromRawHandle;
#[test]
fn map_invalid_handle() {
let fd = unsafe { std::fs::File::from_raw_handle(INVALID_HANDLE_VALUE) };
let e = MmapRegion::from_fd(&fd, 1024, 0).unwrap_err();
assert_eq!(e.raw_os_error(), Some(libc::EBADF));
}
}
@@ -0,0 +1,967 @@
// Portions Copyright 2019 Red Hat, Inc.
//
// Copyright 2017 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 THIRT-PARTY file.
//! Types for volatile access to memory.
//!
//! Two of the core rules for safe rust is no data races and no aliased mutable references.
//! `VolatileRef` and `VolatileSlice`, along with types that produce those which implement
//! `VolatileMemory`, allow us to sidestep that rule by wrapping pointers that absolutely have to be
//! accessed volatile. Some systems really do need to operate on shared memory and can't have the
//! compiler reordering or eliding access because it has no visibility into what other systems are
//! doing with that hunk of memory.
//!
//! For the purposes of maintaining safety, volatile memory has some rules of its own:
//! 1. No references or slices to volatile memory (`&` or `&mut`).
//! 2. Access should always been done with a volatile read or write.
//! The First rule is because having references of any kind to memory considered volatile would
//! violate pointer aliasing. The second is because unvolatile accesses are inherently undefined if
//! done concurrently without synchronization. With volatile access we know that the compiler has
//! not reordered or elided the access.
use std::cmp::min;
use std::fmt;
use std::io::{self, Read, Write};
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::copy;
use std::ptr::{read_volatile, write_volatile};
use std::result;
use std::slice::{from_raw_parts, from_raw_parts_mut};
use std::usize;
use bytes::{ByteValued, Bytes};
/// VolatileMemory related error codes
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Error {
/// `addr` is out of bounds of the volatile memory slice.
OutOfBounds { addr: usize },
/// Taking a slice at `base` with `offset` would overflow `usize`.
Overflow { base: usize, offset: usize },
/// Writing to memory failed
IOError(io::Error),
/// Incomplete read or write
PartialBuffer { expected: usize, completed: usize },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::OutOfBounds { addr } => write!(f, "address 0x{:x} is out of bounds", addr),
Error::Overflow { base, offset } => write!(
f,
"address 0x{:x} offset by 0x{:x} would overflow",
base, offset
),
Error::IOError(error) => write!(f, "{}", error),
Error::PartialBuffer {
expected,
completed,
} => write!(
f,
"only used {} bytes in {} long buffer",
completed, expected
),
}
}
}
/// Result of volatile memory operations
pub type Result<T> = result::Result<T, Error>;
/// Convenience function for computing `base + offset` which returns
/// `Err(Error::Overflow)` instead of panicking in the case `base + offset` exceeds
/// `usize::MAX`.
///
/// # Examples
///
/// ```
/// # use vm_memory::volatile_memory::*;
/// # fn get_slice(offset: usize, count: usize) -> Result<()> {
/// let mem_end = compute_offset(offset, count)?;
/// if mem_end > 100 {
/// return Err(Error::OutOfBounds{addr: mem_end});
/// }
/// # Ok(())
/// # }
/// ```
pub fn compute_offset(base: usize, offset: usize) -> Result<usize> {
match base.checked_add(offset) {
None => Err(Error::Overflow { base, offset }),
Some(m) => Ok(m),
}
}
/// Trait for types that support raw volatile access to their data.
pub trait VolatileMemory {
/// Gets the size of this slice.
fn len(&self) -> usize;
/// Check whether the region is empty.
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Gets a slice of memory at `offset` that is `count` bytes in length and supports volatile
/// access.
fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice>;
/// Gets a slice of memory for the entire region that supports volatile access.
fn as_volatile_slice(&self) -> VolatileSlice {
self.get_slice(0, self.len()).unwrap()
}
/// Gets a `VolatileRef` at `offset`.
fn get_ref<T: ByteValued>(&self, offset: usize) -> Result<VolatileRef<T>> {
let slice = self.get_slice(offset, size_of::<T>())?;
unsafe {
// This is safe because the pointer is range-checked by get_slice, and
// the lifetime is the same as self.
Ok(VolatileRef::<T>::new(slice.addr))
}
}
/// Check that addr + count is valid and return the sum.
fn compute_end_offset(&self, base: usize, offset: usize) -> Result<usize> {
let mem_end = compute_offset(base, offset)?;
if mem_end > self.len() {
return Err(Error::OutOfBounds { addr: mem_end });
}
Ok(mem_end)
}
}
impl<'a> VolatileMemory for &'a mut [u8] {
fn len(&self) -> usize {
<[u8]>::len(self)
}
fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
let _ = self.compute_end_offset(offset, count)?;
unsafe {
// This is safe because the pointer is range-checked by compute_end_offset, and
// the lifetime is the same as the original slice.
Ok(VolatileSlice::new(
(self.as_ptr() as usize + offset) as *mut _,
count,
))
}
}
}
#[repr(C, packed)]
struct Packed<T>(T);
/// A slice of raw memory that supports volatile access.
#[derive(Copy, Clone, Debug)]
pub struct VolatileSlice<'a> {
addr: *mut u8,
size: usize,
phantom: PhantomData<&'a u8>,
}
impl<'a> VolatileSlice<'a> {
/// Creates a slice of raw memory that must support volatile access.
///
/// To use this safely, the caller must guarantee that the memory at `addr` is `size` bytes long
/// and is available for the duration of the lifetime of the new `VolatileSlice`. The caller
/// must also guarantee that all other users of the given chunk of memory are using volatile
/// accesses.
pub unsafe fn new(addr: *mut u8, size: usize) -> VolatileSlice<'a> {
VolatileSlice {
addr,
size,
phantom: PhantomData,
}
}
/// Gets the address of this slice's memory.
pub fn as_ptr(&self) -> *mut u8 {
self.addr
}
/// Gets the size of this slice.
pub fn len(&self) -> usize {
self.size
}
/// Check whether the slice is empty.
pub fn is_empty(&self) -> bool {
self.size == 0
}
/// Creates a copy of this slice with the address increased by `count` bytes, and the size
/// reduced by `count` bytes.
pub fn offset(self, count: usize) -> Result<VolatileSlice<'a>> {
let new_addr = (self.addr as usize)
.checked_add(count)
.ok_or(Error::Overflow {
base: self.addr as usize,
offset: count,
})?;
let new_size = self
.size
.checked_sub(count)
.ok_or(Error::OutOfBounds { addr: new_addr })?;
unsafe {
// Safe because the memory has the same lifetime and points to a subset of the
// memory of the original slice.
Ok(VolatileSlice::new(new_addr as *mut u8, new_size))
}
}
/// Copies `self.len()` or `buf.len()` times the size of `T` bytes, whichever is smaller, to
/// `buf`.
///
/// The copy happens from smallest to largest address in `T` sized chunks using volatile reads.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::path::Path;
/// # use vm_memory::VolatileMemory;
/// # fn test_write_null() -> Result<(), ()> {
/// let mut mem = [0u8; 32];
/// let mem_ref = &mut mem[..];
/// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
/// let mut buf = [5u8; 16];
/// vslice.copy_to(&mut buf[..]);
/// for v in &buf[..] {
/// assert_eq!(buf[0], 0);
/// }
/// # Ok(())
/// # }
/// ```
pub fn copy_to<T>(&self, buf: &mut [T]) -> usize
where
T: ByteValued,
{
let mut addr = self.addr;
let mut i = 0;
for v in buf.iter_mut().take(self.size / size_of::<T>()) {
unsafe {
*v = read_volatile(addr as *const Packed<T>).0;
addr = addr.add(size_of::<T>());
};
i += 1;
}
i
}
/// Copies `self.len()` or `slice.len()` bytes, whichever is smaller, to `slice`.
///
/// The copies happen in an undefined order.
/// # Examples
///
/// ```
/// # use vm_memory::VolatileMemory;
/// # fn test_write_null() -> Result<(), ()> {
/// let mut mem = [0u8; 32];
/// let mem_ref = &mut mem[..];
/// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
/// vslice.copy_to_volatile_slice(vslice.get_slice(16, 16).map_err(|_| ())?);
/// # Ok(())
/// # }
/// ```
pub fn copy_to_volatile_slice(&self, slice: VolatileSlice) {
unsafe {
// Safe because the pointers are range-checked when the slices
// are created, and they never escape the VolatileSlices.
// FIXME: ... however, is it really okay to mix non-volatile
// operations such as copy with read_volatile and write_volatile?
copy(self.addr, slice.addr, min(self.size, slice.size));
}
}
/// Copies `self.len()` or `buf.len()` times the size of `T` bytes, whichever is smaller, to
/// this slice's memory.
///
/// The copy happens from smallest to largest address in `T` sized chunks using volatile writes.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::path::Path;
/// # use vm_memory::VolatileMemory;
/// # fn test_write_null() -> Result<(), ()> {
/// let mut mem = [0u8; 32];
/// let mem_ref = &mut mem[..];
/// let vslice = mem_ref.get_slice(0, 32).map_err(|_| ())?;
/// let buf = [5u8; 64];
/// vslice.copy_from(&buf[..]);
/// for i in 0..4 {
/// assert_eq!(vslice.get_ref::<u32>(i * 4).map_err(|_| ())?.load(), 0x05050505);
/// }
/// # Ok(())
/// # }
/// ```
pub fn copy_from<T>(&self, buf: &[T])
where
T: ByteValued,
{
let mut addr = self.addr;
for &v in buf.iter().take(self.size / size_of::<T>()) {
unsafe {
// Safe because the pointers are range-checked when the slices
// are created, and they never escape the VolatileSlices.
write_volatile(addr as *mut Packed<T>, Packed::<T>(v));
addr = addr.add(size_of::<T>());
}
}
}
// These function are private and only used for the read/write functions. It is not valid in
// general to take slices of volatile memory.
unsafe fn as_slice(&self) -> &[u8] {
from_raw_parts(self.addr, self.size)
}
// safe because it's expected interior mutability
#[allow(clippy::mut_from_ref)]
unsafe fn as_mut_slice(&self) -> &mut [u8] {
from_raw_parts_mut(self.addr, self.size)
}
}
impl<'a> Bytes<usize> for VolatileSlice<'a> {
type E = Error;
/// Writes a slice to the region at the specified address.
/// Returns the number of bytes written. The number of bytes written can
/// be less than the length of the slice if there isn't enough room in the
/// region.
///
/// # Examples
/// * Write a slice at offset 256.
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let res = vslice.write(&[1,2,3,4,5], 1020);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), 4);
/// ```
fn write(&self, buf: &[u8], addr: usize) -> Result<usize> {
if addr >= self.size {
return Err(Error::OutOfBounds { addr });
}
unsafe {
// Guest memory can't strictly be modeled as a slice because it is
// volatile. Writing to it with what compiles down to a memcpy
// won't hurt anything as long as we get the bounds checks right.
let mut slice: &mut [u8] = &mut self.as_mut_slice()[addr..];
Ok(slice.write(buf).map_err(Error::IOError)?)
}
}
/// Reads to a slice from the region at the specified address.
/// Returns the number of bytes read. The number of bytes read can be less than the length
/// of the slice if there isn't enough room in the region.
///
/// # Examples
/// * Read a slice of size 16 at offset 256.
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let buf = &mut [0u8; 16];
/// let res = vslice.read(buf, 1010);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), 14);
/// ```
fn read(&self, mut buf: &mut [u8], addr: usize) -> Result<usize> {
if addr >= self.size {
return Err(Error::OutOfBounds { addr });
}
unsafe {
// Guest memory can't strictly be modeled as a slice because it is
// volatile. Writing to it with what compiles down to a memcpy
// won't hurt anything as long as we get the bounds checks right.
let slice: &[u8] = &self.as_slice()[addr..];
Ok(buf.write(slice).map_err(Error::IOError)?)
}
}
/// Writes a slice to the region at the specified address.
///
/// # Examples
/// * Write a slice at offset 256.
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let res = vslice.write_slice(&[1,2,3,4,5], 256);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), ());
/// ```
fn write_slice(&self, buf: &[u8], addr: usize) -> Result<()> {
let len = self.write(buf, addr)?;
if len != buf.len() {
return Err(Error::PartialBuffer {
expected: buf.len(),
completed: len,
});
}
Ok(())
}
/// Reads to a slice from the region at the specified address.
///
/// # Examples
/// * Read a slice of size 16 at offset 256.
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let buf = &mut [0u8; 16];
/// let res = vslice.read_slice(buf, 256);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), ());
/// ```
fn read_slice(&self, buf: &mut [u8], addr: usize) -> Result<()> {
let len = self.read(buf, addr)?;
if len != buf.len() {
return Err(Error::PartialBuffer {
expected: buf.len(),
completed: len,
});
}
Ok(())
}
/// Writes data from a readable object like a File and writes it to the region.
///
/// # Examples
///
/// * Read bytes from /dev/urandom
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # use std::fs::File;
/// # use std::path::Path;
/// # fn test_read_random() -> Result<u32, ()> {
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let mut file = File::open(Path::new("/dev/urandom")).map_err(|_| ())?;
/// vslice.read_from(32, &mut file, 128).map_err(|_| ())?;
/// let rand_val: u32 = vslice.read_obj(40).map_err(|_| ())?;
/// # Ok(rand_val)
/// # }
/// ```
fn read_from<F>(&self, addr: usize, src: &mut F, count: usize) -> Result<usize>
where
F: Read,
{
let end = self.compute_end_offset(addr, count)?;
unsafe {
// It is safe to overwrite the volatile memory. Accessing the guest
// memory as a mutable slice is OK because nothing assumes another
// thread won't change what is loaded.
let dst = &mut self.as_mut_slice()[addr..end];
src.read(dst).map_err(Error::IOError)
}
}
/// Writes data from a readable object like a File and writes it to the region.
///
/// # Examples
///
/// * Read bytes from /dev/urandom
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # use std::fs::File;
/// # use std::path::Path;
/// # fn test_read_random() -> Result<u32, ()> {
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let mut file = File::open(Path::new("/dev/urandom")).map_err(|_| ())?;
/// vslice.read_exact_from(32, &mut file, 128).map_err(|_| ())?;
/// let rand_val: u32 = vslice.read_obj(40).map_err(|_| ())?;
/// # Ok(rand_val)
/// # }
/// ```
fn read_exact_from<F>(&self, addr: usize, src: &mut F, count: usize) -> Result<()>
where
F: Read,
{
let end = self.compute_end_offset(addr, count)?;
unsafe {
// It is safe to overwrite the volatile memory. Accessing the guest
// memory as a mutable slice is OK because nothing assumes another
// thread won't change what is loaded.
let dst = &mut self.as_mut_slice()[addr..end];
src.read_exact(dst).map_err(Error::IOError)?;
}
Ok(())
}
/// Reads data from the region to a writable object.
///
/// # Examples
///
/// * Write 128 bytes to /dev/null
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # use std::fs::File;
/// # use std::path::Path;
/// # fn test_write_null() -> Result<(), ()> {
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let mut file = File::open(Path::new("/dev/null")).map_err(|_| ())?;
/// vslice.write_to(32, &mut file, 128).map_err(|_| ())?;
/// # Ok(())
/// # }
/// ```
fn write_to<F>(&self, addr: usize, dst: &mut F, count: usize) -> Result<usize>
where
F: Write,
{
let end = self.compute_end_offset(addr, count)?;
unsafe {
// It is safe to read from volatile memory. Accessing the guest
// memory as a slice is OK because nothing assumes another thread
// won't change what is loaded.
let src = &self.as_mut_slice()[addr..end];
dst.write(src).map_err(Error::IOError)
}
}
/// Reads data from the region to a writable object.
///
/// # Examples
///
/// * Write 128 bytes to /dev/null
///
/// ```
/// # use vm_memory::{Bytes, VolatileMemory};
/// # use std::fs::File;
/// # use std::path::Path;
/// # fn test_write_null() -> Result<(), ()> {
/// # let mut mem = [0u8; 1024];
/// # let mut mem_ref = &mut mem[..];
/// # let vslice = mem_ref.as_volatile_slice();
/// let mut file = File::open(Path::new("/dev/null")).map_err(|_| ())?;
/// vslice.write_all_to(32, &mut file, 128).map_err(|_| ())?;
/// # Ok(())
/// # }
/// ```
fn write_all_to<F>(&self, addr: usize, dst: &mut F, count: usize) -> Result<()>
where
F: Write,
{
let end = self.compute_end_offset(addr, count)?;
unsafe {
// It is safe to read from volatile memory. Accessing the guest
// memory as a slice is OK because nothing assumes another thread
// won't change what is loaded.
let src = &self.as_mut_slice()[addr..end];
dst.write_all(src).map_err(Error::IOError)?;
}
Ok(())
}
}
impl<'a> VolatileMemory for VolatileSlice<'a> {
fn len(&self) -> usize {
self.size
}
fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
let _ = self.compute_end_offset(offset, count)?;
Ok(unsafe {
// This is safe because the pointer is range-checked by compute_end_offset, and
// the lifetime is the same as self.
VolatileSlice::new((self.addr as usize + offset) as *mut u8, count)
})
}
}
/// A memory location that supports volatile access of a `T`.
///
/// # Examples
///
/// ```
/// # use vm_memory::VolatileRef;
/// let mut v = 5u32;
/// assert_eq!(v, 5);
/// let v_ref = unsafe { VolatileRef::<u32>::new(&mut v as *mut u32 as *mut u8) };
/// assert_eq!(v_ref.load(), 5);
/// v_ref.store(500);
/// assert_eq!(v, 500);
#[derive(Debug)]
pub struct VolatileRef<'a, T: ByteValued>
where
T: 'a,
{
addr: *mut Packed<T>,
phantom: PhantomData<&'a T>,
}
#[allow(clippy::len_without_is_empty)]
impl<'a, T: ByteValued> VolatileRef<'a, T> {
/// Creates a reference to raw memory that must support volatile access of `T` sized chunks.
///
/// To use this safely, the caller must guarantee that the memory at `addr` is big enough for a
/// `T` and is available for the duration of the lifetime of the new `VolatileRef`. The caller
/// must also guarantee that all other users of the given chunk of memory are using volatile
/// accesses.
pub unsafe fn new(addr: *mut u8) -> VolatileRef<'a, T> {
VolatileRef {
addr: addr as *mut Packed<T>,
phantom: PhantomData,
}
}
/// Gets the address of this slice's memory.
pub fn as_ptr(&self) -> *mut u8 {
self.addr as *mut u8
}
/// Gets the size of this slice.
///
/// # Examples
///
/// ```
/// # use std::mem::size_of;
/// # use vm_memory::VolatileRef;
/// let v_ref = unsafe { VolatileRef::<u32>::new(0 as *mut _) };
/// assert_eq!(v_ref.len(), size_of::<u32>() as usize);
/// ```
pub fn len(&self) -> usize {
size_of::<T>()
}
/// Does a volatile write of the value `v` to the address of this ref.
#[inline(always)]
pub fn store(&self, v: T) {
unsafe { write_volatile(self.addr, Packed::<T>(v)) };
}
/// Does a volatile read of the value at the address of this ref.
#[inline(always)]
pub fn load(&self) -> T {
// For the purposes of demonstrating why read_volatile is necessary, try replacing the code
// in this function with the commented code below and running `cargo test --release`.
// unsafe { *(self.addr as *const T) }
unsafe { read_volatile(self.addr).0 }
}
/// Converts this `T` reference to a raw slice with the same size and address.
pub fn to_slice(&self) -> VolatileSlice<'a> {
unsafe { VolatileSlice::new(self.addr as *mut u8, size_of::<T>()) }
}
}
#[cfg(test)]
mod tests {
extern crate tempfile;
use super::*;
use self::tempfile::tempfile;
use std::sync::Arc;
use std::thread::{sleep, spawn};
use std::time::Duration;
use std::fs::File;
use std::path::Path;
#[derive(Clone)]
struct VecMem {
mem: Arc<Vec<u8>>,
}
impl VecMem {
fn new(size: usize) -> VecMem {
let mut mem = Vec::new();
mem.resize(size, 0);
VecMem { mem: Arc::new(mem) }
}
}
impl VolatileMemory for VecMem {
fn len(&self) -> usize {
self.mem.len()
}
fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice> {
let _ = self.compute_end_offset(offset, count)?;
Ok(unsafe {
VolatileSlice::new((self.mem.as_ptr() as usize + offset) as *mut _, count)
})
}
}
#[test]
fn ref_store() {
let mut a = [0u8; 1];
{
let a_ref = &mut a[..];
let v_ref = a_ref.get_ref(0).unwrap();
v_ref.store(2u8);
}
assert_eq!(a[0], 2);
}
#[test]
fn ref_load() {
let mut a = [5u8; 1];
{
let a_ref = &mut a[..];
let c = {
let v_ref = a_ref.get_ref::<u8>(0).unwrap();
assert_eq!(v_ref.load(), 5u8);
v_ref
};
// To make sure we can take a v_ref out of the scope we made it in:
c.load();
// but not too far:
// c
} //.load()
;
}
#[test]
fn ref_to_slice() {
let mut a = [1u8; 5];
let a_ref = &mut a[..];
let v_ref = a_ref.get_ref(1).unwrap();
v_ref.store(0x1234_5678u32);
let ref_slice = v_ref.to_slice();
assert_eq!(v_ref.as_ptr() as usize, ref_slice.as_ptr() as usize);
assert_eq!(v_ref.len(), ref_slice.len());
assert!(!ref_slice.is_empty());
}
#[test]
fn observe_mutate() {
let a = VecMem::new(1);
let a_clone = a.clone();
let v_ref = a.get_ref::<u8>(0).unwrap();
v_ref.store(99);
spawn(move || {
sleep(Duration::from_millis(10));
let clone_v_ref = a_clone.get_ref::<u8>(0).unwrap();
clone_v_ref.store(0);
});
// Technically this is a race condition but we have to observe the v_ref's value changing
// somehow and this helps to ensure the sleep actually happens before the store rather then
// being reordered by the compiler.
assert_eq!(v_ref.load(), 99);
// Granted we could have a machine that manages to perform this many volatile loads in the
// amount of time the spawned thread sleeps, but the most likely reason the retry limit will
// get reached is because v_ref.load() is not actually performing the required volatile read
// or v_ref.store() is not doing a volatile write. A timer based solution was avoided
// because that might use a syscall which could hint the optimizer to reload v_ref's pointer
// regardless of volatile status. Note that we use a longer retry duration for optimized
// builds.
#[cfg(debug_assertions)]
const RETRY_MAX: usize = 500_000_000;
#[cfg(not(debug_assertions))]
const RETRY_MAX: usize = 10_000_000_000;
let mut retry = 0;
while v_ref.load() == 99 && retry < RETRY_MAX {
retry += 1;
}
assert_ne!(retry, RETRY_MAX, "maximum retry exceeded");
assert_eq!(v_ref.load(), 0);
}
#[test]
fn slice_len() {
let a = VecMem::new(100);
let s = a.get_slice(0, 27).unwrap();
assert_eq!(s.len(), 27);
let s = a.get_slice(34, 27).unwrap();
assert_eq!(s.len(), 27);
let s = s.get_slice(20, 5).unwrap();
assert_eq!(s.len(), 5);
}
#[test]
fn slice_is_empty() {
let a = VecMem::new(100);
let s = a.get_slice(0, 27).unwrap();
assert!(!s.is_empty());
let s = a.get_slice(34, 0).unwrap();
assert!(s.is_empty());
}
#[test]
fn slice_overflow_error() {
use std::usize::MAX;
let a = VecMem::new(1);
let res = a.get_slice(MAX, 1).unwrap_err();
assert_matches!(
res,
Error::Overflow {
base: MAX,
offset: 1,
}
);
}
#[test]
fn slice_oob_error() {
let a = VecMem::new(100);
a.get_slice(50, 50).unwrap();
let res = a.get_slice(55, 50).unwrap_err();
assert_matches!(res, Error::OutOfBounds { addr: 105 });
}
#[test]
fn ref_overflow_error() {
use std::usize::MAX;
let a = VecMem::new(1);
let res = a.get_ref::<u8>(MAX).unwrap_err();
assert_matches!(
res,
Error::Overflow {
base: MAX,
offset: 1,
}
);
}
#[test]
fn ref_oob_error() {
let a = VecMem::new(100);
a.get_ref::<u8>(99).unwrap();
let res = a.get_ref::<u16>(99).unwrap_err();
assert_matches!(res, Error::OutOfBounds { addr: 101 });
}
#[test]
fn ref_oob_too_large() {
let a = VecMem::new(3);
let res = a.get_ref::<u32>(0).unwrap_err();
assert_matches!(res, Error::OutOfBounds { addr: 4 });
}
#[test]
fn slice_store() {
let a = VecMem::new(5);
let s = a.as_volatile_slice();
let r = a.get_ref(2).unwrap();
r.store(9u16);
assert_eq!(s.read_obj::<u16>(2).unwrap(), 9);
}
#[test]
fn test_write_past_end() {
let a = VecMem::new(5);
let s = a.as_volatile_slice();
let res = s.write(&[1, 2, 3, 4, 5, 6], 0);
assert!(res.is_ok());
assert_eq!(res.unwrap(), 5);
}
#[test]
fn slice_read_and_write() {
let a = VecMem::new(5);
let s = a.as_volatile_slice();
let sample_buf = [1, 2, 3];
assert!(s.write(&sample_buf, 5).is_err());
assert!(s.write(&sample_buf, 2).is_ok());
let mut buf = [0u8; 3];
assert!(s.read(&mut buf, 5).is_err());
assert!(s.read_slice(&mut buf, 2).is_ok());
assert_eq!(buf, sample_buf);
}
#[test]
fn obj_read_and_write() {
let a = VecMem::new(5);
let s = a.as_volatile_slice();
assert!(s.write_obj(55u16, 4).is_err());
assert!(s.write_obj(55u16, core::usize::MAX).is_err());
assert!(s.write_obj(55u16, 2).is_ok());
assert_eq!(s.read_obj::<u16>(2).unwrap(), 55u16);
assert!(s.read_obj::<u16>(4).is_err());
assert!(s.read_obj::<u16>(core::usize::MAX).is_err());
}
#[test]
fn mem_read_and_write() {
let a = VecMem::new(5);
let s = a.as_volatile_slice();
assert!(s.write_obj(!0u32, 1).is_ok());
let mut file = if cfg!(unix) {
File::open(Path::new("/dev/zero")).unwrap()
} else {
File::open(Path::new("c:\\Windows\\system32\\ntoskrnl.exe")).unwrap()
};
assert!(s.read_exact_from(2, &mut file, size_of::<u32>()).is_err());
assert!(s
.read_exact_from(core::usize::MAX, &mut file, size_of::<u32>())
.is_err());
assert!(s.read_exact_from(1, &mut file, size_of::<u32>()).is_ok());
let mut f = tempfile().unwrap();
assert!(s.read_exact_from(1, &mut f, size_of::<u32>()).is_err());
format!("{:?}", s.read_exact_from(1, &mut f, size_of::<u32>()));
let value = s.read_obj::<u32>(1).unwrap();
if cfg!(unix) {
assert_eq!(value, 0);
} else {
assert_eq!(value, 0x0090_5a4d);
}
let mut sink = Vec::new();
assert!(s.write_all_to(1, &mut sink, size_of::<u32>()).is_ok());
assert!(s.write_all_to(2, &mut sink, size_of::<u32>()).is_err());
assert!(s
.write_all_to(core::usize::MAX, &mut sink, size_of::<u32>())
.is_err());
format!("{:?}", s.write_all_to(2, &mut sink, size_of::<u32>()));
if cfg!(unix) {
assert_eq!(sink, vec![0; size_of::<u32>()]);
} else {
assert_eq!(sink, vec![0x4d, 0x5a, 0x90, 0x00]);
};
}
#[test]
fn unaligned_read_and_write() {
let a = VecMem::new(7);
let s = a.as_volatile_slice();
let sample_buf: [u8; 7] = [1, 2, 0xAA, 0xAA, 0xAA, 0xAA, 4];
assert!(s.write_slice(&sample_buf, 0).is_ok());
let r = a.get_ref::<u32>(2).unwrap();
assert_eq!(r.load(), 0xAAAA_AAAA);
r.store(0x5555_5555);
let sample_buf: [u8; 7] = [1, 2, 0x55, 0x55, 0x55, 0x55, 4];
let mut buf: [u8; 7] = Default::default();
assert!(s.read_slice(&mut buf, 0).is_ok());
assert_eq!(buf, sample_buf);
}
}