Merge pull request #880 from adriadam10/feat/kernel-mtypeprotect-type

feat(kernel): apply the memory type in sceKernelMtypeprotect
This commit is contained in:
Dean 2026-10-07 15:28:25 +13:00 committed by GitHub
commit f6f47bf1de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 103 additions and 3 deletions

View File

@ -649,6 +649,33 @@ int DoMprotect(const void* addr, size_t len, int prot) {
return 0;
}
int DoMtypeprotect(const void* addr, size_t len, int type, int prot) {
const int result = DoMprotect(addr, len, prot);
const auto address = reinterpret_cast<std::uintptr_t>(addr);
constexpr auto pageMask = static_cast<std::uintptr_t>(PS5_PAGE_SIZE - 1);
const auto first = address & ~pageMask;
const auto end = (address + len + pageMask) & ~pageMask;
std::vector<std::pair<std::uint64_t, std::size_t>> physical;
{
std::lock_guard lock(g_directLock);
auto it = g_directMappings.lower_bound(first);
if (it != g_directMappings.begin() && std::prev(it)->second.end > first) --it;
while (it != g_directMappings.end() && it->first < end) {
const auto base = it->first;
const auto mapping = it->second;
it = g_directMappings.erase(it);
if (base < first) g_directMappings.emplace(base, DirectMapping{first, mapping.phys, mapping.memoryType, mapping.backing});
const auto low = std::max(base, first);
const auto high = std::min(mapping.end, end);
g_directMappings.emplace(low, DirectMapping{high, mapping.phys + low - base, type, mapping.backing});
physical.emplace_back(mapping.phys + low - base, high - low);
if (mapping.end > end) it = g_directMappings.emplace(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing}).first;
}
}
for (const auto& [phys, bytes] : physical) DirectMemoryRetype(static_cast<int64_t>(phys), bytes, type);
return result;
}
int DoMunmap(void* addr, size_t len) {
Trace("unmap %p+0x%zx", addr, len);
if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0 || !addr) return SCE_KERNEL_ERROR_EINVAL;

View File

@ -16,10 +16,12 @@ void CreateDirectMemoryBacking(int64_t start, size_t len, int memoryType);
bool QueryDirectMapping(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end, std::uint64_t* offset, int* memoryType);
void ForgetDirectMemory(int64_t start, size_t len);
bool DirectMemoryFind(int64_t offset, bool findNext, int64_t* start, int64_t* end, int* memoryType);
void DirectMemoryRetype(int64_t start, size_t len, int memoryType);
size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit);
int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, size_t alignment);
int DoMapAnon(void** addr, size_t len, int prot, int flags);
int DoMprotect(const void* addr, size_t len, int prot);
int DoMtypeprotect(const void* addr, size_t len, int type, int prot);
int DoMunmap(void* addr, size_t len);
int DoReserveVirtual(void** addr, size_t len, int flags, size_t alignment);
bool GuestProtection(uintptr_t addr, int* prot);

View File

