Skip to content

fix(libkernel): answer sysarch I386_GET_FSBASE, I386_GET_GSBASE and undefined operations as measured on a PS5 - #7654

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
ionut27001:fix/libkernel-sysarch-i386-bases
Oct 19, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
ionut27001:fix/libkernel-sysarch-i386-bases

Conversation

@ionut27001

@ionut27001 ionut27001 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

sysarch threw for I386_GET_FSBASE (7), I386_GET_GSBASE (9) and for every operation number that FreeBSD 11's amd64 sysarch has no case for. In a payload process on a PS5 (firmware 12.70), 7 and 9 wrote the low 32 bits of the base, and nine undefined numbers, called with a null argument, failed with EINVAL. Here 7 and 9 now do the same on Linux x86-64 hosts, and every undefined number fails with EINVAL on every host: the nine tried, and the rest by FreeBSD 11's default case.

Call Payload process, firmware 12.70 main before this PR here now
sysarch(7, buf) 0, and 4 bytes written: the low 32 bits of the FS base, which was above 4 GiB; the next 4 bytes are left alone throws the same on Linux x86-64 hosts
sysarch(9, buf) the same with the GS base: 0 on the main thread, and the address a helper thread set as its own (whether that address was above 4 GiB was not logged) throws the same on Linux x86-64 hosts
sysarch(7, NULL), sysarch(9, NULL) -1 with EFAULT throws the same on Linux x86-64 hosts
operations 2, 5, 6, 12, 127, 133, 134, 1000 and -1, with a null argument -1 with EINVAL throws -1 with EINVAL for every number FreeBSD 11's amd64 sysarch has no case for, on every host

On other hosts 7 and 9 still throw, like the other segment-base operations. FreeBSD 11's amd64 sysarch ends with default: error = EINVAL; numbers other than the nine tried follow that default case here and were not measured. Operation 6 (I386_VM86) is named in its header but has no case on amd64, so it gets EINVAL here, as it did in the payload process.

GS base of a new thread. The debt entry ended with an unfinished sentence saying that a new thread's GS base is not modelled. In the payload process, a GS base that one thread set left the main thread's unchanged, and threads that thread made with pthread_create and scePthreadCreate started with its GS base. Here the host thread of a guest thread is created on the creating thread (scePthreadCreate in Thread.cpp), and Linux's copy_thread gives a new thread its creator's GS base (CLONE_SETTLS sets only the FS base). guest_sysarch now checks this on Linux for a thread made with scePthreadCreate.

Not changed: AMD64_GET_XFPUSTATE (132) returned 0 there for lengths 0, 64, 128, 256 and 512 (512 was also the largest length a search accepted) and failed with EINVAL for 1024, 2048, 4096 and 8192 and with EFAULT for a null argument or address, and I386_GET_XFPUSTATE (11) failed with EFAULT for a null argument. Both still throw here; the debt entry records these answers. I386_SET_FSBASE, I386_SET_GSBASE, the LDT and I/O permission operations were not called.

Debt: the sysarch entry in docs/dev/debt/libkernel.md records the measurements above, and its unfinished last sentence is replaced.

Overlaps: #7658, opened after this PR, rewrites the same parts of sysarch: it replaces the NotImplemented_nid_no_patch call for unhandled operations, 7 and 9 included, with a std::runtime_error saying the operation "is not supported", turns the fallback for other hosts into std::runtime_error("sysarch: segment bases on this host"), and changes the matching lines of GuestSysarch.cpp: the sysarch(7, nullptr) check and the other-host message that this PR's new sysarch(7, &base) check expects. This PR answers 7 and 9 and returns EINVAL for undefined operations instead, so the two conflict in Process.cpp and GuestSysarch.cpp, and whichever lands second has to reconcile those lines. No other open PR changes sysarch or GuestSysarch.cpp. Seven other open PRs (#5021, #6879, #7039, #7201, #7332, #7543 and #7804) change other functions in Process.cpp, and more than 50 others change docs/dev/debt/libkernel.md. tools/pr_overlap.py --branch did not finish within 25 minutes here, so each of them was merged with git merge-tree, once with main and once with this branch: with this branch, none of them conflicts in a file where it does not already conflict with main.

How the console was measured

  • Setup:
    • The console runs firmware 12.70 and is jailbroken.
    • The probe is an ELF payload built with ps5-payload-dev/sdk v0.44 and clang 18.1.8, started through a payload manager, so it ran in a payload process, not in a title. A title process was not measured.
  • Provenance: the resolve lines show that sysarch and scePthreadCreate were taken from libkernel_sys.sprx and that the pthread_create the probe calls is the one libkernel_sys.sprx exports.
  • Method:
    • For 7 and 9, p74 fills an 8-byte buffer with 0xaa and compares its first 4 bytes with the low 32 bits of the base that AMD64_GET_FSBASE (128) or AMD64_GET_GSBASE (130) returned, or, on the helper thread below, of the GS base it set (which 130 read back).
    • A helper thread sets its own GS base with AMD64_SET_GSBASE (131) to the address of a buffer in the probe. While it is set, the main thread reads its own GS base, and the helper makes one thread with pthread_create and one with scePthreadCreate, each of which reads its own. The helper then sets its GS base back to 0. The FS base was not changed.
    • The undefined operation numbers were called with a null argument. AMD64_GET_XFPUSTATE was called with lengths 0, 64, 128 and so on up to 8192, and a search found the largest length it accepted.
  • Files: the source and the full log are in the attached sysarch-probe.zip.
Console output (excerpt, lines copied unchanged)
p74: meta fw=0x12700001 pid=192 sdk=v0.44 compiler=Clang 18.1.8 built=Oct 18 2026 19:32:04
p74: resolve sym=sysarch module=libkernel_sys.sprx exported=yes
p74: resolve sym=scePthreadCreate module=libkernel_sys.sprx exported=yes
p74: resolve sym=pthread_create module=libkernel_sys.sprx same_as_called=yes

p74: case main_bases fs_ret=0 gs_ret=0 fs_above_4g=1 gs=0
p74: case main_i386_get_fsbase op=7 ret=0 errno=0 low_is_base_low32=1 upper_untouched=1
p74: case main_i386_get_gsbase op=9 ret=0 errno=0 low_is_base_low32=1 upper_untouched=1
p74: case null op=7 ret=-1 errno=14
p74: case null op=9 ret=-1 errno=14

p74: case helper_set ret=0 errno=0 reread_ret=0 reread_matches=1
p74: case helper_i386_get_gsbase op=9 ret=0 errno=0 low_is_base_low32=1 upper_untouched=1
p74: case main_while_helper_set helper_ready=1 ret=0 gs_unchanged=1
p74: case child_pthread create_ret=0 ret=0 gs_is_zero=0 gs_is_creator=1 other=0
p74: case child_scePthreadCreate create_ret=0 ret=0 gs_is_zero=0 gs_is_creator=1 other=0
p74: case helper_restore ret=0 errno=0 reread_ret=0 gs_is_zero=1

p74: case undefined op=2 ret=-1 errno=22
p74: case undefined op=5 ret=-1 errno=22
p74: case undefined op=6 ret=-1 errno=22
p74: case undefined op=12 ret=-1 errno=22
p74: case undefined op=127 ret=-1 errno=22
p74: case undefined op=133 ret=-1 errno=22
p74: case undefined op=134 ret=-1 errno=22
p74: case undefined op=1000 ret=-1 errno=22
p74: case undefined op=-1 ret=-1 errno=22

p74: case xfpu len=512 ret=0 errno=0 last_changed_byte=512
p74: case xfpu len=1024 ret=-1 errno=22 last_changed_byte=0
p74: case xfpu max_len=512
p74: case xfpu null_addr ret=-1 errno=14
p74: case null op=132 ret=-1 errno=14
p74: case null op=11 ret=-1 errno=14

The p74: prefix names the probe; the rest of each line is copied unchanged.

Tested

Build: Windows 11 x86-64, full build with WinLibs MinGW-w64 GCC 15.2.0 (posix-seh, ucrt, r7) and -DBUILD_TESTING=ON.

Tests: with this branch on top of main 8d17389, built in one tree together with fix/libkernel-kevent-timer-readd (which changes only kevent), guest_sysarch and the other guest_* tests pass: 470 of 471, and guest_video_out_wqhd_detection_no_vulkan is skipped on this machine. Of two full runs the second passed; the first, right after the build, timed out in guest_requested_exit, which this PR does not touch. On Windows guest_sysarch checks the undefined operations and that 7, 129, 130 and 132 still throw.

Not run here: the Linux x86-64 part of guest_sysarch (7 and 9 against the FS and GS bases, EFAULT for a null argument, and the GS base of a thread made with scePthreadCreate). There is no Linux machine here, so they were only syntax-checked with g++ -fsyntax-only -Wall -Wextra and __linux__ defined, without warnings: the changed sysarch function copied out with stub declarations for syscall and the ARCH_* constants, and the whole test file. Full builds do not run automatically for pull requests; a collaborator's manual Build run would run them on Linux. A reviewer reported a Linux run of guest_sysarch in the comments.

