Skip to content

fix(relinker): reject a needed module name that several bundled modules match only by extension - #11479

Open
roboterspinne wants to merge 3 commits into
boykopovar:mainfrom
roboterspinne:fix/relinker-ambiguous-needed-module
Open

roboterspinne wants to merge 3 commits into
boykopovar:mainfrom
roboterspinne:fix/relinker-ambiguous-needed-module

Conversation

@roboterspinne

@roboterspinne roboterspinne commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

A needed name with no exact filename match (on Windows, no case-folded match) is aliased to a bundled module whose name differs only in its .prx, .sprx, .suprx or .debug_prx extension (1a9ef2f; .suprx since 0a324d8), and when two discovered modules share that stem, std::find_if in GuestModuleBuilder::Build takes the first module in the order they are passed in (the CLI sorts them by path) without checking for a second. On untouched 4017310 with --module-dir Media, Media/needed.prx and Media/needed.sprx and an executable needing needed.debug_prx converted with exit 0 and the Linux executable's DT_NEEDED named only $ORIGIN/app0/Media/needed.prx.guest.prx; with Media/needed.sprx and Media/needed.suprx and needed.prx it named only needed.sprx.guest.prx, so the started module followed byte order of the paths. 1a9ef2f's message calls two candidates for one needed name ambiguous, but only the recursive outside-directory search threw Ambiguous needed module, and 41126bf removed it; the other resolution paths in Build reject ambiguity (Ambiguous guest dependency name, Ambiguous guest module identity).

  • Rule. Next to neededAliases, Build records a second module with the same ModuleStem (Windows folding as before) for each aliased name. In the alias loop, after the existing if (guestNames.contains(name)) continue;, it throws Ambiguous needed module: <name> (<first path> and <second path>); the CLI exits 2 and writes no output. A module whose filename or SONAME equals the needed name still claims it, a single stem match is still aliased, and guest-module dependencies, which never use the stem alias, are unchanged. The rule covers every scanned module directory (sce_module, sce_modules, prx and --module-dir) and the modules the online converter passes.
  • Tests. test_guest_needed_modules.py gets two cases for Linux and --windows, after debug-name-ambiguous (whose first/ is not a module directory and is unchanged): sce_module/needed.prx with sce_module/needed.sprx carrying SONAME needed.debug_prx must still convert, and the same pair without the SONAME, needing needed.debug_prx, must exit 2 with the message and no output.
  • Overlap. pr_overlap.py reports no shared lines with open pull requests that merge with main. It cannot compare this branch with fix(relinker): analyze referenced code beyond SCE scan boundaries #10861 (GuestModuleBuilder.cpp), because that pull request does not merge with main. Checked by hand: fix(relinker): analyze referenced code beyond SCE scan boundaries #10861 changes only the syscall scan of the code segments in Build (lines 403 to 433 on main, 412 to 442 here), not the neededAliases lookup and alias loop that this PR changes (lines 112 to 180 here, 112 to 171 on main). A trial merge of this branch with fix(relinker): analyze referenced code beyond SCE scan boundaries #10861 conflicts in the same three files and regions as main with fix(relinker): analyze referenced code beyond SCE scan boundaries #10861 (GuestModuleBuilder.cpp, RelinkerPipeline.cpp and docs/dev/debt/relinker.md). In GuestModuleBuilder.cpp both conflicts are in the unwind range setup and the masked syscall scan that fix(relinker): mask embedded data tables in inter-FDE gaps #8076 added (lines 404 to 408 and 414 to 423 on main, 413 to 417 and 423 to 432 here). Apart from the conflict labels, the conflicted GuestModuleBuilder.cpp of the two trial merges differs only in the lines of this PR, which merge cleanly, and the other two conflicted files are identical. fix(relinker): skip backup copies of guest modules in the online converter too #10997 also edits GuestModuleBuilder.cpp; it merges with this branch without conflict and appends RemoveBackupCopies after Build (which ends at line 499 here), far from those lines.

Tested

