Skip to content

feat(libkernel_sys): implement fstat - #9008

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
verusferro:feat/libkernel-sys-fstat
Oct 23, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
verusferro:feat/libkernel-sys-fstat

Conversation

@verusferro

@verusferro verusferro commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

fstat in libkernel_sys was a NotImplemented_nid_no_patch stub from the discovery exports. It now calls libkernel's _fstat, as fstatfs in the same file already calls _fstatfs. It has FreeBSD's int fstat(int, struct stat*) signature, and it gives the same status, EFAULT for a null buffer and EBADF for a descriptor that is not open as libkernel's fstat. guest_kernel_sys compares it with _fstat, as it does fstatfs with _fstatfs.

Tested

Ubuntu 24.04 (WSL2) x86-64, GCC 13.3, Release, full build with BUILD_TESTING=ON:

  • ctest -R '^(patched_libraries|guest_kernel_sys|conventions_unknown_nids)$': 3/3 pass; patched_libraries reports 357 patched libraries loaded, 0 failed.
  • Patched libkernel_sys.prx exports fstat (mqQMh1zPPT8) and imports _fstat (A0O5kF5x4LQ), which patched libkernel.prx exports.
  • guest_kernel_sys gains an fstat check beside the fstatfs one: fstat and _fstat on the same descriptor give a byte-identical FileStat with st_size 1; a null buffer gives -1/EFAULT; a closed descriptor and -1 give -1/EBADF. Making the forwarder return 0 fails the null-buffer check.
  • Standalone probe against the built libraries through the patched NIDs as well: on an opened 5-byte file fstat returns 0 with st_size 5 and S_IFREG, byte-identical to _fstat.
  • tools/progress.py: libkernel_sys 13 → 14 done, 532 → 531 todo.
  • python3 tools/check_conventions.py --base origin/main: passed. python3 tools/pr_overlap.py: feat(libkernel_sys): implement chown, lchown, fchownat and linkat #9103 and feat(libkernel_sys): implement statfs symlinkat and futimesat #9161 remove the stub next to the fstat stub in Export.Discovery.cpp, and feat(libkernel_sys): implement sceKernelGetModuleInfoInternal and sceKernelGetModuleListInternal #9294 adds functions where fstat goes in Export.cpp; none implements fstat, and whichever lands second keeps both sides.

Windows: built and tested by @ycbuzi in the comments. Not tested: macOS, titles.

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

fstat was a NotImplemented stub in libkernel_sys. It now calls libkernel's
_fstat, as fstatfs already calls _fstatfs, so it reports the same status,
EFAULT for a null buffer and EBADF for a descriptor that is not open.
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 9294 core/libs/prx/libkernel_sys/Export.cpp L276

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_sys/Export.cpp
PR 5528 core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 6044 core/libs/tests/GuestKernelSys.cpp
PR 6525 core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 7201 core/libs/prx/libkernel_sys/Export.cpp
PR 8457 core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 8701 core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 9019 core/libs/prx/libkernel_sys/Export.Discovery.cpp
core/libs/prx/libkernel_sys/Export.cpp
PR 9103 core/libs/prx/libkernel_sys/Export.Discovery.cpp
core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 9382 core/libs/prx/libkernel_sys/Export.Discovery.cpp
core/libs/prx/libkernel_sys/Export.cpp
PR 9442 core/libs/prx/libkernel_sys/Export.Discovery.cpp
core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 9573 core/libs/prx/libkernel_sys/Export.Discovery.cpp
PR 9693 core/libs/prx/libkernel_sys/Export.Discovery.cpp
core/libs/prx/libkernel_sys/Export.cpp
core/libs/tests/GuestKernelSys.cpp
PR 9826 core/libs/prx/libkernel_sys/Export.cpp

@ycbuzi

ycbuzi commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Windows run of 29f1a34 merged onto main 80565c5 (594200db72, clean), the platform the description lists as not tested. Windows 11 Home 10.0.26200, MinGW-w64 GCC 15.2.0 (ucrt-posix-seh r7), Release, BUILD_TESTING=ON.

Built libs, libkernel_sys, guest_kernel_sys_tests and patched_libraries_tests with no errors; the two changed files compile without warnings, and the three the full build prints come from files this PR does not change. ctest --repeat until-fail:2 on patched_libraries, guest_kernel_sys and conventions_unknown_nids, the set in the description: all three pass on both repeats.

I ported the description's probe to Windows: a program linked against the unpatched libkernel_sys, libkernel and libc import libraries that opens a 5-byte file with the guest open. All nine checks pass:

Check Result
fstat returns 0, st_size 5, S_IFREG
FileStat against _fstat on the same descriptor byte-identical
null buffer -1, EFAULT
closed descriptor, and -1 -1, EBADF

Not run: the patched-NID form of the probe, a title.

@mpratsaki

Copy link
Copy Markdown
Contributor

A public homebrew that reaches this stub on Windows today: LakeSnes from the ps5-payload-dev websrv v0.34 bundle (lakesnes.elf). Static analysis only; nothing was run.

  • lakesnes.elf has libkernel_sys.sprx in DT_NEEDED and imports fstat unscoped by name (the SDK form, 226 unscoped imports, no scoped ones).
  • Relinked with main 2457319ab (--windows --trap-sdk-syscall-stubs --skip-sce-module), the startup library list is … libkernel_sys.prx, libSceLibcInternal.prx, libSceNet.prx, libc.prx, libkernel.prx: canonical libkernel.prx is added last since fix(relinker): resolve kernel variant imports via canonical kernel #8867.
  • No loaded library exports fstat by name, so the stub falls back to its NID mqQMh1zPPT8 and takes the first library in that list exporting it. Since feat(prx): add missing libkernel_sys exports #8837, libkernel_sys.prx does (fstat_nid_postfix in Export.Discovery.cpp, a NotImplemented_nid_no_patch stub), ahead of libkernel's fstat_nid_disambig1_nid_postfix. So LakeSnes binds fstat to the throwing stub, where this PR's forwarding to _fstat would give it libkernel's implementation.

Same search over the v0.1.3 Windows libs/ export tables for its other imports: with #8867's added libkernel.prx all 226 resolve, and fstat is the only one that a #8837 stub reaches first. Of the 527 #8837 stubs, fstat and pwrite (#9019) are the two whose NID libkernel.prx v0.1.3 also exports.

AI-assisted: yes (Claude Code).

@ycbuzi

ycbuzi commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

@mpratsaki I ran it on Windows: LakeSnes doesn't get as far as fstat. lakesnes.elf from websrv v0.34 (LakeSnes.zip sha256 60ce0b19…, matching the release digest), converted with --windows --trap-sdk-syscall-stubs --skip-sce-module --trace-imports on main 0c6a5bd and on this PR merged onto it (0cadf6d264), stops at start-up with FATAL: unhandled exception 0xc0000005 and 0 presents, with no ROM and with each of the two bundled ROMs: 3 of 3 runs on each build, the same frames every time (lakesnes.exe+0x13e66b, +0x13e5d4).

The fault is the SDK startup's first call through its entry argument: _start passes rdi on unchanged, the function it calls keeps it in rbx, and the first function that one calls runs call *(%rbx) with edi = 1 and rsi = "sceKernelDlsym" (lakesnes.exe+0x13e9a1). That matches payload_init and __crt_syscall_init in the SDK's crt calling payload_args_t::sys_dynlib_dlsym, the entry #9381 describes on Linux. So on Windows LakeSnes can't show this PR's effect until that entry is supplied. Windows 11 Home 10.0.26200, MinGW-w64 GCC 15.2.0, Release, RTX 4070 SUPER.

@mpratsaki

Copy link
Copy Markdown
Contributor

@ycbuzi Thanks for running it and for tracing the fault. You're right, and "reaches this stub" in my comment overstated a static result: the import binding I described happens at start-up, but LakeSnes is an SDK payload and stops in payload_init (#9381) before it can call fstat. So it isn't evidence that this PR changes a running program; please don't count it as title evidence for #9008.

@harisibrar1 harisibrar1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked against main 6ef5a1c and FreeBSD 11.4 sys/sys/stat.h / ps5-payload-dev/sdk sce_stubs/libkernel_sys.c:

  • The forwarder at libkernel_sys/Export.cpp:277-281 matches FreeBSD's int fstat(int, struct stat *) and the existing fstatfs → _fstatfs pattern above it. Errors come from libkernel's fstat (Stdio.cpp:896-912): EFAULT for a null buffer on an open descriptor, EBADF otherwise.
  • The probe from the description isn't in the PR. fstatfs has the same check in core/libs/tests/GuestKernelSys.cpp:278-285: same-descriptor comparison with _fstatfs, nullptr → -1/14, and closed or -1 descriptor → -1/9. Adding the fstat/_fstat equivalent there would keep the forwarder covered if either side changes.
  • No debt entry looks needed, since the behaviour is libkernel's unchanged fstat.
  • Builds on Windows (WinLibs GCC 15.2.0 r7, full BUILD_TESTING=ON build) merged onto main 6ef5a1c; the only failing step is the existing ImageArrayWindowPastLayerLimit.cpp alignment error that #8310 fixes. Not run here: this machine's Smart App Control blocks freshly built test executables.

Reviewed with AI assistance (Claude Code); the checks above were run locally on my machine.

fstat must fill the same FileStat as _fstat for an open descriptor, return EFAULT for a null buffer and EBADF for a closed or invalid descriptor, as the fstatfs check beside it does for _fstatfs.
@verusferro

Copy link
Copy Markdown
Contributor Author

@harisibrar1 Thanks. Added in fa5fc8d beside the fstatfs check: fstat and _fstat on the same descriptor give a byte-identical FileStat, a null buffer gives -1/EFAULT, and a closed descriptor and -1 give -1/EBADF. Making the forwarder return 0 fails the new null-buffer check. guest_kernel_sys, patched_libraries and conventions_unknown_nids pass on Linux (Ubuntu 24.04, GCC 13.3, Release).

@oneandonlydean oneandonlydean self-assigned this Oct 22, 2026
@oneandonlydean
oneandonlydean merged commit 5d2eb58 into boykopovar:main Oct 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants