Skip to content

fix(libkernel): return ESRCH from scePthreadAttrGet for a thread that has ended - #7957

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
Lindamea1:fix/libkernel-attrget-esrch-ended-thread
Oct 20, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
Lindamea1:fix/libkernel-attrget-esrch-ended-thread

Conversation

@Lindamea1

Copy link
Copy Markdown
Contributor

What

On main, scePthreadAttrGet checks only for null arguments and then copies the attributes of whatever the handle points at. A thread that has ended still answers 0: one that was joined, one that finished and is not joined yet, and one that ended detached. For the last kind ReleaseThread deleted the object when the thread ended, so the call reads freed memory. A probe run against libkernel.prx built from main 5eba12d printed the following (cut to the scePthreadAttrGet results; the last line comes from a copy of the slot search described below, run by a 65th thread after 64 others had registered and been joined):

joined thread:            scePthreadAttrGet=0
ended, not joined thread: scePthreadAttrGet=0
detached thread, ended:   scePthreadAttrGet=0
thread #65 (all 64 earlier ones joined): registry index=-1

FreeBSD 11.4 answers ESRCH in all three cases. _pthread_attr_get_np returns EINVAL for a null thread or attribute, then looks the thread up with _thr_find_thread(curthread, pthread, /*include dead*/0). _thr_find_thread returns ESRCH for a thread that is no longer in the thread hash or is in PS_DEAD, and otherwise returns with the thread locked; the attributes are read before the unlock. exit_thread sets PS_DEAD after the thread-specific data destructors have run (thr_exit.c#L287-L305). Where the console was measured (firmware 13.60) it answers ended threads the same way: a second scePthreadJoin returns 0x80020003 (measured in #4160, implemented in #4481) and pthread_getname_np on a joined thread returns ESRCH (recorded in its debt entry, implemented in #4533).

Monster Hunter Wilds (PPSA24191) depends on this answer. It gives each thread an index from a table of 64 slots. A thread that is not in the table yet walks the slots in order and takes the first one that is empty or whose thread scePthreadAttrGet reports gone: for each filled slot it passes, it calls scePthreadAttrInit, scePthreadAttrGet on the slot's thread and scePthreadAttrDestroy, and takes the slot only if scePthreadAttrGet returned 0x80020003. With main's answer no slot is ever freed, so once 64 distinct threads have taken one, later threads get none unless their handle happens to equal an old one. No stop has been observed: here the title does not get past a flat grey screen yet (it needs #7852 and --linux-fixed-base to get that far), and none of the 7 runs logged so far started more than 50 guest threads.

#5584 asked for the same check in pthread_attr_get_np (with pthread_getaffinity_np) and was closed because no title was known to reach it. Monster Hunter Wilds does not import pthread_attr_get_np, so this PR leaves it as it is and changes only scePthreadAttrGet, the call the title makes. The difference from #5584 is a title whose code calls scePthreadAttrGet and acts on 0x80020003; there is still no stop or log line.

The change: after the existing EINVAL checks, scePthreadAttrGet returns ESRCH (0x80020003) unless the handle is a live guest thread in the thread registry, the test pthread_setaffinity_np and pthread_kill already make through GuestThreadIsLive. The lookup compares pointers, so a freed handle is not read. The new LockLiveGuestThread returns the registry lock held when the thread is live, and the attributes are copied under it. A thread's object leaves the registry in its destructor under the same lock, so a detached thread that exits during the call is not freed while its fields are read. GuestThreadIsLive now calls LockLiveGuestThread. A thread counts as ended once its thread-specific data destructors have run, where FreeBSD sets PS_DEAD, so a thread that queries itself from one of its destructors still gets its attributes. Running threads, including the main thread and other adopted host threads, are in the registry and answer as before. pthread_attr_get_np and the other calls that take a thread handle are unchanged.

Not measured on a console. The new libkernel debt entry records that, that pthread_attr_get_np still answers 0 for an ended thread, and that on Windows a host thread adopted by scePthreadSelf that ends without scePthreadExit keeps answering 0, since its object is never marked ended or freed.

Tested

Gentoo Linux, kernel 7.2.8, GCC 16.2.1, glibc 2.43, Ninja, Release, BUILD_TESTING=ON, on main 43a2aba.

Not run on Windows, macOS or a console.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

🤖 Generated with Claude Code

… 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>
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 3986 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 5021 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 6044 core/libs/tests/GuestPthreadAttr.cpp
PR 6149 docs/dev/debt/libkernel.md
PR 6469 docs/dev/debt/libkernel.md
PR 6519 docs/dev/debt/libkernel.md
PR 6658 docs/dev/debt/libkernel.md
PR 6740 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
docs/dev/debt/libkernel.md
PR 6879 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 6977 docs/dev/debt/libkernel.md
PR 7049 docs/dev/debt/libkernel.md
PR 7088 docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 docs/dev/debt/libkernel.md
PR 7149 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7192 docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 7464 docs/dev/debt/libkernel.md
PR 7509 docs/dev/debt/libkernel.md
PR 7554 docs/dev/debt/libkernel.md
PR 7556 docs/dev/debt/libkernel.md
PR 7605 docs/dev/debt/libkernel.md
PR 7949 docs/dev/debt/libkernel.md

@oneandonlydean oneandonlydean self-assigned this Oct 20, 2026
@oneandonlydean
oneandonlydean merged commit 56a0be3 into boykopovar:main Oct 20, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants