Skip to content
This repository has been archived by the owner on Nov 6, 2024. It is now read-only.

Add serde support for bindings commonly used in migration/snapshotting #101

Merged
merged 7 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .buildkite/custom-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@
]
},
{
"test_name": "check-warnings-fam",
"command": "RUSTFLAGS=\"-D warnings\" cargo check --features=fam-wrappers",
"test_name": "build-serde-gnu",
"command": "cargo build --release --features=serde",
"platform": [
"x86_64",
"aarch64"
]
},
{
"test_name": "build-serde-musl",
"command": "cargo build --release --features=serde --target {target_platform}-unknown-linux-musl",
"platform": [
"x86_64",
"aarch64"
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog
## [Unreleased]

### Added

- An opt-in feature `serde` that enables [`serde`](https://serde.rs)-based
(de)serialization of various bindings.

## [0.7.0]

### Changed
Expand Down
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,32 @@ cp arm/mod.rs arm64/

Also, you will need to add the new architecture to `kvm-bindings/lib.rs`.

When regenerating bindings, care must be taken to re-add various `zerocopy`
derives under the `serde` feature. All items that require derives are
listed in the `x86_64/serialize.rs` and `arm64/serialize.rs` inside the
`serde_impls!` macro invocation, and missing derives will cause these
modules to fail compilation. For all items listed here, the following
derive should be present:

```rs
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
```

Any types whose name contains a suffix akin to `__bindgen_ty_<number>` and
which is contained in any struct listed in `serialize.rs` will also need
to have this derive added (otherwise compilation will fail). Note that
these types are not explicitly listed in `serialize.rs`, as their names
can change across `bindgen.rs` versions.

Lastly, in `x86_64/bindings.rs`, the derives also need to be added to
`struct __BindgenBitfieldUnit<Storage>` and `struct __IncompleteArrayField<T>`.
Additionally, these structs need to have their layout changed from `#[repr(C)]`
to `#[repr(transparent)]`. This is needed because `zerocopy` traits can only be
derived on generic structures that are `repr(transparent)` or `repr(packed)`.

### Future Improvements
All the above steps are scriptable, so in the next iteration I will add a
script to generate the bindings.
Expand Down
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ rustdoc-args = ["--cfg", "docsrs"]

[features]
fam-wrappers = ["vmm-sys-util"]
# It is not needed to enable the `serde` feature of `vmm-sys-util` here, because due to how cargo merges features,
# if a downstream crate enables vmm-sys-util in its Cargo.toml, it will get enabled globally.
serde = ["dep:serde", "serde/derive", "dep:zerocopy"]


[dependencies]
vmm-sys-util = { version = "0.12.1", optional = true }
serde = { version = "1.0.0", optional = true, features = ["derive"] }
zerocopy = { version = "0.7.32", optional = true, features = ["derive"] }

[dev-dependencies]
bincode = "1.3.3"
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Rust FFI bindings to KVM, generated using
[bindgen](https://crates.io/crates/bindgen). It currently has support for the
following target architectures:
- x86_64
- arm
- arm64

The bindings exported by this crate are statically generated using header files
Expand All @@ -17,7 +16,7 @@ kernel version they are using. For example, the `immediate_exit` field from the
capability is available. Using invalid fields or features may lead to undefined
behaviour.

# Usage
## Usage
First, add the following to your `Cargo.toml`:
```toml
kvm-bindings = "0.3"
Expand All @@ -35,7 +34,19 @@ this crate. Example:
kvm-bindings = { version = "0.3", features = ["fam-wrappers"]}
```

# Dependencies
## Dependencies
The crate has an `optional` dependency to
[vmm-sys-util](https://crates.io/crates/vmm-sys-util) when enabling the
`fam-wrappers` feature.

It also has an optional dependency on [`serde`](serde.rs) when enabling the
`serde` feature, to allow serialization of bindings. Serialization of
bindings happens as opaque binary blobs via [`zerocopy`](https://google.github.io/comprehensive-rust/bare-metal/useful-crates/zerocopy.html).
Due to the kernel's ABI compatibility, this means that bindings serialized
in version `x` of `kvm-bindings` can be deserialized in version `y` of the
crate, even if the bindings have had been regenerated in the meantime.

## Regenerating Bindings
andreeaflorescu marked this conversation as resolved.
Show resolved Hide resolved

Please see [`CONTRIBUTING.md`](CONTRIBUTING.md) for details on how to generate the bindings
or add support for new architectures.
2 changes: 1 addition & 1 deletion coverage_config_aarch64.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"coverage_score": 60.9,
"exclude_path": "",
"crate_features": "fam-wrappers"
"crate_features": "fam-wrappers,serde"
}
4 changes: 2 additions & 2 deletions coverage_config_x86_64.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"coverage_score": 100,
"coverage_score": 64,
"exclude_path": ".*bindings\\.rs",
"crate_features": "fam-wrappers"
"crate_features": "fam-wrappers,serde"
}
24 changes: 24 additions & 0 deletions src/arm64/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,10 @@ pub type __wsum = __u32;
pub type __poll_t = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
pub struct user_pt_regs {
pub regs: [__u64; 31usize],
pub sp: __u64,
Expand Down Expand Up @@ -1019,6 +1023,10 @@ fn bindgen_test_layout_user_pt_regs() {
#[repr(C)]
#[repr(align(16))]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
pub struct user_fpsimd_state {
pub vregs: [__uint128_t; 32usize],
pub fpsr: __u32,
Expand Down Expand Up @@ -1499,6 +1507,10 @@ fn bindgen_test_layout_user_za_header() {
#[repr(C)]
#[repr(align(16))]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
pub struct kvm_regs {
pub regs: user_pt_regs,
pub sp_el1: __u64,
Expand Down Expand Up @@ -1574,6 +1586,10 @@ fn bindgen_test_layout_kvm_regs() {
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
pub struct kvm_vcpu_init {
pub target: __u32,
pub features: [__u32; 7usize],
Expand Down Expand Up @@ -6511,6 +6527,10 @@ fn bindgen_test_layout_kvm_vapic_addr() {
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
pub struct kvm_mp_state {
pub mp_state: __u32,
}
Expand Down Expand Up @@ -8786,6 +8806,10 @@ fn bindgen_test_layout_kvm_reg_list() {
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
)]
pub struct kvm_one_reg {
pub id: __u64,
pub addr: __u64,
Expand Down
3 changes: 3 additions & 0 deletions src/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ pub mod bindings;
#[cfg(feature = "fam-wrappers")]
pub mod fam_wrappers;

#[cfg(feature = "serde")]
mod serialize;

pub use self::bindings::*;
#[cfg(feature = "fam-wrappers")]
pub use self::fam_wrappers::*;
60 changes: 60 additions & 0 deletions src/arm64/serialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use bindings::{
kvm_mp_state, kvm_one_reg, kvm_regs, kvm_vcpu_init, user_fpsimd_state, user_pt_regs,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use zerocopy::{transmute, AsBytes};

serde_impls! {
user_pt_regs,
user_fpsimd_state,
kvm_regs,
kvm_vcpu_init,
kvm_mp_state,
kvm_one_reg
}

#[cfg(test)]
mod tests {
use bindings::*;
use serde::{Deserialize, Serialize};

fn is_serde<T: Serialize + for<'de> Deserialize<'de> + Default>() {
let serialized = bincode::serialize(&T::default()).unwrap();
let deserialized = bincode::deserialize::<T>(serialized.as_ref()).unwrap();
let serialized_again = bincode::serialize(&deserialized).unwrap();
// Compare the serialized state after a roundtrip, to work around issues with
// bindings not implementing `PartialEq`.
assert_eq!(serialized, serialized_again);
}

#[test]
fn static_assert_serde_implementations() {
// This test statically (= at compile-time) asserts that various bindgen generated
// structures implement serde's `Serialize` and `Deserialize` traits.
// This is to make sure that we do not accidentally remove those implementations
// when regenerating bindings. If this test fails to compile, please add
//
// #[cfg_attr(
// feature = "serde",
// derive(zerocopy::AsBytes, zerocopy::FromBytes, zerocopy::FromZeroes)
// )]
//
// to all structures causing compilation errors (we need the zerocopy traits, as the
// `Serialize` and `Deserialize` implementations are provided by the `serde_impls!` macro
// above, which implements serialization based on zerocopy's `FromBytes` and `AsBytes`
// traits that it expects to be derived).
//
// NOTE: This only include "top-level" items, and does not list out bindgen-anonymous types
// (e.g. types like `kvm_vcpu_events__bindgen_ty_5`). These types can change name across
// bindgen versions. If after re-adding the derives to all the below items you can compile
// errors about anonymous types not implementing `Serialize`/`Deserialize`, please also add
// the derives to all anonymous types references in the definitions of the below items.

is_serde::<user_pt_regs>();
is_serde::<user_fpsimd_state>();
is_serde::<kvm_regs>();
is_serde::<kvm_vcpu_init>();
is_serde::<kvm_mp_state>();
is_serde::<kvm_one_reg>();
}
}
10 changes: 10 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
#[cfg(feature = "fam-wrappers")]
extern crate vmm_sys_util;

#[cfg(feature = "serde")]
extern crate serde;

#[cfg(feature = "serde")]
extern crate zerocopy;

#[cfg(feature = "serde")]
#[macro_use]
mod serialize;

#[cfg(target_arch = "x86_64")]
mod x86_64;
#[cfg(target_arch = "x86_64")]
Expand Down
43 changes: 43 additions & 0 deletions src/serialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//! Module containing serialization utilities

/// Macro that generates serde::Serialize and serde::Deserialize implementations for the given types.
/// This macro assumes that the types implement zerocopy::FromBytes and zerocopy::AsBytes, and uses
/// these implementations to serialize as opaque byte arrays. During deserialization, it will
/// try to deserialize as a `Vec`. If this deserialized `Vec` has a length that equals `size_of::<T>`,
/// it will transmute to `T` (using zerocopy), otherwise the `Vec` will either be zero-padded, or truncated.
/// This will hopefully allow live update of bindings across kernel versions even if the kernel adds
/// new fields to the end of some struct (we heavily rely on the kernel not making ABI breaking changes here).
macro_rules! serde_impls {
($($typ: ty),*) => {
$(
impl Serialize for $typ {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
let bytes = self.as_bytes();
serializer.serialize_bytes(bytes)
}
}

impl<'de> Deserialize<'de> for $typ {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>
{
let bytes = Vec::<u8>::deserialize(deserializer)?;

let mut backing = [0u8; std::mem::size_of::<$typ>()];

let limit = bytes.len().min(backing.len());

backing[..limit].copy_from_slice(&bytes[..limit]);

Ok(transmute!(backing))
}
}
)*
}
}
Loading