Skip to content

fix(relinker): find the PLT before the code in PS4 modules - #6354

Open
SandstromPL wants to merge 2 commits into
boykopovar:mainfrom
SandstromPL:fix/relinker-plt-before-code
Open

SandstromPL wants to merge 2 commits into
boykopovar:mainfrom
SandstromPL:fix/relinker-plt-before-code

Conversation

@SandstromPL

@SandstromPL SandstromPL commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Until Dawn (CUSA02636) bundles libSceFios2.prx and libc.prx (01.00). Their conversion stops in the code analysis, for libSceFios2.prx with:

PLT not found after the last unwind function (offset 0x3d033)

SuperCatElvis reported it on #3093 as stop 9 of the title's conversion, on main d92a8da. They reached it after #5138 and scratch fixes for the other stops before it. Both modules have their PLT at 0xc8, at the start of the text segment, before the code. In libSceFios2.prx the last unwind function ends at 0x3d033, and what follows is the module's DT_FINI routine at 0x3d040, 0x7d bytes without an unwind entry, then read-only data that includes the zlib CRC-32 table. libc.prx has the same layout: its DT_FINI routine at 0x828a0 follows its last unwind function [0x82890, 0x82896), and about 213 KiB of read-only data follow the routine.

#3142 (Get-fix's commit, carried by MarcZX100) stopped the pipeline's syscall and call-site scans of executables at the end of the PLT that follows the last unwind function, so that the read-only data after the code is not decoded as code. My #5195 moved that search into FindSceCodeEnd and applied the same end to the code analysis of the Windows and macOS outputs, for executables and guest modules. The search only looks after the last unwind function, so these two modules fail there.

When no PLT follows the last unwind function, FindSceCodeEnd now also looks before it:

  • It looks for the same PLT0 (push [GOT+8]; jmp [GOT+16]) anywhere from the start of the segment up to the end of the last unwind function.
  • That PLT (PLT0 plus 16 bytes per DT_SCE_PLTRELSZ entry) must not overlap an unwind function. Otherwise the conversion fails with PLT overlaps an unwind function.
  • The extent then ends at the end of the last unwind function, so the data after it stays out. Without that, stop 10 follows: overlapping instruction boundaries (offset 0x3d1fe) inside the CRC-32 table.
  • A PLT found in neither place fails with PLT not found before or after the unwind functions.

A PLT after the code, as in the executables converted so far, is found first, with the same result as before. That includes #5195's case of code without unwind entries between the last function and the PLT.

When the PLT comes first, there is no PLT after the code to bound the extent. The end of the last unwind function is then the only bound that doesn't guess the length of code without an unwind entry. #3093 ruled this bound out for the PLT-after layout because code without unwind entries can sit before the PLT, and that layout is unchanged. The cost: when the PLT comes before the code, code after the last unwind function that has no unwind entry is not analysed, and nothing reports it. An entry point, a branch target or an fs: access found there is dropped without an error; the debt entry records this.

  • In both modules this is the DT_FINI routine, which the Windows and macOS outputs call when the program exits (WindowsGuestStartup::Finalize, and the fini stub that MacOsImage.cpp registers with __cxa_atexit). The code analysis starts from DT_FINI only inside the extent, so both routines are dropped, and an fs: access in one would run unchanged on the host.
  • For these two modules this should lose nothing. SuperCatElvis reports that the libSceFios2.prx routine makes no fs: access, and that the libc.prx one reads the pointer at 0xbc880 and calls 0x2fa50 through it; I've asked whether that one has an fs: access. The code analysis rewrites no indirect call: the relocation sets the pointer, and 0x2fa50 is already a root as a function symbol (symbol 1411 in SuperCatElvis's table), and with fix(relinker): accept matching defined relative symbols #6357 also as a relocation target.
  • With the same trimming, their run got past stops 9 and 10. Their full conversion then completed only after one more scratch change, for stop 11: libc.prx's RELATIVE relocation with a symbol, which MarcZX100's fix(relinker): accept matching defined relative symbols #6357 handles.
  • Both PLTs end before the first unwind function, so the overlap check passes: [0xc8, 0x5b8) before 0x5c0 in libc.prx, and [0xc8, 0x5d8) before 0x5e0 in libSceFios2.prx, each followed by 8 zero bytes.

Tested

Ubuntu 24.04, GCC 13.3, Release with BUILD_TESTING=ON, on main 71bd287. Locally the libusb submodule isn't checked out, so I removed its two lines from CMakeLists.txt. libSceUsbd then fails to compile, and ninja -k 0 built everything that does not depend on it.