Old code, new test: with Process.cpp reverted to main and the new test kept, guest_sysarch aborts with sysarch: operation 2 not implemented.

Console: behaviour measured on a PS5 (firmware 12.70), as described above.

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

…ndefined operations as measured on a PS5

In a payload process on a PS5 (firmware 12.70), I386_GET_FSBASE (7) and
I386_GET_GSBASE (9) wrote 4 bytes, the low 32 bits of the FS and GS
bases, and failed with EFAULT for a null argument; both threw here. On
Linux x86-64 hosts they now do the same.

The nine operation numbers tried that FreeBSD 11's amd64 sysarch has no
case for (2, 5, 6, 12, 127, 133, 134, 1000 and -1), each with a null
argument, failed with EINVAL there, as its default case does, and threw
here. Every such number now returns EINVAL on every host.

Threads made with pthread_create and scePthreadCreate started with their
creator's GS base there; the test now checks that a guest thread does so
on Linux. The sysarch debt entry records these measurements and what
AMD64_GET_XFPUSTATE and I386_GET_XFPUSTATE returned there; both still
throw here.
@github-actions

github-actions Bot commented Oct 18, 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 5021 core/libs/prx/libkernel/System/src/Process.cpp
docs/dev/debt/libkernel.md
PR 6149 docs/dev/debt/libkernel.md
PR 6446 docs/dev/debt/libkernel.md
PR 6469 docs/dev/debt/libkernel.md
PR 6519 docs/dev/debt/libkernel.md
PR 6740 docs/dev/debt/libkernel.md
PR 6879 core/libs/prx/libkernel/System/src/Process.cpp
PR 6936 docs/dev/debt/libkernel.md
PR 6977 docs/dev/debt/libkernel.md
PR 7039 core/libs/prx/libkernel/System/src/Process.cpp
docs/dev/debt/libkernel.md
PR 7049 docs/dev/debt/libkernel.md
PR 7075 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 7129 docs/dev/debt/libkernel.md
PR 7149 docs/dev/debt/libkernel.md
PR 7192 docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/System/src/Process.cpp
PR 7299 docs/dev/debt/libkernel.md
PR 7332 core/libs/prx/libkernel/System/src/Process.cpp
docs/dev/debt/libkernel.md
PR 7509 docs/dev/debt/libkernel.md
PR 7554 docs/dev/debt/libkernel.md
PR 7658 core/libs/prx/libkernel/System/src/Process.cpp
core/libs/tests/GuestSysarch.cpp

@wachi35676

Copy link
Copy Markdown
Contributor

Linux run of the part listed as not run: WSL Ubuntu 24.04, GCC 13.3, base a373e85e5. guest_sysarch passes, covering:

  • 7 and 9 writing the low 32 bits with the upper 4 bytes untouched;
  • EFAULT for a null argument;
  • a scePthreadCreate thread starting with its creator's GS base.

With Process.cpp reverted it aborts with sysarch: operation 2 not implemented. Windows 11 with GCC 15.2.0 gives the same results.

The copyout matches FreeBSD 11.4 as well as 11.0: sys_machdep.c#L253-L256 and #L267-L270 copy out a uint32_t i386base, and #L327-L328 is the EINVAL default.

Note: #7658, opened after this PR, rewrites the same NotImplemented line in sysarch and the sysarch(7, nullptr) test line, so whichever lands second needs a rebase.

AI-assisted: yes (Claude Code); results above were run on this machine.

@oneandonlydean oneandonlydean self-assigned this Oct 19, 2026
@oneandonlydean
oneandonlydean merged commit 8982791 into boykopovar:main Oct 19, 2026
15 checks passed
primesoul-web added a commit to primesoul-web/AnyPS5 that referenced this pull request Oct 19, 2026
…n over the database exports

- sysarch: boykopovar#7654's PS5-measured behaviour (I386_GET_FSBASE/GSBASE writing the
  low 32 bits, EINVAL for undefined operations, GS-base inheritance) replaces
  this branch's documented error paths; the messages the branch's GuestSysarch
  expected are gone, so Process.cpp and GuestSysarch.cpp come from upstream.
- Hmd2: al3w0f205's database exports join the guest test (their three throw
  expectations run after the reprojection session walk) and the debt file
  (their aerolib bullet follows the session bullet, which now links the export
  table to satisfy the new debt-file rule from boykopovar#6723).
- Http: sceHttpAuthCacheFlush/sceHttpAuthCookieFlush merged around the cookie
  hunk without overlap.
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.

3 participants