| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fidl/fuchsia.kernel/cpp/wire.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/guest.h> |
| #include <lib/zx/port.h> |
| #include <lib/zx/resource.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/vcpu.h> |
| #include <lib/zx/vmar.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls/hypervisor.h> |
| #include <zircon/syscalls/port.h> |
| #include <zircon/types.h> |
| |
| #include <string> |
| #include <thread> |
| |
| #include <fbl/auto_lock.h> |
| #include <fbl/mutex.h> |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "arch.h" |
| #include "constants.h" |
| #include "hypervisor_tests.h" |
| #include "src/lib/fxl/test/test_settings.h" |
| |
| namespace { |
| |
| template <typename T> |
| zx::result<zx::resource> GetResource() { |
| auto client_end = component::Connect<T>(); |
| if (client_end.is_error()) { |
| return client_end.take_error(); |
| } |
| auto result = fidl::WireCall(*client_end)->Get(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| return zx::ok(std::move(result.value().resource)); |
| } |
| |
| // Return true if the platform we are running on supports running guests. |
| bool PlatformSupportsGuests() { |
| // Get hypervisor permissions. |
| auto hypervisor = GetResource<fuchsia_kernel::HypervisorResource>(); |
| FX_CHECK(hypervisor.is_ok()) << "Could not get hypervisor resource."; |
| |
| // Try create a guest. |
| zx::guest guest; |
| zx::vmar vmar; |
| zx_status_t status = zx::guest::create(*hypervisor, 0, &guest, &vmar); |
| if (status != ZX_OK) { |
| FX_CHECK(status == ZX_ERR_NOT_SUPPORTED) |
| << "Unexpected error attempting to create Zircon guest object: " |
| << zx_status_get_string(status); |
| return false; |
| } |
| |
| // Create a single VCPU. |
| zx::vcpu vcpu; |
| status = zx::vcpu::create(guest, /*options=*/0, /*entry=*/0, &vcpu); |
| if (status != ZX_OK) { |
| FX_CHECK(status == ZX_ERR_NOT_SUPPORTED) |
| << "Unexpected error attempting to create VCPU: " << zx_status_get_string(status); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| // Setup a guest in fixture |test|. |
| // |
| // |start| and |end| point to the start and end of the code that will be copied into the guest for |
| // execution. If |start| and |end| are null, no code is copied. |
| void SetupGuest(TestCase* test, const char* start, const char* end) { |
| ASSERT_GE(VMO_SIZE, end - start); |
| |
| ASSERT_EQ(zx::vmo::create(VMO_SIZE, 0, &test->vmo), ZX_OK); |
| ASSERT_EQ(zx::vmar::root_self()->map(kHostMapFlags, 0, test->vmo, 0, VMO_SIZE, &test->host_addr), |
| ZX_OK); |
| cpp20::span<uint8_t> guest_memory(reinterpret_cast<uint8_t*>(test->host_addr), VMO_SIZE); |
| |
| // Add ZX_RIGHT_EXECUTABLE so we can map into guest address space. |
| auto vmex = GetResource<fuchsia_kernel::VmexResource>(); |
| ASSERT_EQ(vmex.status_value(), ZX_OK); |
| ASSERT_EQ(test->vmo.replace_as_executable(*vmex, &test->vmo), ZX_OK); |
| |
| auto hypervisor = GetResource<fuchsia_kernel::HypervisorResource>(); |
| ASSERT_EQ(hypervisor.status_value(), ZX_OK); |
| zx_status_t status = zx::guest::create(*hypervisor, 0, &test->guest, &test->vmar); |
| ASSERT_EQ(status, ZX_OK); |
| |
| zx_gpaddr_t guest_addr; |
| ASSERT_EQ(test->vmar.map(kGuestMapFlags, 0, test->vmo, 0, VMO_SIZE, &guest_addr), ZX_OK); |
| ASSERT_EQ(test->guest.set_trap(ZX_GUEST_TRAP_MEM, EXIT_TEST_ADDR, PAGE_SIZE, zx::port(), 0), |
| ZX_OK); |
| |
| // Set up a simple page table structure for the guest. |
| SetUpGuestPageTable(guest_memory); |
| |
| // Copy guest code into guest memory at address `GUEST_ENTRY`. |
| if (start != nullptr && end != nullptr) { |
| memcpy(guest_memory.data() + GUEST_ENTRY, start, end - start); |
| } |
| |
| status = zx::vcpu::create(test->guest, 0, GUEST_ENTRY, &test->vcpu); |
| ASSERT_EQ(status, ZX_OK); |
| } |
| |
| namespace { |
| |
| bool ExceptionThrown(const zx_packet_guest_mem_t& guest_mem, const zx::vcpu& vcpu) { |
| #if __x86_64__ |
| // The size of the instruction matches "mov imm, (EXIT_TEST_ADDR)", therefore |
| // we assume that we exited the VM correctly. |
| if (guest_mem.instruction_size == 12) { |
| return false; |
| } |
| zx_vcpu_state_t vcpu_state; |
| if (vcpu.read_state(ZX_VCPU_STATE, &vcpu_state, sizeof(vcpu_state)) != ZX_OK) { |
| return true; |
| } |
| // Print out debug values from the exception handler. |
| fprintf(stderr, "Unexpected exception in guest\n"); |
| fprintf(stderr, "vector = %lu\n", vcpu_state.rax); |
| fprintf(stderr, "error code = %lu\n", vcpu_state.rbx); |
| fprintf(stderr, "rip = 0x%lx\n", vcpu_state.rcx); |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| } // namespace |
| |
| void EnterAndCleanExit(TestCase* test) { |
| zx_port_packet_t packet = {}; |
| ASSERT_EQ(test->vcpu.enter(&packet), ZX_OK); |
| EXPECT_EQ(packet.type, ZX_PKT_TYPE_GUEST_MEM); |
| EXPECT_EQ(packet.guest_mem.addr, static_cast<zx_gpaddr_t>(EXIT_TEST_ADDR)); |
| #if __x86_64__ |
| EXPECT_EQ(packet.guest_mem.default_operand_size, 4u); |
| #endif |
| if (test->interrupts_enabled) { |
| ASSERT_FALSE(ExceptionThrown(packet.guest_mem, test->vcpu)); |
| } |
| } |
| |
| // Provide our own main so that we can abort testing if no guest support is detected. |
| int main(int argc, char** argv) { |
| fxl::CommandLine cl = fxl::CommandLineFromArgcArgv(argc, argv); |
| if (!fxl::SetTestSettings(cl)) { |
| return EXIT_FAILURE; |
| } |
| |
| // Ensure the platform supports running guests. |
| if (!PlatformSupportsGuests()) { |
| fprintf(stderr, "No support for running guests on current platform. Aborting tests.\n"); |
| return EXIT_FAILURE; |
| } |
| |
| // Run tests. |
| testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |