#9272 keeps liveStacks and protectedStackPages alive through static
destruction so that a late mprotect from an atexit handler can still
look up guest thread stacks. The mutex guarding them is still a static
std::mutex. libstdc++'s std::mutex has a trivial destructor, so it keeps
working on Linux, but libc++ destroys the pthread mutex, and a later lock
throws "mutex lock failed: Invalid argument": on macOS the exit probe of
guest_memory_lifetime fails at its mapping protection stage. The lock is
now allocated and never destroyed, like the maps.
The same change is part of #7201 and #8658.
Co-Authored-By: marion909 <[email protected]>
Co-Authored-By: iamthehimansh <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
liveStacks, protectedStackPages and guestStackMappings were plain
globals, so libkernel's static destructors freed them at exit while
detached threads could still unregister their stacks or look one up.
ThreadSanitizer reports the race in guest_pthread_join_detach: the main
thread's static destruction of liveStacks against a detached thread's
UnregisterStack under stackLock. The maps are now created once and
never destroyed, as Apr.cpp does for its globals.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both were missing from libkernel.prx, so a title importing either
(dYv-+If2GPk, RRnSj8h8VR4) failed to load. They take a relative timeout
in microseconds and are scePthreadRwlockTimedrdlock and
scePthreadRwlockTimedwrlock with POSIX error numbers; a zero lock is
initialized as by pthread_rwlock_rdlock. The signature comes from
shadPS4, which registers one function for each NID and its
scePthreadRwlockTimed* twin; FreeBSD 11.4 has no such calls. The throwing
libkernel_sys copies are removed, as an export may live in one library
only. Recorded in the libkernel debt file.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
_Call_once, _Tss_create, _Tss_delete, _Tss_get, _Tss_set, _Thrd_equal,
_Thrd_current and _Thrd_exit call scePthreadOnce, scePthreadKeyCreate,
scePthreadKeyDelete, scePthreadGetspecific, scePthreadSetspecific,
scePthreadEqual, scePthreadSelf and scePthreadExit and return their
results unchanged, as the console's libSceLibcInternal does.
_Mtx_current_owns returns 1 there. libkernel registers the calls with
libc at load, next to the pthread_create registration.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RbLgECLQ2bpDM7oe2urGZK
Animal Well (PPSA22520) imports _Thrd_detach (L7f7zYwBvZA) from libc.prx and nothing exports it, so the Windows loader stops at startup. Register libkernel's pthread_detach with libc like pthread_create and pthread_join, and return _Thrd_error (4) when it fails.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GtXavMkgCTCdmRpweviHcQ
scePthreadMutexUnlock resolved the slot with resolveMutex(mutex, false),
which throws "Mutex is not initialized" while the slot still holds the
static initializer (0) or the adaptive static initializer (1), so
unlocking a statically initialized mutex that was never locked ended
the process. FreeBSD 11.4's mutex_unlock_common returns EPERM for both
without initializing the slot, and EINVAL for the destroyed marker.
Load the slot once: the destroyed marker still returns 0x80020016, and
the two initializers now return 0x80020001 and stay in the slot, so a
later lock still creates the mutex.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
_pthread_cond_timedwait fails an abstime with a negative tv_sec with EINVAL
before it looks at the condition variable or the mutex. pthread_cond_timedwait
took it as a deadline in the past and answered ETIMEDOUT.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
_pthread_barrier_init fails a count above INT_MAX with EINVAL.
pthread_barrier_init accepted any count above zero.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
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 <[email protected]>
PPSA34015 (EA SPORTS FC 27) stopped with "Joining guest thread: Access is
denied": scePthreadJoin waited on a thread that another call released
under it, and then waited on its closed handle. As FreeBSD 11.4's
thr_join.c and thr_detach.c do with the joiner field, scePthreadDetach
now returns EINVAL while a thread waits to join the target, a second
concurrent scePthreadJoin returns EOPNOTSUPP, a cancelled join backs out,
and a completed join leaves the thread detached.
Locking, trylocking, unlocking a destroyed mutex and signalling, broadcasting,
waiting on or destroying a destroyed condition variable threw a C++ exception
into the guest. FreeBSD 11.4 libthr returns EINVAL for all of them (thr_mutex.c
check_and_init_mutex and mutex_unlock_common compare against THR_MUTEX_DESTROYED,
thr_cond.c CHECK_AND_INIT_COND and _pthread_cond_destroy against
THR_COND_DESTROYED). The condition wait path already reported EINVAL for a
destroyed mutex.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
libkernel exported scePthreadAttrSetschedpolicy and
scePthreadAttrSetinheritsched but not the matching getters,
scePthreadAttrGetschedpolicy (NID NMyIQ9WgWbU) and
scePthreadAttrGetinheritsched (NID lpMP8HhkBbg), so a title that
imports either one stopped at startup on the unresolved import. Dragon
Ball Xenoverse 2 (PPSA08587) imports both.
Implement them as FreeBSD 11.4 libthr implements
pthread_attr_getschedpolicy and pthread_attr_getinheritsched
(lib/libthr/thread/thr_attr.c): a null or uninitialized attribute
returns EINVAL (0x80020016), as does a null output of
scePthreadAttrGetschedpolicy; otherwise the attribute's policy or
inheritance is stored and 0 is returned. libthr writes through a null
output of the inheritance getter unchecked, so that case throws, as
pthread_attr_getinheritsched and scePthreadAttrGetsolosched do.
Neither getter was measured on a console. The scePthreadAttr entry in
docs/dev/debt/libkernel.md, which describes the calls measured on a
PS5, now says so and records the throw.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An adopted thread (the main thread, or a host thread that calls
scePthreadSelf) reported its whole Windows stack reservation, and so
did sceKernelIsStack on a host thread before adoption. A relinked
executable reserves 0x210000 bytes for its main thread and commits the
top 0x200000, so the lowest 64 KiB of the reported stack hold only
reserved pages and Windows' guard pages. mprotect fails there, so
HotSpot in Java 25 LAN / DNS (PPSA99869) could not create the main
thread's stack guard pages ("Attempt to allocate stack guard pages
failed").
When everything above the bottom allocation granule is committed
read-write, the host stack is now reported from there, as created
guest threads report only their committed stack. Other host stacks
keep the whole range. The committed-pages walk is shared with
StartNativeThread and throws when VirtualQuery fails.