diff --git a/CMakeLists.txt b/CMakeLists.txt index d83307f..21831d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.22) + set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD 23) set(CMAKE_C_STANDARD_REQUIRED TRUE) @@ -12,6 +13,8 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() +# Enable asserts in release mode. +string( REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") # Optimizations set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) @@ -22,6 +25,7 @@ endif() project(Pound) + find_package(fmt 10.2.1 CONFIG) find_package(SDL3 3.2.10 CONFIG) add_subdirectory(3rd_Party) @@ -38,6 +42,24 @@ add_subdirectory(src/frontend) add_subdirectory(src/host) add_subdirectory(src/targets/switch1/hardware) +# Detect Host System Endianness. +include(TestBigEndian) +TEST_BIG_ENDIAN(WORDS_BIGENDIAN) +if(WORDS_BIGENDIAN) + message(STATUS "Host Endianness: Big Endian") +else() + message(STATUS "Host Endianness: Little Endian") +endif() + +# Configure Preprocessor Definitions based on Endianness. +if(WORDS_BIGENDIAN) + target_compile_definitions(Pound PRIVATE HOST_IS_BIG_ENDIAN=1) + target_compile_definitions(Pound PRIVATE HOST_IS_LITTLE_ENDIAN=0) +else() + target_compile_definitions(Pound PRIVATE HOST_IS_BIG_ENDIAN=0) + target_compile_definitions(Pound PRIVATE HOST_IS_LITTLE_ENDIAN=1) +endif() + target_compile_options(Pound PRIVATE -Wall -Wpedantic -Wshadow -Wpointer-arith diff --git a/src/host/memory/arena.cpp b/src/host/memory/arena.cpp index e2ded77..b359ff8 100644 --- a/src/host/memory/arena.cpp +++ b/src/host/memory/arena.cpp @@ -31,11 +31,11 @@ arena_t arena_init(size_t capacity) } // new more memsafe code (ownedbywuigi) (i give up on windows compatibility for now, will stick to the old unsafe code) -const void* arena_allocate(memory::arena_t* arena, const std::size_t size) +void* arena_allocate(memory::arena_t* arena, const std::size_t size) { assert(arena != nullptr); assert(arena->size + size <= arena->capacity); - const void* const data = static_cast(arena->data) + arena->size; + void* const data = static_cast(arena->data) + arena->size; arena->size += size; return data; } diff --git a/src/host/memory/arena.h b/src/host/memory/arena.h index 59722b6..f37801d 100644 --- a/src/host/memory/arena.h +++ b/src/host/memory/arena.h @@ -58,7 +58,7 @@ memory::arena_t arena_init(size_t capacity); * arena_allocate - Allocate memory from a pre-initialized arena. * * SYNOPSIS - * const void* arena_allocate(arena_t* arena, std::size_t size); + * void* arena_allocate(arena_t* arena, std::size_t size); * * DESCRIPTION * The function allocates size bytes from the specified arena. It assumes @@ -72,7 +72,7 @@ memory::arena_t arena_init(size_t capacity); * NOTES * Requires arena_t to be initialized with arena_init() or similar. */ -const void* arena_allocate(arena_t* arena, const std::size_t size); +void* arena_allocate(arena_t* arena, const std::size_t size); /* * NAME diff --git a/src/kvm/CMakeLists.txt b/src/kvm/CMakeLists.txt index 6bee731..468a5aa 100644 --- a/src/kvm/CMakeLists.txt +++ b/src/kvm/CMakeLists.txt @@ -3,6 +3,7 @@ set(KVM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/mmu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mmio.cpp ${CMAKE_CURRENT_SOURCE_DIR}/kvm.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/guest.cpp ) target_sources(Pound PRIVATE ${KVM_SOURCES}) diff --git a/src/kvm/endian.h b/src/kvm/endian.h new file mode 100644 index 0000000..57fd2d5 --- /dev/null +++ b/src/kvm/endian.h @@ -0,0 +1,27 @@ +#ifndef POUND_KVM_ENDIAN_H +#define POUND_KVM_ENDIAN_H + +#define GUEST_IS_LITTLE_ENDIAN 1 + +#ifdef _WIN32 + +#include +#define bswap_16(x) _byteswap_ushort(x) +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) + +#include +#define bswap_16(x) OSSwaoInt16(x) +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#else + +#include + +#endif + + +#endif // POUND_KVM_ENDIAN_H diff --git a/src/kvm/guest.cpp b/src/kvm/guest.cpp new file mode 100644 index 0000000..9cd107f --- /dev/null +++ b/src/kvm/guest.cpp @@ -0,0 +1,35 @@ +#include "guest.h" +#include + +namespace pound::kvm::memory +{ +guest_memory_t* guest_memory_create(pound::host::memory::arena_t* arena) +{ + assert(nullptr != arena); + assert(nullptr != arena->data); + + guest_memory_t* memory = (guest_memory_t*)pound::host::memory::arena_allocate(arena, sizeof(guest_memory_t)); + size_t ram_size = arena->capacity - arena->size; + uint8_t* ram_block = (uint8_t*)pound::host::memory::arena_allocate(arena, ram_size); + + /* + * This requires casting away the 'const' qualifier, which is generally unsafe. + * However, it is safe in this specific context because: + * + * a) We are operating on a newly allocated heap object (`memory`). + * b) No other part of the system has a reference to this object yet. + * c) This is a one-time initialization; the const contract will be + * honored for the rest of the object's lifetime after this function + * returns. + * + * This allows us to create an immutable descriptor object on the + * heap. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + *(uint8_t**)&memory->base = ram_block; + *(uint64_t*)&memory->size = ram_size; +#pragma GCC diagnostic pop + return memory; +} +} // namespace pound::kvm::memory diff --git a/src/kvm/guest.h b/src/kvm/guest.h index 60012f0..0118b68 100644 --- a/src/kvm/guest.h +++ b/src/kvm/guest.h @@ -1,55 +1,109 @@ -#pragma once +#ifndef POUND_KVM_GUEST_H +#define POUND_KVM_GUEST_H -#include -#include +#include +#include +#include + +#include "endian.h" + +#include "host/memory/arena.h" namespace pound::kvm::memory { /* - * guest_memory_t - Describes a contiguous block of guest physical RAM. + * guest_memory_t - A non-owning descriptor for a block of guest physical RAM. * @base: Pointer to the start of the host-allocated memory block. * @size: The size of the memory block in bytes. + * + * + * This structure describes a contiguous block of guest physical memory. It acts + * as a handle or a "view" into a region of host memory, but it does not manage + * the lifetime of that memory itself. + * + * --- Ownership --- + * The guest_memory_t struct does NOT own the memory block pointed to by @base. + * Ownership of the underlying memory buffer is retained by the host memory + * arena from which it was allocated. The party responsible for creating the + * arena is also responsible for ultimately freeing it. This struct is merely a + * descriptor and can be safely passed by value or pointer without transferring + * ownership. + * + * --- Lifetime --- + * An instance of this struct should be considered valid only for as long as the + * backing memory arena is valid. Typically, this means it is created once +- * during virtual machine initialization and lives for the entire duration of + * the emulation session. Its lifetime is tied to the lifetime of the parent + * KVM instance. + * + * --- Invariants --- + * Both fields of this struct are declared `const`. This establishes the + * invariant that once a guest_memory_t descriptor is created and initialized + * by guest_memory_create(), its size and base address are immutable for the + * lifetime of the object. This prevents accidental resizing or repointing of + * the guest's physical RAM. */ typedef struct { - uint8_t* base; - uint64_t size; + uint8_t* const base; + const uint64_t size; } guest_memory_t; /* - * gpa_to_hva() - Translate a Guest Physical Address to a Host Virtual Address. - * @memory: The guest memory region to translate within. - * @gpa: The Guest Physical Address (offset) to translate. + * guest_memory_create() - Allocates and initializes a guest memory region from + * an arena. + * @arena: A pointer to a host memory arena that will be the source for all + * allocations. * - * This function provides a fast, direct translation for a flat guest memory - * model. It relies on the critical pre-condition that the guest's physical - * RAM is backed by a single, contiguous block of virtual memory in the host's - * userspace (typically allocated with mmap()). + * This function sets up the primary guest RAM block. It uses a provided host + * memory arena as the backing store for both the guest_memory_t descriptor + * struct and the guest RAM itself. * - * In this model, memory->base is the Host Virtual Address (HVA) of the start of - * the backing host memory. The provided Guest Physical Address (gpa) is not - * treated as a pointer, but as a simple byte offset from the start of the guest's - * physical address space (PAS). + * The function first allocates a small chunk from the arena for the guest_memory_t + * struct. It then dedicates the *entire remaining capacity* of the arena to be + * the main guest RAM block. * - * The translation is therefore a single pointer-offset calculation. This establishes - * a direct 1:1 mapping between the guest's PAS and the host's virtual memory block. + * Preconditions: + * - @arena must be a valid, non-NULL pointer to an initialized host arena. + * - @arena->data must point to a valid, host-allocated memory buffer. + * - The arena provided should be dedicated solely to this guest memory block; + * its entire remaining capacity will be consumed. * - * The function asserts that GPA is within bounds. The caller is responsible for - * ensuring the validity of the GPA prior to calling. - * - * Return: A valid host virtual address pointer corresponding to the GPA. + * Return: A pointer to a fully initialized guest_memory_t struct. The `base` + * pointer will point to the start of the guest RAM block within the arena, + * and `size` will reflect the size of that block. */ -static inline uint8_t* gpa_to_hva(guest_memory_t* memory, uint64_t gpa) -{ - assert(nullptr != memory); - assert(nullptr != memory->base); - assert(gpa < memory->size); - uint8_t* hva = memory->base + gpa; - return hva; -} +guest_memory_t* guest_memory_create(pound::host::memory::arena_t* arena); -// TODO(GloriousTacoo:aarch64) Implement big to little endian conversion for guest_mem read and write functions. +/* + * guest_mem_access_result_t - Defines the set of possible outcomes for a guest + * memory access operation. + * @GUEST_MEM_ACCESS_OK: The memory operation completed + * successfully. + * @GUEST_MEM_FAULT_UNALIGNED: The access was unaligned, and the + * emulated CPU requires an Alignment + * Fault to be raised. The operation was + * NOT completed. The host must inject a + * data abort into the guest. + * @GUEST_MEM_ACCESS_FAULT_BOUNDARY: An access fell outside the bounds of + * the defined memory region. The + * operation was NOT completed, The host + * must inject a Data Abort for a + * translation/permission fault into the + * guest. + * @GUEST_MEM_ACCESS_ERROR_INTERNAL: An unrecoverable internal error occured + * within the memory subsystem. This + * indicates a fatal host bug, not a guest + * induced fault. + */ +typedef enum +{ + GUEST_MEM_ACCESS_OK = 0, + GUEST_MEM_ACCESS_FAULT_UNALIGNED, + GUEST_MEM_ACCESS_FAULT_BOUNDARY, + GUEST_MEM_ACCESS_ERROR_INTERNAL, +} guest_mem_access_result_t; /* * ============================================================================ @@ -58,69 +112,174 @@ static inline uint8_t* gpa_to_hva(guest_memory_t* memory, uint64_t gpa) */ /* - * guest_mem_readb() - Read one byte from guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to read from. - * Returns the 8-bit value read from memory. + * guest_mem_readb() - Read one byte from guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to read from. + * @out_val: A pointer to a uint8_t where the result will be stored. + * + * This function safely reads a single 8-bit value from the guest's physical + * RAM. It performs a bounds check to ensure the access is within the allocated + * memory region. + * + * Preconditions: + * - @memory and @out_val must be valid, non-NULL pointers. + * - @memory->base must point to a valid, host-allocated memory buffer. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY if the @gpa is outside the valid memory + * range. */ -static inline uint8_t guest_mem_readb(guest_memory_t* memory, uint64_t gpa) +inline guest_mem_access_result_t guest_mem_readb(guest_memory_t* memory, uint64_t gpa, uint8_t* out_val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert(gpa <= memory->size); - uint8_t* hva = gpa_to_hva(memory, gpa); - return *hva; + assert(nullptr != out_val); + + if (gpa >= memory->size) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + uint8_t* hva = memory->base + gpa; + *out_val = *hva; + + return GUEST_MEM_ACCESS_OK; } /* - * guest_mem_readw() - Read a 16-bit word from guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to read from (must be 2-byte aligned). - * Returns the 16-bit value, corrected for host endianness. + * guest_mem_readw() - Read a 16-bit word from guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to read from. + * @out_val: A pointer to a uint16_t where the result will be stored. + * + * This function safely reads a 16-bit little-endian value from guest RAM. + * It performs both boundary and alignment checks before the access. + * It will also perform a byte swap if the host system is not little-endian. + * + * Preconditions: + * - @memory and @out_val must be valid, non-NULL pointers. + * - @memory->base must point to a valid, host-allocated memory buffer. + * - The guest address @gpa must be 2-byte aligned. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY on an out-of-bounds access or + * %GUEST_MEM_ACCESS_FAULT_UNALIGNED if @gpa is not 2-byte aligned. */ -static inline uint16_t guest_mem_readw(guest_memory_t* memory, uint64_t gpa) +inline guest_mem_access_result_t guest_mem_readw(guest_memory_t* memory, uint64_t gpa, uint16_t* out_val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert((gpa + sizeof(uint16_t)) <= memory->size); - // Check if gpa is aligned to 2 bytes. - assert((gpa & 1) == 0); - uint16_t* hva = (uint16_t*)gpa_to_hva(memory, gpa); - return *hva; + assert(nullptr != out_val); + + if (gpa > (memory->size - sizeof(uint16_t))) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + if ((gpa & 1) != 0) + { + return GUEST_MEM_ACCESS_FAULT_UNALIGNED; + } + + uint8_t* hva = memory->base + gpa; + memcpy(out_val, hva, sizeof(uint16_t)); + +#if HOST_IS_LITTLE_ENDIAN != GUEST_IS_LITTLE_ENDIAN + *out_val = bswap_16(*out_val); +#endif + return GUEST_MEM_ACCESS_OK; } /* - * guest_mem_readl() - Read a 32-bit long-word from guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to read from (must be 4-byte aligned). - * Returns the 32-bit value, corrected for host endianness. + * guest_mem_readl() - Read a 32-bit long-word from guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to read from. + * @out_val: A pointer to a uint32_t where the result will be stored. + * + * This function safely reads a 32-bit little-endian value from guest RAM. + * It performs both boundary and alignment checks before the access. + * It will also perform a byte swap if the host system is not little-endian. + * + * Preconditions: + * - @memory and @out_val must be valid, non-NULL pointers. + * - @memory->base must point to a valid, host-allocated memory buffer. + * - The guest address @gpa must be 4-byte aligned. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY on an out-of-bounds access or + * %GUEST_MEM_ACCESS_FAULT_UNALIGNED if @gpa is not 4-byte aligned. */ -static inline uint32_t guest_mem_readl(guest_memory_t* memory, uint64_t gpa) +inline guest_mem_access_result_t guest_mem_readl(guest_memory_t* memory, uint64_t gpa, uint32_t* out_val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert((gpa + sizeof(uint32_t)) <= memory->size); - // Check if gpa is aligned to 4 bytes. - assert((gpa & 3) == 0); - uint32_t* hva = (uint32_t*)gpa_to_hva(memory, gpa); - return *hva; + assert(nullptr != out_val); + + if (gpa > (memory->size - sizeof(uint32_t))) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + if ((gpa & 3) != 0) + { + return GUEST_MEM_ACCESS_FAULT_UNALIGNED; + } + + uint8_t* hva = memory->base + gpa; + memcpy(out_val, hva, sizeof(uint32_t)); + +#if HOST_IS_LITTLE_ENDIAN != GUEST_IS_LITTLE_ENDIAN + *out_val = bswap_32(*out_val); +#endif + return GUEST_MEM_ACCESS_OK; } /* - * guest_mem_readq() - Read a 64-bit quad-word from guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to read from (must be 8-byte aligned). - * Returns the 64-bit value, corrected for host endianness. + * guest_mem_readq() - Read a 64-bit quad-word from guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to read from. + * @out_val: A pointer to a uint64_t where the result will be stored. + * + * This function safely reads a 64-bit little-endian value from guest RAM. + * It performs both boundary and alignment checks before the access. + * It will also perform a byte swap if the host system is not little-endian. + * + * Preconditions: + * - @memory and @out_val must be valid, non-NULL pointers. + * - @memory->base must point to a valid, host-allocated memory buffer. + * - The guest address @gpa must be 8-byte aligned. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY on an out-of-bounds access or + * %GUEST_MEM_ACCESS_FAULT_UNALIGNED if @gpa is not 8-byte aligned. */ -static inline uint64_t guest_mem_readq(guest_memory_t* memory, uint64_t gpa) +inline guest_mem_access_result_t guest_mem_readq(guest_memory_t* memory, uint64_t gpa, uint64_t* out_val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert((gpa + sizeof(uint64_t)) <= memory->size); - // Check if gpa is aligned to 8 bytes. - assert((gpa & 7) == 0); - uint64_t* hva = (uint64_t*)gpa_to_hva(memory, gpa); - return *hva; + assert(nullptr != out_val); + + if (gpa > (memory->size - sizeof(uint64_t))) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + if ((gpa & 7) != 0) + { + return GUEST_MEM_ACCESS_FAULT_UNALIGNED; + } + + uint8_t* hva = memory->base + gpa; + memcpy(out_val, hva, sizeof(uint64_t)); + +#if HOST_IS_LITTLE_ENDIAN != GUEST_IS_LITTLE_ENDIAN + *out_val = bswap_64(*out_val); +#endif + return GUEST_MEM_ACCESS_OK; } /* @@ -130,67 +289,171 @@ static inline uint64_t guest_mem_readq(guest_memory_t* memory, uint64_t gpa) */ /* - * guest_mem_writeb() - Write one byte to guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to write to. + * guest_mem_writeb() - Write one byte to guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to write to. * @val: The 8-bit value to write. + * + * This function safely writes a single 8-bit value to the guest's physical + * RAM. It performs a bounds check to ensure the access is within the allocated + * memory region before performing the write. + * + * Preconditions: + * - @memory must be a valid, non-NULL pointer. + * - @memory->base must point to a valid, host-allocated memory buffer. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY if the @gpa is outside the valid memory + * range. */ -static inline void guest_mem_writeb(guest_memory_t* memory, uint64_t gpa, uint8_t val) +inline guest_mem_access_result_t guest_mem_writeb(guest_memory_t* memory, uint64_t gpa, uint8_t val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert(gpa <= memory->size); - uint8_t* hva = gpa_to_hva(memory, gpa); + + if (gpa >= memory->size) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + uint8_t* hva = memory->base + gpa; *hva = val; + return GUEST_MEM_ACCESS_OK; } /* - * guest_mem_writew() - Write a 16-bit word to guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to write to (must be 2-byte aligned). - * @val: The 16-bit value to write (will be converted to guest endianness). + * guest_mem_writew() - Write a 16-bit word to guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to write to. + * @val: The 16-bit value to write. + * + * This function safely writes a 16-bit little-endian value to guest RAM. + * It performs both boundary and alignment checks before the access. + * It will also perform a byte swap if the host system is not little-endian. + * + * Preconditions: + * - @memory must be a valid, non-NULL pointer. + * - @memory->base must point to a valid, host-allocated memory buffer. + * - The guest address @gpa must be 2-byte aligned. + * + * Return: %GUEST_MEM_ACCESS_OK on success. Returns + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY on an out-of-bounds access or + * %GUEST_MEM_ACCESS_FAULT_UNALIGNED if @gpa is not 2-byte aligned. */ -static inline void guest_mem_writew(guest_memory_t* memory, uint64_t gpa, uint16_t val) +inline guest_mem_access_result_t guest_mem_writew(guest_memory_t* memory, uint64_t gpa, uint16_t val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert((gpa + sizeof(uint16_t)) <= memory->size); - // Check if gpa is aligned to 2 bytes. - assert((gpa & 1) == 0); - uint16_t* hva = (uint16_t*)gpa_to_hva(memory, gpa); - *hva = val; + + if (gpa > (memory->size - sizeof(uint16_t))) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + if ((gpa & 1) != 0) + { + return GUEST_MEM_ACCESS_FAULT_UNALIGNED; + } + + uint8_t* hva = memory->base + gpa; + +#if HOST_IS_LITTLE_ENDIAN != GUEST_IS_LITTLE_ENDIAN + val = bswap_16(val); +#endif + + memcpy(hva, &val, sizeof(uint16_t)); + return GUEST_MEM_ACCESS_OK; } /* - * guest_mem_writel() - Write a 32-bit long-word to guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to write to (must be 4-byte aligned). + * guest_mem_writel() - Write a 32-bit long-word to guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to write to. * @val: The 32-bit value to write. + * + * This function safely writes a 32-bit little-endian value to guest RAM. + * It performs both boundary and alignment checks before the access. + * It will also perform a byte swap if the host system is not little-endian. + * + * Preconditions: + * - @memory must be a valid, non-NULL pointer. + * - @memory->base must point to a valid, host-allocated memory buffer. + * - The guest address @gpa must be 4-byte aligned. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY on an out-of-bounds access or + * %GUEST_MEM_ACCESS_FAULT_UNALIGNED if @gpa is not 4-byte aligned. */ -static inline void guest_mem_writel(guest_memory_t* memory, uint64_t gpa, uint32_t val) -{ - assert(nullptr != memory->base); - assert((gpa + sizeof(uint32_t)) <= memory->size); - // Check if gpa is aligned to 4 bytes. - assert((gpa & 3) == 0); - uint32_t* hva = (uint32_t*)gpa_to_hva(memory, gpa); - *hva = val; -} - -/* - * guest_mem_writeq() - Write a 64-bit quad-word to guest memory. - * @memory: The guest memory region. - * @gpa: The Guest Physical Address to write to (must be 8-byte aligned). - * @val: The 64-bit value to write. - */ -static inline void guest_mem_writeq(guest_memory_t* memory, uint64_t gpa, uint64_t val) +inline guest_mem_access_result_t guest_mem_writel(guest_memory_t* memory, uint64_t gpa, uint32_t val) { assert(nullptr != memory); assert(nullptr != memory->base); - assert((gpa + sizeof(uint64_t)) <= memory->size); - // Check if gpa is aligned to 8 bytes. - assert((gpa & 7) == 0); - uint64_t* hva = (uint64_t*)gpa_to_hva(memory, gpa); - *hva = val; + + if (gpa > (memory->size - sizeof(uint32_t))) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + if ((gpa & 3) != 0) + { + return GUEST_MEM_ACCESS_FAULT_UNALIGNED; + } + + uint8_t* hva = memory->base + gpa; + +#if HOST_IS_LITTLE_ENDIAN != GUEST_IS_LITTLE_ENDIAN + val = bswap_32(val); +#endif + + memcpy(hva, &val, sizeof(uint32_t)); + return GUEST_MEM_ACCESS_OK; +} + +/* + * guest_mem_writeq() - Write a 64-bit quad-word to guest physical memory. + * @memory: A pointer to the guest memory region. + * @gpa: The guest physical address to write to. + * @val: The 64-bit value to write. + * + * This function safely writes a 64-bit little-endian value to guest RAM. + * It performs both boundary and alignment checks before the access. + * It will also perform a byte swap if the host system is not little-endian. + * + * Preconditions: + * - @memory must be a valid, non-NULL pointer. + * - @memory->base must point to a valid, host-allocated memory buffer. + * - The guest address @gpa must be 8-byte aligned. + * + * Return: + * %GUEST_MEM_ACCESS_OK on success. + * %GUEST_MEM_ACCESS_FAULT_BOUNDARY on an out-of-bounds access or + * %GUEST_MEM_ACCESS_FAULT_UNALIGNED if @gpa is not 8-byte aligned. + */ +inline guest_mem_access_result_t guest_mem_writeq(guest_memory_t* memory, uint64_t gpa, uint64_t val) +{ + assert(nullptr != memory); + assert(nullptr != memory->base); + + if (gpa > (memory->size - sizeof(uint64_t))) + { + return GUEST_MEM_ACCESS_FAULT_BOUNDARY; + } + + if ((gpa & 7) != 0) + { + return GUEST_MEM_ACCESS_FAULT_UNALIGNED; + } + + uint8_t* hva = memory->base + gpa; + +#if HOST_IS_LITTLE_ENDIAN != GUEST_IS_LITTLE_ENDIAN + val = bswap_64(val); +#endif + + memcpy(hva, &val, sizeof(uint64_t)); + return GUEST_MEM_ACCESS_OK; } } // namespace pound::kvm::memory +#endif // POUND_KVM_GUEST_H diff --git a/src/kvm/kvm.cpp b/src/kvm/kvm.cpp index 16ba9c1..36d966a 100644 --- a/src/kvm/kvm.cpp +++ b/src/kvm/kvm.cpp @@ -67,113 +67,13 @@ void take_synchronous_exception(kvm_vcpu_t* vcpu, uint8_t exception_class, uint3 * vcpu->pc = vcpu->vbar_el1 + offset; */ } -/** THIS FUNCTION WAS MADE WITH AI AND IS CALLED WHEN RUNNING THE CPU TEST FROM THE GUI! - * - * @brief Runs a comprehensive suite of tests on the guest memory access functions using the project's logging system. - * - * This function systematically tests the read and write capabilities of the memory - * subsystem for all standard data sizes (8, 16, 32, and 64 bits). It verifies - * that data written to memory can be correctly read back. - * - * It specifically checks: - * 1. A standard aligned address in the middle of RAM. - * - * @param memory A pointer to an initialized guest_memory_t struct. - * @return true if all tests pass, false otherwise. - */ -bool test_guest_ram_access(pound::kvm::memory::guest_memory_t* memory) -{ - LOG_INFO(Memory, "--- [ Starting Guest RAM Access Test ] ---"); - if (memory == nullptr || memory->base == nullptr || memory->size < 4096) - { - LOG_CRITICAL(Memory, "Invalid memory block provided. Cannot run tests."); - return false; - } - - bool all_tests_passed = true; - -#define RUN_TEST(description, condition) \ - do \ - { \ - char log_buffer[256]; \ - if (condition) \ - { \ - snprintf(log_buffer, sizeof(log_buffer), " [TEST] %-45s... [PASS]", description); \ - LOG_INFO(Memory, log_buffer); \ - } \ - else \ - { \ - snprintf(log_buffer, sizeof(log_buffer), " [TEST] %-45s... [FAIL]", description); \ - LOG_ERROR(Memory, log_buffer); \ - all_tests_passed = false; \ - } \ - } while (0) - -#define VERIFY_ACCESS(size, suffix, addr, write_val) \ - do \ - { \ - guest_mem_write##suffix(memory, addr, write_val); \ - uint##size##_t read_val = guest_mem_read##suffix(memory, addr); \ - bool success = (read_val == write_val); \ - RUN_TEST("Write/Read " #size "-bit", success); \ - if (!success) \ - { \ - char error_buffer[256]; \ - snprintf(error_buffer, sizeof(error_buffer), " -> At GPA 0x%016llx, Expected 0x%016llx, Got 0x%016llx", \ - (unsigned long long)addr, (unsigned long long)write_val, (unsigned long long)read_val); \ - LOG_ERROR(Memory, error_buffer); \ - } \ - } while (0) - - // --- 1. Test a typical, aligned address in the middle of memory --- - LOG_INFO(Memory, "[INFO] Testing standard access at a midrange address (GPA 0x1000)..."); - uint64_t test_addr = 0x1000; - VERIFY_ACCESS(8, b, test_addr + 0, 0xA5); - VERIFY_ACCESS(16, w, test_addr + 2, 0xBEEF); - VERIFY_ACCESS(32, l, test_addr + 4, 0xDEADBEEF); - VERIFY_ACCESS(64, q, test_addr + 8, 0xCAFEBABE01234567); - - // --- 2. Test the very beginning of the memory block --- - LOG_INFO(Memory, "[INFO] Testing boundary access at the start of RAM (GPA 0x0)..."); - VERIFY_ACCESS(64, q, 0x0, 0xFEEDFACEDEADBEEF); - - // --- 3. Test the very end of the memory block --- - LOG_INFO(Memory, "[INFO] Testing boundary access at the end of RAM..."); - - uint64_t end_addr_b = memory->size - 1; - uint64_t end_addr_w = (memory->size - 2) & ~1ULL; - uint64_t end_addr_l = (memory->size - 4) & ~3ULL; - uint64_t end_addr_q = (memory->size - 8) & ~7ULL; - - VERIFY_ACCESS(8, b, end_addr_b, 0xFE); - VERIFY_ACCESS(16, w, end_addr_w, 0xFEFE); - VERIFY_ACCESS(32, l, end_addr_l, 0xFEFEFEFE); - VERIFY_ACCESS(64, q, end_addr_q, 0xFEFEFEFEFEFEFEFE); - - // --- 4. Final Verdict --- - LOG_INFO(Memory, "--- [ Guest RAM Access Test Finished ] ---"); - if (all_tests_passed) - { - LOG_INFO(Memory, ">>> Result: ALL TESTS PASSED"); - } - else - { - LOG_ERROR(Memory, ">>> Result: SOME TESTS FAILED"); - } - LOG_INFO(Memory, "----------------------------------------------"); - - return all_tests_passed; -} - void cpuTest() { pound::host::memory::arena_t guest_memory_arena = pound::host::memory::arena_init(GUEST_RAM_SIZE); assert(nullptr != guest_memory_arena.data); - pound::kvm::memory::guest_memory_t guest_ram = {}; - guest_ram.base = static_cast(guest_memory_arena.data); - guest_ram.size = guest_memory_arena.capacity; + memory::guest_memory_t* guest_ram = memory::guest_memory_create(&guest_memory_arena); - (void)test_guest_ram_access(&guest_ram); + //(void)test_guest_ram_access(guest_ram); } } // namespace pound::kvm diff --git a/src/kvm/mmu.cpp b/src/kvm/mmu.cpp index 870d803..e4cdde1 100644 --- a/src/kvm/mmu.cpp +++ b/src/kvm/mmu.cpp @@ -340,7 +340,8 @@ int mmu_gva_to_gpa(pound::kvm::kvm_vcpu_t* vcpu, guest_memory_t* memory, uint64_ } const uint64_t level_entry_address = table_address + (level_index * page_table_entry_size); - const uint64_t descriptor = guest_mem_readq(memory, level_entry_address); + uint64_t descriptor = 0; + guest_mem_readq(memory, level_entry_address, &descriptor); uint64_t offset_mask = (1ULL << offset_bits) - 1; uint64_t page_offset = gva & offset_mask; uint64_t page_address_mask = ~offset_mask;