Merge pull request #50 from oneandonlydean/feat/libkernel-mappings
feat(libkernel): hinted and no-overwrite mappings, virtual range names and usleep - **Non-fixed mapping with an address hint:** `MapAligned` threw `Non-fixed mapping address hints are not implemented`. On the PS5 the hint is where the search starts. On Linux the mapping is now placed at the first free, aligned range at or above the hint, found from `/proc/self/maps` and mapped with `MAP_FIXED_NOREPLACE` so nothing existing can be replaced. `RequireAvailable` is now only checked for fixed mappings, because a hint may point at used memory. Windows still throws. - **`SCE_KERNEL_MAP_NO_OVERWRITE` (`0x80`):** it was rejected as an unknown flag. With `MAP_FIXED` on Linux it maps with `MAP_FIXED_NOREPLACE`, and it no longer takes the "remap fixed into registered" path, so it fails instead of replacing a registered range. The unsupported-flags error now includes the flag value. - **Virtual range names:** `sceKernelSetVirtualRangeName` was `NotImplemented` and `sceKernelClearVirtualRangeName` wasn't exported. Names are now kept in a registry, split and trimmed on overlap. `sceKernelVirtualQuery` reports the name and splits its reported region at name boundaries. `sceKernelMapNamedDirectMemory` and `sceKernelMapNamedFlexibleMemory` now name the range instead of ignoring the name. - **`usleep`** is exported (POSIX, beside `nanosleep`).
This commit is contained in:
commit
d653d85538
|
|
@ -15,6 +15,10 @@
|
|||
|
||||
#if defined(__linux__)
|
||||
#include <sys/mman.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#else
|
||||
#include <windows.h>
|
||||
|
||||
|
|
@ -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<std::pair<std::uintptr_t, std::uintptr_t>> 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<std::uintptr_t>(alignment) - 1);
|
||||
for (const auto& [begin, end] : used) {
|
||||
if (end <= candidate) continue;
|
||||
if (begin >= candidate + len) break;
|
||||
candidate = (end + alignment - 1) & ~(static_cast<std::uintptr_t>(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<void*>(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<long long>(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<std::uintptr_t>(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);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
|
@ -31,6 +32,42 @@ struct PrtAperture {
|
|||
size_t length;
|
||||
};
|
||||
|
||||
struct NamedRange {
|
||||
uintptr_t end;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::mutex g_rangeNameLock;
|
||||
std::map<uintptr_t, NamedRange> 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<uintptr_t>(info->start, containing->first);
|
||||
info->end = std::min<uintptr_t>(info->end, containing->second.end);
|
||||
std::strncpy(info->name, containing->second.name.c_str(), sizeof(info->name) - 1);
|
||||
return;
|
||||
}
|
||||
info->start = std::max<uintptr_t>(info->start, containing->second.end);
|
||||
}
|
||||
if (next != g_rangeNames.end()) info->end = std::min<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<std::uint64_t>(microseconds) * 1000ULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) {
|
||||
if (tp == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
|
@ -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<unsigned char*>(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<std::uintptr_t>(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<void*>(static_cast<std::uintptr_t>(-1));
|
||||
const auto reject = [&](std::size_t length, int protection, int flags, int fd,
|
||||
|
|
|
|||
Loading…
Reference in New Issue