Windows 11, GCC 15.2.0 (WinLibs UCRT), relinker-only Release build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON); synthetic fixtures only, no title or homebrew.

  • Counter-check: guest_needed_modules with the new cases on untouched 4017310 fails at the Linux same-stem case with AssertionError: (<stdout>, ''), return code 0 and output written, after the SONAME control passed. Run directly with the same inputs, untouched Linux and --windows both exit 0 for both cases; the untouched Windows program runs and exits 42. With the change the same-stem case exits 2 for both targets with no output, the SONAME case still exits 0 and its Windows program exits 42, and the two --module-dir Media inputs above exit 2, as does sce_module/needed.prx with Media/needed.sprx under --module-dir Media for both targets (untouched: exit 0, Linux DT_NEEDED lists both). With the change guest_needed_modules passed in 3.5 s on 3a2c51a, which has no relinker changes since 4017310.
  • Merged current main (265f77b) into the branch without conflicts (ab4eb25; the earlier merge of 518d36e is f15e985); since 3a2c51a main changed both files of this PR (fix(relinker): mask embedded data tables in inter-FDE gaps #8076, fix(relinker): validate every entry of the Linux run path #10607, test(relinker): decode relinker output as UTF-8 in the path-checking tests #9513, fix(relinker): reject non-TLS guest relocations bound to a guest TLS export #10732), none of them in lines of this PR, and neither file since 518d36e; tools/check_main_sync.py --base upstream/main reports 0 behind and a clean merge at 265f77b. On ab4eb25, ctest -R 'guest_module_directories|needed|guest_|module' -j 6 (run next to a second build): 34 tests passed, among them guest_needed_modules (5.4 s), guest_module_dir (8.0 s), guest_module_directories (3.3 s), guest_module_identity (8.7 s), guest_needed_nested_modules (5.0 s), guest_deferred_dependencies (1.3 s), guest_runpath (3.3 s) and linux_guest_named_imports (1.1 s).
  • Full suite on ab4eb25: ctest --test-dir build-relinker -j 6: 309 tests, 306 passed, long_paths skipped; failures known on this machine on main as well: select_tests (Windows symlink fixture) and patch_advisor_unit (path with spaces, which open test(tools): compare quoted and resolved paths in the patch advisor test #10667 fixes); both failed again alone.
  • python tools/check_conventions.py --base upstream/main with this title and description passed on ab4eb25.
  • Not tested on Linux or macOS; CI runs the tests on all three hosts.

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: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

…es match only by extension

A needed name with no exact filename match was aliased to the first
discovered module whose name differed only in its .prx, .sprx, .suprx
or .debug_prx extension, so with two such modules the pick followed
path order. GuestModuleBuilder now throws "Ambiguous needed module"
naming both paths, as the other resolution paths in Build already do
for ambiguous dependency names and module identities. Such an input
now fails without an output instead of giving the name to one module
by path order. A module whose filename or SONAME equals the needed
name still wins, and a single extension match is still aliased.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Linux, Windows, macOS arm64/x86_64 and relinker ASan checks pass, and the current merge preview is clean. The branch is now 80 commits behind current main, though, and the repository requires pull request branches to stay up to date. Please rebase onto current main and rerun the checks; I’ll recheck the updated head.

@THEMANnt THEMANnt left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed f15e9852d169 against 427dd36164bb. No actionable findings.

The alias path records a second same-stem module and rejects an unresolved needed-name alias instead of choosing by path order. Exact filename and SONAME matches still claim the request, while guest-module dependencies remain unchanged. This prevents order-dependent aliasing while retaining single suffix matches.

Validation: Hosted relinker CTests passed on the pinned head: Windows 309, Ubuntu 313, and both macOS jobs 303 each; ASan’s 34 TLS-coverage tests passed. Full-product Windows/Linux jobs were skipped. Locally, the current-main-plus-patch relinker and test targets built with GCC 15.3; the focused 34-test selection passed with filesystem access for the unrelated hard-link fixture. A 12-run review-only sweep covered four suffix/directory ambiguity paths in Linux and Windows modes, reversed creation order, exact filename claims, input preservation, and no output on rejection. Unpatched main accepted the ambiguous fixture and wrote output.

The local tree lacked the pinned PR objects; relevant patch and fixture paths were unchanged across its 190-commit gap, but local execution is not the exact PR head. Hosted jobs tested exact head f15e9852d169.

@roboterspinne

Copy link
Copy Markdown
Contributor Author

@MarcZX10 Synced: ab4eb25 merges current main 265f77b into the branch without conflicts (a merge rather than a rebase, so the reviewed head f15e985 stays in the history); neither file of this PR changed on main since 518d36e. On ab4eb25 I rebuilt relinker-only and reran ctest -R 'guest_module_directories|needed|guest_|module': 34/34 passed; the full suite passed 306 of 309 tests, with long_paths skipped and the same two known local failures as on main. The Tested section now names ab4eb25, and the hosted relinker matrix runs on it. Please re-review on this head once it is green, so the changes-requested state is cleared.

@LordVersA

Copy link
Copy Markdown

Independent macOS validation of exact head ab4eb259b6da3aaeff8f6d671322b24226d88394:

  • Configured a relinker-only Release build with -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON.
  • The complete build passed (261/261 targets).
  • Focused guest_needed_modules regression passed.
  • The complete relinker test suite passed: 303/303 tests in 51.75 seconds.
  • git diff --check HEAD^2..HEAD passed.

Environment: macOS 27.0.1 arm64, Apple clang 21.0.0, CMake 4.4.3, Ninja 1.12.1.

I also reviewed the focused delta: the added coverage preserves an exact SONAME match and rejects two extension-only matches for the same requested module. I did not test a title or PS5 console behavior.

@github-actions

github-actions Bot commented Oct 28, 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 10792 core/relinker/relinker/src/guest/GuestModuleBuilder.cpp

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing exact head ab4eb259b6da3aaeff8f6d671322b24226d88394.

What I ran

  • Inspected the exact production and test changes, overlap report, review discussion, and current hosted checks.
  • The branch's last synced base is 265f77bf99; current main has not changed GuestModuleBuilder.cpp or test_guest_needed_modules.py since that sync.
  • Hosted relinker checks pass on macOS arm64/x86_64, Linux, and Windows; ASan, conventions, labels, and eligibility pass.
  • I did not run a local build or tests.

Results

Check Result Evidence
Ambiguous suffix alias behavior Reviewed Exact PR diff
Focused regression and adjacent guest-module tests Pass, author-reported 34/34 on synced head; independent macOS focused and full suite pass
Hosted relinker platform matrix Pass macOS arm64/x86_64, Linux, Windows
Hosted ASan and policy checks Pass GitHub Actions
Local build and CTest Not run This review

Findings

Blocking

None.

Non-blocking

  • The resolver now collects all matching suffix stems, but only rejects ambiguity after exact filename/SONAME matches have had a chance to claim the needed name. Single suffix matches and exact SONAME matches are covered, along with ambiguous matches on Linux and Windows. This removes the prior path-order-dependent choice.
  • #10861 overlaps in GuestModuleBuilder.cpp and has broader merge conflicts. The PR discussion manually reconciles the changed regions; retain both the TLS scan changes and this alias resolution when merging.

Sources

Not verified

  • I did not run the relinker build or CTest locally; the author and independent reviewer reports plus hosted jobs provide the test evidence.

To get this to a good state

No blocking issue remains in the reviewed head. Preserve both PRs' changes when resolving the #10861 overlap.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants