#include "SceTypes.hpp" #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #endif 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 scePthreadDetach(Pthread thread); int APS5_VABI pthread_join_nid_postfix(Pthread thread, void** value); int APS5_VABI pthread_detach_nid_postfix(Pthread thread); void APS5_VABI scePthreadExit(void* retval); Pthread APS5_VABI scePthreadSelf(); int APS5_VABI scePthreadGetthreadid(void); int APS5_VABI pthread_getthreadid_np_nid_postfix(void); void APS5_VABI scePthreadTestcancel(); void APS5_VABI pthread_testcancel_nid_postfix(void); int APS5_VABI scePthreadSetcancelstate(int state, int* old_state); int APS5_VABI scePthreadSetcanceltype(int type, int* old_type); void APS5_VABI pthread_set_name_np_nid_postfix(Pthread thread, const char* name); int APS5_VABI scePthreadGetname(Pthread thread, char* name); int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); int APS5_VABI scePthreadAttrInit(PthreadAttr* attr); int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr); int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr); int APS5_VABI scePthreadAttrGetstackaddr(const PthreadAttr* attr, void** stack_addr); int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, size_t* stack_size); int APS5_VABI mprotect_nid_postfix(void* address, std::size_t length, int protection) noexcept; int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end); } static bool StackContains(Pthread thread, const void* address) { PthreadAttr attr = nullptr; if (scePthreadAttrInit(&attr) != 0 || scePthreadAttrGet(thread, &attr) != 0) return false; void* stack = nullptr; size_t size = 0; const bool queried = scePthreadAttrGetstackaddr(&attr, &stack) == 0 && scePthreadAttrGetstacksize(&attr, &size) == 0; scePthreadAttrDestroy(&attr); const auto begin = reinterpret_cast(stack); const auto value = reinterpret_cast(address); return queried && stack != nullptr && size != 0 && value >= begin && value - begin < size; } static constexpr int SCE_OK = 0; static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001; static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x8002000b; static constexpr int GUEST_EDEADLK = 11; static constexpr int GUEST_EINVAL = 22; static constexpr int MUTEX_TYPE_RECURSIVE = 2; static constexpr int PTHREAD_CANCEL_ENABLE = 0; static constexpr int PTHREAD_CANCEL_ASYNCHRONOUS = 2; static constexpr std::intptr_t WorkerRetval = 0x1234; static void Require(bool value) { if (!value) std::abort(); } #ifdef _WIN32 static constexpr std::uintptr_t GuestPage = 0x4000; static constexpr int GuestProtNone = 0; static constexpr int GuestProtReadWrite = 3; static void ReportedStack(Pthread thread, std::uintptr_t* begin, std::uintptr_t* end) { PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK && scePthreadAttrGet(thread, &attr) == SCE_OK); void* stack = nullptr; size_t size = 0; Require(scePthreadAttrGetstackaddr(&attr, &stack) == SCE_OK && scePthreadAttrGetstacksize(&attr, &size) == SCE_OK); scePthreadAttrDestroy(&attr); *begin = reinterpret_cast(stack); *end = *begin + size; } static void RequireGuardable(std::uintptr_t begin, std::uintptr_t end) { Require(begin % GuestPage == 0 && end - begin >= GuestPage); for (auto cursor = begin; cursor < end;) { MEMORY_BASIC_INFORMATION memory{}; Require(VirtualQuery(reinterpret_cast(cursor), &memory, sizeof(memory)) == sizeof(memory)); Require(memory.State == MEM_COMMIT && memory.Protect == PAGE_READWRITE); cursor = reinterpret_cast(memory.BaseAddress) + memory.RegionSize; } auto* lowest = reinterpret_cast(begin); Require(mprotect_nid_postfix(lowest, GuestPage, GuestProtReadWrite) == 0); Require(mprotect_nid_postfix(lowest, GuestPage, GuestProtNone) == 0); Require(mprotect_nid_postfix(lowest, GuestPage, GuestProtReadWrite) == 0); } static void RequireGuardableStack(Pthread thread) { std::uintptr_t begin = 0; std::uintptr_t end = 0; ReportedStack(thread, &begin, &end); RequireGuardable(begin, end); } static DWORD WINAPI DefaultSizeHostThread(void*) { int local = 0; void* start = nullptr; void* end = nullptr; Require(sceKernelIsStack(&local, &start, &end) == SCE_OK); RequireGuardable(reinterpret_cast(start), reinterpret_cast(end)); RequireGuardableStack(scePthreadSelf()); return 0; } static DWORD WINAPI PartlyCommittedHostThread(void*) { ULONG_PTR low = 0; ULONG_PTR high = 0; GetCurrentThreadStackLimits(&low, &high); std::uintptr_t begin = 0; std::uintptr_t end = 0; ReportedStack(scePthreadSelf(), &begin, &end); Require(begin == low && end == high); return 0; } static void RunHostThread(LPTHREAD_START_ROUTINE routine, SIZE_T committed) { const HANDLE thread = CreateThread(nullptr, committed, routine, nullptr, 0, nullptr); Require(thread != nullptr); Require(WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0); Require(CloseHandle(thread) != 0); } #endif static std::int64_t ThreadIdField(Pthread thread) { std::int64_t tid = 0; std::memcpy(&tid, static_cast(thread), sizeof(tid)); return tid; } struct WorkerContext { Pthread thread = nullptr; Pthread selfFromWorker = nullptr; std::int64_t workerTid = 0; int workerThreadId = 0; int workerPosixThreadId = 0; bool workerStackReported = false; PthreadMutex* mutex = nullptr; int unlockResult = 0; bool testcancelReturned = false; int sceJoinSelf = 0; int posixJoinSelf = 0; }; static void* APS5_VABI Worker(void* arg) { auto& context = *static_cast(arg); context.selfFromWorker = scePthreadSelf(); context.workerTid = ThreadIdField(context.selfFromWorker); context.workerThreadId = scePthreadGetthreadid(); context.workerPosixThreadId = pthread_getthreadid_np_nid_postfix(); int local = 0; context.workerStackReported = StackContains(context.selfFromWorker, &local); context.unlockResult = scePthreadMutexUnlock(context.mutex); context.sceJoinSelf = scePthreadJoin(context.selfFromWorker, nullptr); context.posixJoinSelf = pthread_join_nid_postfix(context.selfFromWorker, nullptr); int oldState = -1; int oldType = -1; context.testcancelReturned = scePthreadSetcancelstate(PTHREAD_CANCEL_ENABLE, &oldState) == SCE_OK && scePthreadSetcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldType) == SCE_OK; scePthreadTestcancel(); pthread_testcancel_nid_postfix(); context.testcancelReturned = context.testcancelReturned && oldState == PTHREAD_CANCEL_ENABLE; scePthreadExit(reinterpret_cast(WorkerRetval)); return nullptr; } int main() { const Pthread mainSelf = scePthreadSelf(); Require(mainSelf != nullptr); Require(scePthreadSelf() == mainSelf); const std::int64_t mainTid = ThreadIdField(mainSelf); Require(mainTid != 0); Require(scePthreadGetthreadid() == static_cast(mainTid)); Require(pthread_getthreadid_np_nid_postfix() == static_cast(mainTid)); int local = 0; Require(StackContains(mainSelf, &local)); #ifdef _WIN32 RequireGuardableStack(mainSelf); RunHostThread(DefaultSizeHostThread, 0); RunHostThread(PartlyCommittedHostThread, 0x10000); #endif Require(scePthreadJoin(mainSelf, nullptr) == SCE_KERNEL_ERROR_EDEADLK); Require(pthread_join_nid_postfix(mainSelf, nullptr) == GUEST_EDEADLK); Require(scePthreadDetach(mainSelf) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadJoin(nullptr, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadDetach(nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_join_nid_postfix(nullptr, nullptr) == GUEST_EINVAL); Require(pthread_detach_nid_postfix(nullptr) == GUEST_EINVAL); PthreadMutexattr attr = nullptr; Require(scePthreadMutexattrInit(&attr) == SCE_OK); Require(scePthreadMutexattrSettype(&attr, MUTEX_TYPE_RECURSIVE) == SCE_OK); PthreadMutex mutex = nullptr; Require(scePthreadMutexInit(&mutex, &attr, nullptr) == SCE_OK); Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); Require(scePthreadMutexLock(&mutex) == SCE_OK); WorkerContext context; context.mutex = &mutex; Require(scePthreadCreate(&context.thread, nullptr, Worker, &context, nullptr) == SCE_OK); Require(context.thread != nullptr); Require(context.thread != mainSelf); void* result = nullptr; Require(scePthreadJoin(context.thread, &result) == SCE_OK); Require(reinterpret_cast(result) == WorkerRetval); Require(context.selfFromWorker != nullptr); Require(context.selfFromWorker == context.thread); Require(context.selfFromWorker != mainSelf); Require(context.workerTid != 0 && context.workerTid != mainTid); Require(context.workerThreadId == static_cast(context.workerTid)); Require(context.workerPosixThreadId == context.workerThreadId && context.workerPosixThreadId != static_cast(mainTid)); Require(context.unlockResult == SCE_KERNEL_ERROR_EPERM); Require(context.sceJoinSelf == SCE_KERNEL_ERROR_EDEADLK); Require(context.posixJoinSelf == GUEST_EDEADLK); Require(context.workerStackReported); Require(context.testcancelReturned); scePthreadTestcancel(); pthread_testcancel_nid_postfix(); Require(scePthreadMutexUnlock(&mutex) == SCE_OK); Require(scePthreadMutexDestroy(&mutex) == SCE_OK); char name[32] = {'x'}; pthread_set_name_np_nid_postfix(mainSelf, "q2-main"); Require(scePthreadGetname(mainSelf, name) == SCE_OK && std::strcmp(name, "q2-main") == 0); pthread_set_name_np_nid_postfix(mainSelf, nullptr); Require(scePthreadGetname(mainSelf, name) == SCE_OK && name[0] == '\0'); pthread_set_name_np_nid_postfix(nullptr, "ignored"); }