From 2bf3202d63f8e9237938a464f93ad2e698abc825 Mon Sep 17 00:00:00 2001 From: Colin Downs-Razouk Date: Tue, 8 Dec 2020 16:00:28 -0800 Subject: [PATCH] x86_64: add integration test case This test was useful for debugging issues with the hypervisor abstraction layer while it was in development. It's similar to some of the kvm integration tests, but runs the x86_64 setup functions. It has some commented out lines for having this test load a real kernel and/or ramdisk, which can also be useful for debugging boot problems. RESTRICT_AUTOMERGE BUG=b:175025264 TEST=cargo test -p x86_64 Change-Id: If5b89fe48d34db50fb962382032881e4e588db6e Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2579896 Tested-by: kokoro Commit-Queue: Colin Downs-Razouk Reviewed-by: Daniel Verkamp --- x86_64/Cargo.toml | 3 + x86_64/src/lib.rs | 3 + x86_64/src/test_integration.rs | 292 +++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 x86_64/src/test_integration.rs diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml index b3f0506c4f..a48c1c7133 100644 --- a/x86_64/Cargo.toml +++ b/x86_64/Cargo.toml @@ -26,3 +26,6 @@ base = { path = "../base" } acpi_tables = {path = "../acpi_tables" } vm_control = { path = "../vm_control" } vm_memory = { path = "../vm_memory" } + +[dev-dependencies] +msg_socket = { path = "../msg_socket"} \ No newline at end of file diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index cc00a790c8..e282dc3320 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -1142,6 +1142,9 @@ impl X8664arch { } } +#[cfg(test)] +mod test_integration; + #[cfg(test)] mod tests { use super::*; diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs new file mode 100644 index 0000000000..5e02807db8 --- /dev/null +++ b/x86_64/src/test_integration.rs @@ -0,0 +1,292 @@ +// Copyright 2020 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. + +#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] + +use devices::IrqChipX86_64; +use hypervisor::{HypervisorX86_64, VcpuExit, VcpuX86_64, VmX86_64}; +use msg_socket; +use vm_memory::{GuestAddress, GuestMemory}; + +use super::cpuid::setup_cpuid; +use super::interrupts::set_lint; +use super::regs::{setup_fpu, setup_msrs, setup_regs, setup_sregs}; +use super::X8664arch; +use super::{acpi, bootparam, mptable, smbios}; +use super::{ + BOOT_STACK_POINTER, END_ADDR_BEFORE_32BITS, KERNEL_64BIT_ENTRY_OFFSET, KERNEL_START_OFFSET, + X86_64_SCI_IRQ, ZERO_PAGE_OFFSET, +}; + +use base::Event; + +use std::collections::BTreeMap; +use std::ffi::CString; +use std::sync::Arc; +use std::thread; +use sync::Mutex; + +use devices::PciConfigIo; + +use vm_control::{ + DiskControlCommand, DiskControlResult, VmIrqRequest, VmIrqRequestSocket, VmIrqResponse, + VmIrqResponseSocket, VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse, +}; + +enum TaggedControlSocket { + VmMemory(VmMemoryControlResponseSocket), + VmIrq(VmIrqResponseSocket), +} + +#[test] +fn simple_kvm_kernel_irqchip_test() { + use devices::KvmKernelIrqChip; + use hypervisor::kvm::*; + simple_vm_test::<_, _, KvmVcpu, _, _, _>( + |guest_mem| { + let kvm = Kvm::new().expect("failed to create kvm"); + let vm = KvmVm::new(&kvm, guest_mem).expect("failed to create kvm vm"); + (kvm, vm) + }, + |vm, vcpu_count, _| { + KvmKernelIrqChip::new(vm, vcpu_count).expect("failed to create KvmKernelIrqChip") + }, + ); +} + +#[test] +fn simple_kvm_split_irqchip_test() { + use devices::KvmSplitIrqChip; + use hypervisor::kvm::*; + simple_vm_test::<_, _, KvmVcpu, _, _, _>( + |guest_mem| { + let kvm = Kvm::new().expect("failed to create kvm"); + let vm = KvmVm::new(&kvm, guest_mem).expect("failed to create kvm vm"); + (kvm, vm) + }, + |vm, vcpu_count, device_socket| { + KvmSplitIrqChip::new(vm, vcpu_count, device_socket) + .expect("failed to create KvmSplitIrqChip") + }, + ); +} + +/// Tests the integration of x86_64 with some hypervisor and devices setup. This test can help +/// narrow down whether boot issues are caused by the interaction between hypervisor and devices +/// and x86_64, or if they are caused by an invalid kernel or image. You can also swap in parts +/// of this function to load a real kernel and/or ramdisk. +fn simple_vm_test(create_vm: FV, create_irq_chip: FI) +where + H: HypervisorX86_64 + 'static, + V: VmX86_64 + 'static, + Vcpu: VcpuX86_64 + 'static, + I: IrqChipX86_64 + 'static, + FV: FnOnce(GuestMemory) -> (H, V), + FI: FnOnce(V, /* vcpu_count: */ usize, VmIrqRequestSocket) -> I, +{ + /* + 0x0000000000000000: 67 89 18 mov dword ptr [eax], ebx + 0x0000000000000003: 89 D9 mov ecx, ebx + 0x0000000000000005: 89 C8 mov eax, ecx + 0x0000000000000007: E6 FF out 0xff, al + */ + let code = [0x67, 0x89, 0x18, 0x89, 0xd9, 0x89, 0xc8, 0xe6, 0xff]; + + // 2GB memory + let memory_size = 0x80000000u64; + let start_addr = GuestAddress(KERNEL_START_OFFSET + KERNEL_64BIT_ENTRY_OFFSET); + + // write to 4th page + let write_addr = GuestAddress(0x4000); + + // guest mem is 400 pages + let guest_mem = X8664arch::setup_memory(memory_size, false).unwrap(); + // let guest_mem = GuestMemory::new(&[(GuestAddress(0), memory_size)]).unwrap(); + let mut resources = X8664arch::get_resource_allocator(&guest_mem, false); + + let (hyp, mut vm) = create_vm(guest_mem.clone()); + let (irqchip_socket, device_socket) = + msg_socket::pair::().expect("failed to create irq socket"); + + let mut irq_chip = create_irq_chip( + vm.try_clone().expect("failed to clone vm"), + 1, + device_socket, + ); + + let mut mmio_bus = devices::Bus::new(); + let exit_evt = Event::new().unwrap(); + + let mut control_sockets = vec![TaggedControlSocket::VmIrq(irqchip_socket)]; + // Create one control socket per disk. + let mut disk_device_sockets = Vec::new(); + let mut disk_host_sockets = Vec::new(); + let disk_count = 0; + for _ in 0..disk_count { + let (disk_host_socket, disk_device_socket) = + msg_socket::pair::().unwrap(); + disk_host_sockets.push(disk_host_socket); + disk_device_sockets.push(disk_device_socket); + } + let (gpu_host_socket, _gpu_device_socket) = + msg_socket::pair::().unwrap(); + + control_sockets.push(TaggedControlSocket::VmMemory(gpu_host_socket)); + + let devices = vec![]; + + let (pci, pci_irqs, _pid_debug_label_map) = arch::generate_pci_root( + devices, + &mut irq_chip, + &mut mmio_bus, + &mut resources, + &mut vm, + 4, + ) + .unwrap(); + let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci))); + + let mut io_bus = X8664arch::setup_io_bus( + irq_chip.pit_uses_speaker_port(), + exit_evt.try_clone().unwrap(), + Some(pci_bus), + memory_size, + ) + .unwrap(); + + let mut serial_params = BTreeMap::new(); + + arch::set_default_serial_parameters(&mut serial_params); + + X8664arch::setup_serial_devices(false, &mut irq_chip, &mut io_bus, &serial_params, None) + .unwrap(); + + let param_args = "nokaslr"; + + let mut cmdline = X8664arch::get_base_linux_cmdline(); + + cmdline.insert_str(¶m_args).unwrap(); + + let params = bootparam::boot_params::default(); + // write our custom kernel code to start_addr + guest_mem.write_at_addr(&code[..], start_addr).unwrap(); + let kernel_end = KERNEL_START_OFFSET + code.len() as u64; + let initrd_image = None; + + // alternatively, load a real initrd and kernel from disk + // let initrd_image = Some(File::open("/mnt/host/source/src/avd/ramdisk.img").expect("failed to open ramdisk")); + // let mut kernel_image = File::open("/mnt/host/source/src/avd/vmlinux.uncompressed").expect("failed to open kernel"); + // let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel"); + + let suspend_evt = Event::new().unwrap(); + let acpi_dev_resource = X8664arch::setup_acpi_devices( + &mut io_bus, + &mut resources, + suspend_evt + .try_clone() + .expect("unable to clone suspend_evt"), + exit_evt.try_clone().expect("unable to clone exit_evt"), + Default::default(), + &mut irq_chip, + (&None, None), + &mut mmio_bus, + ) + .unwrap(); + + X8664arch::setup_system_memory( + &guest_mem, + memory_size, + &CString::new(cmdline).expect("failed to create cmdline"), + initrd_image, + None, + kernel_end, + params, + ) + .expect("failed to setup system_memory"); + + // Note that this puts the mptable at 0x9FC00 in guest physical memory. + mptable::setup_mptable(&guest_mem, 1, pci_irqs).expect("failed to setup mptable"); + smbios::setup_smbios(&guest_mem).expect("failed to setup smbios"); + + acpi::create_acpi_tables(&guest_mem, 1, X86_64_SCI_IRQ, acpi_dev_resource.0); + + let guest_mem2 = guest_mem.clone(); + + let handle = thread::Builder::new() + .name("crosvm_simple_vm_vcpu".to_string()) + .spawn(move || { + let vcpu = *vm + .create_vcpu(0) + .expect("failed to create vcpu") + .downcast::() + .map_err(|_| ()) + .expect("failed to downcast vcpu"); + + irq_chip + .add_vcpu(0, &vcpu) + .expect("failed to add vcpu to irqchip"); + + setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, false).unwrap(); + setup_msrs(&vcpu, END_ADDR_BEFORE_32BITS).unwrap(); + + setup_regs( + &vcpu, + start_addr.offset() as u64, + BOOT_STACK_POINTER as u64, + ZERO_PAGE_OFFSET as u64, + ) + .unwrap(); + + let mut vcpu_regs = vcpu.get_regs().unwrap(); + // instruction is + // mov [eax],ebx + // so we're writing 0x12 (the contents of ebx) to the address + // in eax (write_addr). + vcpu_regs.rax = write_addr.offset() as u64; + vcpu_regs.rbx = 0x12; + // ecx will contain 0, but after the second instruction it will + // also contain 0x12 + vcpu_regs.rcx = 0x0; + vcpu.set_regs(&vcpu_regs).expect("set regs failed"); + + setup_fpu(&vcpu).unwrap(); + setup_sregs(&guest_mem, &vcpu).unwrap(); + set_lint(0, &mut irq_chip).unwrap(); + + let run_handle = vcpu.take_run_handle(None).unwrap(); + loop { + match vcpu.run(&run_handle).expect("run failed") { + VcpuExit::IoOut { + port: 0xff, + size, + data, + } => { + // We consider this test to be done when this particular + // one-byte port-io to port 0xff with the value of 0x12, which was in + // register eax + assert_eq!(size, 1); + assert_eq!(data[0], 0x12); + break; + } + r => { + panic!("unexpected exit {:?}", r); + } + } + } + let regs = vcpu.get_regs().unwrap(); + // ecx and eax should now contain 0x12 + assert_eq!(regs.rcx, 0x12); + assert_eq!(regs.rax, 0x12); + }) + .unwrap(); + + if let Err(e) = handle.join() { + panic!("failed to join vcpu thread: {:?}", e); + } + + assert_eq!( + guest_mem2.read_obj_from_addr::(write_addr).unwrap(), + 0x12 + ); +}