diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 2e5364b1b..79f65c06a 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -136,6 +136,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_barrier PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") 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:$") + 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) diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index 373de6c2a..2babc2d83 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -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(addr), &stackStart, &stackEnd)) { + VirtualQueryInfo info{}; + if (sceKernelVirtualQuery(addr, 0, &info, sizeof(info)) != 0) return SCE_KERNEL_ERROR_EACCES; + } + if (start) *start = reinterpret_cast(stackStart); + if (end) *end = reinterpret_cast(stackEnd); return 0; } diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index b95ca4f87..ffd954255 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -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 diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index 3dc69cbab..d4d564ea7 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include #include @@ -42,6 +44,73 @@ static std::atomic threadAtexitCount{nullptr}; static std::atomic threadAtexitReport{nullptr}; static thread_local PthreadPrivate* currentThread = nullptr; static thread_local bool threadFinishing = false; +static std::mutex stackLock; +static std::map> 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(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(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 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 args) { void* arg = args->arg; PthreadPrivate* self = args->self; currentThread = self; + RegisterStack(self); args.reset(); finishThread(self, entry(arg)); currentThread = nullptr; diff --git a/core/libs/tests/GuestKernelIsStack.cpp b/core/libs/tests/GuestKernelIsStack.cpp new file mode 100644 index 000000000..b2c1097d2 --- /dev/null +++ b/core/libs/tests/GuestKernelIsStack.cpp @@ -0,0 +1,74 @@ +#include "SceTypes.hpp" +#include +#include +#include + +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(0x8002000d); +static int global = 0; + +static void Require(bool value) { if (!value) std::abort(); } + +struct WorkerContext { + std::atomic queried{false}; + std::atomic 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(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(&local) && static_cast(&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(mapped), page) == SCE_OK); + Require(sceKernelIsStack(mapped, &start, &end) == SCE_KERNEL_ERROR_EACCES); +}