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:
Ronald Caesar 2025-09-13 22:47:01 -04:00
parent 768355712d
commit 8b483849f4
9 changed files with 467 additions and 218 deletions

View file

@ -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