sce_code_extent (relinker/tests/test_sce_code_extent.py) can now put the PLT before the code: at 0x180, before the unwind function at 0x200, with the data right after the function. It can also add a second unwind function before the PLT, and put an unsupported TLS instruction in the function. As in #5195, the fixture is converted as the main executable (--skip-sce-module), so each case goes through both callers. No case converts an sce_module module. A module like libSceFios2.prx reaches the same FindSceCodeEnd only through the code analysis, which the Windows and macOS guest module writers run on the module's own bytes and program headers.

Case Result
PLT before the code (Windows and macOS) converts, with the import's call site in the PLT
the same, with a lea in the function pointing into the data after it, at fs-prefixed overlapping bytes (the shape of stop 10) converts; the bytes after the function are not analysed
the same, with the ELF entry point right after the function, on an unsupported TLS instruction converts; the entry point after the code is not analysed (the trade-off above)
the same, with another unwind function before the PLT converts
the same, with an unsupported TLS instruction in the function after the PLT throws Unsupported ... guest TLS instruction, so the function after the PLT is still analysed
a PLT ending where the function begins, and a PLT starting where an earlier function ends convert (touching is not overlapping)
a PLT over the start of the function, and over its end throws PLT overlaps an unwind function
no PLT throws PLT not found before or after the unwind functions (renamed)

On main all 15 cases fail with PLT not found after the last unwind function. With the change they pass, 5 runs out of 5. The test also fails if:

  • the overlap check is removed, or either of its comparisons is made inclusive;
  • the backward search stops 16 bytes before the end of the last function, which misses a PLT straddling it;
  • the extent runs to the end of the segment, so the data is scanned (Cannot decode instruction);
  • the extent ends at the PLT, so the function after it is cut off (invalid function range).

The registry check in the test now compares call sites case-insensitively: the relinker writes 0x1F0, and earlier PLT addresses happened to have no hex letters.

Other checks:

@SuperCatElvis, thanks for the answers on #3093. If you can, could you swap this in for your stop-9 change and check that both modules get past stops 9 and 10?

Checklist

🤖 Generated with Claude Code

This was referenced Oct 15, 2026
@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 5021 docs/dev/debt/relinker.md
PR 6328 docs/dev/debt/relinker.md
PR 6977 core/relinker/relinker/src/analysis/SceCodeExtent.cpp
core/relinker/relinker/tests/test_sce_code_extent.py
docs/dev/debt/relinker.md
PR 7173 docs/dev/debt/relinker.md
PR 7531 docs/dev/debt/relinker.md

FindSceCodeEnd looked for the PLT only after the last function of the
unwind table and failed with "PLT not found after the last unwind
function" otherwise. The libSceFios2.prx and libc.prx that Until Dawn
(CUSA02636) bundles have their PLT at 0xc8, at the start of the text
segment, before the code. In both, the last unwind function is
followed by the module's DT_FINI routine, which has no unwind entry,
and then by read-only data.

When no PLT follows the last unwind function, the search now also looks
before it. A PLT found there must not overlap an unwind function, and
the code extent then ends at the last unwind function, so what follows
it stays out of the scans and the code analysis, including code
without an unwind entry such as the DT_FINI routine of both modules
(recorded in the debt file). A missing PLT now fails with "PLT not
found before or after the unwind functions".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the docs/dev/debt/relinker.md conflict with boykopovar#5994: keep its
sce_module syscall-scan entry, which now also stops at the end of the
last unwind function when the PLT comes before the code (it calls the
same FindSceCodeEnd), and its /libexec/ld-elf.so.1 start in the code
analysis entry, with this PR's end for a PLT before the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@DProkos

DProkos commented Oct 18, 2026

Copy link
Copy Markdown

Windows check of 8156186 merged onto current main (48d61f53b, clean merge).

Windows 11 Pro 26200, WinLibs GCC 15.2.0 r7 UCRT, Ninja, Release, ANYPS5_RELINKER_ONLY=ON, BUILD_TESTING=ON, Python 3.11.0.

  • sce_code_extent: passed.
  • Full relinker suite: 196/196 passed.

Negative control: with SceCodeExtent.cpp restored to main and the updated test kept, sce_code_extent fails on sce-plt-missing (Windows output). main still reports the old message, PLT not found after the last unwind function (offset 0x210), where the test expects PLT not found before or after the unwind functions.

Not run: titles or homebrew.

AI-assisted: yes

This branch has not been deployed

No deployments
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.

2 participants