feat(kernel): support private anonymous guest memory mappings

This commit is contained in:
SamG-Coder 2026-09-25 06:06:31 +10:00
parent 9cc8f872cc
commit 1bbdf7daa6
5 changed files with 178 additions and 0 deletions

View File

@ -104,6 +104,15 @@ endforeach()
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
if(BUILD_TESTING)
add_executable(guest_memory_tests tests/GuestMemory.cpp)
target_include_directories(guest_memory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_memory_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_memory_tests)
add_test(NAME guest_memory COMMAND guest_memory_tests)
if(WIN32)
set_tests_properties(guest_memory PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_executable(guest_string_tests
tests/GuestStrings.cpp
prx/libc/src/StringExtensions.cpp

View File

@ -7,6 +7,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Semaphore/src/Semaphore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/Export.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/DirectMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/PosixMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/MemoryPool.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/MemoryPoolExport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Mutex.cpp

View File

@ -0,0 +1,81 @@
#include "DirectMemory.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <limits>
#include <new>
#include <stdexcept>
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
// FreeBSD/PS5 ABI values, independent of the host's errno and mmap constants.
constexpr int GuestInvalid = 22;
constexpr int GuestNoMemory = 12;
constexpr int GuestNotSupported = 45;
constexpr int GuestPrivate = 0x2;
constexpr int GuestAnonymous = 0x1000;
bool RoundLength(std::size_t length, std::size_t& rounded) {
constexpr auto mask = PS5_PAGE_SIZE - 1;
if (length == 0 || length > std::numeric_limits<std::size_t>::max() - mask)
return false;
rounded = (length + mask) & ~mask;
return true;
}
void SetError(int error) {
*__error_nid_postfix() = error;
}
}
extern "C" {
void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection,
int flags, int descriptor, std::int64_t offset) noexcept {
const auto failed = [](int error) -> void* {
SetError(error);
return reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
};
std::size_t rounded;
if (!RoundLength(length, rounded) || (protection & ~7) != 0)
return failed(GuestInvalid);
if (flags != (GuestPrivate | GuestAnonymous))
return failed(GuestNotSupported);
if (descriptor != -1 || offset != 0)
return failed(GuestInvalid);
// A non-fixed address is a hint; the kernel may choose another address.
(void)address;
void* mapped = nullptr;
try {
const auto result = DoMapAnon(&mapped, rounded, protection, 0);
if (result != 0) return failed(GuestNoMemory);
return mapped;
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::exception&) {
return failed(GuestNoMemory);
}
}
int APS5_VABI munmap_nid_postfix(void* address, std::size_t length) noexcept {
const auto failed = [](int error) {
SetError(error);
return -1;
};
std::size_t rounded;
const auto start = reinterpret_cast<std::uintptr_t>(address);
if (!RoundLength(length, rounded) || start == 0 ||
(start & (PS5_PAGE_SIZE - 1)) != 0 ||
rounded > std::numeric_limits<std::uintptr_t>::max() - start)
return failed(GuestInvalid);
try {
if (DoMunmap(address, rounded) != 0) return failed(GuestInvalid);
return 0;
} catch (const std::bad_alloc&) {
return failed(GuestNoMemory);
} catch (const std::exception&) {
// The allocation tracker rejects foreign, pinned, and noncontiguous ranges.
return failed(GuestInvalid);
}
}
}

View File

@ -0,0 +1,70 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <limits>
extern "C" {
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool condition) {
if (!condition) {
std::fputs("Guest memory check failed\n", stderr);
std::abort();
}
}
int main() {
constexpr std::size_t page = 0x4000;
const auto failed = reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
const auto reject = [&](std::size_t length, int protection, int flags, int fd,
std::int64_t offset, int error) {
*__error_nid_postfix() = 0;
Require(mmap_nid_postfix(nullptr, length, protection, flags, fd, offset) == failed);
Require(*__error_nid_postfix() == error);
};
reject(0, 3, 0x1002, -1, 0, 22);
reject(std::numeric_limits<std::size_t>::max(), 3, 0x1002, -1, 0, 22);
reject(page, 8, 0x1002, -1, 0, 22);
reject(page, 3, 0x1002, 0, 0, 22);
reject(page, 3, 0x1002, -1, 1, 22);
reject(page, 3, 0x1001, -1, 0, 45); // shared
reject(page, 3, 0x1012, -1, 0, 45); // fixed
reject(page, 3, 0x2, 0, 0, 45); // file-backed
reject(page, 3, 0x22, -1, 0, 45); // Linux MAP_ANON is not guest MAP_ANON
auto* memory = static_cast<unsigned char*>(mmap_nid_postfix(nullptr, page * 3 - 1, 3, 0x1002, -1, 0));
Require(memory != failed && (reinterpret_cast<std::uintptr_t>(memory) & (page - 1)) == 0);
for (std::size_t i = 0; i < page * 3; ++i) Require(memory[i] == 0);
memory[0] = 42;
memory[page * 2] = 73;
{
GuestAllocations::Mutation mutation;
const auto range = mutation.Find(memory);
Require(range.bytes == page * 3 && range.readable && range.writable);
}
Require(munmap_nid_postfix(memory + 1, page) == -1 && *__error_nid_postfix() == 22);
Require(munmap_nid_postfix(memory, 0) == -1 && *__error_nid_postfix() == 22);
Require(memory[0] == 42);
Require(munmap_nid_postfix(memory + page, 1) == 0); // round to one guest page
Require(memory[0] == 42 && memory[page * 2] == 73);
Require(munmap_nid_postfix(memory, page) == 0);
Require(memory[page * 2] == 73);
Require(munmap_nid_postfix(memory + page * 2, page) == 0);
Require(munmap_nid_postfix(memory, page) == -1);
for (int protection : {0, 1, 3, 5}) {
void* mapped = mmap_nid_postfix(memory, 1, protection, 0x1002, -1, 0);
Require(mapped != failed);
{
GuestAllocations::Mutation mutation;
const auto range = mutation.Find(mapped);
Require(range.readable == ((protection & 3) != 0));
Require(range.writable == ((protection & 2) != 0));
}
Require(munmap_nid_postfix(mapped, 1) == 0);
}
}

View File

@ -100,3 +100,20 @@ calling convention, bounded unterminated input, concatenation truncation,
interleaved tokenizer state, and case-insensitive searches. Windows dependency
tests also cover implicit libc visibility, ordering, duplicate avoidance, and
unrelated inputs. The complete `libs` target rebuild succeeds.
## Follow-up: anonymous memory mapping
The game's `tooling/native/app_heap.c` requests private anonymous read/write
memory for its OpenGL heap. `mmap` and `munmap` now route this use through
AnyPS5's existing tracked guest allocations, with 16 KiB page rounding and
partial unmapping. Guest MAP_ANON is 0x1000, not the Linux host value 0x20.
Failures return MAP_FAILED or -1 and set guest errno through `__error`.
Shared, fixed-address, and file-backed mappings remain explicitly unsupported
(guest EOPNOTSUPP 45). Unmapping currently requires a contiguous tracked range
within one allocation; holes, foreign memory, and pinned ranges are rejected.
Six CTest suites pass, including real allocations, zero-fill, alignment,
read/write access, tracking, partial unmapping, protection metadata, and invalid
argument/unsupported-mode errors. The unchanged converted game now resolves
the memory-mapping imports and stops at `-hn1tcVHq5Q` (`sceLibcMspaceCreate`).
It still has not reached its entry point or drawn a frame.