feat(libkernel): implement sceKernelIsStack

Track the stack range of each running guest thread and answer
sceKernelIsStack from the calling thread's stack (the host limits for an
adopted thread) or from that registry. A mapped address outside every
stack succeeds with null bounds and an unmapped one returns EACCES; the
output pointers may be null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Merijn 2026-10-05 19:08:45 +02:00
parent e248f404ef
commit b530c9b784
5 changed files with 166 additions and 4 deletions

View File

@ -136,6 +136,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_pthread_barrier PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_kernel_is_stack_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelIsStack.cpp)
target_include_directories(guest_kernel_is_stack_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_kernel_is_stack_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_kernel_is_stack_tests)
add_test(NAME guest_kernel_is_stack COMMAND guest_kernel_is_stack_tests)
set_tests_properties(guest_kernel_is_stack PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_kernel_is_stack PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_thread_self_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadSelf.cpp)
target_include_directories(guest_thread_self_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_thread_self_tests PRIVATE libkernel libc)

View File

@ -5,6 +5,7 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
#include "prx/libkernel/Pthread/include/Pthread.hpp"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
@ -306,10 +307,14 @@ int APS5_VABI sceKernelQueryMemoryProtection(void* addr, void** start, void** en
}
int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end) {
(void)addr;
(void)start;
(void)end;
NotImplemented_nid_no_patch(__func__);
std::uintptr_t stackStart = 0;
std::uintptr_t stackEnd = 0;
if (!GuestThreadStack(reinterpret_cast<std::uintptr_t>(addr), &stackStart, &stackEnd)) {
VirtualQueryInfo info{};
if (sceKernelVirtualQuery(addr, 0, &info, sizeof(info)) != 0) return SCE_KERNEL_ERROR_EACCES;
}
if (start) *start = reinterpret_cast<void*>(stackStart);
if (end) *end = reinterpret_cast<void*>(stackEnd);
return 0;
}

View File

@ -99,4 +99,6 @@ struct PthreadPrivate {
PthreadPrivate() : _finished(false), _retval(nullptr), _detached(false), _adopted(false) {}
};
bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end);
#endif

View File

@ -7,6 +7,8 @@
#include <cstdlib>
#include <cstring>
#include <functional>
#include <map>
#include <utility>
#include <string>
#include <future>
#include <memory>
@ -42,6 +44,73 @@ static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
static thread_local PthreadPrivate* currentThread = nullptr;
static thread_local bool threadFinishing = false;
static std::mutex stackLock;
static std::map<PthreadPrivate*, std::pair<std::uintptr_t, std::uintptr_t>> liveStacks;
static bool NativeStackLimits(std::uintptr_t* low, std::uintptr_t* high) {
#ifdef _WIN32
ULONG_PTR lowLimit = 0;
ULONG_PTR highLimit = 0;
GetCurrentThreadStackLimits(&lowLimit, &highLimit);
*low = lowLimit;
*high = highLimit;
#else
pthread_attr_t attr;
if (pthread_getattr_np(pthread_self(), &attr) != 0)
return false;
void* address = nullptr;
std::size_t size = 0;
const bool queried = pthread_attr_getstack(&attr, &address, &size) == 0;
pthread_attr_destroy(&attr);
if (!queried)
return false;
*low = reinterpret_cast<std::uintptr_t>(address);
*high = *low + size;
#endif
return *high > *low;
}
static bool CurrentStack(std::uintptr_t* low, std::uintptr_t* high) {
if (currentThread && currentThread->stackAddress) {
*low = reinterpret_cast<std::uintptr_t>(currentThread->stackAddress);
*high = *low + currentThread->stackSize;
return true;
}
return NativeStackLimits(low, high);
}
static void RegisterStack(PthreadPrivate* self) {
std::uintptr_t low = 0;
std::uintptr_t high = 0;
if (!CurrentStack(&low, &high))
throw std::runtime_error("Cannot query guest thread stack");
std::lock_guard lock(stackLock);
liveStacks[self] = {low, high};
}
static void UnregisterStack(PthreadPrivate* self) {
std::lock_guard lock(stackLock);
liveStacks.erase(self);
}
bool GuestThreadStack(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end) {
std::uintptr_t low = 0;
std::uintptr_t high = 0;
if (CurrentStack(&low, &high) && address >= low && address < high) {
*start = low;
*end = high;
return true;
}
std::lock_guard lock(stackLock);
for (const auto& [thread, range] : liveStacks) {
if (address >= range.first && address < range.second) {
*start = range.first;
*end = range.second;
return true;
}
}
return false;
}
#ifndef _WIN32
static thread_local std::unique_ptr<PthreadPrivate> adoptedThread;
// pthread_exit force-unwinds through guest frames, whose personality resolves to
@ -81,6 +150,7 @@ static void finishThread(PthreadPrivate* self, void* retval) {
if (threadFinishing)
throw std::runtime_error("Guest thread is already finishing");
threadFinishing = true;
UnregisterStack(self);
if (const auto callback = threadDtors.load())
callback();
{
@ -97,6 +167,7 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
void* arg = args->arg;
PthreadPrivate* self = args->self;
currentThread = self;
RegisterStack(self);
args.reset();
finishThread(self, entry(arg));
currentThread = nullptr;

View File

@ -0,0 +1,74 @@
#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);
}
static constexpr int SCE_OK = 0;
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;
};
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.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(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);
}