Files
AnyPS5/core/libs/tests/GuestPthreadStack.cpp
T
NevesHenryandClaude Opus 5.5 faf983ae01 fix(libkernel): give Linux guest threads the stack size they request
scePthreadCreate on Linux started guest threads with std::thread, so every
thread got the host default stack (RLIMIT_STACK, usually 8 MiB) whatever the
title asked for. A thread that asks for more than that and uses it crashes
with SIGSEGV. Once the thread ran, scePthreadAttrGet also reported the host
size instead of the requested one.

Create the thread with pthread_create and a stack of the requested size plus
what glibc keeps at its top (static TLS, the thread control block and the
start_thread frame). That reserve is measured once with a probe thread.
The thread reports [top - reserve - size, top - reserve) as its stack, as
Windows reports [top - size, top), and the creator waits until the range is
set, as on Windows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 08:57:00 +02:00

77 lines
3.0 KiB
C++

#include "SceTypes.hpp"
#include <cstddef>
#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 scePthreadAttrInit(PthreadAttr* attr);
int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr);
int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr);
int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stackSize);
int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackAddress, std::size_t* stackSize);
Pthread APS5_VABI scePthreadSelf();
}
static constexpr int SCE_OK = 0;
static constexpr std::size_t FRAME_SIZE = 4096;
static constexpr std::size_t FRAME_MARGIN = 2 * FRAME_SIZE;
static constexpr std::size_t STACK_SIZES[] = {16384, 65536, 1u << 20, 16u << 20};
thread_local unsigned char threadLocalBlock[512 * 1024];
static void Require(bool value) { if (!value) std::abort(); }
static std::uintptr_t Descend(std::uintptr_t floor);
static std::uintptr_t (*volatile descend)(std::uintptr_t) = Descend;
static std::uintptr_t Descend(std::uintptr_t floor) {
volatile unsigned char frame[FRAME_SIZE];
frame[0] = 0x5a;
frame[FRAME_SIZE - 1] = 0xa5;
const auto here = reinterpret_cast<std::uintptr_t>(&frame[0]);
const auto deepest = here < floor + FRAME_MARGIN ? here : descend(floor);
Require(frame[0] == 0x5a && frame[FRAME_SIZE - 1] == 0xa5);
return deepest;
}
static void* APS5_VABI Worker(void* arg) {
const auto requested = *static_cast<const std::size_t*>(arg);
threadLocalBlock[0] = 1;
threadLocalBlock[sizeof(threadLocalBlock) - 1] = 2;
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(scePthreadAttrGet(scePthreadSelf(), &attr) == SCE_OK);
void* address = nullptr;
std::size_t size = 0;
Require(scePthreadAttrGetstack(&attr, &address, &size) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
Require(size == requested);
const auto begin = reinterpret_cast<std::uintptr_t>(address);
const auto local = reinterpret_cast<std::uintptr_t>(&size);
Require(local > begin && local < begin + size);
Require(local - begin + FRAME_SIZE >= requested);
const auto deepest = Descend(begin);
Require(deepest >= begin && deepest < begin + FRAME_MARGIN);
Require(threadLocalBlock[0] == 1 && threadLocalBlock[sizeof(threadLocalBlock) - 1] == 2);
return arg;
}
int main() {
for (auto stackSize : STACK_SIZES) {
PthreadAttr attr = nullptr;
Require(scePthreadAttrInit(&attr) == SCE_OK);
Require(scePthreadAttrSetstacksize(&attr, stackSize) == SCE_OK);
Pthread thread = nullptr;
Require(scePthreadCreate(&thread, &attr, Worker, &stackSize, nullptr) == SCE_OK);
Require(scePthreadAttrDestroy(&attr) == SCE_OK);
void* result = nullptr;
Require(scePthreadJoin(thread, &result) == SCE_OK);
Require(result == &stackSize);
}
}