Skip to content

libSceSystemService: RetroArch (PPSA99169) stops on sceSystemServiceLoadExec("/app0/picker/picker.bin"), a second executable the title ships #4793

Closed

Description

@marshmll

Title: RetroArch PS5 (PPSA99169, catalog record v1.0.0-beta.1, release ZIP checked against its sha256).
Host: Linux (Fedora 44, kernel 7.2.9), GCC 16.2.1, AMD RX 9060 XT (RADV, Mesa 26.2.4). Run in a rootless Podman container without network.
Build: main ee49621 with the logging fix of #4792 applied (without it the stop leaves no message, see there), Release, -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON. eboot.bin and sce_module/ unwrapped from their SELF containers, relinker --registry.

Where it stops

At start-up, every run:

[libc] unhandled exception 'St13runtime_error' thrown from 0x...
terminate called after throwing an instance of 'std::runtime_error'
  what():  sceSystemServiceLoadExec: executable replacement not implemented

The title's eboot.bin is a front end. Its trace (app0/trace.txt) shows it choosing the picker:

frontend: mode '', test run 0, picker test 0, picker 1, es-de 1, remembered ask, L1 0 -> picker

and it then calls sceSystemServiceLoadExec (gdb at the call, rdi/rsi):

  • path: /app0/picker/picker.bin
  • arguments: two entries, a pointer to an empty string, then NULL

The release ships three executables, all SELF containers whose ELF type is 0xFE10: eboot.bin (38.9 MB), picker/picker.bin (29.6 MB) and es-de/es-de.bin (48.2 MB). (webui/ps5-retroarch-webui.elf is a plain ET_DYN ELF, not passed to LoadExec.)

Why #1525 does not cover it

#1525 implemented sceSystemServiceLoadExec("/app0/eboot.bin", argv) as a restart of the running title (Linux: execv("/proc/self/exe") after LibcRunShutdown; Windows: a new process that waits for the old one) and was closed because no title reached the call. This title reaches it with a different executable, which #1525 left throwing because the relinker converts only the title's executable.

Proposal

  1. Relinker: convert the other executables a title ships, as --module-dir does for modules, for example a repeatable --executable <path> (relative to the module parent) that writes the converted executable next to the converted modules under app0/, with the same libs/ rpath.
  2. sceSystemServiceLoadExec: map the guest path to that converted executable, run the runtime shutdown and replace the process as feat(libSceSystemService): restart the running title in sceSystemServiceLoadExec #1525 did for the restart. A path with no converted executable keeps throwing, with the path in the message.

Questions:

  • An option per executable, or should the relinker convert every 0xFE10 executable it finds under the title directory?
  • arguments here is {"", NULL}. feat(libSceSystemService): restart the running title in sceSystemServiceLoadExec #1525 read the array as NULL-terminated without the program name (as shadPS4 does), which would give the picker one empty argument; the empty entry could also be an empty program name. Is either confirmed?

AI-assisted: yes

Activity

  1. MarcZX10 commented on Oct 13, 2026

    @MarcZX10
    Contributor

    Opened #4925 for the narrow diagnostic gap: unsupported sceSystemServiceLoadExec calls now include the requested path in the exception, with a regression check for /app0/picker/picker.bin.

    This does not implement conversion or process replacement for secondary executables, so #4793 remains open for that behavior.

  2. p3n1cillin commented on Oct 13, 2026

    @p3n1cillin

    Reproduced on Windows 11 (MinGW GCC 15.2.0, main 510a218, catalog record v1.0.0-beta.1 checked against its sha256): the first call is sceSystemServiceLoadExec("/app0/picker/picker.bin", {"", NULL}), read with gdb at the call (rdi, rsi). I'd like to take this on; here is what the title needs and a plan, with one question before I write it.

    The whole chain (source at bead807):

    • eboot.bin → /app0/picker/picker.bin or /app0/es-de/es-de.bin (src/frontend_mode_ps5.cpp)
    • picker → LoadExec(next->next, {next->argument, NULL}), e.g. /app0/eboot.bin with --ps5-mode=retroarch (frontends/picker/picker.cpp:445)
    • ES-DE → LoadExec("/app0/eboot.bin", {..., "--ps5-mode=quit", NULL}) (frontends/es-de/ps5/main_ps5.cpp:180)

    So the argument list holds only arguments (no program name), and both the second executables and the eboot.bin restart with arguments are reached. eboot.bin finds its mode by scanning every argv entry for --ps5-mode=, so whether the console puts the path in argv[0] doesn't decide this title.

    Runtime facts that shape the design: /app0, /data and /download0 resolve against the working directory, and on Windows the library path ($ORIGIN becomes .) and the guest module paths are working-directory relative too. A second converted executable started with the same working directory therefore sees the same app0. The Windows entry stub already turns the host command line into the guest argv (CommandLineToArgvW), so LoadExec arguments can be passed as command-line arguments.

    Plan

    1. Relinker: a repeatable --executable <path> (relative to the module parent, like --module-dir) runs the pipeline for that ELF too and writes it as app0/<path>.exe on Windows (app0/<path>.elf on Linux), beside the original's directory.
    2. sceSystemServiceLoadExec: "exit" as today; /app0/eboot.bin restarts the main executable; any other /app0/... path starts its converted executable if it exists, else throws with the path (as test(libSceSystemService): retain rejected LoadExec paths in errors #4925 reports it). The arguments go on the new process's command line. The old process runs the guest shutdown and exits without waiting, so its direct memory is released before the new one allocates (Windows: CreateProcessW with the inherited working directory; Linux: execv). feat: convert a title's other executables and start them with sceSystemServiceLoadExec #4786 is the eboot.bin half of this and would become part of it.

    Question: a guest module's conversion depends on the executable that needs it: deferred initialization comes from that executable's dynamic section, so picker.bin and eboot.bin can produce different libc.prx.guest.prx files. Should each extra executable get its own converted module tree (for example app0/picker/picker.bin.modules/sce_module/...), or would you rather have one shared tree with initialization decided at run time?

  3. p3n1cillin commented on Oct 13, 2026

    @p3n1cillin

    Implemented in #4786. On the module question: libc.prx.guest.prx comes out byte-identical for eboot.bin, picker.bin and es-de.bin, so the executables share one module tree, and the relinker fails if a module ever converts differently. The extra executables go beside the output (picker.picker.bin.exe) rather than under app0/, because the Windows entry stub sets the working directory to the executable's own directory. The RetroArch picker now starts and hands off in both directions; ES-DE also needs tzname (#5009).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions