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:
parent
e248f404ef
commit
b530c9b784
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue