Skip to content

fix(relinker): skip zero-return guest libc exports for internal libc imports - #2705

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
DariusHQ:fix/windows-guest-libc-placeholders
Oct 11, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
DariusHQ:fix/windows-guest-libc-placeholders

Conversation

@DariusHQ

Copy link
Copy Markdown
Contributor

What

On Windows, a libSceLibcInternal import that no native library exports falls back to the title's own libc.prx (#1454). The libc.prx loader shim that ps5-native-app-boilerplate builds into homebrew titles (#1497) exports 1874 functions at three addresses: 0x30 and 0x40 hold xor eax, eax; ret, and 0x50, where 1872 of them point, holds pxor xmm0, xmm0; xor eax, eax; ret. A missing function then binds to an export that returns 0, and the title starts instead of stopping with unresolved ELF import. The review of #1454 relied on "an absent symbol still fails startup", which does not hold with this shim.

The Windows guest module writer now records the exported functions whose code is one of these two sequences (GuestRuntime::ZeroReturnExports), and the entry stub leaves the guest libc.prx out of the search for those names. A guest export with code of its own still provides a missing libSceLibcInternal function, as #1454 intended, and native libraries keep their priority. Guest modules' own imports go through the same search, so they are covered too. On Linux the shim intercepts imports through the guest export renaming that #1497 describes and #1529 fixes; this change is Windows only.

The problem stayed hidden while a fake-signed sce_module/libc.prx was skipped without a message. Since #2605 the module has to be unwrapped, and then these imports bind to the shim on Windows.

Homebrew catalog

