mirror of
https://github.com/pound-emu/pound.git
synced 2025-12-11 07:36:57 +00:00
arm64/mem: Refactor guest memory access and made it endian aware
Refactors the core guest memory access subsystem (guest.h) to be safer and portable accross host systems with different endianness. The previous implementation used direct pointer casting, which is not endian safe. 1. All read and write functions have been converted from unsafe pointer casts to memcpy(). This resolves alignment warning -Wcast-align. 2. The access functions no longer rely on asserts for error checking. They now perform explicit boundary and alignment checking and returns a guest_mem_access_result_t status code. 3. A new header (endian.h) provides cross platform byte swapping macros. The memory access functions use these macros to ensure that the guest always sees memory in the correct endian format, regardless of the host's native byte order. The host endianness is now automatically detected via CMake. 3. Asserts are now explicitly enabled in release builds to catch critical errors. Signed-off-by: Ronald Caesar <github43132@proton.me>
This commit is contained in:
parent
768355712d
commit
8b483849f4
9 changed files with 467 additions and 218 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<uint8_t*>(arena->data) + arena->size;
|
||||
void* const data = static_cast<uint8_t*>(arena->data) + arena->size;
|
||||
arena->size += size;
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
27
src/kvm/endian.h
Normal file
27
src/kvm/endian.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef POUND_KVM_ENDIAN_H
|
||||
#define POUND_KVM_ENDIAN_H
|
||||
|
||||
#define GUEST_IS_LITTLE_ENDIAN 1
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <stdlib.h>
|
||||
#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 <libkern/OSByteOrder.h>
|
||||
#define bswap_16(x) OSSwaoInt16(x)
|
||||
#define bswap_32(x) OSSwapInt32(x)
|
||||
#define bswap_64(x) OSSwapInt64(x)
|
||||
|
||||
#else
|
||||
|
||||
#include <byteswap.h>
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif // POUND_KVM_ENDIAN_H
|
||||
35
src/kvm/guest.cpp
Normal file
35
src/kvm/guest.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "guest.h"
|
||||
#include <assert.h>
|
||||
|
||||
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
|
||||
485
src/kvm/guest.h
485
src/kvm/guest.h
|
|
@ -1,55 +1,109 @@
|
|||
#pragma once
|
||||
#ifndef POUND_KVM_GUEST_H
|
||||
#define POUND_KVM_GUEST_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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
|
||||
|
|
|
|||
104
src/kvm/kvm.cpp
104
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<uint8_t*>(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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue