Merge branch 'arm64'

Ronald Caesar (4):
      arm64/mem: Refactor guest memory access and made it endian aware
      build: Refactor CMake build system
      common: Add design doc for log framework.
      common: Implement logging framework
This commit is contained in:
Ronald Caesar 2025-09-20 07:50:18 -04:00
commit e8ce7a4921
No known key found for this signature in database
GPG key ID: 04307C401999C596
24 changed files with 1015 additions and 499 deletions

View file

@ -1,5 +1,3 @@
# Copyright 2025 Xenon Emulator Project. All rights reserved.
set(BUILD_SHARED_LIBS OFF)
set(BUILD_TESTING OFF)
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON SYSTEM ON)
@ -19,3 +17,20 @@ if (NOT TARGET SDL3::SDL3)
set(SDL_PIPEWIRE OFF)
add_subdirectory(SDL3)
endif()
# ImGui
set(IMGUI_SRC
imgui/imgui.cpp
imgui/imgui_demo.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
imgui/backends/imgui_impl_sdl3.cpp
imgui/backends/imgui_impl_opengl3.cpp
)
add_library(imgui STATIC ${IMGUI_SRC})
target_link_libraries(imgui PRIVATE SDL3::SDL3)
target_include_directories(imgui PUBLIC
imgui
imgui/backends
)

View file

@ -1,81 +1,106 @@
# Copyright 2025 Xenon Emulator Project. All rights reserved.
cmake_minimum_required(VERSION 3.22)
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd_Party/imgui)
#------------------------
# ---- Project Setup ----
#------------------------
project(Pound)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# Enable asserts in release mode. Windows throws a metric fuck ton of compiler
# errors when asserts are disabled.
#if (UNIX)
# string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
#endif()
# Optimizations
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
if (WIN32)
add_compile_options($<$<CONFIG:Release>:/Oi>)
add_compile_options($<$<CONFIG:Release>:/Ot>)
endif()
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
project(Pound)
#-------------------------------
# ---- Dependency Discovery ----
#-------------------------------
find_package(OpenGL REQUIRED)
find_package(fmt 10.2.1 CONFIG)
find_package(SDL3 3.2.10 CONFIG)
add_subdirectory(3rd_Party)
#-----------------------------
# ---- Target Definitions ----
#-----------------------------
if(WIN32)
add_compile_options(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
endif()
add_executable(Pound
src/main.cpp
)
add_subdirectory(src/kvm)
add_subdirectory(3rd_Party)
add_subdirectory(src/common)
add_subdirectory(src/frontend)
add_subdirectory(src/host)
add_subdirectory(src/kvm)
add_subdirectory(src/targets/switch1/hardware)
target_compile_options(Pound PRIVATE -Wall -Wpedantic
-Wshadow
-Wpointer-arith
-Wcast-qual
-Wcast-align
-Wconversion
)
#--------------------------------
# ---- Target Configurations ----
#--------------------------------
# Link libraries
target_link_libraries(Pound PRIVATE fmt::fmt SDL3::SDL3)
include(TestBigEndian)
TEST_BIG_ENDIAN(WORDS_BIGENDIAN)
list(APPEND POUND_PROJECT_TARGETS common frontend host kvm)
foreach(TARGET ${POUND_PROJECT_TARGETS})
# Apply Endianness definitions to all our targets.
if(WORDS_BIGENDIAN)
target_compile_definitions(${TARGET} PRIVATE HOST_IS_BIG_ENDIAN=1 HOST_IS_LITTLE_ENDIAN=0)
else()
target_compile_definitions(${TARGET} PRIVATE HOST_IS_BIG_ENDIAN=0 HOST_IS_LITTLE_ENDIAN=1)
endif()
target_compile_options(${TARGET} PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:
-Wall
-Wpedantic
-Wshadow
-Wpointer-arith
-Wcast-qual
-Wcast-align
-Wconversion>
)
# Set Compile time log level for all targets.
# 1: Trace
# 2: Debug
# 3: Info
# 4: Warning
# 5: Error
# 6: Fatal
target_compile_definitions(${TARGET} PRIVATE COMPILE_TIME_LOG_LEVEL=1)
endforeach()
# Optimizations
set_property(TARGET Pound PROPERTY CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
if (WIN32)
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
# Disables Warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
target_compile_options(Pound PRIVATE $<$<CONFIG:Release>:/Oi>)
target_compile_options(Pound PRIVATE $<$<CONFIG:Release>:/Ot>)
endif()
target_link_libraries(Pound PRIVATE
common
frontend
host
kvm
# ImGui
set(IMGUI_SRC
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/imgui_demo.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
OpenGL::GL
fmt::fmt
SDL3::SDL3
imgui
)
target_sources(Pound PRIVATE ${IMGUI_SRC})
target_include_directories(Pound PRIVATE
${IMGUI_DIR}
${IMGUI_DIR}/backends
)
find_package(OpenGL REQUIRED)
target_link_libraries(Pound PRIVATE OpenGL::GL)
# add ./gui directory

View file

@ -1,59 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <stdio.h>
#include "Arch.h"
#include "Assert.h"
#include "Logging/Backend.h"
#ifdef _MSC_VER
#define Crash() __debugbreak()
#else
#if defined(ARCH_X86_64)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_X86)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_AARCH64)
#define Crash() __asm__ __volatile__("brk 0")
#elif defined(ARCH_PPC)
#include <signal.h>
#ifdef SIGTRAP
#define Crash() raise(SIGTRAP)
#else
#define Crash() raise(SIGABRT)
#endif
#else
#define Crash() __builtin_trap()
#endif
#endif // _MSVC_VER
void throw_fail_impl()
{
::fflush(stdout);
Crash();
}
void assert_fail_impl()
{
printf("Assertion Failed!\n");
throw_fail_impl();
}
[[noreturn]] void unreachable_impl()
{
::fflush(stdout);
Crash();
throw std::runtime_error("Unreachable code");
}
void assert_fail_debug_msg(const std::string& msg)
{
printf("Assertion Failed! %s\n", msg.c_str());
assert_fail_impl();
}
void throw_fail_debug_msg(const std::string& msg)
{
printf("Assertion Failed! %s\n", msg.c_str());
throw_fail_impl();
}

View file

@ -1,121 +0,0 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include "Logging/Log.h"
// Sometimes we want to try to continue even after hitting an assert.
// However touching this file yields a global recompilation as this header is included almost
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
void assert_fail_impl();
void throw_fail_impl();
[[noreturn]] void unreachable_impl();
void assert_fail_debug_msg(const std::string& msg);
void throw_fail_debug_msg(const std::string& msg);
#ifdef _MSC_VER
#define POUND_NO_INLINE __declspec(noinline)
#else
#define POUND_NO_INLINE __attribute__((noinline))
#endif
#define THROW(_a_) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
throw_fail_impl(); \
} \
})
#define ASSERT(_a_) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
assert_fail_impl(); \
} \
})
#define THROW_MSG(_a_, ...) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
throw_fail_impl(); \
} \
})
#define ASSERT_MSG(_a_, ...) \
( \
[&]() POUND_NO_INLINE \
{ \
if (!(_a_)) [[unlikely]] \
{ \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
assert_fail_impl(); \
} \
})
#define UNREACHABLE() \
do \
{ \
LOG_CRITICAL(Debug, "Unreachable code!"); \
unreachable_impl(); \
} while (0)
#define UNREACHABLE_MSG(...) \
do \
{ \
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
unreachable_impl(); \
} while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
#define DEBUG_ASSERT(_a_) \
do \
{ \
} while (0)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
do \
{ \
} while (0)
#endif
#define UNIMPLEMENTED() THROW_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) THROW_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do \
{ \
ASSERT(_a_); \
if (!(_a_)) [[unlikely]] \
{ \
_b_ \
} \
} while (0)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do \
{ \
ASSERT_MSG(_a_, __VA_ARGS__); \
if (!(_a_)) [[unlikely]] \
{ \
_b_ \
} \
} while (0)

View file

@ -1,16 +1,20 @@
#Copyright 2025 Pound Emulator Project.All rights reserved.
add_library(common STATIC)
set(COMMON_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Assert.cpp
target_sources(common PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Config.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IoFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PathUtil.cpp
${CMAKE_CURRENT_SOURCE_DIR}/StringUtil.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Thread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/logging.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Logging/Backend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Logging/Filter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Logging/TextFormatter.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/Logging/TextFormatter.cpp
)
target_sources(Pound PRIVATE ${COMMON_SOURCES})
target_link_libraries(common PUBLIC fmt::fmt)
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..)
target_include_directories(common PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -0,0 +1,124 @@
## **Design Document: Core Logging Subsystem**
**Author:** GloriousTaco, Lead Developer
**Status:** FINAL
**Version:** 1.0
**Date:** 2025-09-14
*Disclaimer: This was mostly written with AI. I'm not a good technical writer*
### **1. Problem Statement**
The Pound project's current logging system is full of object oriented abstractions with no documentation. The system currently risides in `src/common/Logging` with no one going anywhere near it. However, as we move on from prototyping to testing, we require a logging framework that provides a performant diagnostic output and is easy to maintain.
### **2. Glossary**
* **Log Level:** A classification of a log message's severity (e.g., TRACE, DEBUG, INFO, WARN, ERROR, FATAL).
* **Log Sink:** A destination for log messages. This can be a console, a file, a network socket, etc.
* **Structured Logging:** A practice of logging messages in a consistent, machine-readable format (e.g., JSON), with key-value pairs, rather than unstructured text strings.
* **Compile-Time Log Level:** The minimum log level that will be compiled into the binary. Messages below this level are completely removed by the preprocessor, incurring zero runtime cost.
* **Runtime Log Level:** The minimum log level that the system will process and output at runtime. This can be changed dynamically without recompiling.
* **PVM:** Pound Virtual Machine, the overall project.
### **3. Breaking Changes**
* This design will deprecate and forbid all usage of `printf`, `fprintf(stderr, ...)` and other direct-to-console I/O for diagnostic purposes within the PVM codebase (excluding `main.cpp` for initial setup/teardown).
* A pre-commit hook will be introduced to enforce this policy, which will cause existing pull requests to fail until they are updated to use the new logging API.
* All existing modules will require modification to adopt the new logging API.
### **4. Success Criteria**
* **Adoption:** 100% of diagnostic messages in the `kvm`, `common`, `host`, and `frontend` modules will use the new logging system.
* **Performance:** In a release build with the runtime log level set to `INFO`, the overhead of disabled `DEBUG` and `TRACE` log statements shall be statistically unmeasurable (<0.1% performance impact) compared to a binary compiled with logging completely disabled.
* **Usability:** A developer can successfully add a new namespaced log message and filter the system output to show logs only from their module within 15 minutes, using only the API header and a quick-start guide.
### **5. Proposed Design**
We will implement a lightweight, header-only, macro-based logging framework heavily inspired by systems like `spdlog` but simplified for our specific needs. The core design is built on the following tenets:
* **Tenet 1: Performance is Paramount.** Logging is a diagnostic tool; it must never be the cause of a performance issue. The system will aggressively optimize away disabled log calls at compile time.
* **Tenet 2: Structure is Mandatory.** All log messages will be structured, capturing not just a message but also the severity level, timestamp, source location (file and line), and module.
* **Tenet 3: Control is Granular.** Developers must have fine-grained control over logging verbosity at both compile time and runtime, on a per-module basis.
* **Tenet 4: Simplicity in Use.** The API presented to developers must be simple and intuitive, encouraging adoption through macros like `LOG_WARN(...)`.
The primary user interface will be a set of macros:
`LOG_TRACE(module, fmt, ...)`
`LOG_DEBUG(module, fmt, ...)`
`LOG_INFO(module, fmt, ...)`
`LOG_WARN(module, fmt, ...)`
`LOG_ERROR(module, fmt, ...)`
`LOG_FATAL(module, fmt, ...)`
### **6. Technical Design**
The system will be composed of three main parts: the frontend macros, the logging core, and the output sink.
1. **Frontend Macros:**
* The `LOG_X` macros will be the only public-facing API.
* the `LOG_FATAL` macro will be terminal. After logging the message, it will immediately terminate the program via a call to `abort()`.
* The Log macros will first check against a `COMPILE_TIME_LOG_LEVEL`. If the message level is below this threshold, the macro will expand to nothing (`(void)0`), ensuring the code and its arguments are completely compiled out.
* If the level is sufficient, the macro will expand into a call to a logging core function, automatically passing `__FILE__`, `__LINE__`, the log level, and the module name.
* This will live in a `common/logging.h` header.
2. **Logging Core:**
* A central `logger_log()` function will be the target of the macros.
* This function will check the message's log level against a globally configured `runtime_log_level` for the specified module. If the level is insufficient, the function returns immediately.
* If the level is sufficient, it will capture the current high-resolution timestamp, format the message string using the `fmt` library (which we already have as a dependency), and pass the formatted output string to the active sink.
* A small utility will manage the runtime log levels for each registered module (e.g., `logger_set_level("kvm", LEVEL_TRACE)`).
3. **Output Sink:**
* The default sink will be a thread-safe, mutex-protected object that writes to `stderr`.
* The output format will be structured and non-negotiable: `[ISO8601 Timestamp] [LEVEL] [module] [file:line] Message`
* Example: `[2025-09-14T11:23:45.1234Z] [ERROR] [kvm] [mmu.cpp:412] Page table fault at GPA 0xDEADBEEF: Invalid permissions.`
* The design will allow for the possibility of replacing this sink in the future (e.g., to log to a file), but the initial implementation will be `stderr` only.
### **7. Components**
* **Application Modules (kvm, frontend, etc.):** These are the *producers* of log messages. They will include `common/logging.h` and use the `LOG_X` macros.
* **Logging Core (`common/logging`):** The central library responsible for filtering, formatting, and dispatching log messages.
* **Sink (`common/logging`):** The *consumer* of formatted log messages. Initially, this is the `stderr` writer.
* **Main Application (`main.cpp`):** The owner of the system. It is responsible for initializing the logging system, setting initial runtime log levels (e.g., from command-line arguments), and shutting it down cleanly.
### **8. Dependencies**
* **`fmt` library:** Will be used for high-performance string formatting. This is already a project dependency.
* **C++ Standard Library:** Specifically `<chrono>` for timestamps and `<mutex>` for thread safety in the sink.
### **9. Major Risks & Mitigations**
* **Risk 1: Performance Overhead.** Careless implementation could lead to significant overhead even for enabled logs (e.g., excessive string allocation, slow timestamping).
* **Mitigation:** The use of the `fmt` library is a known high-performance choice. We will benchmark the logging of 1 million messages in a tight loop to quantify the overhead and ensure it meets the success criteria.
* **Risk 2: Thread Safety Issues.** Improper locking in the sink could lead to garbled output or race conditions when multiple threads log simultaneously.
* **Mitigation:** A single `std::mutex` will protect all writes to the sink. Code will be peer-reviewed specifically for thread-safety concerns.
* **Risk 3: Slow Adoption / Incorrect Usage.** Developers may continue to use `printf` out of habit.
* **Mitigation:** The API will be designed for extreme ease of use. A short, clear guide will be written. Critically, a pre-commit hook and CI linting job will be added to the build system to automatically fail any code that uses `printf`/`fprintf`. This makes the correct path the only path.
### **10. Out of Scope**
* **File-based Logging:** The initial version will only log to `stderr`. A file sink is a desirable future feature but is not required for V1.
* **Network Logging:** Transmitting logs over a network is not a requirement.
* **Log Rotation:** Since we are not logging to files, rotation is not applicable.
* **Dynamic Sink Swapping:** The sink will be configured at startup and fixed for the application's lifetime.
### **12. Alternatives Considered**
* **Alternative #1: Use a full-featured third-party library (e.g., `spdlog`, `glog`).**
* **Pros:** Mature, feature-rich, well-tested.
* **Cons:** Adds another third-party dependency to maintain. May contain features (async logging, complex file sinks) that add unnecessary complexity and code size for our specific use case. Our needs are simple enough that a minimal, custom implementation is justified.
* **Reasons Discarded:** The primary reason is to minimize external dependencies and code footprint. We can achieve 95% of the value with 10% of the complexity by building a minimal system tailored to our exact needs, leveraging our existing `fmt` dependency.
* **Alternative #2: "Do Nothing" (Continue using `printf`).**
* **Pros:** No development effort required.
* **Cons:** Unstructured, impossible to filter, no severity levels, no timestamps, not thread-safe. Fails to meet every requirement for a mission-safe diagnostic system.
* **Reasons Discarded:** This is a non-starter. It is fundamentally unsuitable for the project's goals.
### **13. Appendix**
#### **Benchmarking Performance**
1. **Baseline Measurement (A):** A simple `for` loop will be created that iterates 1 million times, performing a trivial, non-optimizable operation (e.g., incrementing a `volatile` integer). The total execution time of this loop will be measured using a high-resolution clock.
2. **Disabled Log Measurement (B):** The same loop will be modified to include a `LOG_DEBUG` call. The test binary will be compiled with a `COMPILE_TIME_LOG_LEVEL` of `INFO`. This means the `LOG_DEBUG` macro will expand to `(void)0` and be completely compiled out. The execution time of this loop will be measured.
3. **Enabled Log Measurement (C):** The same loop will be run, but with the `runtime_log_level` set to allow the `LOG_DEBUG` messages to be processed and written to the sink. The sink's output will be redirected to `/dev/null` to measure the cost of formatting and dispatch, not the I/O cost of the terminal itself. The execution time will be measured.
**Analysis:**
* The difference between **(B)** and **(A)** should be zero or statistically insignificant, proving the success criterion that disabled logs have no overhead.
* The difference between **(C)** and **(A)** represents the full overhead of an enabled log call. This allows us to calculate the average cost-per-message on our specific hardware.

View file

@ -4,7 +4,6 @@
#include "IoFile.h"
#include "Assert.h"
#include "Error.h"
#include "Logging/Log.h"
#include "PathUtil.h"
@ -287,7 +286,6 @@ uptr IOFile::GetFileMapping()
mapping = hfile;
fileMapping = std::bit_cast<uptr>(mapping);
ASSERT_MSG(fileMapping, "{}", Base::GetLastErrorMsg());
return fileMapping;
#else
fileMapping = fileno(file);

View file

@ -1,8 +1,7 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "common/Assert.h"
#include "Filter.h"
#include <algorithm>
namespace Base {
namespace Log {
@ -33,13 +32,13 @@ template <typename Iterator>
bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
const auto levelSeparator = std::find(begin, end, ':');
if (levelSeparator == end) {
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string_view(begin, end));
// LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string_view(begin, end));
return false;
}
const Level level = GetLevelByName(levelSeparator + 1, end);
if (level == Level::Count) {
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end));
//LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end));
return false;
}
@ -50,7 +49,7 @@ bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
const Class logClass = GetClassByName(begin, levelSeparator);
if (logClass == Class::Count) {
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
//LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
return false;
}
@ -88,7 +87,6 @@ const char* GetLogClassName(Class logClass) {
default:
break;
}
UNREACHABLE();
}
const char* GetLevelName(Level logLevel) {
@ -108,7 +106,6 @@ const char* GetLevelName(Level logLevel) {
break;
}
#undef LVL
UNREACHABLE();
}
Filter::Filter(Level defaultLevel) {

View file

@ -1,12 +1,12 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "common/Assert.h"
#include "common/Config.h"
#include "TextFormatter.h"
#include "Filter.h"
#include "LogEntry.h"
#include <algorithm>
namespace Base {
namespace Log {
@ -57,7 +57,7 @@ void PrintColoredMessage(const Entry &entry) {
color = ESC "[0;92m";
break;
case Level::Count:
UNREACHABLE();
break;
}
PrintMessage(color, entry);

166
src/common/logging.cpp Normal file
View file

@ -0,0 +1,166 @@
#include "logging.h"
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
/*
* Stupid shit like this is why I hate Windows. WHY CANT YOU JUST FOLLOW
* THE POSIX STAMDARD GOD DAMN.
*/
#define gmtime_r(src, dest) gmtime_s(dest, src)
#endif // _WIN32
/*
* TIMESTMP_BUFFER_LEN - The required buffer size for the timestamp format.
*
* Calculated for "%Y-%m-%dT%H:%M:%SZ":
* YYYY-mm-ddTHH:MM:SSZ
* 4+1+2+1+2+1+2+1+2+1+2+1 = 20 characters
* +1 for the null terminator = 21 bytes.
*
* We define the buffer as 32 bytes to provide a safe margin for future
* format changes, such as adding sub-second precision (e.g., ".123").
*/
#define TIMESTMP_BUFFER_LEN 32
/*
* LOG_LINE_BUFFER_SIZE - A reasonable max size for a single log line.
*
* If it's too long, it will be truncated.
*/
#define LOG_LINE_BUFFER_SIZE 1024
/*
* Static strings to use when all else fails.
* Its unique format makes it easy to search for in logs.
*/
static const char* const FAILED_TIMESTAMP = "[TIMESTAMP_UNAVAILABLE]";
static const char* const FAILED_LOG_LEVEL = "[LOG_LEVEL_UNAVAILABLE]";
log_level_t runtime_log_level = LOG_LEVEL_NONE;
/*
* Pre-allocate a buffer for the timestamp string.
* Make it static so it's not constantly re-allocated on the stack.
*/
static char timestamp_buffer[TIMESTMP_BUFFER_LEN] = {};
const char* get_current_timestamp_str(void);
void log_message(log_level_t level, const char* module_name, const char* file, int line, const char* message, ...)
{
assert(nullptr != message);
const char* timestamp_str = get_current_timestamp_str();
const char* level_str = nullptr;
if (level < runtime_log_level)
{
return;
}
else if (level <= LOG_LEVEL_NONE)
{
level_str = FAILED_LOG_LEVEL;
}
else
{
switch (level)
{
case LOG_LEVEL_TRACE:
level_str = "TRACE";
break;
case LOG_LEVEL_DEBUG:
level_str = "DEBUG";
break;
case LOG_LEVEL_INFO:
level_str = "INFO";
break;
case LOG_LEVEL_WARNING:
level_str = "WARNING";
break;
case LOG_LEVEL_ERROR:
level_str = "ERROR";
break;
case LOG_LEVEL_FATAL:
level_str = "FATAL";
break;
default:
level_str = FAILED_LOG_LEVEL;
break;
}
}
char buffer[LOG_LINE_BUFFER_SIZE] = {};
/* Keep track of our position in the buffer */
size_t offset = 0;
offset += (size_t)snprintf(buffer + offset, LOG_LINE_BUFFER_SIZE - offset, "[%s] [%s] [%s] [%s:%d] ", timestamp_str,
level_str, module_name, file, line);
/* Handle the user's variadic format string. */
va_list args;
va_start(args, message);
if (offset < LOG_LINE_BUFFER_SIZE)
{
(void)vsnprintf(buffer + offset, LOG_LINE_BUFFER_SIZE - offset, message, args);
}
va_end(args);
/* Print the entire, fully-formed buffer to stderr in a SINGLE, ATOMIC call. */
fprintf(stderr, "%s\n", buffer);
/*
* TODO(GloriousTaco:common): For guaranteed atomicity across
* threads, this entire function should be protected by a mutex.
* The fprintf call itself is usually thread-safe at the *call* level, but
* our logic requires atomicity at the *message* level.
*
* lock_logging_mutex();
* fprintf(stderr, "%s\n", buffer);
* unlock_logging_mutex();
*/
}
const char* get_current_timestamp_str(void)
{
time_t now;
/* time() can fail, though it's rare. */
if (time(&now) == (time_t)-1)
{
return FAILED_TIMESTAMP;
}
struct tm time_since_epoch = {};
#ifdef WIN32
/* https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-s-gmtime32-s-gmtime64-s?view=msvc-170 */
(void) gmtime_r(&now, &time_since_epoch);
#if 0
TODO(GloriousTacoo:common): Someone fix this error handling. I have little
patience for anything Windows related.
if ((struct tm)-1 == &time_since_epoch)
{
return FAILED_TIMESTAMP;
}
#endif
#else
if (nullptr == gmtime_r(&now, &time_since_epoch))
{
return FAILED_TIMESTAMP;
}
#endif // WIN32
size_t bytes_written = strftime(timestamp_buffer, TIMESTMP_BUFFER_LEN, "%Y-%m-%dT%H:%M:%SZ", &time_since_epoch);
if (0 == bytes_written)
{
return FAILED_TIMESTAMP;
}
return timestamp_buffer;
}

136
src/common/logging.h Normal file
View file

@ -0,0 +1,136 @@
#ifndef POUND_COMMON_LOGGING_H
#define POUND_COMMON_LOGGING_H
/*
* API USAGE CONTRACT:
* -------------------
*
* Any source file (.c) that uses this logging framework MUST define
* the LOG_MODULE macro *before* including this header file. This
* provides a human-readable name for the component that is generating
* the log message.
*
* Example Usage in a .c file:
*
* #define LOG_MODULE "KVM_MMU"
* #include "common/logging.h"
*
* void mmu_translate_address(uint64_t gpa) {
* LOG_DEBUG("Translating GPA: 0x%llx", gpa);
* // ...
* if (error) {
* LOG_ERROR("Page table fault for GPA: 0x%llx", gpa);
* }
* }
*/
/*
* log_level_t - Defines the severity for log messages.
*/
typedef enum log_level
{
LOG_LEVEL_NONE = 0,
LOG_LEVEL_TRACE,
LOG_LEVEL_DEBUG,
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR,
LOG_LEVEL_FATAL,
} log_level_t;
extern log_level_t runtime_log_level;
/*
* This function is apart of the initial implementation and should NEVER be
* called directly. Use LOG_DEBUG, etc. macros instead.
*/
void log_message(log_level_t level, const char* module_name, const char* file, int line, const char* message, ...);
// -------------------------
// ---- Internal Macros ----
// -------------------------
#define __LOG_MODULE_NOT_DEFINED__
#ifndef LOG_MODULE
#define LOG_MODULE __LOG_MODULE_NOT_DEFINED__
#endif
/* DO NOT USE THESE DIRECTLY. They are internal helpers. */
#define _STRINGIFY_HELPER(x) #x
#define _STRINGIFY(x) _STRINGIFY_HELPER(x)
#define CHECK_LOG_MODULE_DEFINED() \
static_assert(__builtin_strcmp(_STRINGIFY(LOG_MODULE), _STRINGIFY(__LOG_MODULE_NOT_DEFINED__)) != 0, \
"LOGGING ERROR: LOG_MODULE must be #defined before #including logging.h. Example: #define " \
"LOG_MODULE \"MY_MODULE\"")
#if COMPILE_TIME_LOG_LEVEL > LOG_LEVEL_TRACE
#define LOG_TRACE(format, ...) (void)0
#else
#define LOG_TRACE(format, ...) \
do \
{ \
CHECK_LOG_MODULE_DEFINED(); \
log_message(LOG_LEVEL_TRACE, LOG_MODULE, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#endif
// -------------------------------
// ---- Public Logging Macros ----
// -------------------------------
#if COMPILE_TIME_LOG_LEVEL > LOG_LEVEL_DEBUG
#define LOG_DEBUG(format, ...) (void)0
#else
#define LOG_DEBUG(format, ...) \
do \
{ \
CHECK_LOG_MODULE_DEFINED(); \
log_message(LOG_LEVEL_DEBUG, LOG_MODULE, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#endif
#if COMPILE_TIME_LOG_LEVEL > LOG_LEVEL_INFO
#define LOG_INFO(format, ...) (void)0
#else
#define LOG_INFO(format, ...) \
do \
{ \
CHECK_LOG_MODULE_DEFINED(); \
log_message(LOG_LEVEL_INFO, LOG_MODULE, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#endif
#if COMPILE_TIME_LOG_LEVEL > LOG_LEVEL_WARNING
#define LOG_WARNING(format, ...) (void)0
#else
#define LOG_WARNING(format, ...) \
do \
{ \
CHECK_LOG_MODULE_DEFINED(); \
log_message(LOG_LEVEL_WARNING, LOG_MODULE, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#endif
#if COMPILE_TIME_LOG_LEVEL > LOG_LEVEL_ERROR
#define LOG_ERROR(format, ...) (void)0
#else
#define LOG_ERROR(format, ...) \
do \
{ \
CHECK_LOG_MODULE_DEFINED(); \
log_message(LOG_LEVEL_ERROR, LOG_MODULE, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#endif
#if COMPILE_TIME_LOG_LEVEL > LOG_LEVEL_FATAL
#define LOG_FATAL(format, ...) (void)0
#else
#define LOG_FATAL(format, ...) \
do \
{ \
CHECK_LOG_MODULE_DEFINED(); \
log_message(LOG_LEVEL_FATAL, LOG_MODULE, __FILE__, __LINE__, format, ##__VA_ARGS__); \
} while (0)
#endif
#endif // POUND_COMMON_LOGGING_H

View file

@ -1,11 +1,14 @@
#Copyright 2025 Pound Emulator Project.All rights reserved.
add_library(frontend STATIC)
#GUI sources
set(GUI_SOURCES ${CMAKE_CURRENT_SOURCE_DIR} / gui.cpp ${CMAKE_CURRENT_SOURCE_DIR} /
color.cpp ${CMAKE_CURRENT_SOURCE_DIR} / panels.cpp)
target_sources(frontend PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/gui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/color.cpp
${CMAKE_CURRENT_SOURCE_DIR}/panels.cpp
)
#Add all GUI sources to the main target
target_sources(Pound PRIVATE ${GUI_SOURCES})
target_link_libraries(frontend PRIVATE common imgui SDL3::SDL3)
#Include directories for GUI
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..)
target_include_directories(frontend PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

@ -1,7 +1,7 @@
#ifndef POUND_COLORS_H
#define POUND_COLORS_H
#include <imgui.h>
#include "imgui.h"
#include <algorithm>
#include <cstdint>

View file

@ -1,6 +1,6 @@
#include "gui.h"
#include "color.h"
#include "common/Assert.h"
#include <assert.h>
#include "common/Logging/Log.h"
#include "imgui_impl_opengl3_loader.h"
@ -16,8 +16,8 @@ static void apply_theme();
bool gui::window_init(window_t* window, const char* title, int64_t width, int64_t height)
{
ASSERT(nullptr != window);
ASSERT(nullptr != title);
assert(nullptr != window);
assert(nullptr != title);
bool ret = ::SDL_Init(SDL_INIT_VIDEO);
if (false == ret)
@ -119,8 +119,8 @@ void gui::window_destroy(gui::window_t* window)
bool gui::init_imgui(gui::window_t* main_window)
{
ASSERT(nullptr != main_window->data);
ASSERT(nullptr != main_window->gl_context);
assert(nullptr != main_window->data);
assert(nullptr != main_window->gl_context);
// Initialize ImGui
IMGUI_CHECKVERSION();

View file

@ -2,14 +2,14 @@
#include <imgui.h>
#include <math.h>
#include "kvm/kvm.h"
#include "common/Assert.h"
#include <assert.h>"
int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* panel, performance_data_t* data,
std::chrono::steady_clock::time_point* last_render)
{
ASSERT(nullptr != panel);
ASSERT(nullptr != data);
ASSERT(nullptr != last_render);
assert(nullptr != panel);
assert(nullptr != data);
assert(nullptr != last_render);
bool is_visible = true;
(void)::ImGui::Begin(PANEL_NAME_PERFORMANCE, &is_visible);
@ -82,7 +82,7 @@ int8_t gui::panel::render_performance_panel(gui::panel::performance_panel_t* pan
int8_t gui::panel::render_cpu_panel(bool* show_cpu_result_popup)
{
ASSERT(nullptr != show_cpu_result_popup);
assert(nullptr != show_cpu_result_popup);
bool is_visible = true;
(void)::ImGui::Begin(PANEL_NAME_CPU, &is_visible, ImGuiWindowFlags_NoCollapse);

View file

@ -1,13 +1,10 @@
# Copyright 2025 Pound Emulator Project. All rights reserved.
add_library(host STATIC)
set(HOST_SOURCES
target_sources(host PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/memory/arena.cpp
)
target_sources(Pound PRIVATE
${HOST_SOURCES}
)
target_include_directories(Pound PRIVATE
target_include_directories(host PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

View file

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

View file

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

View file

@ -1,10 +1,15 @@
#Copyright 2025 Pound Emulator Project.All rights reserved.
add_library(kvm STATIC)
set(KVM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/mmu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mmio.cpp
${CMAKE_CURRENT_SOURCE_DIR}/kvm.cpp
target_sources(kvm PRIVATE
mmu.cpp
mmio.cpp
kvm.cpp
guest.cpp
)
target_sources(Pound PRIVATE ${KVM_SOURCES})
target_link_libraries(kvm PRIVATE common host)
target_include_directories(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} /..)
target_include_directories(kvm PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
)

27
src/kvm/endian.h Normal file
View 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
View file

@ -0,0 +1,35 @@
#include "guest.h"
#include <cassert>
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

View file

@ -1,55 +1,109 @@
#pragma once
#ifndef POUND_KVM_GUEST_H
#define POUND_KVM_GUEST_H
#include <cassert>
#include <cstdint>
#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

View file

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

View file

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