mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
Without MAP_FIXED the address is a hint, as for FreeBSD's mmap: the reservation goes at the hint when that range is free, else at the first free range above it. The hint was dropped, so every hinted reservation landed at the lowest free address. RetroArch (PPSA99169) reserves its cores' direct-memory heap near 0x2000000000 outside the GPU window (0x200000000-0x300000000) and refuses a range inside it; with every reservation at 0x200400000 the heap was never made, and Dolphin's first aligned allocation returned null. AI-assisted (Claude Code). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1674 lines
82 KiB
C++
1674 lines
82 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libc/include/GuestAllocations.hpp"
|
|
#include "prx/libc/include/GuestHeap.hpp"
|
|
#include "prx/libc/include/GuestArena.hpp"
|
|
#include "prx/libc/include/GuestWriteWatch.hpp"
|
|
#include "prx/libkernel/File/include/FileFlags.hpp"
|
|
#include "prx/libkernel/KernelErrors.hpp"
|
|
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include "SceTypes.hpp"
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <source_location>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <vector>
|
|
#if defined(__linux__)
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
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 mprotect_nid_postfix(void*, std::size_t, int) noexcept;
|
|
int APS5_VABI madvise_nid_postfix(void*, std::size_t, int);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
int APS5_VABI sceKernelMapNamedFlexibleMemory(void**, std::size_t, int, int, const char*);
|
|
int APS5_VABI sceKernelMapNamedFlexibleMemoryInternal(void**, std::size_t, int, int, const char*);
|
|
int APS5_VABI sceKernelAvailableFlexibleMemorySize(std::size_t*);
|
|
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);
|
|
int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*);
|
|
int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t);
|
|
int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t);
|
|
int APS5_VABI sceKernelCheckedReleaseDirectMemory(std::int64_t, std::size_t);
|
|
int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t);
|
|
int APS5_VABI sceKernelMemoryPoolReserve(void*, std::size_t, std::size_t, int, void**);
|
|
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
|
|
int APS5_VABI sceKernelClose(int);
|
|
std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t);
|
|
std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t);
|
|
int APS5_VABI sceKernelAioInitializeImpl(void*, std::int32_t);
|
|
int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*);
|
|
int APS5_VABI sceKernelAioWaitRequest(std::int32_t, std::int32_t*, std::uint32_t*);
|
|
int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*);
|
|
int APS5_VABI sceKernelMlock_nid_postfix(void*, std::uint64_t);
|
|
int APS5_VABI sceKernelGetDirectMemoryType(std::int64_t, int*, std::int64_t*, std::int64_t*);
|
|
int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int);
|
|
void* APS5_VABI dlopen_nid_postfix(const char*, int);
|
|
void* APS5_VABI dlsym_nid_postfix(void*, const char*);
|
|
int APS5_VABI dlclose_nid_postfix(void*);
|
|
}
|
|
|
|
static void Require(bool condition, std::source_location location = std::source_location::current()) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "Guest memory check failed at %s:%u\n", location.file_name(), static_cast<unsigned>(location.line()));
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
static const char* NameAt(const void* address) {
|
|
static VirtualQueryInfo info;
|
|
Require(sceKernelVirtualQuery(address, 0, &info, sizeof(info)) == 0);
|
|
return info.name;
|
|
}
|
|
|
|
static void CheckReleaseFlexibleMemory() {
|
|
constexpr std::size_t length = 0x10000;
|
|
std::size_t before = 0;
|
|
std::size_t available = 0;
|
|
Require(sceKernelAvailableFlexibleMemorySize(&before) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0 && mapped != nullptr);
|
|
static_cast<volatile unsigned char*>(mapped)[length - 1] = 1;
|
|
Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length);
|
|
Require(sceKernelReleaseFlexibleMemory(mapped, length) == 0);
|
|
Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before);
|
|
void* again = mapped;
|
|
Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x90) == 0 && again == mapped);
|
|
Require(static_cast<volatile unsigned char*>(again)[length - 1] == 0);
|
|
Require(sceKernelMunmap(again, length) == 0);
|
|
}
|
|
|
|
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);
|
|
void* hinted = first;
|
|
Require(sceKernelMapFlexibleMemory(&hinted, length, 3, 0) == 0);
|
|
Require(hinted > first && (reinterpret_cast<std::uintptr_t>(hinted) & 0x3fff) == 0);
|
|
static_cast<volatile unsigned char*>(hinted)[length - 1] = 1;
|
|
void* overwrite = first;
|
|
Require(sceKernelMapFlexibleMemory(&overwrite, 0x4000, 3, 0x90) == static_cast<int>(0x8002000cu));
|
|
Require(overwrite == first);
|
|
Require(sceKernelMunmap(hinted, length) == 0);
|
|
void* exclusive = hinted;
|
|
Require(sceKernelMapFlexibleMemory(&exclusive, length, 3, 0x90) == 0);
|
|
Require(exclusive == hinted);
|
|
static_cast<volatile unsigned char*>(exclusive)[0] = 1;
|
|
Require(sceKernelMunmap(exclusive, length) == 0);
|
|
Require(sceKernelMunmap(first, length) == 0);
|
|
}
|
|
|
|
static void CheckAudioCoprocessorProtection() {
|
|
constexpr std::size_t length = 0x4000;
|
|
void* writable = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&writable, length, 0x200, 0) == 0);
|
|
static_cast<volatile unsigned char*>(writable)[length - 1] = 7;
|
|
Require(static_cast<volatile unsigned char*>(writable)[length - 1] == 7);
|
|
Require(sceKernelMprotect(writable, length, 0x100) == 0);
|
|
Require(static_cast<volatile unsigned char*>(writable)[length - 1] == 7);
|
|
Require(sceKernelMprotect(writable, length, 0x3f2) == 0);
|
|
static_cast<volatile unsigned char*>(writable)[0] = 9;
|
|
Require(sceKernelMunmap(writable, length) == 0);
|
|
bool rejected = false;
|
|
void* undefined = nullptr;
|
|
try { sceKernelMapFlexibleMemory(&undefined, length, 0x400, 0); } catch (const std::invalid_argument&) { rejected = true; }
|
|
Require(rejected && undefined == nullptr);
|
|
}
|
|
|
|
static void CheckInternalNamedFlexibleMapping() {
|
|
constexpr std::size_t length = 0x10000;
|
|
std::size_t before = 0;
|
|
std::size_t available = 0;
|
|
Require(sceKernelAvailableFlexibleMemorySize(&before) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapNamedFlexibleMemoryInternal(&mapped, length, 3, 0, "internal mapping") == 0 && mapped != nullptr);
|
|
Require(std::strcmp(NameAt(mapped), "internal mapping") == 0);
|
|
Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length);
|
|
Require(sceKernelMunmap(mapped, length) == 0);
|
|
Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before);
|
|
void* flagged = nullptr;
|
|
Require(sceKernelMapNamedFlexibleMemoryInternal(&flagged, length, 3, 0x8000, "internal flag") == 0 && flagged != nullptr);
|
|
Require(std::strcmp(NameAt(flagged), "internal flag") == 0);
|
|
static_cast<volatile unsigned char*>(flagged)[length - 1] = 1;
|
|
Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length);
|
|
Require(sceKernelMunmap(flagged, length) == 0);
|
|
bool rejected = false;
|
|
void* unknown = nullptr;
|
|
try { sceKernelMapNamedFlexibleMemoryInternal(&unknown, length, 3, 0x20000, "internal mapping"); } catch (const std::exception&) { rejected = true; }
|
|
Require(rejected && unknown == nullptr);
|
|
Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before);
|
|
}
|
|
|
|
static void CheckBatchMapStopsAtInvalidEntry() {
|
|
constexpr std::size_t page = 0x4000;
|
|
constexpr int mapFlexible = 3;
|
|
constexpr int unmap = 1;
|
|
constexpr int protect = 2;
|
|
const auto flexible = [&] { return KernelBatchMapEntry{nullptr, 0, page, 3, 0, 0, mapFlexible}; };
|
|
for (const std::int32_t operation : {5, 6, -1, std::numeric_limits<std::int32_t>::max()}) {
|
|
KernelBatchMapEntry entries[3] = {flexible(), flexible(), flexible()};
|
|
entries[1].operation = operation;
|
|
int processed = -1;
|
|
Require(sceKernelBatchMap2(entries, 3, &processed, 0) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(processed == 1);
|
|
Require(entries[0].start != nullptr && entries[1].start == nullptr && entries[2].start == nullptr);
|
|
Require(sceKernelMunmap(entries[0].start, page) == 0);
|
|
}
|
|
KernelBatchMapEntry entries[3] = {flexible(), flexible(), flexible()};
|
|
entries[1].operation = protect;
|
|
entries[1].length = 0;
|
|
int processed = -1;
|
|
Require(sceKernelBatchMap2(entries, 3, &processed, 0) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(processed == 1 && entries[0].start != nullptr);
|
|
Require(entries[1].start == nullptr && entries[2].start == nullptr);
|
|
KernelBatchMapEntry unmaps[2] = {entries[0], entries[0]};
|
|
unmaps[0].operation = unmap;
|
|
unmaps[1].operation = unmap;
|
|
unmaps[1].length = 0;
|
|
processed = -1;
|
|
Require(sceKernelBatchMap2(unmaps, 2, &processed, 0) == SCE_KERNEL_ERROR_EINVAL && processed == 1);
|
|
Require(sceKernelBatchMap2(entries, 1, &processed, 0) == 0 && processed == 1);
|
|
Require(sceKernelMunmap(entries[0].start, page) == 0);
|
|
}
|
|
|
|
static void CheckCheckedReleaseDirectMemory() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys + 1, page) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys, page + 1) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys, 0) == 0);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys, page * 3) == SCE_KERNEL_ERROR_ENOENT);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0);
|
|
Require(sceKernelMunmap(mapped, page * 2) == 0);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys + page, page) == 0);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys, page * 2) == SCE_KERNEL_ERROR_ENOENT);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys, page) == 0);
|
|
Require(sceKernelCheckedReleaseDirectMemory(phys, page) == SCE_KERNEL_ERROR_ENOENT);
|
|
}
|
|
|
|
static void CheckDirectMemoryFollowsPhysicalPages() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* first = nullptr;
|
|
Require(sceKernelMapDirectMemory(&first, page * 2, 3, 0, phys, 0) == 0);
|
|
static_cast<unsigned char*>(first)[0] = 11;
|
|
static_cast<unsigned char*>(first)[page + 5] = 22;
|
|
void* alias = nullptr;
|
|
Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0);
|
|
Require(alias != first && static_cast<unsigned char*>(alias)[5] == 22);
|
|
static_cast<unsigned char*>(alias)[5] = 37;
|
|
Require(static_cast<unsigned char*>(first)[page + 5] == 37);
|
|
static_cast<unsigned char*>(first)[page + 6] = 48;
|
|
Require(static_cast<unsigned char*>(alias)[6] == 48);
|
|
Require(sceKernelMprotect(alias, page, 1) == 0);
|
|
static_cast<unsigned char*>(first)[page + 5] = 59;
|
|
Require(static_cast<unsigned char*>(alias)[5] == 59);
|
|
Require(sceKernelMprotect(alias, page, 3) == 0);
|
|
static_cast<unsigned char*>(alias)[5] = 22;
|
|
Require(static_cast<unsigned char*>(first)[page + 5] == 22);
|
|
Require(sceKernelMunmap(alias, page) == 0);
|
|
static_cast<unsigned char*>(first)[page + 5] = 22;
|
|
Require(sceKernelMunmap(first, page * 2) == 0);
|
|
void* filler = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&filler, page * 2, 3, 0) == 0);
|
|
void* second = nullptr;
|
|
Require(sceKernelMapDirectMemory(&second, page, 3, 0, phys + page, 0) == 0);
|
|
Require(second != first && static_cast<unsigned char*>(second)[5] == 22);
|
|
void* reserved = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0);
|
|
void* fixed = reserved;
|
|
Require(sceKernelMapDirectMemory(&fixed, page, 1, 0x10, phys, 0) == 0);
|
|
Require(fixed == reserved && static_cast<unsigned char*>(fixed)[0] == 11);
|
|
Require(sceKernelMunmap(fixed, page) == 0);
|
|
Require(sceKernelMunmap(second, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
std::int64_t again = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &again) == 0 && again == phys);
|
|
void* fresh = nullptr;
|
|
Require(sceKernelMapDirectMemory(&fresh, page * 2, 3, 0, again, 0) == 0);
|
|
Require(static_cast<unsigned char*>(fresh)[0] == 0 && static_cast<unsigned char*>(fresh)[page + 5] == 0);
|
|
Require(sceKernelMunmap(fresh, page * 2) == 0);
|
|
Require(sceKernelMunmap(filler, page * 2) == 0);
|
|
Require(sceKernelReleaseDirectMemory(again, page * 2) == 0);
|
|
}
|
|
|
|
static void CheckReleaseDirectMemoryClearsMappings() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0);
|
|
VirtualQueryInfo before{};
|
|
Require(sceKernelVirtualQuery(mapped, 0, &before, sizeof(before)) == 0);
|
|
Require(before.is_direct && before.offset == static_cast<std::uint64_t>(phys));
|
|
Require(sceKernelReleaseDirectMemory(phys + page, page) == 0);
|
|
VirtualQueryInfo split{};
|
|
Require(sceKernelVirtualQuery(mapped, 0, &split, sizeof(split)) == 0);
|
|
Require(split.is_direct && split.offset == static_cast<std::uint64_t>(phys));
|
|
VirtualQueryInfo dropped{};
|
|
const int droppedResult = sceKernelVirtualQuery(static_cast<unsigned char*>(mapped) + page, 0, &dropped, sizeof(dropped));
|
|
Require(droppedResult != 0 || !dropped.is_direct);
|
|
void* alias = nullptr;
|
|
Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys, 0) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page) == 0);
|
|
for (void* view : {mapped, alias}) {
|
|
VirtualQueryInfo cleared{};
|
|
Require(sceKernelVirtualQuery(view, 0, &cleared, sizeof(cleared)) != 0);
|
|
VirtualQueryInfo next{};
|
|
if (sceKernelVirtualQuery(view, 1, &next, sizeof(next)) == 0) Require(next.start != reinterpret_cast<std::uintptr_t>(view));
|
|
}
|
|
}
|
|
|
|
static void CheckReleaseDirectMemoryRejectsInvalidRanges() {
|
|
constexpr std::size_t page = 0x4000;
|
|
Require(sceKernelReleaseDirectMemory(-1, page) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(0, 0) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(0, 1) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(0, page - 1) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(1, page) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(0, std::numeric_limits<std::size_t>::max()) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(0, DIRECT_MEMORY_SIZE + page) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(DIRECT_MEMORY_SIZE - page, page * 2) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(DIRECT_MEMORY_SIZE, page) == SCE_KERNEL_ERROR_EINVAL);
|
|
}
|
|
|
|
static void CheckFixedMappingReplacesPartialOverlap() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0);
|
|
void* reserved = nullptr;
|
|
Require(sceKernelMapDirectMemory(&reserved, page * 3, 3, 0, phys, 0) == 0);
|
|
Require(sceKernelMunmap(reserved, page * 3) == 0);
|
|
void* head = reserved;
|
|
Require(sceKernelMapDirectMemory(&head, page, 3, 0x10, phys, 0) == 0 && head == reserved);
|
|
void* fixed = reserved;
|
|
Require(sceKernelMapDirectMemory(&fixed, page * 3, 3, 0x10, phys, 0) == 0);
|
|
Require(fixed == reserved);
|
|
static_cast<unsigned char*>(fixed)[page * 2 + 7] = 0x5c;
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0);
|
|
Require(info.is_direct && info.offset == static_cast<std::uint64_t>(phys));
|
|
Require(static_cast<unsigned char*>(fixed)[page * 2 + 7] == 0x5c);
|
|
Require(sceKernelMunmap(fixed, page * 3) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
|
|
}
|
|
|
|
static void CheckGetDirectMemoryType() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t first = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 3, &first) == 0);
|
|
std::int64_t second = 0;
|
|
Require(sceKernelAllocateDirectMemory(first + page * 2, first + page * 3, page, 0, 1, &second) == 0 && second == first + page * 2);
|
|
int type = -1;
|
|
std::int64_t start = -1;
|
|
std::int64_t end = -1;
|
|
Require(sceKernelGetDirectMemoryType(first, &type, &start, &end) == 0);
|
|
Require(type == 3 && start == first && end == first + page * 2);
|
|
type = -1;
|
|
Require(sceKernelGetDirectMemoryType(first + page * 2 - 1, &type, &start, &end) == 0);
|
|
Require(type == 3 && start == first && end == first + page * 2);
|
|
Require(sceKernelGetDirectMemoryType(first + page * 2, &type, &start, &end) == 0);
|
|
Require(type == 1 && start == second && end == second + page);
|
|
Require(sceKernelGetDirectMemoryType(first, nullptr, &start, &end) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelGetDirectMemoryType(first, &type, nullptr, &end) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelGetDirectMemoryType(first, &type, &start, nullptr) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(sceKernelReleaseDirectMemory(first, page) == 0);
|
|
type = -1;
|
|
start = -1;
|
|
end = -1;
|
|
Require(sceKernelGetDirectMemoryType(first, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT);
|
|
Require(type == -1 && start == -1 && end == -1);
|
|
Require(sceKernelGetDirectMemoryType(-1, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT);
|
|
Require(sceKernelGetDirectMemoryType(first + page, &type, &start, &end) == 0);
|
|
Require(type == 3 && start == first + page && end == first + page * 2);
|
|
Require(sceKernelReleaseDirectMemory(first + page, page * 2) == 0);
|
|
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 CheckDirectMemoryGpuProtBits() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0);
|
|
void* mapping = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapping, page, 0x3f2, 0, phys, 0) == 0);
|
|
static_cast<unsigned char*>(mapping)[0] = 11;
|
|
Require(static_cast<unsigned char*>(mapping)[0] == 11);
|
|
Require(sceKernelMunmap(mapping, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page) == 0);
|
|
}
|
|
|
|
static bool GpuMapped(const void* pointer) {
|
|
GuestAllocations::Mutation mutation;
|
|
return mutation.Find(pointer).gpu;
|
|
}
|
|
|
|
static void CheckGpuAccessFollowsProtection() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* cpu = nullptr;
|
|
Require(sceKernelMapDirectMemory(&cpu, page, 3, 0, phys, 0) == 0);
|
|
Require(!GpuMapped(cpu));
|
|
void* gpu = nullptr;
|
|
Require(sceKernelMapDirectMemory(&gpu, page, 0x32, 0, phys + static_cast<std::int64_t>(page), 0) == 0);
|
|
Require(GpuMapped(gpu));
|
|
Require(sceKernelMprotect(cpu, page, 0x13) == 0);
|
|
Require(GpuMapped(cpu));
|
|
Require(sceKernelMprotect(gpu, page, 3) == 0);
|
|
Require(!GpuMapped(gpu));
|
|
static int imageProbe = 0;
|
|
const auto probe = reinterpret_cast<std::uintptr_t>(&imageProbe);
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
mutation.RegisterMainImage();
|
|
}
|
|
{
|
|
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
const auto image = std::find_if(lease.begin(), lease.end(), [&](const auto& range) { return probe >= range->address && probe - range->address < range->bytes; });
|
|
Require(image != lease.end() && !(*image)->releasable && !(*image)->gpu);
|
|
}
|
|
Require(sceKernelMunmap(cpu, page) == 0);
|
|
Require(sceKernelMunmap(gpu, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
void* flexible = nullptr;
|
|
Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 3, 0, "cpu") == 0);
|
|
Require(!GpuMapped(flexible));
|
|
Require(sceKernelMunmap(flexible, page) == 0);
|
|
Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 0x33, 0, "gpu") == 0);
|
|
Require(GpuMapped(flexible));
|
|
Require(sceKernelMunmap(flexible, page) == 0);
|
|
}
|
|
|
|
static void CheckHintedVirtualReservation() {
|
|
constexpr std::size_t page = 0x4000;
|
|
void* probe = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&probe, page * 8, 0, 0) == 0);
|
|
Require(sceKernelMunmap(probe, page * 8) == 0);
|
|
void* const hint = static_cast<unsigned char*>(probe) + page * 2;
|
|
void* placed = hint;
|
|
Require(sceKernelReserveVirtualRange(&placed, page * 2, 0, 0) == 0);
|
|
Require(placed == hint);
|
|
void* above = hint;
|
|
Require(sceKernelReserveVirtualRange(&above, page * 2, 0, 0) == 0);
|
|
Require(above > hint);
|
|
Require(sceKernelMunmap(above, page * 2) == 0);
|
|
Require(sceKernelMunmap(placed, page * 2) == 0);
|
|
}
|
|
|
|
static void CheckFixedVirtualReservation() {
|
|
constexpr std::size_t page = 0x4000;
|
|
void* probe = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&probe, page * 4, 0, 0) == 0);
|
|
Require(sceKernelMunmap(probe, page * 4) == 0);
|
|
void* const requested = static_cast<unsigned char*>(probe) + page;
|
|
void* fixed = requested;
|
|
Require(sceKernelReserveVirtualRange(&fixed, page * 2, 0x400010, 0) == 0);
|
|
Require(fixed == requested);
|
|
void* again = requested;
|
|
Require(sceKernelReserveVirtualRange(&again, page * 2, 0x400010, 0) == 0);
|
|
Require(again == requested);
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* mapped = requested;
|
|
Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x10, phys, 0) == 0);
|
|
Require(mapped == requested);
|
|
static_cast<unsigned char*>(mapped)[0] = 11;
|
|
VirtualQueryInfo before{};
|
|
Require(sceKernelVirtualQuery(mapped, 0, &before, sizeof(before)) == 0);
|
|
Require(before.is_direct);
|
|
void* reserved = requested;
|
|
Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0x10, 0) == 0);
|
|
Require(reserved == requested);
|
|
VirtualQueryInfo after{};
|
|
Require(sceKernelVirtualQuery(reserved, 0, &after, sizeof(after)) == 0);
|
|
Require(!after.is_committed && !after.is_direct && after.protection == 0);
|
|
void* remapped = requested;
|
|
Require(sceKernelMapDirectMemory(&remapped, page * 2, 3, 0x10, phys, 0) == 0);
|
|
Require(remapped == requested);
|
|
VirtualQueryInfo revived{};
|
|
Require(sceKernelVirtualQuery(remapped, 0, &revived, sizeof(revived)) == 0);
|
|
Require(revived.is_direct);
|
|
bool refused = false;
|
|
try {
|
|
sceKernelReserveVirtualRange(&reserved, page * 2, 0x90, 0);
|
|
} catch (const std::exception&) {
|
|
refused = true;
|
|
}
|
|
Require(refused);
|
|
Require(sceKernelMunmap(reserved, page * 2) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
void* pooled = nullptr;
|
|
Require(sceKernelMemoryPoolReserve(requested, page * 2, 0, 0x10, &pooled) == 0);
|
|
Require(pooled == requested);
|
|
Require(sceKernelMunmap(pooled, page * 2) == 0);
|
|
}
|
|
|
|
static void CheckReservedRangeIsNotCommitted() {
|
|
constexpr std::size_t page = 0x4000;
|
|
void* reserved = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, 0) == 0);
|
|
const auto start = reinterpret_cast<std::uintptr_t>(reserved);
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(reserved, 0, &info, sizeof(info)) == 0);
|
|
Require(!info.is_committed && !info.is_direct && !info.is_flexible && info.protection == 0);
|
|
Require(info.start == start && info.end == start + page * 2);
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0);
|
|
void* fixed = reserved;
|
|
Require(sceKernelMapDirectMemory(&fixed, page, 3, 0x10, phys, 0) == 0);
|
|
Require(fixed == reserved);
|
|
static_cast<unsigned char*>(fixed)[0] = 7;
|
|
Require(sceKernelVirtualQuery(reserved, 0, &info, sizeof(info)) == 0);
|
|
Require(info.is_committed && info.is_direct && info.protection == 3);
|
|
Require(info.start == start && info.end == start + page);
|
|
Require(sceKernelVirtualQuery(static_cast<unsigned char*>(reserved) + page, 0, &info, sizeof(info)) == 0);
|
|
Require(!info.is_committed && info.protection == 0);
|
|
Require(info.start == start + page && info.end == start + page * 2);
|
|
Require(sceKernelMunmap(reserved, page * 2) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page) == 0);
|
|
}
|
|
|
|
static void CheckNoOverwriteRefusesLiveMapping() {
|
|
constexpr std::size_t page = 0x4000;
|
|
constexpr int outOfMemory = static_cast<int>(0x8002000cu);
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapped, page, 3, 0, phys, 0) == 0);
|
|
static_cast<unsigned char*>(mapped)[0] = 13;
|
|
void* again = mapped;
|
|
Require(sceKernelMapDirectMemory(&again, page, 3, 0x90, phys + page, 0) == outOfMemory);
|
|
Require(again == mapped);
|
|
void* flexible = mapped;
|
|
Require(sceKernelMapFlexibleMemory(&flexible, page, 3, 0x90) == outOfMemory);
|
|
Require(static_cast<unsigned char*>(mapped)[0] == 13);
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0);
|
|
Require(info.is_direct && info.offset == static_cast<std::uint64_t>(phys));
|
|
Require(sceKernelMunmap(mapped, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
constexpr std::uint32_t ReadWriteProtection = PAGE_READWRITE;
|
|
#else
|
|
constexpr std::uint32_t ReadWriteProtection = 0x04;
|
|
#endif
|
|
|
|
static void CheckNoOverwriteRejectsHostOccupiedMapping() {
|
|
constexpr std::size_t page = 0x4000;
|
|
void* reservation = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&reservation, page * 4, 0, 0) == 0);
|
|
Require(sceKernelMunmap(reservation, page * 4) == 0);
|
|
void* target = static_cast<unsigned char*>(reservation) + page;
|
|
GuestArena::GuestArenaCommit_nid_postfix(target, page, ReadWriteProtection, page);
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0);
|
|
void* fixed = target;
|
|
bool rejected = false;
|
|
try {
|
|
rejected = sceKernelMapDirectMemory(&fixed, page, 3, 0x90, phys, 0) != 0;
|
|
} catch (const std::exception&) {
|
|
rejected = true;
|
|
}
|
|
Require(rejected);
|
|
GuestArena::GuestArenaReset_nid_postfix(target, page);
|
|
Require(sceKernelReleaseDirectMemory(phys, page) == 0);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
static DWORD ImageProtection(const void* pointer) {
|
|
MEMORY_BASIC_INFORMATION memory{};
|
|
Require(VirtualQuery(pointer, &memory, sizeof(memory)) == sizeof(memory));
|
|
return memory.Protect & 0xffu;
|
|
}
|
|
|
|
static unsigned char* GuestModulePage(void* module, std::size_t page) {
|
|
auto* data = static_cast<unsigned char*>(dlsym_nid_postfix(module, "guestMemoryModuleData"));
|
|
Require(data != nullptr);
|
|
auto* target = reinterpret_cast<unsigned char*>((reinterpret_cast<std::uintptr_t>(data) + page - 1) & ~(page - 1));
|
|
Require(target + page <= data + 0x10000);
|
|
return target;
|
|
}
|
|
|
|
static bool RegisteredGuestRange(const void* pointer, std::size_t bytes) {
|
|
GuestAllocations::Mutation mutation;
|
|
return mutation.Overlaps(pointer, bytes);
|
|
}
|
|
|
|
static void CheckGuestModuleImageProtection() {
|
|
constexpr std::size_t page = 0x4000;
|
|
const auto path = std::filesystem::relative(GUEST_MEMORY_MODULE).generic_string();
|
|
void* module = dlopen_nid_postfix(path.c_str(), 2);
|
|
Require(module != nullptr);
|
|
auto* target = GuestModulePage(module, page);
|
|
Require(!RegisteredGuestRange(target, page));
|
|
Require(sceKernelMprotect(target, page, 1) == 0);
|
|
Require(RegisteredGuestRange(target, page));
|
|
Require(ImageProtection(target) == PAGE_READONLY);
|
|
Require(sceKernelMprotect(target, page, 3) == 0);
|
|
Require(ImageProtection(target) == PAGE_READWRITE || ImageProtection(target) == PAGE_WRITECOPY);
|
|
target[1] = 7;
|
|
Require(target[1] == 7);
|
|
Require(sceKernelMprotect(target, page, 1) == 0);
|
|
Require(ImageProtection(target) == PAGE_READONLY);
|
|
|
|
{
|
|
auto held = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
bool failed = false;
|
|
try {
|
|
dlclose_nid_postfix(module);
|
|
} catch (const std::runtime_error&) {
|
|
failed = true;
|
|
}
|
|
Require(failed);
|
|
Require(GuestModulePage(module, page) == target);
|
|
Require(RegisteredGuestRange(target, page));
|
|
Require(ImageProtection(target) == PAGE_READONLY);
|
|
}
|
|
|
|
auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
std::atomic<bool> closed{false};
|
|
std::thread closer([&] {
|
|
Require(dlclose_nid_postfix(module) == 0);
|
|
closed = true;
|
|
});
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
Require(!closed);
|
|
Require(ImageProtection(target) == PAGE_READONLY);
|
|
lease.clear();
|
|
closer.join();
|
|
Require(!RegisteredGuestRange(target, page));
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
mutation.Add(target, page, true, true, false);
|
|
mutation.Remove(target);
|
|
}
|
|
|
|
module = dlopen_nid_postfix(path.c_str(), 2);
|
|
Require(module != nullptr);
|
|
target = GuestModulePage(module, page);
|
|
Require(!RegisteredGuestRange(target, page));
|
|
Require(sceKernelMprotect(target, page, 1) == 0);
|
|
Require(ImageProtection(target) == PAGE_READONLY);
|
|
Require(RegisteredGuestRange(target, page));
|
|
Require(dlclose_nid_postfix(module) == 0);
|
|
Require(!RegisteredGuestRange(target, page));
|
|
|
|
bool rejected = false;
|
|
try {
|
|
sceKernelMprotect(GetModuleHandleA("kernel32.dll"), page, 1);
|
|
} catch (const std::invalid_argument&) {
|
|
rejected = true;
|
|
}
|
|
Require(rejected);
|
|
}
|
|
#endif
|
|
|
|
static void CheckUnhintedKernelMappingsLandInTheWindow() {
|
|
constexpr std::size_t page = 0x4000;
|
|
constexpr std::uintptr_t windowStart = 0x200000000ull;
|
|
constexpr std::uintptr_t windowEnd = 0xFC00000000ull;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0);
|
|
void* direct = nullptr;
|
|
Require(sceKernelMapDirectMemory(&direct, page, 3, 0, phys, 0) == 0);
|
|
Require(direct != nullptr);
|
|
const auto directAddress = reinterpret_cast<std::uintptr_t>(direct);
|
|
Require(directAddress >= windowStart);
|
|
Require(directAddress + page <= windowEnd);
|
|
void* flexible = nullptr;
|
|
Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 3, 0, "arena-window-test") == 0);
|
|
Require(flexible != nullptr);
|
|
const auto flexibleAddress = reinterpret_cast<std::uintptr_t>(flexible);
|
|
Require(flexibleAddress >= windowStart);
|
|
Require(flexibleAddress + page <= windowEnd);
|
|
Require(sceKernelMunmap(direct, page) == 0);
|
|
Require(sceKernelMunmap(flexible, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page) == 0);
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
static void CheckUnmappedArenaRangeStaysReserved() {
|
|
constexpr std::size_t page = 0x4000;
|
|
void* address = nullptr;
|
|
Require(sceKernelMapNamedFlexibleMemory(&address, page, 3, 0, "reserved-after-unmap") == 0);
|
|
Require(address != nullptr);
|
|
Require(sceKernelMunmap(address, page) == 0);
|
|
void* hostile = mmap(address, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
|
|
Require(hostile == MAP_FAILED && errno == EEXIST);
|
|
void* again = nullptr;
|
|
Require(sceKernelMapNamedFlexibleMemory(&again, page, 3, 0, "reserved-after-unmap-2") == 0);
|
|
Require(again != nullptr);
|
|
Require(sceKernelMunmap(again, page) == 0);
|
|
}
|
|
#endif
|
|
|
|
static void CheckArenaPlacesUnhintedMappingsInsideTheWindow() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::uintptr_t base = 0;
|
|
std::size_t size = 0;
|
|
GuestArena::GuestArenaRange_nid_postfix(&base, &size);
|
|
Require(size != 0);
|
|
auto* placed = GuestArena::GuestArenaAllocate_nid_postfix(page, page);
|
|
Require(placed != nullptr);
|
|
const auto address = reinterpret_cast<std::uintptr_t>(placed);
|
|
Require(address >= base);
|
|
Require(address + page <= base + size);
|
|
GuestArena::GuestArenaRelease_nid_postfix(placed, page);
|
|
}
|
|
|
|
static void CheckArenaReusesAFreedRange() {
|
|
constexpr std::size_t page = 0x4000;
|
|
auto* first = GuestArena::GuestArenaAllocate_nid_postfix(page, page);
|
|
Require(first != nullptr);
|
|
GuestArena::GuestArenaRelease_nid_postfix(first, page);
|
|
auto* second = GuestArena::GuestArenaAllocate_nid_postfix(page, page);
|
|
Require(second == first);
|
|
GuestArena::GuestArenaRelease_nid_postfix(second, page);
|
|
}
|
|
|
|
static void CheckArenaHonoursHintsWithoutGoingBelowThem() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::uintptr_t base = 0;
|
|
std::size_t size = 0;
|
|
GuestArena::GuestArenaRange_nid_postfix(&base, &size);
|
|
const auto hint = base + page;
|
|
auto* placed = GuestArena::GuestArenaAllocateAtOrAbove_nid_postfix(hint, page, page);
|
|
Require(placed != nullptr);
|
|
Require(reinterpret_cast<std::uintptr_t>(placed) >= hint);
|
|
Require(GuestArena::GuestArenaContains_nid_postfix(placed, page));
|
|
GuestArena::GuestArenaRelease_nid_postfix(placed, page);
|
|
}
|
|
|
|
static void CheckFixedMappingsReachTheApplicationAreaEnd() {
|
|
constexpr std::size_t page = 0x4000;
|
|
constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull;
|
|
std::uintptr_t base = 0;
|
|
std::size_t size = 0;
|
|
GuestArena::GuestArenaRange_nid_postfix(&base, &size);
|
|
Require(base == 0x200000000ull && base + size == applicationAreaEnd);
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* const requested = reinterpret_cast<void*>(applicationAreaEnd - page * 2);
|
|
void* mapped = requested;
|
|
Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x90, phys, 0) == 0);
|
|
Require(mapped == requested);
|
|
static_cast<volatile unsigned char*>(mapped)[page * 2 - 1] = 7;
|
|
void* alias = nullptr;
|
|
Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0);
|
|
Require(static_cast<volatile unsigned char*>(alias)[page - 1] == 7);
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.is_direct && info.end == applicationAreaEnd);
|
|
void* beyond = reinterpret_cast<void*>(applicationAreaEnd);
|
|
bool refused = false;
|
|
try {
|
|
refused = sceKernelMapDirectMemory(&beyond, page, 3, 0x10, phys, 0) != 0;
|
|
} catch (const std::exception&) {
|
|
refused = true;
|
|
}
|
|
Require(refused && beyond == reinterpret_cast<void*>(applicationAreaEnd));
|
|
Require(sceKernelMunmap(alias, page) == 0);
|
|
Require(sceKernelMunmap(mapped, page * 2) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
static std::size_t LockedKilobytes() {
|
|
std::ifstream status("/proc/self/status");
|
|
std::string line;
|
|
while (std::getline(status, line)) {
|
|
if (line.rfind("VmLck:", 0) == 0) return std::strtoull(line.c_str() + 6, nullptr, 10);
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void CheckMlock() {
|
|
constexpr std::size_t page = 0x4000;
|
|
#ifdef _WIN32
|
|
constexpr std::size_t length = 0x400000;
|
|
#else
|
|
constexpr std::size_t length = 0x10000;
|
|
#endif
|
|
constexpr int outOfMemory = static_cast<int>(0x8002000cu);
|
|
constexpr int invalid = static_cast<int>(0x80020016u);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0);
|
|
auto* bytes = static_cast<unsigned char*>(mapped);
|
|
Require(sceKernelMlock_nid_postfix(mapped, 0) == 0);
|
|
#if defined(__linux__)
|
|
const auto lockedBefore = LockedKilobytes();
|
|
#endif
|
|
Require(sceKernelMlock_nid_postfix(bytes + 1, length - page) == 0);
|
|
#ifdef _WIN32
|
|
SIZE_T minimum = 0;
|
|
SIZE_T maximum = 0;
|
|
DWORD limits = 0;
|
|
Require(GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &limits) && minimum >= length && maximum > minimum);
|
|
Require(VirtualUnlock(mapped, length));
|
|
Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED);
|
|
#elif defined(__linux__)
|
|
Require(LockedKilobytes() - lockedBefore == length / 1024);
|
|
#endif
|
|
Require(sceKernelMlock_nid_postfix(mapped, length) == 0);
|
|
Require(sceKernelMlock_nid_postfix(mapped, length) == 0);
|
|
bytes[length - 1] = 7;
|
|
Require(sceKernelMlock_nid_postfix(reinterpret_cast<void*>(std::numeric_limits<std::uintptr_t>::max() - page + 1), page * 2) == invalid);
|
|
void* reserved = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0);
|
|
Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory);
|
|
Require(sceKernelMunmap(reserved, page) == 0);
|
|
Require(sceKernelMunmap(mapped, length) == 0);
|
|
Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory);
|
|
}
|
|
|
|
static void CheckSharedDirectMemoryLifecycle() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0);
|
|
void* first = nullptr;
|
|
void* second = nullptr;
|
|
Require(sceKernelMapDirectMemory(&first, page * 3, 3, 0, phys, 0) == 0);
|
|
Require(sceKernelMapDirectMemory(&second, page * 3, 3, 0, phys, 0) == 0);
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(second, 0, &info, sizeof(info)) == 0);
|
|
Require(info.is_direct && !info.is_flexible && info.offset == static_cast<std::uint64_t>(phys));
|
|
auto* left = static_cast<unsigned char*>(first);
|
|
auto* right = static_cast<unsigned char*>(second);
|
|
left[0] = 31;
|
|
right[page] = 47;
|
|
left[page * 2] = 63;
|
|
Require(right[0] == 31 && left[page] == 47 && right[page * 2] == 63);
|
|
void* inaccessible = nullptr;
|
|
Require(sceKernelMapDirectMemory(&inaccessible, page, 0, 0, phys + page, 0) == 0);
|
|
left[page] = 48;
|
|
Require(sceKernelMprotect(inaccessible, page, 1) == 0);
|
|
Require(static_cast<const unsigned char*>(inaccessible)[0] == 48);
|
|
Require(sceKernelMunmap(inaccessible, page) == 0);
|
|
Require(sceKernelMunmap(left + page, page) == 0);
|
|
right[page] = 79;
|
|
Require(left[0] == 31 && left[page * 2] == 63);
|
|
void* middle = left + page;
|
|
Require(sceKernelMapDirectMemory(&middle, page, 3, 0x10, phys + page, 0) == 0);
|
|
Require(left[page] == 79);
|
|
Require(sceKernelVirtualQuery(middle, 0, &info, sizeof(info)) == 0);
|
|
Require(info.offset == static_cast<std::uint64_t>(phys) + page && info.start == reinterpret_cast<std::uintptr_t>(middle));
|
|
Require(sceKernelMprotect(second, page * 3, 0) == 0);
|
|
left[page] = 95;
|
|
Require(sceKernelMprotect(second, page * 3, 1) == 0);
|
|
Require(right[page] == 95);
|
|
Require(sceKernelMunmap(first, page) == 0);
|
|
Require(sceKernelMunmap(left + page * 2, page) == 0);
|
|
Require(sceKernelMunmap(middle, page) == 0);
|
|
Require(sceKernelMprotect(second, page * 3, 3) == 0);
|
|
right[page * 2] = 111;
|
|
void* reserved = nullptr;
|
|
Require(sceKernelReserveVirtualRange(&reserved, page * 3, 0, 0) == 0);
|
|
void* fixed = static_cast<unsigned char*>(reserved) + page;
|
|
Require(sceKernelMapDirectMemory(&fixed, page, 3, 0x10, phys + page * 2, 0) == 0);
|
|
Require(static_cast<unsigned char*>(fixed)[0] == 111);
|
|
static_cast<unsigned char*>(fixed)[0] = 127;
|
|
Require(right[page * 2] == 127);
|
|
Require(sceKernelMapFlexibleMemory(&fixed, page, 3, 0x10) == 0);
|
|
Require(static_cast<unsigned char*>(fixed)[0] == 0);
|
|
Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0);
|
|
Require(!info.is_direct && info.is_flexible && info.offset == 0);
|
|
static_cast<unsigned char*>(fixed)[0] = 143;
|
|
Require(right[page * 2] == 127);
|
|
Require(sceKernelMunmap(reserved, page * 3) == 0);
|
|
Require(sceKernelMunmap(second, page * 3) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys + page, page) == 0);
|
|
std::int64_t replacement = 0;
|
|
Require(sceKernelAllocateDirectMemory(phys + page, phys + page * 2, page, 0, 0, &replacement) == 0);
|
|
Require(replacement == phys + page);
|
|
void* mixed = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mixed, page * 3, 3, 0, phys, 0) == 0);
|
|
const auto* data = static_cast<const unsigned char*>(mixed);
|
|
Require(data[0] == 31 && data[page] == 0 && data[page * 2] == 127);
|
|
Require(sceKernelMunmap(mixed, page * 3) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
|
|
}
|
|
|
|
static void CheckHeapAlignment() {
|
|
std::vector<std::pair<unsigned char*, std::size_t>> blocks;
|
|
for (std::size_t bytes = 1; bytes <= 600; ++bytes) blocks.emplace_back(static_cast<unsigned char*>(GuestHeap::GuestHeapAllocate_nid_postfix(bytes)), bytes);
|
|
for (const std::size_t bytes : {std::size_t{4000}, std::size_t{70000}, std::size_t{0x30000}}) blocks.emplace_back(static_cast<unsigned char*>(GuestHeap::GuestHeapAllocate_nid_postfix(bytes)), bytes);
|
|
auto* grown = static_cast<unsigned char*>(GuestHeap::GuestHeapReallocate_nid_postfix(GuestHeap::GuestHeapAllocate_nid_postfix(8), 333));
|
|
blocks.emplace_back(grown, 333);
|
|
for (std::size_t index = 0; index < blocks.size(); ++index) {
|
|
const auto [pointer, bytes] = blocks[index];
|
|
Require((reinterpret_cast<std::uintptr_t>(pointer) & 31u) == 0);
|
|
std::memset(pointer, static_cast<int>(index & 0xffu), bytes);
|
|
}
|
|
for (std::size_t index = 0; index < blocks.size(); ++index) {
|
|
const auto [pointer, bytes] = blocks[index];
|
|
for (std::size_t at = 0; at < bytes; ++at) Require(pointer[at] == static_cast<unsigned char>(index & 0xffu));
|
|
GuestHeap::GuestHeapFree_nid_postfix(pointer);
|
|
}
|
|
}
|
|
|
|
static void CheckHeapAfterMappingReuse() {
|
|
constexpr std::size_t bytes = 0x30000;
|
|
auto* pointer = static_cast<unsigned char*>(GuestHeap::GuestHeapAllocate_nid_postfix(bytes));
|
|
std::memset(pointer, 0x5a, bytes);
|
|
Require(pointer[0] == 0x5a && pointer[bytes - 1] == 0x5a);
|
|
GuestHeap::GuestHeapFree_nid_postfix(pointer);
|
|
pointer = static_cast<unsigned char*>(GuestHeap::GuestHeapAllocate_nid_postfix(bytes));
|
|
std::memset(pointer, 0xa5, bytes);
|
|
Require(pointer[0] == 0xa5 && pointer[bytes - 1] == 0xa5);
|
|
GuestHeap::GuestHeapFree_nid_postfix(pointer);
|
|
}
|
|
|
|
static void CheckSharedWriteTracking() {
|
|
#ifdef _WIN32
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0);
|
|
void* first = nullptr;
|
|
void* second = nullptr;
|
|
Require(sceKernelMapDirectMemory(&first, page * 3, 3, 0, phys, 0) == 0);
|
|
Require(sceKernelMapDirectMemory(&second, page * 3, 3, 0, phys, 0) == 0);
|
|
const auto collect = [](void* address, std::size_t bytes, bool clear = true) {
|
|
std::array<void*, 32> pages{};
|
|
std::size_t count = pages.size();
|
|
Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast<std::uintptr_t>(address), bytes, pages.data(), &count, clear));
|
|
return count;
|
|
};
|
|
Require(collect(first, page * 3) == 12);
|
|
Require(collect(second, page * 3) == 12);
|
|
Require(collect(first, page * 3) == 0);
|
|
Require(collect(second, page * 3) == 0);
|
|
auto* left = static_cast<volatile unsigned char*>(first);
|
|
auto* right = static_cast<volatile unsigned char*>(second);
|
|
left[page + 5] = 21;
|
|
Require(right[page + 5] == 21);
|
|
Require(collect(first, page * 3, false) == 4);
|
|
Require(collect(first, page * 3, false) == 4);
|
|
Require(collect(first, page * 3) == 4);
|
|
Require(collect(second, page * 3) == 4);
|
|
Require(collect(first, page * 3) == 0);
|
|
right[page * 2] = 42;
|
|
Require(collect(first, page * 3) == 4);
|
|
Require(collect(second, page * 3) == 4);
|
|
Require(collect(second, page * 3) == 0);
|
|
Require(sceKernelMprotect(first, page * 3, 1) == 0);
|
|
collect(first, page * 3);
|
|
collect(second, page * 3);
|
|
right[0] = 63;
|
|
Require(left[0] == 63 && collect(first, page * 3) == 4);
|
|
Require(sceKernelMprotect(first, page * 3, 3) == 0);
|
|
collect(first, page * 3);
|
|
left[0] = 84;
|
|
Require(right[0] == 84 && collect(second, page * 3) != 0);
|
|
void* third = nullptr;
|
|
Require(sceKernelMapDirectMemory(&third, page, 3, 0, phys + page, 0) == 0);
|
|
collect(first, page * 3);
|
|
collect(second, page * 3);
|
|
static_cast<volatile unsigned char*>(third)[0] = 105;
|
|
Require(collect(first, page * 3) == 4 && collect(second, page * 3) == 4);
|
|
Require(sceKernelMunmap(third, page) == 0);
|
|
Require(sceKernelMunmap(first, page * 3) == 0);
|
|
Require(sceKernelMunmap(second, page * 3) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
|
|
#endif
|
|
}
|
|
|
|
static void CheckFailedCollectKeepsWrites() {
|
|
#ifdef _WIN32
|
|
constexpr std::size_t page = 0x4000;
|
|
const auto collect = [](void* address, std::size_t bytes, std::size_t& count) {
|
|
std::array<void*, 32> pages{};
|
|
count = pages.size();
|
|
return GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast<std::uintptr_t>(address), bytes, pages.data(), &count, true);
|
|
};
|
|
std::size_t count = 0;
|
|
auto* block = static_cast<unsigned char*>(GuestArena::GuestArenaAllocate_nid_postfix(page * 3, page));
|
|
GuestArena::GuestArenaCommit_nid_postfix(block, page, PAGE_READWRITE, page);
|
|
GuestArena::GuestArenaCommit_nid_postfix(block + page * 2, page, PAGE_READWRITE, page);
|
|
Require(collect(block, page, count) && collect(block + page * 2, page, count));
|
|
static_cast<volatile unsigned char*>(block)[8] = 1;
|
|
Require(!collect(block, page * 3, count));
|
|
Require(collect(block, page, count) && count == 1);
|
|
GuestArena::GuestArenaRelease_nid_postfix(block, page * 3);
|
|
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0);
|
|
auto* shared = static_cast<volatile unsigned char*>(mapped);
|
|
Require(collect(mapped, page * 2, count));
|
|
shared[8] = 2;
|
|
Require(sceKernelMprotect(const_cast<unsigned char*>(shared + page), page, 0) == 0);
|
|
Require(!collect(mapped, page * 2, count));
|
|
Require(collect(mapped, page, count) && count == 4);
|
|
Require(sceKernelMunmap(mapped, page * 2) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
#endif
|
|
}
|
|
|
|
static void CheckPinnedSharedPages() {
|
|
#ifdef _WIN32
|
|
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<volatile unsigned char*>(mapped);
|
|
const auto collect = [&] {
|
|
std::array<void*, 32> pages{};
|
|
std::size_t count = pages.size();
|
|
Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast<std::uintptr_t>(mapped), page * 3, pages.data(), &count, true));
|
|
return count;
|
|
};
|
|
const auto protection = [&](std::size_t offset) {
|
|
MEMORY_BASIC_INFORMATION info{};
|
|
Require(VirtualQuery(const_cast<unsigned char*>(bytes + offset), &info, sizeof(info)) == sizeof(info));
|
|
return info.Protect;
|
|
};
|
|
collect();
|
|
Require(collect() == 0);
|
|
Require(protection(0) == PAGE_READONLY && protection(page) == PAGE_READONLY);
|
|
GuestArena::GuestArenaPinWritable_nid_postfix(const_cast<unsigned char*>(bytes + page), page);
|
|
Require(protection(page) == PAGE_READWRITE && protection(0) == PAGE_READONLY && protection(page * 2) == PAGE_READONLY);
|
|
Require(collect() == 4);
|
|
Require(collect() == 4);
|
|
Require(protection(page) == PAGE_READWRITE);
|
|
bytes[page + 8] = 7;
|
|
bytes[0] = 9;
|
|
Require(collect() == 8);
|
|
Require(collect() == 4);
|
|
Require(protection(0) == PAGE_READONLY && protection(page) == PAGE_READWRITE);
|
|
GuestArena::GuestArenaUnpinWritable_nid_postfix(const_cast<unsigned char*>(bytes + page), page);
|
|
collect();
|
|
Require(collect() == 0);
|
|
Require(protection(page) == PAGE_READONLY);
|
|
bytes[page + 8] = 11;
|
|
Require(collect() == 4);
|
|
Require(sceKernelMunmap(mapped, page * 3) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
|
|
#endif
|
|
}
|
|
|
|
static void CheckReadsIntoSharedWriteTracking() {
|
|
#ifdef _WIN32
|
|
constexpr std::size_t page = 0x4000;
|
|
const auto path = std::filesystem::path("anyps5-tracked-read-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin");
|
|
std::vector<char> seed(page * 2);
|
|
for (std::size_t i = 0; i < seed.size(); ++i) seed[i] = static_cast<char>(i * 7 + 1);
|
|
{
|
|
std::ofstream stream(path, std::ios::binary);
|
|
stream.write(seed.data(), static_cast<std::streamsize>(seed.size()));
|
|
}
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0);
|
|
auto* bytes = static_cast<unsigned char*>(mapped);
|
|
const auto collect = [&] {
|
|
std::array<void*, 32> pages{};
|
|
std::size_t count = pages.size();
|
|
Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast<std::uintptr_t>(mapped), page * 2, pages.data(), &count, true));
|
|
return count;
|
|
};
|
|
const int fd = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
Require(fd >= 0);
|
|
collect();
|
|
Require(collect() == 0);
|
|
Require(sceKernelPread(fd, mapped, page * 2, 0) == static_cast<std::int64_t>(page * 2));
|
|
Require(std::memcmp(bytes, seed.data(), page * 2) == 0);
|
|
Require(collect() == 8);
|
|
const int sequential = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0);
|
|
Require(sequential >= 0);
|
|
Require(sceKernelRead(sequential, bytes + page, page) == static_cast<std::int64_t>(page));
|
|
Require(sceKernelClose(sequential) == 0);
|
|
Require(std::memcmp(bytes + page, seed.data(), page) == 0);
|
|
Require(collect() == 4);
|
|
Require(sceKernelAioInitializeImpl(nullptr, 0) == 0);
|
|
KernelAioResult result{-1, 0};
|
|
KernelAioRwRequest request{static_cast<std::int64_t>(page), page, bytes, &result, fd};
|
|
std::int32_t id = 0;
|
|
std::int32_t state = 0;
|
|
Require(sceKernelAioSubmitReadCommands(&request, 1, 0, &id) == 0);
|
|
Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0);
|
|
Require(result.state == 3 && result.return_value == static_cast<std::int64_t>(page));
|
|
Require(std::memcmp(bytes, seed.data() + page, page) == 0);
|
|
Require(collect() == 4);
|
|
std::int32_t deleted = -1;
|
|
Require(sceKernelAioDeleteRequest(id, &deleted) == 0);
|
|
Require(sceKernelMprotect(mapped, page * 2, 1) == 0);
|
|
Require(sceKernelPread(fd, mapped, page, 0) == static_cast<std::int64_t>(SCE_KERNEL_ERROR_EFAULT));
|
|
KernelAioResult refusedResult{-1, 0};
|
|
KernelAioRwRequest refusedRequest{0, page, bytes, &refusedResult, fd};
|
|
Require(sceKernelAioSubmitReadCommands(&refusedRequest, 1, 0, &id) == 0);
|
|
Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0);
|
|
Require(refusedResult.return_value == static_cast<std::int64_t>(SCE_KERNEL_ERROR_EFAULT));
|
|
Require(sceKernelAioDeleteRequest(id, &deleted) == 0);
|
|
Require(sceKernelClose(fd) == 0);
|
|
Require(sceKernelMunmap(mapped, page * 2) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0);
|
|
std::filesystem::remove(path);
|
|
#endif
|
|
}
|
|
|
|
#if defined(__linux__)
|
|
using PageRuns = std::vector<std::pair<std::uintptr_t, std::uintptr_t>>;
|
|
|
|
static bool CollectRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) {
|
|
runs.clear();
|
|
const auto address = reinterpret_cast<std::uintptr_t>(base);
|
|
std::pair<std::uintptr_t, PageRuns*> context{address, &runs};
|
|
return GuestWriteWatch::GuestWriteWatchCollect_nid_postfix(address + offset, bytes, [](void* context, std::uintptr_t begin, std::uintptr_t end) {
|
|
auto& [origin, into] = *static_cast<std::pair<std::uintptr_t, PageRuns*>*>(context);
|
|
if (!into->empty() && into->back().second == (begin - origin) / 4096) into->back().second = (end - origin) / 4096;
|
|
else into->emplace_back((begin - origin) / 4096, (end - origin) / 4096);
|
|
}, &context);
|
|
}
|
|
|
|
static bool Written(const void* base, std::size_t bytes, PageRuns expected) {
|
|
PageRuns runs;
|
|
const bool complete = CollectRuns(base, 0, bytes, runs);
|
|
if (complete && runs == expected) return true;
|
|
std::fprintf(stderr, "write watch collect %s, written pages:", complete ? "complete" : "incomplete");
|
|
for (const auto& [first, last] : runs) std::fprintf(stderr, " [%zu, %zu)", static_cast<std::size_t>(first), static_cast<std::size_t>(last));
|
|
std::fputs("\n", stderr);
|
|
return false;
|
|
}
|
|
|
|
static bool CollectArmedRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) {
|
|
runs.clear();
|
|
const auto address = reinterpret_cast<std::uintptr_t>(base);
|
|
std::pair<std::uintptr_t, PageRuns*> context{address, &runs};
|
|
return GuestWriteWatch::GuestWriteWatchCollectArmed_nid_postfix(address + offset, bytes, [](void* context, std::uintptr_t begin, std::uintptr_t end) {
|
|
auto& [origin, into] = *static_cast<std::pair<std::uintptr_t, PageRuns*>*>(context);
|
|
if (!into->empty() && into->back().second == (begin - origin) / 4096) into->back().second = (end - origin) / 4096;
|
|
else into->emplace_back((begin - origin) / 4096, (end - origin) / 4096);
|
|
}, &context);
|
|
}
|
|
|
|
static void CheckWriteWatchArmedCollect() {
|
|
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) return;
|
|
constexpr std::size_t length = 0x100000;
|
|
constexpr std::size_t small = 4096;
|
|
void* mapping = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&mapping, length, 3, 0) == 0);
|
|
auto* bytes = static_cast<volatile unsigned char*>(mapping);
|
|
PageRuns runs;
|
|
Require(CollectRuns(mapping, 0, length / 2, runs) && runs == PageRuns{{0, length / small / 2}});
|
|
bytes[5 * small] = 1;
|
|
bytes[200 * small] = 1;
|
|
Require(CollectArmedRuns(mapping, 0, length, runs) && runs == PageRuns{{5, 6}});
|
|
Require(Written(mapping, length, {{length / small / 2, length / small}}));
|
|
Require(Written(mapping, length, {}));
|
|
Require(sceKernelMunmap(mapping, length) == 0);
|
|
}
|
|
|
|
static bool CollectFreshRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) {
|
|
runs.clear();
|
|
const auto address = reinterpret_cast<std::uintptr_t>(base);
|
|
std::pair<std::uintptr_t, PageRuns*> context{address, &runs};
|
|
return GuestWriteWatch::GuestWriteWatchCollectFresh_nid_postfix(address + offset, bytes, [](void* context, std::uintptr_t begin, std::uintptr_t end) {
|
|
auto& [origin, into] = *static_cast<std::pair<std::uintptr_t, PageRuns*>*>(context);
|
|
if (!into->empty() && into->back().second == (begin - origin) / 4096) into->back().second = (end - origin) / 4096;
|
|
else into->emplace_back((begin - origin) / 4096, (end - origin) / 4096);
|
|
}, &context);
|
|
}
|
|
|
|
static void CheckWriteWatchFreshCollect() {
|
|
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) return;
|
|
constexpr std::size_t length = 0x100000;
|
|
constexpr std::size_t small = 4096;
|
|
void* mapping = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&mapping, length, 3, 0) == 0);
|
|
auto* bytes = static_cast<volatile unsigned char*>(mapping);
|
|
PageRuns runs;
|
|
Require(CollectRuns(mapping, 0, length / 2, runs) && runs == PageRuns{{0, length / small / 2}});
|
|
bytes[5 * small] = 1;
|
|
Require(CollectFreshRuns(mapping, 0, length, runs) && runs == PageRuns{{length / small / 2, length / small}});
|
|
Require(CollectFreshRuns(mapping, 0, length, runs) && runs.empty());
|
|
bytes[200 * small] = 1;
|
|
Require(Written(mapping, length, {{5, 6}, {200, 201}}));
|
|
Require(sceKernelMunmap(mapping, length) == 0);
|
|
}
|
|
|
|
static void CheckWriteWatch() {
|
|
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
|
std::puts("write watch unavailable: not tested");
|
|
return;
|
|
}
|
|
constexpr std::size_t length = 0x100000;
|
|
constexpr std::size_t small = 4096;
|
|
void* mapping = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&mapping, length, 3, 0) == 0);
|
|
auto* bytes = static_cast<volatile unsigned char*>(mapping);
|
|
const auto address = reinterpret_cast<std::uintptr_t>(mapping);
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length));
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address + small, small));
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length + small));
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(&length), sizeof(length)));
|
|
Require(Written(mapping, length, {{0, length / small}}));
|
|
Require(Written(mapping, length, {}));
|
|
bytes[5 * small + 17] = 1;
|
|
Require(Written(mapping, length, {{5, 6}}));
|
|
Require(Written(mapping, length, {}));
|
|
static_cast<void>(bytes[10 * small]);
|
|
Require(Written(mapping, length, {}));
|
|
bytes[7 * small] = 1;
|
|
bytes[9 * small] = 1;
|
|
PageRuns runs;
|
|
Require(CollectRuns(mapping, 8 * small, small, runs) && runs.empty());
|
|
Require(CollectRuns(mapping, 7 * small + 100, 1, runs) && runs == PageRuns{{7, 8}});
|
|
Require(Written(mapping, length, {{9, 10}}));
|
|
int pipe[2];
|
|
Require(::pipe(pipe) == 0);
|
|
Require(::write(pipe[1], "kernel", 6) == 6);
|
|
Require(::read(pipe[0], const_cast<unsigned char*>(bytes + 20 * small + 8), 6) == 6);
|
|
::close(pipe[0]);
|
|
::close(pipe[1]);
|
|
Require(bytes[20 * small + 8] == 'k' && Written(mapping, length, {{20, 21}}));
|
|
std::thread([&] { bytes[30 * small + 5] = 3; }).join();
|
|
Require(Written(mapping, length, {{30, 31}}));
|
|
std::vector<unsigned char> source(2 * small, 0xab);
|
|
std::memcpy(const_cast<unsigned char*>(bytes + 40 * small + 2048), source.data(), source.size());
|
|
Require(Written(mapping, length, {{40, 43}}));
|
|
Require(sceKernelMprotect(mapping, length, 1) == 0);
|
|
Require(sceKernelMprotect(mapping, length, 3) == 0);
|
|
Require(Written(mapping, length, {}));
|
|
bytes[50 * small] = 1;
|
|
Require(Written(mapping, length, {{50, 51}}));
|
|
constexpr std::size_t guestPage = 0x4000;
|
|
auto* middle = const_cast<unsigned char*>(bytes + 4 * guestPage);
|
|
Require(sceKernelMunmap(middle, guestPage) == 0);
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length));
|
|
Require(!CollectRuns(mapping, 0, length, runs) && runs.empty());
|
|
void* fixed = middle;
|
|
Require(sceKernelMapFlexibleMemory(&fixed, guestPage, 3, 0x10) == 0 && fixed == middle);
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length));
|
|
Require(Written(mapping, length, {{16, 20}}));
|
|
Require(Written(mapping, length, {}));
|
|
middle[1] = 1;
|
|
Require(Written(mapping, length, {{16, 17}}));
|
|
Require(sceKernelMunmap(mapping, length) == 0);
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, small));
|
|
constexpr std::size_t tableSpan = 0x200000;
|
|
constexpr std::size_t spanned = 2 * tableSpan;
|
|
void* raw = mmap(nullptr, spanned + tableSpan, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
Require(raw != MAP_FAILED);
|
|
const auto rawAddress = reinterpret_cast<std::uintptr_t>(raw);
|
|
void* region = reinterpret_cast<void*>((rawAddress + tableSpan - 1) & ~(tableSpan - 1));
|
|
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(region, spanned);
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(region), spanned));
|
|
Require(Written(region, spanned, {{0, spanned / small}}));
|
|
Require(Written(region, spanned, {}));
|
|
static_cast<volatile unsigned char*>(region)[tableSpan + 3 * small] = 1;
|
|
static_cast<volatile unsigned char*>(region)[7 * small] = 1;
|
|
Require(Written(region, spanned, {{7, 8}, {tableSpan / small + 3, tableSpan / small + 4}}));
|
|
Require(Written(region, spanned, {}));
|
|
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(region, spanned);
|
|
Require(munmap(raw, spanned + tableSpan) == 0);
|
|
}
|
|
|
|
static std::string BackingOf(const void* address) {
|
|
std::ifstream maps("/proc/self/maps");
|
|
std::string line;
|
|
while (std::getline(maps, line)) {
|
|
if (std::strtoull(line.c_str(), nullptr, 16) != reinterpret_cast<std::uintptr_t>(address)) continue;
|
|
const auto path = line.find('/');
|
|
return path == std::string::npos ? std::string() : line.substr(path);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static void CheckDirectMemoryBackingNeedsNoFilesystem() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0);
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapDirectMemory(&mapped, page, 3, 0, phys, 0) == 0);
|
|
const auto backing = BackingOf(mapped);
|
|
if (backing.rfind("/memfd:", 0) != 0) std::fprintf(stderr, "direct memory backing: %s\n", backing.c_str());
|
|
Require(backing.rfind("/memfd:", 0) == 0);
|
|
Require(sceKernelMunmap(mapped, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page) == 0);
|
|
}
|
|
|
|
static void CheckDirectMemorySharedBacking() {
|
|
constexpr std::size_t page = 0x4000;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0);
|
|
void* first = nullptr;
|
|
Require(sceKernelMapDirectMemory(&first, page * 2, 3, 0, phys + page, 0) == 0);
|
|
const auto address = reinterpret_cast<std::uintptr_t>(first);
|
|
static_cast<volatile unsigned char*>(first)[page + 5] = 0x5a;
|
|
int file = -1;
|
|
std::uint64_t offset = 0;
|
|
const auto base = static_cast<std::uint64_t>(phys);
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address + page, page, &file, &offset));
|
|
Require(offset == base + page * 2);
|
|
unsigned char byte = 0;
|
|
Require(pread(file, &byte, 1, static_cast<off_t>(offset + 5)) == 1 && byte == 0x5a);
|
|
const int seals = fcntl(file, F_GET_SEALS);
|
|
Require(seals >= 0 && (seals & F_SEAL_SHRINK) != 0 && (seals & F_SEAL_GROW) != 0);
|
|
Require((fcntl(file, F_GETFD) & FD_CLOEXEC) != 0);
|
|
close(file);
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address, page * 2, &file, &offset) && offset == base + page);
|
|
close(file);
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address + page + 0x1000, 0x1000, &file, &offset) && offset == page * 2 + 0x1000);
|
|
close(file);
|
|
Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page * 3, &file, &offset));
|
|
Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, 0, &file, &offset));
|
|
void* flexible = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&flexible, page * 2, 3, 0) == 0);
|
|
const auto flexibleAddress = reinterpret_cast<std::uintptr_t>(flexible);
|
|
Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page, &file, &offset));
|
|
void* low = flexible;
|
|
Require(sceKernelMapDirectMemory(&low, page, 3, 0x10, phys, 0) == 0);
|
|
void* high = static_cast<unsigned char*>(flexible) + page;
|
|
Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys + page, 0) == 0);
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset) && offset == base);
|
|
close(file);
|
|
Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys + page * 2, 0) == 0);
|
|
Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset));
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset) && offset == base + page * 2);
|
|
close(file);
|
|
Require(sceKernelMunmap(high, page) == 0);
|
|
Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset));
|
|
Require(sceKernelMunmap(flexible, page) == 0);
|
|
Require(sceKernelMunmap(first, page * 2) == 0);
|
|
Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page, &file, &offset));
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
|
|
|
|
std::int64_t left = 0;
|
|
std::int64_t right = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &left) == 0);
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &right) == 0);
|
|
void* leftView = nullptr;
|
|
void* rightView = nullptr;
|
|
Require(sceKernelMapDirectMemory(&leftView, page, 3, 0, left, 0) == 0);
|
|
Require(sceKernelMapDirectMemory(&rightView, page, 3, 0, right, 0) == 0);
|
|
int leftFile = -1;
|
|
int rightFile = -1;
|
|
std::uint64_t leftOffset = 0;
|
|
std::uint64_t rightOffset = 0;
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(reinterpret_cast<std::uintptr_t>(leftView), page, &leftFile, &leftOffset) && leftOffset == static_cast<std::uint64_t>(left));
|
|
Require(GuestArena::GuestArenaSharedBacking_nid_postfix(reinterpret_cast<std::uintptr_t>(rightView), page, &rightFile, &rightOffset) && rightOffset == static_cast<std::uint64_t>(right));
|
|
struct stat leftInfo {};
|
|
struct stat rightInfo {};
|
|
Require(fstat(leftFile, &leftInfo) == 0 && fstat(rightFile, &rightInfo) == 0 && leftInfo.st_ino == rightInfo.st_ino && leftInfo.st_dev == rightInfo.st_dev);
|
|
close(leftFile);
|
|
close(rightFile);
|
|
std::memset(leftView, 0x77, page);
|
|
Require(sceKernelMunmap(leftView, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(left, page) == 0);
|
|
std::int64_t again = 0;
|
|
Require(sceKernelAllocateDirectMemory(left, left + static_cast<std::int64_t>(page), page, 0, 0, &again) == 0 && again == left);
|
|
void* againView = nullptr;
|
|
Require(sceKernelMapDirectMemory(&againView, page, 3, 0, again, 0) == 0);
|
|
for (std::size_t i = 0; i < page; ++i) Require(static_cast<const unsigned char*>(againView)[i] == 0);
|
|
Require(sceKernelMunmap(againView, page) == 0);
|
|
Require(sceKernelMunmap(rightView, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(again, page) == 0);
|
|
Require(sceKernelReleaseDirectMemory(right, page) == 0);
|
|
}
|
|
|
|
static void CheckDirectMemoryWriteWatch() {
|
|
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
|
std::puts("write watch unavailable: direct memory not tested");
|
|
return;
|
|
}
|
|
constexpr std::size_t page = 0x4000;
|
|
constexpr std::size_t small = 4096;
|
|
std::int64_t phys = 0;
|
|
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0);
|
|
void* first = nullptr;
|
|
Require(sceKernelMapDirectMemory(&first, page * 3, 3, 0, phys, 0) == 0);
|
|
const auto address = reinterpret_cast<std::uintptr_t>(first);
|
|
auto* bytes = static_cast<volatile unsigned char*>(first);
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page * 3));
|
|
Require(Written(first, page * 3, {{0, page * 3 / small}}));
|
|
Require(Written(first, page * 3, {}));
|
|
bytes[page + 5] = 1;
|
|
Require(Written(first, page * 3, {{page / small, page / small + 1}}));
|
|
void* alias = nullptr;
|
|
Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0);
|
|
const auto aliasAddress = reinterpret_cast<std::uintptr_t>(alias);
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(aliasAddress, page));
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address + page, page));
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page) && GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address + page * 2, page));
|
|
PageRuns runs;
|
|
Require(!CollectRuns(first, 0, page * 3, runs));
|
|
bytes[0] = 2;
|
|
static_cast<volatile unsigned char*>(alias)[7] = 3;
|
|
Require(bytes[page + 7] == 3);
|
|
Require(CollectRuns(first, 0, page, runs) && runs == PageRuns{{0, 1}});
|
|
Require(CollectRuns(first, page * 2, page, runs) && runs.empty());
|
|
Require(sceKernelMunmap(alias, page) == 0);
|
|
Require(sceKernelMunmap(first, page * 3) == 0);
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page));
|
|
void* flexible = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&flexible, page * 3, 3, 0) == 0);
|
|
Require(Written(flexible, page * 3, {{0, page * 3 / small}}));
|
|
Require(Written(flexible, page * 3, {}));
|
|
void* fixed = static_cast<unsigned char*>(flexible) + page;
|
|
Require(sceKernelMapDirectMemory(&fixed, page, 3, 0x10, phys + page * 2, 0) == 0);
|
|
Require(fixed == static_cast<unsigned char*>(flexible) + page);
|
|
Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(flexible), page * 3));
|
|
Require(Written(flexible, page * 3, {{page / small, page * 2 / small}}));
|
|
Require(Written(flexible, page * 3, {}));
|
|
static_cast<volatile unsigned char*>(fixed)[9] = 4;
|
|
Require(Written(flexible, page * 3, {{page / small, page / small + 1}}));
|
|
Require(sceKernelMunmap(flexible, page * 3) == 0);
|
|
Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0);
|
|
}
|
|
#endif
|
|
|
|
static void CheckVirtualQuerySplitFlexibleRanges() {
|
|
constexpr std::size_t page = 0x4000;
|
|
void* mapped = nullptr;
|
|
Require(sceKernelMapFlexibleMemory(&mapped, page * 3, 3, 0) == 0);
|
|
auto* bytes = static_cast<unsigned char*>(mapped);
|
|
bytes[0] = 17;
|
|
bytes[page * 2] = 29;
|
|
Require(sceKernelMprotect(bytes + page, page, 1) == 0);
|
|
for (std::size_t index = 0; index < 3; ++index) {
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0);
|
|
Require(info.start == reinterpret_cast<std::uintptr_t>(bytes + page * index));
|
|
Require(info.end == info.start + page);
|
|
Require(info.protection == (index == 1 ? 1 : 3));
|
|
Require(info.is_committed && info.is_flexible && !info.is_direct);
|
|
}
|
|
Require(sceKernelMunmap(bytes + page, page) == 0);
|
|
for (std::size_t index : {std::size_t{0}, std::size_t{2}}) {
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0);
|
|
Require(info.start == reinterpret_cast<std::uintptr_t>(bytes + page * index));
|
|
Require(info.end == info.start + page);
|
|
}
|
|
VirtualQueryInfo next{};
|
|
Require(sceKernelVirtualQuery(bytes + page, 1, &next, sizeof(next)) == 0);
|
|
Require(next.start == reinterpret_cast<std::uintptr_t>(bytes + page * 2));
|
|
Require(next.end == next.start + page && next.protection == 3);
|
|
Require(bytes[0] == 17 && bytes[page * 2] == 29);
|
|
Require(sceKernelMunmap(bytes, page) == 0);
|
|
Require(sceKernelMunmap(bytes + page * 2, page) == 0);
|
|
}
|
|
|
|
static void CheckVirtualQueryForNonReadableGuestRange() {
|
|
constexpr std::size_t page = 0x4000;
|
|
auto* mapped = static_cast<unsigned char*>(mmap_nid_postfix(nullptr, page, 3, 0x1002, -1, 0));
|
|
const auto failed = reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
|
|
Require(mapped != failed);
|
|
Require(mprotect_nid_postfix(mapped, page, 0) == 0);
|
|
VirtualQueryInfo info{};
|
|
Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0);
|
|
Require(info.start == reinterpret_cast<std::uintptr_t>(mapped));
|
|
Require(info.end == info.start + page && info.protection == 0);
|
|
Require(sceKernelMunmap(mapped, page) == 0);
|
|
}
|
|
|
|
static void CheckVirtualQueryPartialMunmap() {
|
|
constexpr std::size_t page = 0x4000;
|
|
const auto failed = reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
|
|
void* raw = mmap_nid_postfix(nullptr, page * 3, 3, 0x1002, -1, 0);
|
|
Require(raw != failed);
|
|
auto* memory = static_cast<unsigned char*>(raw);
|
|
VirtualQueryInfo before{};
|
|
Require(sceKernelVirtualQuery(memory, 0, &before, sizeof(before)) == 0);
|
|
Require(before.start == reinterpret_cast<std::uintptr_t>(memory));
|
|
Require(before.end == reinterpret_cast<std::uintptr_t>(memory) + page * 3);
|
|
Require(munmap_nid_postfix(memory + page, page) == 0);
|
|
VirtualQueryInfo first{};
|
|
Require(sceKernelVirtualQuery(memory, 0, &first, sizeof(first)) == 0);
|
|
Require(first.start == reinterpret_cast<std::uintptr_t>(memory));
|
|
Require(first.end == reinterpret_cast<std::uintptr_t>(memory) + page);
|
|
VirtualQueryInfo middle{};
|
|
Require(sceKernelVirtualQuery(memory + page, 0, &middle, sizeof(middle)) == SCE_KERNEL_ERROR_EACCES);
|
|
VirtualQueryInfo next{};
|
|
Require(sceKernelVirtualQuery(memory + page, 1, &next, sizeof(next)) == 0);
|
|
Require(next.start == reinterpret_cast<std::uintptr_t>(memory) + page * 2);
|
|
Require(next.end == reinterpret_cast<std::uintptr_t>(memory) + page * 3);
|
|
VirtualQueryInfo tail{};
|
|
Require(sceKernelVirtualQuery(memory + page * 2, 0, &tail, sizeof(tail)) == 0);
|
|
Require(tail.start == reinterpret_cast<std::uintptr_t>(memory) + page * 2);
|
|
Require(tail.end == reinterpret_cast<std::uintptr_t>(memory) + page * 3);
|
|
Require(munmap_nid_postfix(memory, page) == 0);
|
|
Require(munmap_nid_postfix(memory + page * 2, page) == 0);
|
|
}
|
|
|
|
int main() {
|
|
#ifdef _WIN32
|
|
_putenv_s("APS5_PIN_WAIT_MS", "1000");
|
|
#endif
|
|
CheckVirtualQuerySplitFlexibleRanges();
|
|
CheckVirtualQueryForNonReadableGuestRange();
|
|
CheckReleaseFlexibleMemory();
|
|
CheckVirtualQueryPartialMunmap();
|
|
CheckNamedAndHintedMappings();
|
|
CheckInternalNamedFlexibleMapping();
|
|
CheckBatchMapStopsAtInvalidEntry();
|
|
CheckCheckedReleaseDirectMemory();
|
|
CheckAudioCoprocessorProtection();
|
|
CheckDirectMemoryFollowsPhysicalPages();
|
|
CheckReleaseDirectMemoryClearsMappings();
|
|
CheckReleaseDirectMemoryRejectsInvalidRanges();
|
|
CheckFixedMappingReplacesPartialOverlap();
|
|
CheckDirectMemoryGpuProtBits();
|
|
CheckGpuAccessFollowsProtection();
|
|
CheckHintedVirtualReservation();
|
|
CheckFixedVirtualReservation();
|
|
CheckReservedRangeIsNotCommitted();
|
|
CheckNoOverwriteRefusesLiveMapping();
|
|
CheckMlock();
|
|
CheckSharedDirectMemoryLifecycle();
|
|
CheckGetDirectMemoryType();
|
|
CheckMtypeprotect();
|
|
CheckHeapAlignment();
|
|
CheckHeapAfterMappingReuse();
|
|
CheckNoOverwriteRejectsHostOccupiedMapping();
|
|
CheckFixedMappingsReachTheApplicationAreaEnd();
|
|
#ifdef _WIN32
|
|
CheckGuestModuleImageProtection();
|
|
#endif
|
|
CheckUnhintedKernelMappingsLandInTheWindow();
|
|
#if defined(__linux__)
|
|
CheckUnmappedArenaRangeStaysReserved();
|
|
#endif
|
|
CheckArenaPlacesUnhintedMappingsInsideTheWindow();
|
|
CheckArenaReusesAFreedRange();
|
|
CheckArenaHonoursHintsWithoutGoingBelowThem();
|
|
CheckSharedWriteTracking();
|
|
CheckReadsIntoSharedWriteTracking();
|
|
CheckPinnedSharedPages();
|
|
CheckFailedCollectKeepsWrites();
|
|
#if defined(__linux__)
|
|
CheckWriteWatch();
|
|
CheckWriteWatchArmedCollect();
|
|
CheckWriteWatchFreshCollect();
|
|
CheckDirectMemoryWriteWatch();
|
|
CheckDirectMemoryBackingNeedsNoFilesystem();
|
|
CheckDirectMemorySharedBacking();
|
|
#endif
|
|
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(mprotect_nid_postfix(memory, 0, 1) == 0);
|
|
Require(mprotect_nid_postfix(nullptr, page, 1) == -1 && *__error_nid_postfix() == 22);
|
|
Require(mprotect_nid_postfix(memory + 1, 1, 1) == 0);
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
const auto range = mutation.Find(memory);
|
|
Require(range.readable && !range.writable);
|
|
}
|
|
Require(memory[0] == 42);
|
|
Require(mprotect_nid_postfix(memory, page, 3) == 0);
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
Require(mutation.Find(memory).writable);
|
|
}
|
|
for (int advice = 0; advice <= 9; ++advice) Require(madvise_nid_postfix(memory, page * 3, advice) == 0);
|
|
Require(memory[0] == 42 && memory[page * 2] == 73);
|
|
Require(madvise_nid_postfix(memory + 1, 0, 4) == 0);
|
|
Require(madvise_nid_postfix(memory, page, 11) == -1 && *__error_nid_postfix() == 22);
|
|
*__error_nid_postfix() = 0;
|
|
Require(madvise_nid_postfix(memory, page, -1) == -1 && *__error_nid_postfix() == 22);
|
|
*__error_nid_postfix() = 0;
|
|
Require(madvise_nid_postfix(memory, 0, 11) == -1 && *__error_nid_postfix() == 22);
|
|
*__error_nid_postfix() = 0;
|
|
Require(madvise_nid_postfix(memory, std::numeric_limits<std::size_t>::max(), 0) == -1 && *__error_nid_postfix() == 22);
|
|
*__error_nid_postfix() = 0;
|
|
Require(madvise_nid_postfix(reinterpret_cast<void*>(0x800000000000), 1, 0) == -1 && *__error_nid_postfix() == 22);
|
|
Require(madvise_nid_postfix(reinterpret_cast<void*>(0x7fffffffc000), 0x4000, 4) == 0);
|
|
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);
|
|
Require(madvise_nid_postfix(memory, page, 4) == 0);
|
|
*__error_nid_postfix() = 0;
|
|
Require(mprotect_nid_postfix(memory, page, 1) == -1 && *__error_nid_postfix() == 22);
|
|
*__error_nid_postfix() = 0;
|
|
Require(mprotect_nid_postfix(memory, std::numeric_limits<std::size_t>::max(), 1) == -1 &&
|
|
*__error_nid_postfix() == 22);
|
|
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);
|
|
}
|
|
|
|
reject(page, 3, 0x2001002, -1, 0, 45);
|
|
constexpr std::size_t superpage = std::size_t{1} << 21;
|
|
auto* aligned = static_cast<unsigned char*>(mmap_nid_postfix(nullptr, superpage + page, 3, 0x1001002, -1, 0));
|
|
Require(aligned != failed && (reinterpret_cast<std::uintptr_t>(aligned) & (superpage - 1)) == 0);
|
|
aligned[0] = 1;
|
|
aligned[superpage + page - 1] = 2;
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
const auto range = mutation.Find(aligned);
|
|
Require(range.bytes == superpage + page && range.readable && range.writable);
|
|
}
|
|
Require(munmap_nid_postfix(aligned, superpage + page) == 0);
|
|
}
|