Files
AnyPS5/core/libs/tests/GuestKernelIsStack.cpp
T
Cesar RodriguezandClaude Opus 5.5 7637cac13e fix(libkernel): place guest thread stacks inside the application area
On Linux, glibc maps the stack of every guest thread near the top of the
host address space (around 0x7fff00000000). On the console, application
stacks lie inside the application area below 0xFC00000000, and guest code
relies on that: ChakraCore's garbage collector indexes a 4 MiB table with
address >> 18, so it only covers addresses below 1 TiB and reports
Js::OutOfMemoryException for a stack above it.

Map each guest thread's stack with DoMapAnon, which allocates from the
guest arena, behind a PROT_NONE guard page, and pass it to pthread_attr_setstack.
The stack is freed after a join; the stack of a detached thread is freed once its
host thread is gone from /proc/self/task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:03:36 +02:00

85 lines
3.4 KiB
C++

#include "SceTypes.hpp"
#include <atomic>
#include <cstdint>
#include <cstdlib>
extern "C" {
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end);
int APS5_VABI sceKernelMapFlexibleMemory(void** addr, std::size_t len, int prot, int flags);
int APS5_VABI sceKernelMunmap(std::uint64_t addr, std::size_t len);
int APS5_VABI sceKernelVirtualQuery(const void* addr, int flags, VirtualQueryInfo* info, std::uint64_t infoSize);
}
static constexpr int SCE_OK = 0;
static constexpr std::uintptr_t APPLICATION_AREA_END = 0x000000FC00000000ull;
static constexpr int SCE_KERNEL_ERROR_EACCES = static_cast<int>(0x8002000d);
static int global = 0;
static void Require(bool value) { if (!value) std::abort(); }
struct WorkerContext {
std::atomic<bool> queried{false};
std::atomic<bool> release{false};
void* local = nullptr;
void* start = nullptr;
void* end = nullptr;
int result = -1;
VirtualQueryInfo region{};
int regionResult = -1;
};
static void* APS5_VABI Worker(void* arg) {
auto& context = *static_cast<WorkerContext*>(arg);
int local = 0;
context.local = &local;
context.result = sceKernelIsStack(&local, &context.start, &context.end);
context.regionResult = sceKernelVirtualQuery(&local, 0, &context.region, sizeof(context.region));
context.queried.store(true);
while (!context.release.load()) {}
return nullptr;
}
int main() {
int local = 0;
void* start = nullptr;
void* end = nullptr;
Require(sceKernelIsStack(&local, &start, &end) == SCE_OK);
Require(start <= static_cast<void*>(&local) && static_cast<void*>(&local) < end);
Require(sceKernelIsStack(&local, nullptr, nullptr) == SCE_OK);
start = &local;
end = &local;
Require(sceKernelIsStack(&global, &start, &end) == SCE_OK);
Require(start == nullptr && end == nullptr);
WorkerContext context;
Pthread thread = nullptr;
Require(scePthreadCreate(&thread, nullptr, Worker, &context, nullptr) == SCE_OK);
while (!context.queried.load()) {}
Require(context.result == SCE_OK);
Require(context.start <= context.local && context.local < context.end);
Require(context.regionResult == SCE_OK);
Require(context.region.start <= reinterpret_cast<std::uintptr_t>(context.local) && reinterpret_cast<std::uintptr_t>(context.local) < context.region.end);
#if defined(__linux__)
Require(reinterpret_cast<std::uintptr_t>(context.local) < APPLICATION_AREA_END);
#endif
Require(sceKernelIsStack(&local, &start, &end) == SCE_OK);
Require(context.end <= start || context.start >= end);
void* workerStart = nullptr;
void* workerEnd = nullptr;
Require(sceKernelIsStack(context.local, &workerStart, &workerEnd) == SCE_OK);
Require(workerStart == context.start && workerEnd == context.end);
context.release.store(true);
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
constexpr std::size_t page = 0x4000;
void* mapped = nullptr;
Require(sceKernelMapFlexibleMemory(&mapped, page, 3, 0) == SCE_OK);
Require(sceKernelIsStack(mapped, &start, &end) == SCE_OK);
Require(start == nullptr && end == nullptr);
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapped), page) == SCE_OK);
Require(sceKernelIsStack(mapped, &start, &end) == SCE_KERNEL_ERROR_EACCES);
}