@ -316,8 +316,7 @@ int APS5_VABI sceKernelCheckedReleaseDirectMemory(int64_t start, size_t len) {
}
int APS5_VABI sceKernelMtypeprotect(const void* addr, size_t len, int type, int prot) {
(void)type;
return DoMprotect(addr, len, prot);
return DoMtypeprotect(addr, len, type, prot);
}
int APS5_VABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, int* prot) {
@ -426,9 +425,11 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries,
result = sceKernelMunmap(reinterpret_cast<uint64_t>(entry.start), entry.length);
break;
case OpProtect:
case OpTypeProtect:
result = DoMprotect(entry.start, entry.length, static_cast<uint8_t>(entry.protection));
break;
case OpTypeProtect:
result = DoMtypeprotect(entry.start, entry.length, static_cast<uint8_t>(entry.type), static_cast<uint8_t>(entry.protection));
break;
case OpMapFlexible:
result = _mapFlexible(&entry.start, entry.length, static_cast<uint8_t>(entry.protection), flags);
break;

View File

@ -1,5 +1,6 @@
#include "prx/libkernel/DirectMemory/MemoryPool.hpp"
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
#include <algorithm>
#include <map>
#include <mutex>
@ -56,6 +57,21 @@ struct PhysicalMemoryPool {
return true;
}
void Retype(uint64_t start, size_t len, int memoryType) {
std::lock_guard<std::mutex> lock(_mutex);
const uint64_t end = start + len;
auto it = _blocks.upper_bound(start);
if (it != _blocks.begin() && std::prev(it)->second.end > start) --it;
while (it != _blocks.end() && it->first < end) {
const uint64_t blockStart = it->first;
const Block block = it->second;
it = _blocks.erase(it);
if (blockStart < start) _blocks[blockStart] = {start, block.type};
_blocks[std::max(blockStart, start)] = {std::min(block.end, end), memoryType};
if (block.end > end) it = _blocks.emplace(end, Block{block.end, block.type}).first;
}
}
size_t FreeRun(uint64_t offset, uint64_t limit) {
std::lock_guard<std::mutex> lock(_mutex);
uint64_t cur = offset & ~static_cast<uint64_t>(PS5_PAGE_SIZE - 1);
@ -125,6 +141,10 @@ bool DirectMemoryFind(int64_t offset, bool findNext, int64_t* start, int64_t* en
return PhysicalMemoryPool::Instance().Find(static_cast<uint64_t>(offset), findNext, start, end, memoryType);
}
void DirectMemoryRetype(int64_t start, size_t len, int memoryType) {
PhysicalMemoryPool::Instance().Retype(static_cast<uint64_t>(start), len, memoryType);
}
size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit) {
return PhysicalMemoryPool::Instance().FreeRun(offset, limit);
}

View File

@ -43,6 +43,8 @@ int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int);
int APS5_VABI sceKernelMunmap(void*, std::size_t);
int APS5_VABI sceKernelReleaseFlexibleMemory(void*, std::size_t);
int APS5_VABI sceKernelMprotect(const void*, std::size_t, int);
int APS5_VABI sceKernelMtypeprotect(const void*, std::size_t, int, int);
int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int);
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);
@ -293,6 +295,52 @@ static void CheckGetDirectMemoryType() {
Require(sceKernelGetDirectMemoryType(second, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT);
}
static void CheckMtypeprotect() {
constexpr std::size_t page = 0x4000;
std::int64_t phys = 0;
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0);
void* mapped = nullptr;
Require(sceKernelMapDirectMemory(&mapped, page * 3, 3, 0, phys, 0) == 0);
auto* bytes = static_cast<unsigned char*>(mapped);
Require(sceKernelMtypeprotect(bytes + page + 1, 1, 3, 1) == 0);
const auto check = [&](std::size_t index, int expectedType, int expectedProtection) {
VirtualQueryInfo info{};
Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0);
Require(info.is_direct && info.memory_type == expectedType && info.protection == expectedProtection);
Require(info.start == reinterpret_cast<std::uintptr_t>(bytes + page * index) && info.end == info.start + page);
Require(info.offset == static_cast<std::uint64_t>(phys) + page * index);
int type = -1;
std::int64_t start = -1;
std::int64_t end = -1;
Require(sceKernelGetDirectMemoryType(phys + static_cast<std::int64_t>(page * index), &type, &start, &end) == 0);
Require(type == expectedType && start == phys + static_cast<std::int64_t>(page * index) && end == start + static_cast<std::int64_t>(page));
};
check(0, 0, 3);
check(1, 3, 1);
check(2, 0, 3);
KernelBatchMapEntry entry{};
entry.start = bytes + page * 2;
entry.length = page;
entry.protection = 3;
entry.type = 5;
entry.operation = 4;
int processed = -1;
Require(sceKernelBatchMap2(&entry, 1, &processed, 0) == 0 && processed == 1);
check(0, 0, 3);
check(1, 3, 1);
check(2, 5, 3);
Require(sceKernelMtypeprotect(bytes, page * 3, 2, 3) == 0);
VirtualQueryInfo whole{};
Require(sceKernelVirtualQuery(bytes, 0, &whole, sizeof(whole)) == 0);
Require(whole.memory_type == 2 && whole.protection == 3);
int type = -1;
std::int64_t start = -1;
std::int64_t end = -1;
Require(sceKernelGetDirectMemoryType(phys + static_cast<std::int64_t>(page * 2), &type, &start, &end) == 0 && type == 2);
Require(sceKernelMunmap(mapped, page * 3) == 0);
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
}
static void CheckFixedVirtualReservation() {
constexpr std::size_t page = 0x4000;
void* probe = nullptr;
@ -860,6 +908,7 @@ int main() {
CheckMlock();
CheckSharedDirectMemoryLifecycle();
CheckGetDirectMemoryType();
CheckMtypeprotect();
CheckHeapAfterMappingReuse();
#ifdef _WIN32
CheckNoOverwriteRejectsHostOccupiedMapping();

View File

@ -88,6 +88,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceKernelAioSubmitReadCommandsMultiple / sceKernelAioSubmitWriteCommandsMultiple / sceKernelAioWaitRequests / sceKernelAioCancelRequest / sceKernelAioCancelRequests / sceKernelAioDeleteRequests](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) - behaviour from shadPS4 (one id per request for the Multiple submits, cancel turns any request aborted and id 0 reports processing, wait mode 2 returns once one request completed); the 128 requests or ids per batch limit is from SharpProspero, with an unknown error, so larger batches throw; wait modes other than 1 (and) and 2 (or) throw; null pointers, negative counts and invalid ids return the codes the single-request siblings use, checked before any state is written
- [sceKernelGetAvailableCpumask](../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) - returns the default thread affinity, 0x1FFF (CPUs 0 to 12), as KytyPS5 does. Titles pin threads within it (PPSA02664 passes 0x1FFB, Hades II PPSA36082 0x3 and 0x3F); whether CPU 13 is available to a title is not confirmed
- [sceKernelMapNamedFlexibleMemoryInternal](../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) - flag 0x8000 unknown; only the sceKernelMapNamedFlexibleMemory flags are accepted
- [sceKernelMtypeprotect](../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - sets the memory type of the direct mappings in the page-rounded range and of the direct memory behind them, as shadPS4 and prosper do (KytyPS5 ignores the type); the type is not validated, flexible pages in the range only get the protection, and a later map of the retyped direct memory reports the type it was allocated with. `sceKernelBatchMap2` operation 4 takes the same path with the entry type
- [Guest arena](../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws
- [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature
- [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature