You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Linux: a bundled libc.prx loader shim (ps5-native-app-boilerplate) binds libc imports to placeholder exports; DOOM PPSA99666 finds no WAD #1497
On Linux, imports bound to a bundled sce_module/libc.prx built by ps5-native-app-boilerplate resolve to placeholder functions that return 0. DOOM Shareware PPSA99666 v1.1.1 (lowbit/ps5-doom, GPL-2.0) ships this module (built at boilerplate commit b1315a9, per its THIRD_PARTY_NOTICES.md). Its snprintf/vsnprintf write nothing, so the title never builds a WAD path, finds no game and exits with code 0. #684 fixed the same interception for Windows ("A title's bundled libc.prx shim can then intercept imports ... with its placeholder exports"); its description notes that Linux keeps the existing behaviour.
What the module is
The boilerplate calls it a "loader shim": the PS5 loader needs a libc.prx next to a native title. It is generated by tooling/native/libc_builder.cpp from tooling/native/runtime/api-surface.txt (2572 API records: NID, binding, type, size). In build_export_symbols, every function export (type == 2) gets the address 0x30, 0x40 (_longjmp, _setjmp) or 0x50. In the shipped module these are xor eax,eax; ret and, at 0x50, pxor xmm0,xmm0; xor eax,eax; ret. Since the title works on hardware, the console presumably serves these imports from the system libc. I haven't verified that on a console.
Relinking eboot.bin (unwrapped from the fake-signed SELF) with the bundled libc.prx binds 70 of the title's imports to the guest module (#guest), among them snprintf, vsnprintf, strlen, memcpy, malloc, fopen, qsort and strcasecmp.
plat_alert builds its message with snprintf(request.message, …, "DOOM: %s", message). AnyPS5 prints [notification] with an empty message, and the request layout matches Notification.cpp (message at offset 45).
/download0/doom.log (written through vsnprintf) stays empty.
The WAD scan lists /app0/wads correctly: getdents64 returns ., .. and doom1.wad, and the guest records match the FreeBSD 11 layout the title parses. But the title never opens doom1.wad. Instead it opens a bare /download0 about 30 times, the result of snprintf("%s/%s", …) that writes nothing.
Workaround and result
Relinking with --exclude-sce-module libc.prx binds those imports to AnyPS5's libraries. The title then finds doom1.wad, shows its launcher and reaches E1M1 on Linux. Moving and firing work (ammo 50 → 48).
Performance (Ubuntu 24.04 container on a Ryzen 9 5900X, Xvfb, no GPU; Mesa llvmpipe for Vulkan):
The launcher presents at 59.94 FPS.
In game it holds 35.00 FPS, DOOM's tic rate, over a 30 s run while moving and turning (window-title counter).
Should the Linux relinker treat imports whose only provider is a module like this the way #684 does on Windows, binding each qualified import to its declared system module? Or should titles that ship this shim be relinked with --exclude-sce-module libc.prx? The flag is deprecated and documented as unsuitable for normal use. Any title built with ps5-native-app-boilerplate should hit the same problem.
Root cause on Linux: GuestModuleBuilder renames every executable import whose NID a guest module exports to its guest-suffixed name (GuestModuleBuilder.cpp:180), whatever module the import declares. DOOM PPSA99666 declares snprintf, vsnprintf, strlen, memcpy, malloc and fopen from libSceLibcInternal (#G#H, module 7) and declares nothing from libc; the bundled shim exports the same NIDs, so 71 imports become <nid>#guest (eLdDw6l0-bU#guest is snprintf) and bind to its placeholders. On Windows the same build binds by the declared module (WindowsEntryStubBuilder) and opens /app0/wads/doom1.wad (checked with main + #1278, #1279, #1307; DOOM built from source at v1.1.1, byte-identical to the release).
Plan: on Linux, rename an import to the guest-suffixed name only when the module it declares is that guest module, as Windows binds; imports without import-module metadata keep the current behaviour, so a title's own libc.prx imported from libc still binds to it (7103f39, test_guest_host_libc.py). I'm working on it.
(Edited: my first version blamed the DT_NEEDED order; the rename is what binds these imports to the guest module.)
PR #1529 ("fix(relinker): keep Linux imports declared from host modules off guest exports") directly addresses this issue. The PR fixes the problem where on Linux, GuestModuleBuilder was renaming every import whose NID a guest module exports to its guest-suffixed name, regardless of which module the import declares.
The fix adds logic to check if an import is declared from a host module vs. a guest module, and only binds to guest exports when appropriate. This should resolve the DOOM PPSA99666 issue where libc imports were being bound to placeholder functions in the bundled libc.prx loader shim.
The PR has been reviewed and approved, pending merge by maintainers.
Activity
hikazey commentedon Oct 8, 2026
johnmaia commentedon Oct 8, 2026
Root cause on Linux:
GuestModuleBuilderrenames every executable import whose NID a guest module exports to its guest-suffixed name (GuestModuleBuilder.cpp:180), whatever module the import declares. DOOM PPSA99666 declaressnprintf,vsnprintf,strlen,memcpy,mallocandfopenfromlibSceLibcInternal(#G#H, module 7) and declares nothing fromlibc; the bundled shim exports the same NIDs, so 71 imports become<nid>#guest(eLdDw6l0-bU#guestissnprintf) and bind to its placeholders. On Windows the same build binds by the declared module (WindowsEntryStubBuilder) and opens/app0/wads/doom1.wad(checked withmain+ #1278, #1279, #1307; DOOM built from source at v1.1.1, byte-identical to the release).Plan: on Linux, rename an import to the guest-suffixed name only when the module it declares is that guest module, as Windows binds; imports without import-module metadata keep the current behaviour, so a title's own
libc.prximported fromlibcstill binds to it (7103f39,test_guest_host_libc.py). I'm working on it.(Edited: my first version blamed the
DT_NEEDEDorder; the rename is what binds these imports to the guest module.)klosixgaming-arch commentedon Oct 8, 2026
PR #1529 ("fix(relinker): keep Linux imports declared from host modules off guest exports") directly addresses this issue. The PR fixes the problem where on Linux,
GuestModuleBuilderwas renaming every import whose NID a guest module exports to its guest-suffixed name, regardless of which module the import declares.The fix adds logic to check if an import is declared from a host module vs. a guest module, and only binds to guest exports when appropriate. This should resolve the DOOM PPSA99666 issue where libc imports were being bound to placeholder functions in the bundled libc.prx loader shim.
The PR has been reviewed and approved, pending merge by maintainers.
fix(relinker): keep Linux imports declared from host modules off gues…
fix(relinker): keep Linux imports declared from host modules off gues…
fix(relinker): keep Linux imports declared from host modules off gues…