ps5-homebrew-catalog at 4db487af: 51 release ZIPs downloaded with a matching sha256, eboot.bin and sce_module/libc.prx unwrapped from their fSELF containers, relinked with --windows --registry on main b5f5362, tools/import_audit.py with --modules and the libraries of the same build. 42 titles relink; 7 stop on Forbidden syscall instruction (#2136) and 2 ship two eboot.bin.

  • 39 of the 42 ship this shim. The other three (PPSA99109, PPSA99110, PPSA99920) ship a libc.prx with code behind its exports.
  • In 36 of the 39, the shim exports at 0x50 some of the libSceLibcInternal imports that import_audit.py reports as absent: 253 imports in all, for example atol, rewind and __isinf in Capy Mobile (PPSA92280) and localeconv in SymphonyStation5 (PPSA17641).
  • The 41 titles with an absent import or a missing library were started once relinked with main and once with this branch. Each stops in the entry stub before any of its code runs. On main the stop is the first absent import that the shim does not export; with this change it is the first absent import. 20 of the 41 stops change. ProsperoRadio (PPSA99001) stops on its missing libSceOpusCeltDec.prx either way, and DOOM (PPSA99666) has no absent import and was not started.
Title main with this change
PPSA17641 SymphonyStation5 _umtx_op (04AjkP0jO9U) localeconv (0hlfW1O4Aa4)
PPSA59322 ccleste utime (1nTKA7pN1jw) ctime (0uAUs3hYuG4)
PPSA74126 Stremio Plus _fstatfs (17Mfe1B3X6U) nextafterf (3m2ro+Di+Ck)
PPSA76677 RommPS utime (1nTKA7pN1jw) ctime (0uAUs3hYuG4)
PPSA92280 Capy Mobile utime (1nTKA7pN1jw) rewind (3QIPIh-GDjw)
PPSA97358 XPSemu dup (iiQjzvfWDq0) __fpclassifyd (qlWiRfOJx1A)
PPSA97510 PSXS5 utime (1nTKA7pN1jw) atoll (fLcU5G6Qrjs)
PPSA98510 SwanStationPS5 utime (1nTKA7pN1jw) atoll (fLcU5G6Qrjs)
PPSA99002 ProsperoLight _umtx_op (04AjkP0jO9U) flockfile (hGljHZEfF0U)
PPSA99003 ProsperoTV arc4random_buf (n1KOc0lTUEs) __isinf (V02oFv+-JzA)
PPSA99006 ProsperoPuzzles _umtx_op (04AjkP0jO9U) atol (+my9jdHCMIQ)
PPSA99039 EVO Player pthread_set_name_np (oxMp8uPqa+U) atoll (fLcU5G6Qrjs)
PPSA99169 RetroArch alarm (DQXJraCc1rA) __isinf (V02oFv+-JzA)
PPSA99505 Jelly5 sceAudioOutExGetMonitorInfo (PhnNN0UEV6Y) __isinf (V02oFv+-JzA)
PPSA99515 Emby5 sceAudioOutExGetMonitorInfo (PhnNN0UEV6Y) __isinf (V02oFv+-JzA)
PPSA99600 Cloud Play getifaddrs (j-gWL6wDros) localeconv (0hlfW1O4Aa4)
PPSA99728 OpenRCT2 Native Port dup2 (wdUufa9g-D8) llroundf (eQhBFnTOp40)
PPSA99808 PICO-8 for PS5 freeifaddrs (N2OjwJJGjeQ) __udivti3 (802pFCwC9w0)
PPSA99810 PSBox Cloud Gaming getifaddrs (j-gWL6wDros) atol (+my9jdHCMIQ)
PPSA99997 Nuvio PS5 sceAudioOutSysGetHdmiMonitorInfo (Tf9-yOJwF-A) atoll (fLcU5G6Qrjs)

19 of these titles are in the Linux table of 2026-10-10 in #1578, and for each of them the new stop is the one listed there.

Tested

Windows 11 Pro (26200), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, full build with BUILD_TESTING=ON on b5f5362:

  • ctest 706/706.
  • windows_import_modules gets three cases. A guest libc.prx whose shared export is xor eax, eax; ret or pxor xmm0, xmm0; xor eax, eax; ret, with no native provider, must stop with unresolved ELF import shared. With a native libSceLibcInternal.prx export, the native one still wins. Without the entry stub change, both zero-return cases start and exit with 0, the export's return value. A mutant that drops the pxor sequence and one that treats every export as zero-return both fail the test; the second stops the existing guest-only case.
  • tools/check_conventions.py --base upstream/main, tools/check_test_registration.py and tools/pr_overlap.py --branch pass.

Linux and macOS not run here. The new cases execute only on Windows, like the existing ones in check_internal_guest_libc.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

@github-actions github-actions Bot added the intake paused Closed while PR intake is limited to authors with at least 10 PRs merged into main label Oct 10, 2026
@github-actions

Copy link
Copy Markdown

Thank you for your interest in the project! We are sorry, but we cannot accept more PRs while we work through the existing backlog. This PR has been automatically closed and labeled "intake paused". This is not a judgment of the quality of your changes.

@github-actions github-actions Bot closed this Oct 10, 2026
@Amoenus Amoenus mentioned this pull request Oct 10, 2026
6 tasks done
@Amoenus

Amoenus commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Independent Windows check of the zero-return libc fallback fix on
Sonic Unleashed Recompiled (PPSA99902), content version 01.003.000.

Replayed head ceae49c onto its base
b5f5362. Local validation commit:
d22ac9d105aab40c3296372facbe22c15ee65a7d.
Windows 11 Enterprise build 26200, WinLibs GCC 15.2.0 r7, Release,
BUILD_TESTING=ON. Relevant runtime libraries kept at the baseline for this test.

  • windows_import_modules passed 5/5 (ctest --repeat until-fail:5).
  • Negative control: the modified test against the baseline relinker fails in
    check_internal_guest_libc. The zero-return fixture reaches the ELF entry point
    and exits 0 instead of reporting an unresolved import. With this fix, it passes.
  • check_conventions.py passed against the recorded baseline.
  • Default and unused-filter=2 real-title conversions both succeed.
  • Baseline title startup stops at iiQjzvfWDq0 (dup) from libkernel.prx.
  • With this fix, both conversions stop at hGljHZEfF0U (flockfile) from
    libSceLibcInternal.prx, exit 0xC0000139, before guest entry.
  • Adding feat(libkernel): implement dup and dup2 #2478's runtime libraries leaves the same flockfile stop.

The actual guest libc's flockfile export is at PE RVA 0x10050 with bytes
66 0f ef c0 31 c0 c3 (pxor xmm0,xmm0; xor eax,eax; ret). It shares that code
with nine other native-library-absent imports in this title. All ten symbol names
were checked by hashing them back to the NID. The module has 1874 named exports
matching the zero-return sequences (2566 named exports total).

This confirms the rejection behavior on another title; it does not unblock that
title or establish the semantics of every zero-return function. No guest call to
flockfile was observed: the failure occurs in import resolution.
No full ctest suite, Linux run or gameplay is claimed. AI-assisted investigation.

Follow-up (PPSA99902): a controlled local startup stack now provides an actual executed example of the zero-return fallback problem. The ELF's getprogname import (7Psx1DlAyE4) binds to the audited guest shim; the call's null result is cached and then passed to strcmp("Project-A.exe", nullptr). The instruction/relocation trace and an isolated export call confirm that return. Changing only the runtime libc-internal library to include existing #2218's pointer-returning implementation passed this fault in five runs and reached Vulkan device/swapchain creation; restoring the old library reproduced it. Next stop is an explicit shader-registration refusal. Full evidence and comparison. This uses a conditional local stack, not unchanged main; #2705 itself was not reapplied for this comparison. No rendering/gameplay claim or new PR.

Recheck after merge: PPSA99902 current-main relinker (2026-10-11)

Rechecked the merged protection on Windows 11 / RTX 5080 with the relinker source exactly matching pinned main f60923bf, built in our experimental integration dff875a7. This uses local/pending runtime prerequisites, not an unmodified-main startup claim.

Fresh default conversion of the original PPSA99902 ELF and original guest libc succeeds. Five launches stop before guest entry at FAIL: unresolved ELF import H-QeERgWuTM from libSceLibcInternal.prx (putc_unlocked), exit 0xC0000139, 0.116–0.153 s, no timeout. Export audit of all 122 built system libraries finds no host provider; both prior and fresh guest modules export the NID. The original guest shim's zero-return code was independently checked earlier and its source hash remains unchanged.

With exactly the same refreshed libraries but the prior conversion/guest module, the title still reaches the DCC multisample refusal in 31.986 s. This supports the intended protection: the older conversion's graphics progress depended on a fallback that is not a working libc implementation. No guest invocation of putc_unlocked is observed.

Full build/122 libraries; 823 full-suite passes and 5 documented display/no-Vulkan skips; 73 affected/new groups ×5 = 365 passes, no skips. Relinker/import tests are included in the full suite; no local relinker modification. Every replay restores the exact 123-library baseline population, guest module and original input hashes. No rendering/gameplay or console/Linux equivalence claim. Missing unlocked I/O is already discussed on #1578 and closed unmerged #1765; this is current validation, not a new PR or duplicate issue. AI-assisted investigation: Codex.

…imports

Since boykopovar#1454, a libSceLibcInternal import that no native library exports
on Windows falls back to the title's own libc.prx. The loader shim that
ps5-native-app-boilerplate builds into homebrew titles exports 1874
functions at three addresses whose code is `xor eax, eax; ret` or
`pxor xmm0, xmm0; xor eax, eax; ret`. Such an import then binds to a
function that returns 0, and the title starts instead of stopping with
`unresolved ELF import`.

The Windows guest module writer records the exported functions whose
code is one of these two sequences, and the entry stub leaves the guest
libc.prx out of the search for those names. A guest export with code of
its own still provides a missing libSceLibcInternal function, and native
libraries keep their priority.
@adriadam10

Copy link
Copy Markdown
Collaborator

This conflicted with main after UnmatchedImportModules was added to GuestRuntime. Rebased onto current main keeping both fields, no other change.

Checked on Linux on the rebased tree (core/relinker build + ctest, windows_import_modules and the other relinker/import tests green). The new cases only run on Windows, so they rest on the relinker (windows-latest) job and @Amoenus's independent run.

Merging once CI is green on the rebased head.

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.

4 participants