Hades (PPSA03355) stops at load on main with "undefined symbol:
9HzfhdtESio", the NID of pthread_getname_np, which it calls on
pthread_self() with a 32-byte buffer. Measured on a PS5 (firmware 13.60)
through dlopen of libkernel: the function takes (thread, buffer), copies
the name and its terminator, returns an empty name for an unnamed
thread, ESRCH for a null or joined thread and EFAULT for a null buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePthreadMutexDestroy threw when the mutex was locked. FreeBSD libthr,
which the console's libkernel follows, returns EBUSY and leaves the
mutex usable; PPSA28997 destroys a locked mutex during its intro and
was aborted.
A condition wait on a recursive mutex locked more than once unlocked the
native mutex only once, so the thread that should signal blocked forever
on it. Unlock the remaining levels before the wait and lock them again
after it, as FreeBSD libthr does in _mutex_cv_unlock and _mutex_cv_lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
sceKernelGetCurrentCpu returned the raw host CPU index. Guest affinities
are bookkeeping only and sceKernelGetAvailableCpumask advertises CPUs 0
to 12, so on a host with more than 13 logical CPUs a title could get 13
or higher, and a thread pinned to one CPU could get another one.
The host index now selects a CPU of the calling thread's affinity
clipped to the available mask, so the result is always an advertised
CPU and a thread pinned to one CPU reports that CPU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
The APC delivery was allocated after SuspendThread, so a target holding
the allocator lock deadlocked the raising thread; it is now allocated
before the suspend. The wait count a raise checks lived in the target's
thread_local storage and dangled once the thread exited; it is now a
member of PthreadPrivate, which TimedWait counts into through a
thread-local pointer bound when the thread starts, and a raise on a
thread that already finished returns ESRCH. TechnicalDebt gets one line
per unverified choice: Windows only, the FreeBSD ucontext layout, the
signal mask, the YMM upper halves, and delivery to threads outside
TimedWait.
AI-assisted: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LEGO Horizon Adventures imports 19 functions that no library exports
and no open pull request adds, so it fails eager binding. They are
declared as throwing stubs. The two libSceAjmWVorbis imports have no
known name and are exported by NID from libSceAjm.native.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 of the 31 NIDs have names in shadPS4's aerolib list. They are declared
under those names; POSIX sleep is implemented. The NpPartner001 and Psml
MFSR2 exports stay unnamed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UFC 6 (PPSA23566) passes type 0 to pthread_mutexattr_settype during
startup. libkernel rejects it with EINVAL and leaves the attribute
unchanged, as fpPS4's decompiled body does, instead of aborting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Returns the CPU mask threads get by default (DEFAULT_THREAD_AFFINITY,
0x1FFF), the value itself rather than an error code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS5 kernel follows FreeBSD 11, whose libthr accepts process-shared
barriers. Guest code runs in a single process, so a process-shared
barrier synchronises the same threads a private one does; the attribute
is stored and reported back by getpshared.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run the SIGUSR1 handler recorded by sceKernelInstallExceptionHandler on the
target thread with a PS5 ucontext (uc_mcontext at 0x40, FreeBSD amd64
layout) of its interrupted state:
- on the calling thread, directly;
- on a thread in a libkernel wait, from an APC: the TimedWait waits are
alertable and count themselves in a per-thread wait state, so the
handler runs inside the wait, which then continues;
- on a running thread, by suspending it and redirecting it to an entry
that calls the handler and restores the possibly modified context.
Other signals return EINVAL, as in shadPS4; raising without a handler
throws. Linux still throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePthreadAttrGet reported a null stack address and a zero size for an
adopted thread (the main thread), and for every guest thread on Linux,
where the stack range was never recorded. A garbage collector that scans
thread stacks through scePthreadAttrGetstackaddr and
scePthreadAttrGetstacksize (Unity IL2CPP in PPSA12544) then skipped the
main thread's stack and freed objects it still referenced. The range now
comes from the host thread: GetCurrentThreadStackLimits on Windows,
pthread_getattr_np on Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implement scePthreadGetschedparam and scePthreadSetschedparam on top of
the thread priority, POSIX utimes and fsync on top of sceKernelUtimes
and sceKernelFsync, and sceUserServiceGetAccessibilityZoomFollowFocus
like the other accessibility queries. Add the remaining imports of
PPSA12544 as unimplemented exports and record unknown NIDs and
signatures in the technical debt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Track the stack range of each running guest thread and answer
sceKernelIsStack from the calling thread's stack (the host limits for an
adopted thread) or from that registry. A mapped address outside every
stack succeeds with null bounds and an unmapped one returns EACCES; the
output pointers may be null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePthreadKeyDelete frees a key slot and the next scePthreadKeyCreate
hands the same index out again, but every thread kept the value it had
stored under that index. The new key read the previous key's value
instead of NULL, and at thread exit its destructor was called on that
stale pointer.
Each slot now carries a sequence number, as in FreeBSD libthr. A thread
value is visible to scePthreadGetspecific and to the exit destructors
only while its sequence matches the slot's, so a new key starts as NULL
in every thread, as POSIX requires, and a deleted key reads NULL.
scePthreadGetspecific stays lock-free and scePthreadSetspecific no
longer takes the key lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The POSIX layer only exported init, destroy, rdlock, wrlock and unlock.
pthread_rwlock_tryrdlock, pthread_rwlock_trywrlock,
pthread_rwlock_timedrdlock and pthread_rwlock_timedwrlock now wrap the
existing scePthreadRwlock functions and accept the static initializer.
The timed variants take an absolute CLOCK_REALTIME deadline. Like
FreeBSD libthr they try the lock first and only validate abstime when
the lock is not free. A null abstime throws, as pthread_mutex_timedlock
does.
scePthreadRwlockTryrdlock and scePthreadRwlockTrywrlock return EBUSY
when the caller already holds the write lock instead of calling into
std::shared_timed_mutex, where that is undefined behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PS5 libthr (FreeBSD) creates error-checking mutexes by default and has
PTHREAD_MUTEX_ADAPTIVE_NP (type 4); relocking an error-checking or adaptive
mutex returns EDEADLK. Default mutexes were Normal and type 4 was mapped to
Normal, so a relock threw instead of returning EDEADLK, which aborted an
Unreal Engine title's recursive directory scan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scePthreadAttrGet wrote priority 700 and left the affinity of the
destination untouched, whatever the thread was created with or set to
afterwards. It now reports the values the thread holds, as
pthread_attr_get_np already does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pthread_barrier_init threw for any non-null attributes object, and no
function could create one. Add pthread_barrierattr_init, _destroy,
_getpshared and _setpshared after FreeBSD libthr, and let
pthread_barrier_init take the object.
PTHREAD_PROCESS_SHARED still throws in pthread_barrierattr_setpshared:
FreeBSD 9.0 rejects it and 11.0 accepts it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The C11 thread layer (_Mtx_*, _Cnd_*, _Thrd_*, _Xtime_get_ticks, std::_Pad) and the libc additions
_FNan, _FDtest, _FExp, __udivti3, _Throw_Cpp_error, generic_category, localeconv, wcsncpy_s and the
aligned operator new came from the --skip-sce-module route, where our libc stood in for the title's.
With the title's sce_module loaded, its libc.prx exports every one of these NIDs and the relinker
lists the guest modules ahead of the host prx, so the imports never reach our copies. Checked both
by the NID tables (the executable imports them from libc, the guest libc.prx exports them) and by
making the four std::_Pad functions throw: the title boots, reaches the menu and loads story mode
without calling them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>