KVM-based microVMM for the Volt platform: - Sub-second VM boot times - Minimal memory footprint - Landlock LSM + seccomp security - Virtio device support - Custom kernel management Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
797 lines
26 KiB
Rust
797 lines
26 KiB
Rust
//! Snapshot/Restore for Volt VMM
|
|
//!
|
|
//! Provides serializable state types and functions to create and restore
|
|
//! VM snapshots. The snapshot format consists of:
|
|
//!
|
|
//! - `state.json`: Serialized VM state (vCPU registers, IRQ chip, devices, metadata)
|
|
//! - `memory.snap`: Raw guest memory dump (mmap'd on restore for lazy loading)
|
|
//!
|
|
//! # Architecture
|
|
//!
|
|
//! ```text
|
|
//! ┌──────────────────────────────────────────────────┐
|
|
//! │ Snapshot Files │
|
|
//! │ ┌──────────────────┐ ┌───────────────────────┐ │
|
|
//! │ │ state.json │ │ memory.snap │ │
|
|
//! │ │ - VcpuState[] │ │ (raw memory dump) │ │
|
|
//! │ │ - IrqchipState │ │ │ │
|
|
//! │ │ - ClockState │ │ Restored via mmap │ │
|
|
//! │ │ - DeviceState │ │ MAP_PRIVATE for CoW │ │
|
|
//! │ │ - Metadata+CRC │ │ demand-paged by OS │ │
|
|
//! │ └──────────────────┘ └───────────────────────┘ │
|
|
//! └──────────────────────────────────────────────────┘
|
|
//! ```
|
|
|
|
pub mod cas;
|
|
pub mod create;
|
|
pub mod inmem;
|
|
pub mod restore;
|
|
|
|
// Re-export CAS types
|
|
pub use cas::{CasManifest, CasChunk, CasDumpResult, CAS_CHUNK_SIZE, CAS_MANIFEST_FILENAME};
|
|
// Re-export restore types
|
|
pub use restore::MemoryMapping;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// ============================================================================
|
|
// Snapshot Metadata
|
|
// ============================================================================
|
|
|
|
/// Snapshot format version
|
|
pub const SNAPSHOT_VERSION: u32 = 1;
|
|
|
|
/// Snapshot metadata with integrity check
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SnapshotMetadata {
|
|
/// Snapshot format version
|
|
pub version: u32,
|
|
/// Total guest memory size in bytes
|
|
pub memory_size: u64,
|
|
/// Number of vCPUs
|
|
pub vcpu_count: u8,
|
|
/// Snapshot creation timestamp (Unix epoch seconds)
|
|
pub created_at: u64,
|
|
/// CRC-64 of the state JSON (excluding this field)
|
|
pub state_crc64: u64,
|
|
/// Memory file size (for validation)
|
|
pub memory_file_size: u64,
|
|
}
|
|
|
|
// ============================================================================
|
|
// vCPU State
|
|
// ============================================================================
|
|
|
|
/// Complete vCPU state captured from KVM
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VcpuState {
|
|
/// vCPU index
|
|
pub id: u8,
|
|
/// General purpose registers (KVM_GET_REGS)
|
|
pub regs: SerializableRegs,
|
|
/// Special registers (KVM_GET_SREGS)
|
|
pub sregs: SerializableSregs,
|
|
/// FPU state (KVM_GET_FPU)
|
|
pub fpu: SerializableFpu,
|
|
/// Model-specific registers (KVM_GET_MSRS)
|
|
pub msrs: Vec<SerializableMsr>,
|
|
/// CPUID entries (KVM_GET_CPUID2)
|
|
pub cpuid_entries: Vec<SerializableCpuidEntry>,
|
|
/// Local APIC state (KVM_GET_LAPIC)
|
|
pub lapic: SerializableLapic,
|
|
/// Extended control registers (KVM_GET_XCRS)
|
|
pub xcrs: Vec<SerializableXcr>,
|
|
/// Multiprocessor state (KVM_GET_MP_STATE)
|
|
pub mp_state: u32,
|
|
/// vCPU events (KVM_GET_VCPU_EVENTS)
|
|
pub events: SerializableVcpuEvents,
|
|
}
|
|
|
|
/// Serializable general-purpose registers (maps to kvm_regs)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableRegs {
|
|
pub rax: u64,
|
|
pub rbx: u64,
|
|
pub rcx: u64,
|
|
pub rdx: u64,
|
|
pub rsi: u64,
|
|
pub rdi: u64,
|
|
pub rsp: u64,
|
|
pub rbp: u64,
|
|
pub r8: u64,
|
|
pub r9: u64,
|
|
pub r10: u64,
|
|
pub r11: u64,
|
|
pub r12: u64,
|
|
pub r13: u64,
|
|
pub r14: u64,
|
|
pub r15: u64,
|
|
pub rip: u64,
|
|
pub rflags: u64,
|
|
}
|
|
|
|
/// Serializable segment register
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableSegment {
|
|
pub base: u64,
|
|
pub limit: u32,
|
|
pub selector: u16,
|
|
pub type_: u8,
|
|
pub present: u8,
|
|
pub dpl: u8,
|
|
pub db: u8,
|
|
pub s: u8,
|
|
pub l: u8,
|
|
pub g: u8,
|
|
pub avl: u8,
|
|
pub unusable: u8,
|
|
}
|
|
|
|
/// Serializable descriptor table register
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableDtable {
|
|
pub base: u64,
|
|
pub limit: u16,
|
|
}
|
|
|
|
/// Serializable special registers (maps to kvm_sregs)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableSregs {
|
|
pub cs: SerializableSegment,
|
|
pub ds: SerializableSegment,
|
|
pub es: SerializableSegment,
|
|
pub fs: SerializableSegment,
|
|
pub gs: SerializableSegment,
|
|
pub ss: SerializableSegment,
|
|
pub tr: SerializableSegment,
|
|
pub ldt: SerializableSegment,
|
|
pub gdt: SerializableDtable,
|
|
pub idt: SerializableDtable,
|
|
pub cr0: u64,
|
|
pub cr2: u64,
|
|
pub cr3: u64,
|
|
pub cr4: u64,
|
|
pub cr8: u64,
|
|
pub efer: u64,
|
|
pub apic_base: u64,
|
|
/// Interrupt bitmap (256 bits = 4 x u64)
|
|
pub interrupt_bitmap: [u64; 4],
|
|
}
|
|
|
|
/// Serializable FPU state (maps to kvm_fpu)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableFpu {
|
|
/// x87 FPU registers (8 x 16 bytes = 128 bytes)
|
|
pub fpr: Vec<Vec<u8>>,
|
|
/// FPU control word
|
|
pub fcw: u16,
|
|
/// FPU status word
|
|
pub fsw: u16,
|
|
/// FPU tag word (abridged)
|
|
pub ftwx: u8,
|
|
/// Last FPU opcode
|
|
pub last_opcode: u16,
|
|
/// Last FPU instruction pointer
|
|
pub last_ip: u64,
|
|
/// Last FPU data pointer
|
|
pub last_dp: u64,
|
|
/// SSE/AVX registers (16 x 16 bytes = 256 bytes)
|
|
pub xmm: Vec<Vec<u8>>,
|
|
/// SSE control/status register
|
|
pub mxcsr: u32,
|
|
}
|
|
|
|
/// Serializable MSR entry
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableMsr {
|
|
pub index: u32,
|
|
pub data: u64,
|
|
}
|
|
|
|
/// Serializable CPUID entry
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableCpuidEntry {
|
|
pub function: u32,
|
|
pub index: u32,
|
|
pub flags: u32,
|
|
pub eax: u32,
|
|
pub ebx: u32,
|
|
pub ecx: u32,
|
|
pub edx: u32,
|
|
}
|
|
|
|
/// Serializable LAPIC state (256 x 4 = 1024 bytes, base64-encoded)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableLapic {
|
|
/// Raw LAPIC register data (1024 bytes)
|
|
pub regs: Vec<u8>,
|
|
}
|
|
|
|
/// Serializable XCR entry
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableXcr {
|
|
pub xcr: u32,
|
|
pub value: u64,
|
|
}
|
|
|
|
/// Serializable vCPU events (maps to kvm_vcpu_events)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableVcpuEvents {
|
|
// Exception state
|
|
pub exception_injected: u8,
|
|
pub exception_nr: u8,
|
|
pub exception_has_error_code: u8,
|
|
pub exception_error_code: u32,
|
|
// Interrupt state
|
|
pub interrupt_injected: u8,
|
|
pub interrupt_nr: u8,
|
|
pub interrupt_soft: u8,
|
|
pub interrupt_shadow: u8,
|
|
// NMI state
|
|
pub nmi_injected: u8,
|
|
pub nmi_pending: u8,
|
|
pub nmi_masked: u8,
|
|
// SMI state
|
|
pub smi_smm: u8,
|
|
pub smi_pending: u8,
|
|
pub smi_smm_inside_nmi: u8,
|
|
pub smi_latched_init: u8,
|
|
// Flags
|
|
pub flags: u32,
|
|
}
|
|
|
|
// ============================================================================
|
|
// IRQ Chip State
|
|
// ============================================================================
|
|
|
|
/// Complete IRQ chip state (PIC + IOAPIC + PIT)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct IrqchipState {
|
|
/// 8259 PIC master (IRQ chip 0)
|
|
pub pic_master: SerializablePicState,
|
|
/// 8259 PIC slave (IRQ chip 1)
|
|
pub pic_slave: SerializablePicState,
|
|
/// IOAPIC state (IRQ chip 2)
|
|
pub ioapic: SerializableIoapicState,
|
|
/// PIT state (KVM_GET_PIT2)
|
|
pub pit: SerializablePitState,
|
|
}
|
|
|
|
/// Serializable 8259 PIC state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializablePicState {
|
|
/// Raw chip data from KVM_GET_IRQCHIP (512 bytes)
|
|
pub raw_data: Vec<u8>,
|
|
}
|
|
|
|
/// Serializable IOAPIC state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableIoapicState {
|
|
/// Raw chip data from KVM_GET_IRQCHIP (512 bytes)
|
|
pub raw_data: Vec<u8>,
|
|
}
|
|
|
|
/// Serializable PIT state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializablePitState {
|
|
/// PIT counter channels (3 channels)
|
|
pub channels: Vec<SerializablePitChannel>,
|
|
/// PIT flags
|
|
pub flags: u32,
|
|
}
|
|
|
|
/// Serializable PIT channel state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializablePitChannel {
|
|
pub count: u32,
|
|
pub latched_count: u16,
|
|
pub count_latched: u8,
|
|
pub status_latched: u8,
|
|
pub status: u8,
|
|
pub read_state: u8,
|
|
pub write_state: u8,
|
|
pub write_latch: u8,
|
|
pub rw_mode: u8,
|
|
pub mode: u8,
|
|
pub bcd: u8,
|
|
pub gate: u8,
|
|
pub count_load_time: i64,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Clock State
|
|
// ============================================================================
|
|
|
|
/// KVM clock state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ClockState {
|
|
/// KVM clock value (nanoseconds)
|
|
pub clock: u64,
|
|
/// Flags from kvm_clock_data
|
|
pub flags: u32,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Device State
|
|
// ============================================================================
|
|
|
|
/// Combined device state for all emulated devices
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DeviceState {
|
|
/// Serial console state
|
|
pub serial: SerializableSerialState,
|
|
/// Virtio-blk device state (if present)
|
|
pub virtio_blk: Option<SerializableVirtioBlkState>,
|
|
/// Virtio-net device state (if present)
|
|
pub virtio_net: Option<SerializableVirtioNetState>,
|
|
/// MMIO transport state for each device
|
|
pub mmio_transports: Vec<SerializableMmioTransportState>,
|
|
}
|
|
|
|
/// Serializable serial console state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableSerialState {
|
|
pub dlab: bool,
|
|
pub ier: u8,
|
|
pub lcr: u8,
|
|
pub mcr: u8,
|
|
pub lsr: u8,
|
|
pub msr: u8,
|
|
pub scr: u8,
|
|
pub dll: u8,
|
|
pub dlh: u8,
|
|
pub thr_interrupt_pending: bool,
|
|
pub input_buffer: Vec<u8>,
|
|
}
|
|
|
|
/// Serializable virtio-blk queue state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableVirtioBlkState {
|
|
/// Features acknowledged by the driver
|
|
pub acked_features: u64,
|
|
/// Whether the device is activated
|
|
pub activated: bool,
|
|
/// Queue state
|
|
pub queues: Vec<SerializableQueueState>,
|
|
/// Read-only flag
|
|
pub read_only: bool,
|
|
/// Backend path (for re-opening on restore)
|
|
pub backend_path: Option<String>,
|
|
}
|
|
|
|
/// Serializable virtio-net queue state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableVirtioNetState {
|
|
/// Features acknowledged by the driver
|
|
pub acked_features: u64,
|
|
/// Whether the device is activated
|
|
pub activated: bool,
|
|
/// Queue state
|
|
pub queues: Vec<SerializableQueueState>,
|
|
/// MAC address
|
|
pub mac: [u8; 6],
|
|
/// TAP device name
|
|
pub tap_name: String,
|
|
}
|
|
|
|
/// Serializable virtqueue state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableQueueState {
|
|
pub max_size: u16,
|
|
pub size: u16,
|
|
pub ready: bool,
|
|
pub desc_table: u64,
|
|
pub avail_ring: u64,
|
|
pub used_ring: u64,
|
|
pub next_avail: u16,
|
|
pub next_used: u16,
|
|
}
|
|
|
|
/// Serializable MMIO transport state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableMmioTransportState {
|
|
/// Device type
|
|
pub device_type: u32,
|
|
/// Current device status register
|
|
pub device_status: u32,
|
|
/// Driver features
|
|
pub driver_features: u64,
|
|
/// Device features selector
|
|
pub device_features_sel: u32,
|
|
/// Driver features selector
|
|
pub driver_features_sel: u32,
|
|
/// Selected queue index
|
|
pub queue_sel: u32,
|
|
/// Interrupt status
|
|
pub interrupt_status: u32,
|
|
/// Configuration generation counter
|
|
pub config_generation: u32,
|
|
/// MMIO base address
|
|
pub base_addr: u64,
|
|
/// IRQ number
|
|
pub irq: u32,
|
|
/// Per-queue addresses
|
|
pub queue_desc: Vec<u64>,
|
|
pub queue_avail: Vec<u64>,
|
|
pub queue_used: Vec<u64>,
|
|
pub queue_num: Vec<u16>,
|
|
pub queue_ready: Vec<bool>,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Complete Snapshot
|
|
// ============================================================================
|
|
|
|
/// Complete VM snapshot (serialized to state.json)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VmSnapshot {
|
|
/// Snapshot metadata
|
|
pub metadata: SnapshotMetadata,
|
|
/// Per-vCPU state
|
|
pub vcpu_states: Vec<VcpuState>,
|
|
/// IRQ chip state
|
|
pub irqchip: IrqchipState,
|
|
/// KVM clock state
|
|
pub clock: ClockState,
|
|
/// Device state
|
|
pub devices: DeviceState,
|
|
/// Memory region layout
|
|
pub memory_regions: Vec<SerializableMemoryRegion>,
|
|
}
|
|
|
|
/// Serializable memory region descriptor
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SerializableMemoryRegion {
|
|
/// Guest physical address
|
|
pub guest_addr: u64,
|
|
/// Size in bytes
|
|
pub size: u64,
|
|
/// Offset into the memory snapshot file
|
|
pub file_offset: u64,
|
|
}
|
|
|
|
// ============================================================================
|
|
// VmSnapshot Implementation
|
|
// ============================================================================
|
|
|
|
impl VmSnapshot {
|
|
/// Deserialize a VmSnapshot from a byte buffer.
|
|
///
|
|
/// This allows loading snapshot state from memory (e.g., CAS blob cache)
|
|
/// instead of reading from a file on disk.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `data` - JSON-encoded snapshot state bytes
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The deserialized `VmSnapshot`, or an error if deserialization fails.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore
|
|
/// // Load state from CAS blob cache
|
|
/// let state_bytes = blob_cache.get("vm-snapshot-state")?;
|
|
/// let snapshot = VmSnapshot::from_bytes(&state_bytes)?;
|
|
/// ```
|
|
pub fn from_bytes(data: &[u8]) -> Result<Self> {
|
|
let snapshot: VmSnapshot = serde_json::from_slice(data)?;
|
|
|
|
// Verify version
|
|
if snapshot.metadata.version != SNAPSHOT_VERSION {
|
|
return Err(SnapshotError::VersionMismatch {
|
|
expected: SNAPSHOT_VERSION,
|
|
actual: snapshot.metadata.version,
|
|
});
|
|
}
|
|
|
|
// Verify CRC-64
|
|
let saved_crc = snapshot.metadata.state_crc64;
|
|
let mut check_snapshot = snapshot.clone();
|
|
check_snapshot.metadata.state_crc64 = 0;
|
|
let check_json = serde_json::to_string_pretty(&check_snapshot)?;
|
|
let computed_crc = compute_crc64(check_json.as_bytes());
|
|
|
|
if saved_crc != computed_crc {
|
|
return Err(SnapshotError::CrcMismatch {
|
|
expected: saved_crc,
|
|
actual: computed_crc,
|
|
});
|
|
}
|
|
|
|
Ok(snapshot)
|
|
}
|
|
|
|
/// Serialize the VmSnapshot to bytes.
|
|
///
|
|
/// This is the inverse of `from_bytes()` and allows storing snapshot
|
|
/// state in memory (e.g., for CAS blob cache).
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The JSON-encoded snapshot state as bytes.
|
|
pub fn to_bytes(&self) -> Result<Vec<u8>> {
|
|
// Create a snapshot with zeroed CRC for computation
|
|
let mut snapshot = self.clone();
|
|
snapshot.metadata.state_crc64 = 0;
|
|
let json = serde_json::to_string_pretty(&snapshot)?;
|
|
|
|
// Compute CRC and update
|
|
let crc = compute_crc64(json.as_bytes());
|
|
snapshot.metadata.state_crc64 = crc;
|
|
|
|
// Re-serialize with correct CRC
|
|
let final_json = serde_json::to_string_pretty(&snapshot)?;
|
|
Ok(final_json.into_bytes())
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Error types
|
|
// ============================================================================
|
|
|
|
/// Snapshot operation errors
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum SnapshotError {
|
|
#[error("IO error: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
Serialization(#[from] serde_json::Error),
|
|
|
|
#[error("KVM error: {0}")]
|
|
Kvm(String),
|
|
|
|
#[error("CRC mismatch: expected {expected:#x}, got {actual:#x}")]
|
|
CrcMismatch { expected: u64, actual: u64 },
|
|
|
|
#[error("Version mismatch: expected {expected}, got {actual}")]
|
|
VersionMismatch { expected: u32, actual: u32 },
|
|
|
|
#[error("Memory size mismatch: expected {expected}, got {actual}")]
|
|
MemorySizeMismatch { expected: u64, actual: u64 },
|
|
|
|
#[error("Memory file size mismatch: expected {expected}, got {actual}")]
|
|
MemoryFileSizeMismatch { expected: u64, actual: u64 },
|
|
|
|
#[error("Missing snapshot file: {0}")]
|
|
MissingFile(String),
|
|
|
|
#[error("Invalid snapshot: {0}")]
|
|
Invalid(String),
|
|
|
|
#[error("mmap failed: {0}")]
|
|
Mmap(String),
|
|
}
|
|
|
|
pub type Result<T> = std::result::Result<T, SnapshotError>;
|
|
|
|
// ============================================================================
|
|
// CRC-64 helper
|
|
// ============================================================================
|
|
|
|
/// Compute CRC-64/ECMA for integrity checking
|
|
pub fn compute_crc64(data: &[u8]) -> u64 {
|
|
use crc::{Crc, CRC_64_ECMA_182};
|
|
const CRC64: Crc<u64> = Crc::<u64>::new(&CRC_64_ECMA_182);
|
|
CRC64.checksum(data)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// Create a minimal valid snapshot for testing
|
|
fn create_test_snapshot() -> VmSnapshot {
|
|
VmSnapshot {
|
|
metadata: SnapshotMetadata {
|
|
version: SNAPSHOT_VERSION,
|
|
memory_size: 128 * 1024 * 1024,
|
|
vcpu_count: 1,
|
|
created_at: 1234567890,
|
|
state_crc64: 0, // Will be computed
|
|
memory_file_size: 128 * 1024 * 1024,
|
|
},
|
|
vcpu_states: vec![VcpuState {
|
|
id: 0,
|
|
regs: SerializableRegs {
|
|
rax: 0, rbx: 0, rcx: 0, rdx: 0,
|
|
rsi: 0, rdi: 0, rsp: 0x7fff_0000, rbp: 0,
|
|
r8: 0, r9: 0, r10: 0, r11: 0,
|
|
r12: 0, r13: 0, r14: 0, r15: 0,
|
|
rip: 0x0010_0000, rflags: 0x0002,
|
|
},
|
|
sregs: SerializableSregs {
|
|
cs: SerializableSegment {
|
|
base: 0, limit: 0xffff_ffff, selector: 0x10,
|
|
type_: 11, present: 1, dpl: 0, db: 0, s: 1, l: 1, g: 1, avl: 0, unusable: 0,
|
|
},
|
|
ds: SerializableSegment {
|
|
base: 0, limit: 0xffff_ffff, selector: 0x18,
|
|
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
|
|
},
|
|
es: SerializableSegment {
|
|
base: 0, limit: 0xffff_ffff, selector: 0x18,
|
|
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
|
|
},
|
|
fs: SerializableSegment {
|
|
base: 0, limit: 0xffff_ffff, selector: 0x18,
|
|
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
|
|
},
|
|
gs: SerializableSegment {
|
|
base: 0, limit: 0xffff_ffff, selector: 0x18,
|
|
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
|
|
},
|
|
ss: SerializableSegment {
|
|
base: 0, limit: 0xffff_ffff, selector: 0x18,
|
|
type_: 3, present: 1, dpl: 0, db: 1, s: 1, l: 0, g: 1, avl: 0, unusable: 0,
|
|
},
|
|
tr: SerializableSegment {
|
|
base: 0, limit: 0, selector: 0,
|
|
type_: 11, present: 1, dpl: 0, db: 0, s: 0, l: 0, g: 0, avl: 0, unusable: 0,
|
|
},
|
|
ldt: SerializableSegment {
|
|
base: 0, limit: 0, selector: 0,
|
|
type_: 2, present: 1, dpl: 0, db: 0, s: 0, l: 0, g: 0, avl: 0, unusable: 1,
|
|
},
|
|
gdt: SerializableDtable { base: 0, limit: 0 },
|
|
idt: SerializableDtable { base: 0, limit: 0 },
|
|
cr0: 0x8000_0011,
|
|
cr2: 0,
|
|
cr3: 0x0010_0000,
|
|
cr4: 0x20,
|
|
cr8: 0,
|
|
efer: 0x500,
|
|
apic_base: 0xfee0_0900,
|
|
interrupt_bitmap: [0; 4],
|
|
},
|
|
fpu: SerializableFpu {
|
|
fpr: vec![vec![0u8; 16]; 8],
|
|
fcw: 0x37f,
|
|
fsw: 0,
|
|
ftwx: 0,
|
|
last_opcode: 0,
|
|
last_ip: 0,
|
|
last_dp: 0,
|
|
xmm: vec![vec![0u8; 16]; 16],
|
|
mxcsr: 0x1f80,
|
|
},
|
|
msrs: vec![],
|
|
cpuid_entries: vec![],
|
|
lapic: SerializableLapic { regs: vec![0u8; 1024] },
|
|
xcrs: vec![],
|
|
mp_state: 0,
|
|
events: SerializableVcpuEvents {
|
|
exception_injected: 0,
|
|
exception_nr: 0,
|
|
exception_has_error_code: 0,
|
|
exception_error_code: 0,
|
|
interrupt_injected: 0,
|
|
interrupt_nr: 0,
|
|
interrupt_soft: 0,
|
|
interrupt_shadow: 0,
|
|
nmi_injected: 0,
|
|
nmi_pending: 0,
|
|
nmi_masked: 0,
|
|
smi_smm: 0,
|
|
smi_pending: 0,
|
|
smi_smm_inside_nmi: 0,
|
|
smi_latched_init: 0,
|
|
flags: 0,
|
|
},
|
|
}],
|
|
irqchip: IrqchipState {
|
|
pic_master: SerializablePicState { raw_data: vec![0u8; 512] },
|
|
pic_slave: SerializablePicState { raw_data: vec![0u8; 512] },
|
|
ioapic: SerializableIoapicState { raw_data: vec![0u8; 512] },
|
|
pit: SerializablePitState {
|
|
channels: vec![
|
|
SerializablePitChannel {
|
|
count: 0, latched_count: 0, count_latched: 0,
|
|
status_latched: 0, status: 0, read_state: 0,
|
|
write_state: 0, write_latch: 0, rw_mode: 0,
|
|
mode: 0, bcd: 0, gate: 0, count_load_time: 0,
|
|
};
|
|
3
|
|
],
|
|
flags: 0,
|
|
},
|
|
},
|
|
clock: ClockState { clock: 1_000_000_000, flags: 0 },
|
|
devices: DeviceState {
|
|
serial: SerializableSerialState {
|
|
dlab: false,
|
|
ier: 0,
|
|
lcr: 0,
|
|
mcr: 0,
|
|
lsr: 0x60,
|
|
msr: 0,
|
|
scr: 0,
|
|
dll: 0,
|
|
dlh: 0,
|
|
thr_interrupt_pending: false,
|
|
input_buffer: vec![],
|
|
},
|
|
virtio_blk: None,
|
|
virtio_net: None,
|
|
mmio_transports: vec![],
|
|
},
|
|
memory_regions: vec![SerializableMemoryRegion {
|
|
guest_addr: 0,
|
|
size: 128 * 1024 * 1024,
|
|
file_offset: 0,
|
|
}],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_snapshot_to_bytes_from_bytes_roundtrip() {
|
|
let original = create_test_snapshot();
|
|
|
|
// Serialize to bytes
|
|
let bytes = original.to_bytes().expect("to_bytes should succeed");
|
|
|
|
// Deserialize from bytes
|
|
let restored = VmSnapshot::from_bytes(&bytes).expect("from_bytes should succeed");
|
|
|
|
// Verify key fields match
|
|
assert_eq!(original.metadata.version, restored.metadata.version);
|
|
assert_eq!(original.metadata.memory_size, restored.metadata.memory_size);
|
|
assert_eq!(original.metadata.vcpu_count, restored.metadata.vcpu_count);
|
|
assert_eq!(original.vcpu_states.len(), restored.vcpu_states.len());
|
|
assert_eq!(original.vcpu_states[0].regs.rip, restored.vcpu_states[0].regs.rip);
|
|
assert_eq!(original.vcpu_states[0].regs.rsp, restored.vcpu_states[0].regs.rsp);
|
|
assert_eq!(original.clock.clock, restored.clock.clock);
|
|
}
|
|
|
|
#[test]
|
|
fn test_snapshot_from_bytes_version_mismatch() {
|
|
let mut snapshot = create_test_snapshot();
|
|
snapshot.metadata.version = 999; // Invalid version
|
|
|
|
let bytes = serde_json::to_vec(&snapshot).unwrap();
|
|
|
|
let result = VmSnapshot::from_bytes(&bytes);
|
|
assert!(matches!(result, Err(SnapshotError::VersionMismatch { .. })));
|
|
}
|
|
|
|
#[test]
|
|
fn test_snapshot_from_bytes_crc_mismatch() {
|
|
let mut snapshot = create_test_snapshot();
|
|
|
|
// Serialize normally first
|
|
let bytes = snapshot.to_bytes().unwrap();
|
|
|
|
// Corrupt the bytes (modify some content while keeping valid JSON)
|
|
let mut json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
|
|
json["clock"]["clock"] = serde_json::json!(12345); // Change clock value
|
|
let corrupted = serde_json::to_vec(&json).unwrap();
|
|
|
|
let result = VmSnapshot::from_bytes(&corrupted);
|
|
assert!(matches!(result, Err(SnapshotError::CrcMismatch { .. })));
|
|
}
|
|
|
|
#[test]
|
|
fn test_snapshot_from_bytes_invalid_json() {
|
|
let invalid_bytes = b"{ this is not valid json }";
|
|
|
|
let result = VmSnapshot::from_bytes(invalid_bytes);
|
|
assert!(matches!(result, Err(SnapshotError::Serialization(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_crc64_consistency() {
|
|
let data1 = b"hello world";
|
|
let data2 = b"hello world";
|
|
let data3 = b"hello worle"; // Different
|
|
|
|
let crc1 = compute_crc64(data1);
|
|
let crc2 = compute_crc64(data2);
|
|
let crc3 = compute_crc64(data3);
|
|
|
|
assert_eq!(crc1, crc2);
|
|
assert_ne!(crc1, crc3);
|
|
}
|
|
}
|