From 9754937a5d5e780a21dbc281c90773f0f365d37e Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Mon, 28 Sep 2026 14:22:48 +1300 Subject: [PATCH] feat(libkernel): hinted and no-overwrite mappings, virtual range names and usleep Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01ButHENCbXXt8h4EZuWUabJ --- .../libkernel/DirectMemory/DirectMemory.cpp | 65 ++++++++++++++++-- .../prx/libkernel/DirectMemory/Export.cpp | 68 ++++++++++++++++--- core/libs/prx/libkernel/Time/Time.cpp | 5 ++ core/libs/tests/GuestMemory.cpp | 41 +++++++++++ 4 files changed, 165 insertions(+), 14 deletions(-) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 2d0569a6e..c003b3564 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -15,6 +15,10 @@ #if defined(__linux__) #include +#include +#include +#include +#include #else #include @@ -128,6 +132,37 @@ static int mprotect(void* addr, size_t len, int prot) { namespace { +constexpr int GuestMapFixedFlag = 0x10; + +#if defined(__linux__) +void* MapAtOrAbove(std::uintptr_t start, size_t len, int prot, size_t alignment) { + constexpr std::uintptr_t UserLimit = 0x7fff00000000ull; + for (int attempt = 0; attempt < 8; ++attempt) { + std::vector> used; + std::ifstream maps("/proc/self/maps"); + std::string line; + while (std::getline(maps, line)) { + std::istringstream fields(line); + std::uintptr_t begin = 0, end = 0; + char dash = 0; + if (fields >> std::hex >> begin >> dash >> end) used.emplace_back(begin, end); + } + std::sort(used.begin(), used.end()); + auto candidate = (start + alignment - 1) & ~(static_cast(alignment) - 1); + for (const auto& [begin, end] : used) { + if (end <= candidate) continue; + if (begin >= candidate + len) break; + candidate = (end + alignment - 1) & ~(static_cast(alignment) - 1); + } + if (candidate + len > UserLimit || candidate + len < candidate) throw std::runtime_error("No free range above the mapping address hint"); + void* result = mmap(reinterpret_cast(candidate), len, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); + if (result != MAP_FAILED) return result; + if (errno != EEXIST) throw std::system_error(errno, std::generic_category(), "Hinted mmap failed"); + } + throw std::runtime_error("Hinted mmap kept racing with other mappings"); +} +#endif + void ValidateLength(size_t len) { if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) { // return SCE_KERNEL_ERROR_EINVAL; @@ -182,7 +217,8 @@ void Trace(const char* format, ...) { bool RemapFixedIntoRegistered(GuestAllocations::Mutation& mutation, void* addr, size_t len, int prot, int flags, int64_t physStart = -1) { constexpr int GuestMapFixed = 0x10; - if (addr == nullptr || (flags & GuestMapFixed) == 0 || !mutation.Covers(addr, len)) return false; + constexpr int GuestMapNoOverwrite = 0x80; + if (addr == nullptr || (flags & GuestMapFixed) == 0 || (flags & GuestMapNoOverwrite) != 0 || !mutation.Covers(addr, len)) return false; ValidateRange(addr, len, PS5_PAGE_SIZE); Trace("remap fixed %p+0x%zx prot=0x%x phys=0x%llx", addr, len, prot, static_cast(physStart)); const auto nativeProtection = LinuxProtFromSce(prot); @@ -208,13 +244,26 @@ void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) ValidateLength(len); alignment = ValidateAlignment(alignment); constexpr int GuestMapFixed = 0x10; + constexpr int GuestMapNoOverwrite = 0x80; constexpr int GuestMapNoCoalesce = 0x400000; - if ((flags & ~(GuestMapFixed | GuestMapNoCoalesce)) != 0) { - throw std::invalid_argument("Unsupported memory mapping flags"); +#if defined(__linux__) + constexpr int SupportedFlags = GuestMapFixed | GuestMapNoOverwrite | GuestMapNoCoalesce; +#else + constexpr int SupportedFlags = GuestMapFixed | GuestMapNoCoalesce; +#endif + if ((flags & ~SupportedFlags) != 0) { + char message[64]; + std::snprintf(message, sizeof(message), "Unsupported memory mapping flags 0x%x", flags); + throw std::invalid_argument(message); } if ((flags & GuestMapFixed) != 0) { ValidateRange(addr, len, alignment); - void* result = mmap(addr, len, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +#if defined(__linux__) + const int placement = (flags & GuestMapNoOverwrite) != 0 ? MAP_FIXED_NOREPLACE : MAP_FIXED; +#else + const int placement = MAP_FIXED; +#endif + void* result = mmap(addr, len, prot, MAP_PRIVATE | MAP_ANONYMOUS | placement, -1, 0); if (result == MAP_FAILED) { // return SCE_KERNEL_ERROR_ENOMEM; throw std::system_error(errno, std::generic_category(), "Fixed mmap failed"); @@ -222,7 +271,11 @@ void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) return result; } if (addr) { +#if defined(__linux__) + return MapAtOrAbove(reinterpret_cast(addr), len, prot, alignment); +#else throw std::invalid_argument("Non-fixed mapping address hints are not implemented"); +#endif } #ifdef _WIN32 return mmap_aligned(len, prot, alignment); @@ -270,7 +323,7 @@ int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, } GuestAllocations::Mutation mutation; if (RemapFixedIntoRegistered(mutation, *addr, len, prot, flags)) return 0; - if (*addr != nullptr) mutation.RequireAvailable(*addr, len); + if (*addr != nullptr && (flags & GuestMapFixedFlag) != 0) mutation.RequireAvailable(*addr, len); void* mapped = MapAligned(*addr, len, LinuxProtFromSce(prot), flags, alignment); try { mutation.Add(mapped, len, (prot & 3) != 0, (prot & 2) != 0); @@ -288,7 +341,7 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags) { if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; GuestAllocations::Mutation mutation; if (RemapFixedIntoRegistered(mutation, *addr, len, prot, flags)) return 0; - if (*addr != nullptr) mutation.RequireAvailable(*addr, len); + if (*addr != nullptr && (flags & GuestMapFixedFlag) != 0) mutation.RequireAvailable(*addr, len); void* mapped = MapAligned(*addr, len, LinuxProtFromSce(prot), flags, PS5_PAGE_SIZE); try { mutation.Add(mapped, len, (prot & 3) != 0, (prot & 2) != 0); diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index b39ce5add..9a9d4eb6a 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,42 @@ struct PrtAperture { size_t length; }; +struct NamedRange { + uintptr_t end; + std::string name; +}; + +std::mutex g_rangeNameLock; +std::map g_rangeNames; + +void EraseRangeNames(uintptr_t start, uintptr_t end) { + auto it = g_rangeNames.lower_bound(start); + if (it != g_rangeNames.begin() && std::prev(it)->second.end > start) --it; + while (it != g_rangeNames.end() && it->first < end) { + const auto rangeStart = it->first; + const auto range = it->second; + it = g_rangeNames.erase(it); + if (rangeStart < start) g_rangeNames.emplace(rangeStart, NamedRange{start, range.name}); + if (range.end > end) it = g_rangeNames.emplace(end, NamedRange{range.end, range.name}).first; + } +} + +void ApplyRangeName(uintptr_t address, VirtualQueryInfo* info) { + std::lock_guard lock(g_rangeNameLock); + auto next = g_rangeNames.upper_bound(address); + if (next != g_rangeNames.begin()) { + const auto containing = std::prev(next); + if (address < containing->second.end) { + info->start = std::max(info->start, containing->first); + info->end = std::min(info->end, containing->second.end); + std::strncpy(info->name, containing->second.name.c_str(), sizeof(info->name) - 1); + return; + } + info->start = std::max(info->start, containing->second.end); + } + if (next != g_rangeNames.end()) info->end = std::min(info->end, next->first); +} + std::mutex g_prtLock; PrtAperture g_prtApertures[PRT_APERTURE_COUNT] = {}; @@ -127,14 +164,18 @@ int APS5_VABI sceKernelMapFlexibleMemory(void** addr_in_out, size_t len, int pro return _mapFlexible(addr_in_out, len, prot, flags); } +int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name); + int APS5_VABI sceKernelMapNamedDirectMemory(void** addr, size_t len, int prot, int flags, int64_t direct_memory_start, size_t alignment, const char* name) { - (void)name; - return DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + const int result = DoMapDirect(addr, len, prot, flags, direct_memory_start, alignment); + if (result == 0 && name) sceKernelSetVirtualRangeName(*addr, len, name); + return result; } int32_t APS5_VABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, size_t len, int prot, int flags, const char* name) { - (void)name; - return _mapFlexible(addr_in_out, len, prot, flags); + const int result = _mapFlexible(addr_in_out, len, prot, flags); + if (result == 0 && name) sceKernelSetVirtualRangeName(*addr_in_out, len, name); + return result; } int APS5_VABI sceKernelMprotect(const void* addr, size_t len, int prot) { @@ -180,6 +221,7 @@ int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInf info->protection = (best->readable ? 1 : 0) | (best->writable ? 2 : 0) | (!best->releasable ? 4 : 0); info->is_direct = best->releasable ? 1u : 0u; info->is_committed = 1; + ApplyRangeName(std::max(address, info->start), info); return 0; } // Memory the registry does not know (the title's own heap blocks, stacks): the host's committed @@ -214,6 +256,7 @@ int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInf info->protection = 1 | (writable ? 2 : 0) | (executable ? 4 : 0); info->is_flexible = 1; info->is_committed = 1; + ApplyRangeName(std::max(address, info->start), info); return 0; } @@ -268,10 +311,19 @@ int APS5_VABI sceKernelConfiguredFlexibleMemorySize(size_t* size) { } int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name) { - (void)addr; - (void)len; - (void)name; - NotImplemented_nid_no_patch(__func__); + const auto start = reinterpret_cast(addr); + if (!addr || len == 0 || !name || len > UINTPTR_MAX - start) return SCE_KERNEL_ERROR_EINVAL; + std::lock_guard lock(g_rangeNameLock); + EraseRangeNames(start, start + len); + g_rangeNames.emplace(start, NamedRange{start + len, std::string(name, strnlen(name, 31))}); + return 0; +} + +int APS5_VABI sceKernelClearVirtualRangeName(const void* addr, uint64_t len) { + const auto start = reinterpret_cast(addr); + if (!addr || len == 0 || len > UINTPTR_MAX - start) return SCE_KERNEL_ERROR_EINVAL; + std::lock_guard lock(g_rangeNameLock); + EraseRangeNames(start, start + len); return 0; } diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index f379bcf0a..2d2ca3cc5 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -245,6 +245,11 @@ int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* return sceKernelNanosleep(rqtp, rmtp); } +int APS5_VABI usleep_nid_postfix(KernelUseconds microseconds) { + TimedWait::SleepNanos(static_cast(microseconds) * 1000ULL); + return 0; +} + int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { if (tp == nullptr) { APS5_INVALID_ARG_EX; diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index e0e906e3d..a1953e7b0 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -1,5 +1,8 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "SceTypes.hpp" +#include +#include #include #include #include @@ -9,6 +12,12 @@ 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(); +int APS5_VABI sceKernelMapNamedFlexibleMemory(void**, std::size_t, int, int, const char*); +int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); +int APS5_VABI sceKernelMunmap(void*, std::size_t); +int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); +int APS5_VABI sceKernelSetVirtualRangeName(const void*, std::uint64_t, const char*); +int APS5_VABI sceKernelClearVirtualRangeName(const void*, std::uint64_t); } static void Require(bool condition) { @@ -18,7 +27,39 @@ static void Require(bool condition) { } } +static const char* NameAt(const void* address) { + static VirtualQueryInfo info; + Require(sceKernelVirtualQuery(address, 0, &info, sizeof(info)) == 0); + return info.name; +} + +static void CheckNamedAndHintedMappings() { + constexpr std::size_t length = 0x10000; + void* first = nullptr; + Require(sceKernelMapNamedFlexibleMemory(&first, length, 3, 0, "first mapping") == 0); + Require(std::strcmp(NameAt(first), "first mapping") == 0); + auto* middle = static_cast(first) + 0x4000; + Require(sceKernelSetVirtualRangeName(middle, 0x4000, "middle") == 0); + Require(std::strcmp(NameAt(first), "first mapping") == 0); + Require(std::strcmp(NameAt(middle), "middle") == 0); + Require(sceKernelClearVirtualRangeName(first, length) == 0); + Require(NameAt(middle)[0] == '\0'); + Require(sceKernelSetVirtualRangeName(nullptr, length, "x") != 0); +#if defined(__linux__) + void* hinted = first; + Require(sceKernelMapFlexibleMemory(&hinted, length, 3, 0) == 0); + Require(hinted > first && (reinterpret_cast(hinted) & 0x3fff) == 0); + bool rejected = false; + void* overwrite = first; + try { sceKernelMapFlexibleMemory(&overwrite, 0x4000, 3, 0x90); } catch (const std::exception&) { rejected = true; } + Require(rejected && overwrite == first); + Require(sceKernelMunmap(hinted, length) == 0); +#endif + Require(sceKernelMunmap(first, length) == 0); +} + int main() { + CheckNamedAndHintedMappings(); constexpr std::size_t page = 0x4000; const auto failed = reinterpret_cast(static_cast(-1)); const auto reject = [&](std::size_t length, int protection, int flags, int fd,