Files
AnyPS5/core/libs/tests/GuestPthreadAttr.cpp
T
Lindamea1andClaude Opus 5.5 3237083172 fix(libkernel): return ESRCH from scePthreadAttrGet for a thread that has ended
scePthreadAttrGet copied the attributes of any non-null handle, so a
thread that was joined or had finished still answered 0, and a thread
that ended detached was read after its object had been freed. FreeBSD
11.4's _pthread_attr_get_np returns ESRCH for a dead or reclaimed thread,
and Monster Hunter Wilds (PPSA24191) reuses a slot of its thread table
only when scePthreadAttrGet returns 0x80020003.

After the EINVAL checks it now returns ESRCH unless the handle is a live
guest thread, and copies the fields while holding the thread registry
lock (LockLiveGuestThread), so a detached thread exiting during the call
is not freed under the copy. GuestThreadIsLive uses the same lookup.
pthread_attr_get_np, which the title does not import, is left as it is;
the debt entry records the difference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:25:14 +01:00

409 lines
20 KiB
C++

#include "SceTypes.hpp"
#include <chrono>
#include <cstddef>
#include <cstdlib>
#include <future>
#include <thread>
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 scePthreadSetprio(Pthread thread, int prio);
int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask);
int APS5_VABI scePthreadAttrInit(PthreadAttr* attr);
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr);
int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr);
int APS5_VABI scePthreadAttrSetinheritsched(PthreadAttr* attr, int inheritSched);
int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param);
int APS5_VABI scePthreadAttrSetaffinity(PthreadAttr* attr, KernelCpumask mask);
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize);
int APS5_VABI scePthreadAttrGetschedparam(const PthreadAttr* attr, KernelSchedParam* param);
int APS5_VABI scePthreadAttrGetaffinity(const PthreadAttr* attr, KernelCpumask* mask);
int APS5_VABI scePthreadAttrGetstacksize(const PthreadAttr* attr, std::size_t* stackSize);
int APS5_VABI scePthreadAttrGetdetachstate(const PthreadAttr* attr, int* state);
int APS5_VABI scePthreadAttrGetsolosched(const PthreadAttr* attr, int* solosched);
int APS5_VABI pthread_attr_setstacksize_nid_postfix(PthreadAttr* attr, std::size_t stackSize);
int APS5_VABI pthread_attr_setsolosched_np_nid_postfix(PthreadAttr* attr, int solosched);
int APS5_VABI pthread_attr_getinheritsched_nid_postfix(const PthreadAttr* attr, int* inherit_sched);
int APS5_VABI pthread_attr_setinheritsched_nid_postfix(PthreadAttr* attr, int inherit_sched);
int APS5_VABI pthread_attr_getscope_nid_postfix(const PthreadAttr* attr, int* scope);
int APS5_VABI pthread_attr_setscope_nid_postfix(PthreadAttr* attr, int scope);
int APS5_VABI pthread_attr_get_np_nid_postfix(Pthread thread, PthreadAttr* attr);
int APS5_VABI pthread_attr_setschedparam_nid_postfix(PthreadAttr* attr, const KernelSchedParam* param);
int APS5_VABI pthread_attr_setschedpolicy_nid_postfix(PthreadAttr* attr, int policy);
int APS5_VABI pthread_attr_getschedpolicy_nid_postfix(const PthreadAttr* attr, int* policy);
int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int state);
int APS5_VABI scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor);
int APS5_VABI scePthreadKeyDelete(PthreadKey key);
int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value);
Pthread APS5_VABI scePthreadSelf();
}
static constexpr int SCE_OK = 0;
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
static constexpr int EXPLICIT_SCHED = 0;
static constexpr int INHERIT_SCHED = 4;
static constexpr int SCOPE_PROCESS = 0;
static constexpr int SCOPE_SYSTEM = 2;
static constexpr int DETACH_JOINABLE = 0;
static constexpr int CREATION_PRIORITY = 256;
static constexpr int UPDATED_PRIORITY = 767;
static constexpr KernelCpumask CREATION_AFFINITY = 0x3;
static constexpr KernelCpumask UPDATED_AFFINITY = 0x1000;
static constexpr std::size_t STACK_SIZE = 2u << 20;
static constexpr std::size_t MIN_STACK_SIZE = 16384;
static constexpr int GUEST_EINVAL = 22;
static constexpr int GUEST_ENOTSUP = 45;
static constexpr int SCE_KERNEL_ERROR_ESRCH = static_cast<int>(0x80020003);
static constexpr int DETACH_DETACHED = 1;
static constexpr int DETACHED_THREADS = 32;
static void Require(bool value) { if (!value) std::abort(); }
struct ReportedAttributes {
int priority = 0;
KernelCpumask affinity = 0;
std::size_t stackSize = 0;
int detachState = -1;
};
extern "C" {
int APS5_VABI pthread_attr_setstack_nid_postfix(PthreadAttr* attr, void* addr, std::size_t size);
int APS5_VABI pthread_attr_getstack_nid_postfix(const PthreadAttr* attr, void** addr, std::size_t* size);
int APS5_VABI pthread_attr_getstackaddr_nid_postfix(const PthreadAttr* attr, void** addr);
}
static ReportedAttributes Query(Pthread thread) {
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(scePthreadAttrGet(thread, &attr) == SCE_OK);
ReportedAttributes reported;
KernelSchedParam param{};
Require(scePthreadAttrGetschedparam(&attr, &param) == SCE_OK);
reported.priority = param.sched_priority;
Require(scePthreadAttrGetaffinity(&attr, &reported.affinity) == SCE_OK);
Require(scePthreadAttrGetstacksize(&attr, &reported.stackSize) == SCE_OK);
Require(scePthreadAttrGetdetachstate(&attr, &reported.detachState) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
return reported;
}
static void CheckStackSizeLimit() {
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
std::size_t stackSize = 0;
Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE) == SCE_OK);
Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE);
Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE - 1) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetstacksize(&attr, 0) == SCE_KERNEL_ERROR_EINVAL);
Require(pthread_attr_setstacksize_nid_postfix(&attr, MIN_STACK_SIZE - 1) == GUEST_EINVAL);
Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
}
extern "C" int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched);
static void CheckSoloSched() {
PthreadAttr attr = nullptr;
Require(pthread_attr_setsolosched_np_nid_postfix(nullptr, 1) == GUEST_EINVAL);
Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 1) == GUEST_EINVAL);
Require(scePthreadAttrInit(&attr) == SCE_OK);
int soloSched = -1;
Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 0);
Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 16) == 0);
Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 16);
constexpr int GuestEopnotsupp = 45;
constexpr int SceKernelErrorEopnotsupp = static_cast<int>(0x8002002Du);
for (int rejected : {-1, 1, 2, 4, 8, 15, 17, 32, 100, 0x7fffffff}) {
Require(pthread_attr_setsolosched_np_nid_postfix(&attr, rejected) == GuestEopnotsupp);
Require(scePthreadAttrSetsolosched(&attr, rejected) == SceKernelErrorEopnotsupp);
Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 16);
}
Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 0) == 0);
Require(scePthreadAttrGetsolosched(&attr, &soloSched) == SCE_OK && soloSched == 0);
PthreadAttr unset = nullptr;
Require(scePthreadAttrSetsolosched(&unset, 16) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrSetsolosched(nullptr, 16) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGetsolosched(&unset, &soloSched) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
Require(pthread_attr_setsolosched_np_nid_postfix(&attr, 1) == GUEST_EINVAL);
}
static void CheckSchedArguments() {
constexpr int SchedFifo = 1;
constexpr int SchedOther = 2;
constexpr int SchedRr = 3;
PthreadAttr attr = nullptr;
PthreadAttr unset = nullptr;
KernelSchedParam param{CREATION_PRIORITY};
Require(pthread_attr_setschedparam_nid_postfix(nullptr, &param) == GUEST_EINVAL);
Require(pthread_attr_setschedparam_nid_postfix(&unset, &param) == GUEST_EINVAL);
Require(pthread_attr_setschedpolicy_nid_postfix(&unset, SchedRr) == GUEST_EINVAL);
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(pthread_attr_setschedparam_nid_postfix(&attr, nullptr) == GUEST_ENOTSUP);
for (const int policy : {SchedFifo, SchedRr}) {
Require(pthread_attr_setschedpolicy_nid_postfix(&attr, policy) == 0);
Require(pthread_attr_setschedparam_nid_postfix(&attr, &param) == 0);
for (const int rejected : {0, 1, CREATION_PRIORITY - 1, UPDATED_PRIORITY + 1, 1000}) {
KernelSchedParam outside{rejected};
Require(pthread_attr_setschedparam_nid_postfix(&attr, &outside) == GUEST_ENOTSUP);
KernelSchedParam reported{-1};
Require(scePthreadAttrGetschedparam(&attr, &reported) == SCE_OK && reported.sched_priority == CREATION_PRIORITY);
}
KernelSchedParam highest{UPDATED_PRIORITY};
Require(pthread_attr_setschedparam_nid_postfix(&attr, &highest) == 0);
Require(pthread_attr_setschedparam_nid_postfix(&attr, &param) == 0);
}
Require(pthread_attr_setschedpolicy_nid_postfix(&attr, SchedOther) == 0);
KernelSchedParam other{1000};
Require(pthread_attr_setschedparam_nid_postfix(&attr, &other) == 0);
int policy = -1;
for (const int rejected : {-1, 0, 4, 100}) {
Require(pthread_attr_setschedpolicy_nid_postfix(&attr, rejected) == GUEST_ENOTSUP);
Require(pthread_attr_getschedpolicy_nid_postfix(&attr, &policy) == 0 && policy == SchedOther);
}
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
}
static void* APS5_VABI Worker(void* arg) {
static_cast<std::future<void>*>(arg)->get();
return nullptr;
}
static void CheckSchedulingAttributes() {
int inherit = -1;
int scope = -1;
Require(pthread_attr_getinheritsched_nid_postfix(nullptr, &inherit) == GUEST_EINVAL);
Require(pthread_attr_setinheritsched_nid_postfix(nullptr, EXPLICIT_SCHED) == GUEST_EINVAL);
Require(pthread_attr_getscope_nid_postfix(nullptr, &scope) == GUEST_EINVAL);
Require(pthread_attr_setscope_nid_postfix(nullptr, SCOPE_SYSTEM) == GUEST_EINVAL);
PthreadAttr attr = nullptr;
Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == GUEST_EINVAL);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == GUEST_EINVAL);
Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == GUEST_EINVAL);
Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == GUEST_EINVAL);
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == INHERIT_SCHED);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, 1) == GUEST_ENOTSUP);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, -1) == GUEST_ENOTSUP);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == 0);
Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == EXPLICIT_SCHED);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, INHERIT_SCHED) == 0);
Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == INHERIT_SCHED);
Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_SYSTEM);
Require(pthread_attr_setscope_nid_postfix(&attr, 1) == GUEST_EINVAL);
Require(pthread_attr_setscope_nid_postfix(&attr, 3) == GUEST_EINVAL);
Require(pthread_attr_setscope_nid_postfix(&attr, -1) == GUEST_EINVAL);
Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_PROCESS) == 0);
Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_PROCESS);
Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == 0);
Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_SYSTEM);
Require(pthread_attr_getscope_nid_postfix(&attr, nullptr) == GUEST_EINVAL);
bool threw = false;
try {
pthread_attr_getinheritsched_nid_postfix(&attr, nullptr);
} catch (const std::exception&) {
threw = true;
}
Require(threw);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == GUEST_EINVAL);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == GUEST_EINVAL);
Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == GUEST_EINVAL);
Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == GUEST_EINVAL);
}
static void CheckThreadScope() {
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == 0);
Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_PROCESS) == 0);
std::promise<void> release;
auto released = release.get_future();
Pthread thread = nullptr;
Require(scePthreadCreate(&thread, &attr, Worker, &released, nullptr) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
PthreadAttr queryAttr = nullptr;
Require(scePthreadAttrInit(&queryAttr) == SCE_OK);
Require(scePthreadAttrGet(thread, &queryAttr) == SCE_OK);
int scope = -1;
Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_PROCESS);
Require(pthread_attr_get_np_nid_postfix(thread, &queryAttr) == 0);
Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_PROCESS);
Require(scePthreadAttrDestroy(&queryAttr) == SCE_OK);
release.set_value();
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(pthread_attr_setinheritsched_nid_postfix(&attr, INHERIT_SCHED) == 0);
Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_PROCESS) == 0);
std::promise<void> releaseInherited;
auto releasedInherited = releaseInherited.get_future();
Pthread inheritedThread = nullptr;
Require(scePthreadCreate(&inheritedThread, &attr, Worker, &releasedInherited, nullptr) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
Require(scePthreadAttrInit(&queryAttr) == SCE_OK);
Require(scePthreadAttrGet(inheritedThread, &queryAttr) == SCE_OK);
scope = -1;
Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_SYSTEM);
Require(pthread_attr_get_np_nid_postfix(inheritedThread, &queryAttr) == 0);
Require(pthread_attr_getscope_nid_postfix(&queryAttr, &scope) == 0 && scope == SCOPE_SYSTEM);
Require(scePthreadAttrDestroy(&queryAttr) == SCE_OK);
releaseInherited.set_value();
Require(scePthreadJoin(inheritedThread, nullptr) == SCE_OK);
}
static int QueryResult(Pthread thread) {
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(scePthreadAttrSetstacksize(&attr, STACK_SIZE) == SCE_OK);
const int result = scePthreadAttrGet(thread, &attr);
std::size_t stackSize = 0;
Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK);
Require(result == SCE_OK || stackSize == STACK_SIZE);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
return result;
}
static bool ReportedEnded(Pthread thread) {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (QueryResult(thread) == SCE_OK) {
if (std::chrono::steady_clock::now() > deadline) return false;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return QueryResult(thread) == SCE_KERNEL_ERROR_ESRCH;
}
struct ExitQuery {
PthreadKey key = -1;
int result = -1;
};
static void APS5_VABI QuerySelfAtExit(void* value) {
auto* query = static_cast<ExitQuery*>(value);
query->result = QueryResult(scePthreadSelf());
}
static void* APS5_VABI SetExitQuery(void* arg) {
auto* query = static_cast<ExitQuery*>(arg);
Require(scePthreadSetspecific(query->key, query) == SCE_OK);
return nullptr;
}
static void* APS5_VABI Finish(void*) {
return nullptr;
}
static void CheckEndedThreads() {
Require(QueryResult(scePthreadSelf()) == SCE_OK);
Pthread joined = nullptr;
Require(scePthreadCreate(&joined, nullptr, Finish, nullptr, nullptr) == SCE_OK);
Require(scePthreadJoin(joined, nullptr) == SCE_OK);
Require(QueryResult(joined) == SCE_KERNEL_ERROR_ESRCH);
PthreadAttr unset = nullptr;
Require(scePthreadAttrGet(joined, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGet(joined, &unset) == SCE_KERNEL_ERROR_EINVAL);
ExitQuery exitQuery;
Require(scePthreadKeyCreate(&exitQuery.key, reinterpret_cast<pthread_key_destructor_func_t>(QuerySelfAtExit)) == SCE_OK);
Pthread finished = nullptr;
Require(scePthreadCreate(&finished, nullptr, SetExitQuery, &exitQuery, nullptr) == SCE_OK);
Require(ReportedEnded(finished));
Require(scePthreadJoin(finished, nullptr) == SCE_OK);
Require(exitQuery.result == SCE_OK);
Require(QueryResult(finished) == SCE_KERNEL_ERROR_ESRCH);
Require(scePthreadKeyDelete(exitQuery.key) == SCE_OK);
PthreadAttr detachedAttr = nullptr;
Require(scePthreadAttrInit(&detachedAttr) == SCE_OK);
Require(scePthreadAttrSetdetachstate(&detachedAttr, DETACH_DETACHED) == SCE_OK);
for (int index = 0; index < DETACHED_THREADS; ++index) {
Pthread detached = nullptr;
Require(scePthreadCreate(&detached, &detachedAttr, Finish, nullptr, nullptr) == SCE_OK);
Require(ReportedEnded(detached));
}
Require(scePthreadAttrDestroy(&detachedAttr) == SCE_OK);
}
int main() {
CheckStackSizeLimit();
CheckSoloSched();
CheckSchedArguments();
CheckSchedulingAttributes();
CheckThreadScope();
CheckEndedThreads();
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_OK);
const KernelSchedParam requested{CREATION_PRIORITY};
Require(scePthreadAttrSetschedparam(&attr, &requested) == SCE_OK);
Require(scePthreadAttrSetaffinity(&attr, CREATION_AFFINITY) == SCE_OK);
Require(scePthreadAttrSetstacksize(&attr, STACK_SIZE) == SCE_OK);
std::promise<void> release;
auto released = release.get_future();
Pthread thread = nullptr;
Require(scePthreadCreate(&thread, &attr, Worker, &released, nullptr) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
auto reported = Query(thread);
Require(reported.priority == CREATION_PRIORITY);
Require(reported.affinity == CREATION_AFFINITY);
Require(reported.stackSize == STACK_SIZE);
Require(reported.detachState == DETACH_JOINABLE);
Require(scePthreadSetprio(thread, UPDATED_PRIORITY) == SCE_OK);
Require(scePthreadSetaffinity(thread, UPDATED_AFFINITY) == SCE_OK);
reported = Query(thread);
Require(reported.priority == UPDATED_PRIORITY);
Require(reported.affinity == UPDATED_AFFINITY);
Require(reported.stackSize == STACK_SIZE);
Require(reported.detachState == DETACH_JOINABLE);
PthreadAttr destroyed = nullptr;
Require(scePthreadAttrGet(thread, &destroyed) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrGet(thread, nullptr) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(scePthreadAttrGet(nullptr, &attr) == SCE_KERNEL_ERROR_EINVAL);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
release.set_value();
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
PthreadAttr stackAttr = nullptr;
char stackMarker;
void* reportedAddress = nullptr;
std::size_t reportedSize = 0;
Require(pthread_attr_setstack_nid_postfix(nullptr, &stackMarker, STACK_SIZE) == 22);
Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, STACK_SIZE) == 22);
Require(pthread_attr_getstackaddr_nid_postfix(nullptr, &reportedAddress) == 22);
Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 22);
Require(scePthreadAttrInit(&stackAttr) == SCE_OK);
Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, nullptr) == 22);
Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 0 && reportedAddress == nullptr);
Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, STACK_SIZE) == 0);
Require(pthread_attr_setstack_nid_postfix(&stackAttr, nullptr, STACK_SIZE) == 22);
Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, 16383) == 22);
Require(pthread_attr_getstack_nid_postfix(&stackAttr, &reportedAddress, &reportedSize) == 0);
Require(reportedAddress == &stackMarker && reportedSize == STACK_SIZE);
reportedAddress = nullptr;
Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 0 && reportedAddress == &stackMarker);
Require(scePthreadAttrDestroy(&stackAttr) == SCE_OK);
Require(pthread_attr_setstack_nid_postfix(&stackAttr, &stackMarker, STACK_SIZE) == 22);
Require(pthread_attr_getstackaddr_nid_postfix(&stackAttr, &reportedAddress) == 22);
}