Skip to content

fix(relinker): skip a dumper's backup copy of a module beside the module - #7380

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
thuanquang:fix/relinker-skip-backup-copies
Oct 18, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
thuanquang:fix/relinker-skip-backup-copies

Conversation

@thuanquang

@thuanquang thuanquang commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

Some dumpers leave decrypted ELF copies next to each module, for example libX.prx.esbak and libX.prxbak beside an ELF libX.prx. ReadModules takes every ELF file in a module directory, so each copy is converted as another module with the same SONAME, and the conversion stops in GuestModuleBuilder:

FAIL: Ambiguous guest dependency name: libSceFace.prx

Reports:

After reading the modules, ReadModules now skips a module as a backup copy when both of these hold:

  • its file name extends the name of another module in the same directory;
  • both have the same SONAME.

It prints which file it kept:

Skipping backup copy: sce_module/libSceFace.prx.esbak (same SONAME as sce_module/libSceFace.prx)

On main, two modules with the same SONAME always stop the conversion with the error above, so this only changes layouts that fail today. Other cases are unchanged:

Not covered:

  • The online converter (demo/web/archive.mjs, core/relinker/wasm/Bindings.cpp) passes its modules to GuestModuleBuilder without ReadModules, so it still stops on these layouts. Its test needs the wasm build, which I can't run here; it can follow in its own PR.
  • A SELF libX.prx with two ELF backups, libX.prx.esbak and libX.prxbak, still stops: neither backup's name extends the other's. No report shows that layout.

Overlap: #6231 rewrites the same line of docs/user/USAGE.md (the module directory rules), so whichever of the two lands second has to merge the sentences.

Tested

Windows 11 26200, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON:

  • guest_module_directories has three new cases, each run for the Linux and Windows targets:
    • libSceFace.prx, libSceFace.prx.esbak and libSceFace.prxbak, all ELF with SONAME libSceFace.prx: one output, libSceFace.prx.guest.prx, and one skip line for each copy. On main this case exits 2 with FAIL: Ambiguous guest dependency name: libSceFace.prx.
    • libfoo.so.1 and libfoo.so.10, each with its own SONAME: both convert and nothing is skipped, as on main.
    • a SELF libSceFace.prx with an ELF libSceFace.prx.esbak: libSceFace.prx.esbak.guest.prx, no skip line, as on main.
  • The relinker tests (guest_module_directories, guest_module_dir, guest_module_identity, guest_needed_modules, guest_needed_nested_modules, input_preservation, windows_import_modules, relinker_executables and the rest) pass.
  • Full ctest --parallel 4 --timeout 120 on main 1e2d9b369 with this change: 1462 tests. The only failures are three AMD GPU tests that also fail without it on this machine: agc_driver_lds_mixed_width_atomics (fix(shader): release the LDS lock with an exchange and drop fences inside it #6976), agc_driver_lds_lock_budget (AGC: AMD device-backed LDS lock-budget case returns half of expected sum #7344) and agc_driver_layered_volume_vertex_no_gpl (AMD Windows: identical AGC executables invert GPL pass/fail results after relocation #4768).
  • The PR's CI relinker job ran the same tests on ubuntu-latest (g++), macos-latest (clang++, arm64 and x86_64), windows-latest (g++) and with ASan, and passed.
  • python tools/check_conventions.py --base origin/main with this title and body passes.
  • Not tested:
    • Furi's and Dreaming Sarah's dumps, which I don't have;
    • the web converter.

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
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: PPSA06147 Furi [PPSA06147] #7020 (relinking Furi without moving files out first)
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6231 docs/user/USAGE.md L11

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 3998 docs/user/USAGE.md
PR 4891 docs/user/USAGE.md
PR 5021 docs/user/USAGE.md
PR 5925 docs/user/USAGE.md
PR 6111 docs/user/USAGE.md
PR 6788 docs/user/USAGE.md
PR 6924 docs/user/USAGE.md
PR 6946 docs/user/USAGE.md
PR 6947 docs/user/USAGE.md
PR 7332 docs/user/USAGE.md

@simonbbby

Copy link
Copy Markdown
Contributor

Independent boundary check of exact head a7499e072f1e796e8d0172dcc76bc488174f4586 on Ubuntu 24.04 x86-64, GCC 13.3, Release relinker-only. Generated ELF/module inputs only; no Furi/Dreaming Sarah assets or title execution.

The stock test_guest_module_directories.py passes. An additional real-CLI matrix passes 46/46 conversions on this head, across Linux and Windows output targets:

  • All six file-creation orders of canonical .prx + .prx.esbak + .prxbak: only the canonical module is emitted.
  • All six creation orders of .prx + .prxbak + .prxbak.esbak: only the shortest/canonical module is emitted, even when a candidate prefix is itself a backup.
  • Prefix-related names with the same SONAME in different directories (sce_module and prx): still refuse with Ambiguous guest dependency name, with no output/app0 tree.
  • Unrelated filenames sharing a SONAME: still refuse.
  • Prefix-related filenames with different SONAMEs, and prefix-related files without DT_SONAME: both modules are emitted; neither is silently suppressed.
  • A single backup without its canonical file is emitted. Two sibling backups (.prx.esbak / .prxbak) without the canonical file still refuse, matching the stated limitation.

Creation order is permuted wherever there is more than one file. Every original executable/module hash remains unchanged through all controls.

To check that these assertions discriminate the guards, I rebuilt only the changed CLI source against the otherwise identical tree:

Production variant Matrix result
Parent NativeModules.cpp 22/46; the 24 canonical/chain backup cases reproduce the ambiguous-name failure
PR source 46/46
Only same-directory guard removed 42/46; the four cross-directory cases wrongly succeed after discarding one module
Only SONAME guard removed 38/46; the eight different/no-SONAME cases wrongly discard one module

The isolated source was restored afterward and is clean. These are Linux-host conversion/output checks, including PE generation, not Windows-host execution, actual title relinks, or the web converter path.

@oneandonlydean
oneandonlydean merged commit bc25d89 into boykopovar:main Oct 18, 2026
13 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