Skip to content

feat(libSceSystemService): LoadExec on macOS hosts - #6248

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
mugurc:feat/libscesystemservice-loadexec-macos
Oct 18, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
mugurc:feat/libscesystemservice-loadexec-macos

Conversation

@mugurc

@mugurc mugurc commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

sceSystemServiceLoadExec on macOS hosts. It found the running executable through /proc/self/exe and replaced the process with fexecve after close_range, none of which macOS has, so on macOS every call other than "exit" threw. Part of the macOS series announced on #1 (#1 (comment)).

  • The running executable's path comes from _NSGetExecutablePath, resolved with std::filesystem::canonical, as libSceNpEntitlementAccess and libSceFont already do on macOS. The rest of PrepareLaunch is the Linux code.
  • The target must be an x86-64 Mach-O executable (MH_MAGIC_64, CPU_TYPE_X86_64, MH_EXECUTE) that the user may run, as Linux requires an x86-64 ELF.
  • macOS has no fexecve, so the process is replaced by path with posix_spawn and POSIX_SPAWN_SETEXEC. POSIX_SPAWN_CLOEXEC_DEFAULT closes every descriptor except the standard streams, which is what close_range does on Linux. The PID stays and the working directory becomes the executable's, as on Linux.
  • Before the replacement it starts the exit and runs the shutdown callbacks (LibcStartExit_nid_postfix, then LibcRunShutdown_nid_postfix), as the Linux and Windows Replace do since fix(libSceSystemService): park guest threads during LoadExec's shutdown as exit does #6237.
  • Descriptor 2 gets the host standard error back before the replacement, as fix(libSceSystemService): give LoadExec's next executable the host standard error #5699 does on Linux, so the next executable writes to the host's standard error and not to where the title redirected descriptor 2. A launch with no argument list keeps the Linux execv path, which macOS has and which already does this.
  • guest_system_exit finds its own executable the same way on macOS, and guest_system_load_exec_standard_error from fix(libSceSystemService): give LoadExec's next executable the host standard error #5699 now runs on macOS too. In docs/dev/debt/libSceSystemService.md the sceSystemServiceLoadExec entry describes macOS instead of listing it as a host that throws, and the LoadExec descriptors entry says how macOS closes the descriptors.

Tested

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 (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 15, 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 6044 core/libs/prx/libSceSystemService/CMakeLists.txt
core/libs/tests/GuestSystemExit.cpp
PR 6447 core/libs/prx/libSceSystemService/Export.cpp
PR 7201 core/libs/prx/libSceSystemService/Export.cpp
core/libs/prx/libSceSystemService/LoadExec.cpp
core/libs/tests/GuestSystemExit.cpp

@milescoviello

Copy link
Copy Markdown
Contributor

A heads-up on ordering with #6237. It changes LoadExec's shutdown on Linux and Windows from LibcRunShutdown_nid_postfix() to a new LibcStartExit_nid_postfix() (what exit does), so a guest thread that calls into a stopped library during it waits for the process to be replaced instead of getting ProcessShutdown back in title code. On Linux, MGS4's launcher and VLC's self-restart abort on main from that.

The macOS Replace here also calls LibcRunShutdown_nid_postfix() just before the spawn. If #6237 lands first, that call would want to become LibcStartExit_nid_postfix() too. If this lands first, I'll change it when I rebase #6237. The two PRs also both edit the sceSystemServiceLoadExec line in docs/dev/debt/libSceSystemService.md.

AI-assisted: yes

@mugurc

mugurc commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Agreed. If #6237 lands first, I will rebase this and the macOS Replace will call LibcStartExit_nid_postfix(). If this lands first, the change is yours in the rebase of #6237. The two edits of the debt line are in different sentences, so either order resolves it by keeping both.

sceSystemServiceLoadExec found its own executable through /proc/self/exe
and replaced the process with fexecve after close_range, none of which
macOS has, so on macOS every call except "exit" threw. On macOS:

- the running executable's path comes from _NSGetExecutablePath, resolved
  with std::filesystem::canonical, as libSceNpEntitlementAccess and
  libSceFont already do;
- the target must be an x86-64 Mach-O executable (MH_MAGIC_64,
  CPU_TYPE_X86_64, MH_EXECUTE) that the user may run;
- macOS has no fexecve, so the process is replaced by path with
  posix_spawn and POSIX_SPAWN_SETEXEC; POSIX_SPAWN_CLOEXEC_DEFAULT closes
  every descriptor but the standard streams, as close_range does on Linux;
- before the replacement the exit starts and the shutdown callbacks run
  (LibcStartExit, then LibcRunShutdown), as on Linux, so guest threads
  that call a stopped library wait instead of unwinding;
- descriptor 2 gets the host standard error back before the replacement,
  as on Linux, and guest_system_load_exec_standard_error runs on macOS.

A launch without arguments keeps the Linux execv path, which macOS has.
guest_system_exit finds its own executable the same way on macOS.
@mugurc
mugurc force-pushed the feat/libscesystemservice-loadexec-macos branch from cc846af to f347461 Compare October 18, 2026 13:59
@github-actions github-actions Bot added build and removed needs rebase Conflicts with main, rebase needed labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 18, 2026
@adriadam10
adriadam10 merged commit ff2fc57 into boykopovar:main Oct 18, 2026
9 of 10 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.

3 participants