Skip to content

fix(relinker): bind imports declared from a guest module only to that module's exports on Linux and macOS - #11880

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
roboterspinne:fix/relinker-guest-declared-module-binding
Oct 28, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
roboterspinne:fix/relinker-guest-declared-module-binding

Conversation

@roboterspinne

Copy link
Copy Markdown
Contributor

What

On Linux and macOS, GuestModuleBuilder::Build renames a guest-module or executable import that declares a bundled guest module (NID#lib#mod, mapped through DT_SCE_NEEDED_MODULE and DT_NEEDED) to <NID>#guest whenever any bundled module exports the NID, also when the declared module does not export it, so the dynamic linker (global-scope lookup on Linux, flat lookup on macOS) binds it to another bundled module that does. The builder's own provider choice (lines 278 to 296 on main, restricted to the declared module by #2212) finds no provider there and records the import as a system import of the declared module, so the outputs contradict the registry and the Windows output. On untouched 48c160c, with sce_module/provider.prx exporting only other, sce_module/third.prx exporting entry and consumer.prx importing entry#A#B declared from provider, the Linux consumer kept entry#guest with DT_NEEDED only $ORIGIN/provider.prx.guest.prx while its --registry file listed {"nid": "entry", "library": "provider.prx"}, the macOS consumer contained entry#guest, and the Windows program exited 0xC0000139; an executable importing entry#A#B declared from provider got entry#guest on Linux and macOS, and with third.prx in --module-dir Media the Linux executable's DT_NEEDED also listed $ORIGIN/app0/Media/third.prx.guest.prx, so the unrelated module started with the executable.

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_symbol_names with the new cases on untouched 48c160c fails at the guest-module case with AssertionError: ('other', ['ZZZZZZZZZZZ#guest', 'entry#guest']), after the control passed. Run directly with the test's inputs, untouched code gives the executable {'entry#guest'} on Linux for both providers. With the change the consumer has ['ZZZZZZZZZZZ#guest', 'entry'], the executable imports {'entry'}, neither macOS output contains entry#guest, and the --module-dir Media executable's DT_NEEDED lists only provider.prx.guest.prx; the controls are unchanged. All files the six Windows conversions (both guest-module inputs and the four executable inputs) write are byte-identical before and after; the Windows programs exit 0xC0000139 for the missing export, and the controls exit 0x2a (guest-module input) and 0x1 (executable input, guest_import_fixtures.executable), before and after.
  • ctest -R 'guest_|module|import|registry' -j 5 on 65dba8a: 44 tests passed; run alone, guest_symbol_names (1.1 s), guest_module_identity (7.4 s), windows_import_modules (10.1 s), call_registry (3.3 s), guest_registry (0.6 s), linux_guest_named_imports (0.6 s), weak_imports (1.1 s) and macos_guest_weak_imports (0.4 s) passed.
  • Merged current main (801d5b9) into the branch without conflicts (a5e1bc5); since 48c160c, main changed GuestModuleBuilder.cpp only through fix(relinker): skip backup copies of guest modules in the online converter too #10997, which appends lines after line 489, away from the lines of this PR. On a5e1bc5, ctest -R 'guest_|module|import' -j 6: 44 tests passed, among them guest_symbol_names (1.3 s), guest_module_identity (9.6 s), guest_needed_modules (4.8 s), import_symbol_types (4.1 s) and windows_import_modules (13.1 s).
  • Full suite on a5e1bc5: ctest --test-dir build-relinker -j 6: 369 tests, 366 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 a5e1bc5.
  • 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)

… module's exports on Linux and macOS

An import that declares a bundled guest module which does not export
its NID was still renamed to the guest suffix on Linux and macOS when
another bundled module exports the NID, so the dynamic linker bound it
to that module. For guest-module imports the builder already chooses
the provider only among the declared module's exports and records such
an import as a system import of that module; the guest-module rename
now follows that decision, and the executable rename applies the same
rule, as Windows does. Such an import keeps its plain NID, and an
executable import no longer makes an unrelated module from --module-dir
start with the executable; undeclared and host-declared imports and
Windows outputs are unchanged.

@rooklz rooklz 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.

Build-verified on macOS (the host lane left untested in the description).

Code parity. Every rule in the description maps to the code at a5e1bc5: systemImports receives exactly the undefined non-empty-name imports without a provider in their declared module (empty names continue before any insert, host-declared imports take the early arm, and the provider loop has restricted candidates to the declared module since #2212), the registry takes its imports only from systemImports (line 479), so the old rename indeed contradicted the registry and the Windows output. For the executable arm, the rebuilt dynamic section carries import names already stripped of #lib#mod with ImportModules keyed by relocation address (SysVDynamicSectionBuilder), so executableModules is the declared module and the old condition fired for any bundled exporter, dragging the unrelated module in through executableProviders. The windows || guard on the new findGuest lookup is needed: on Windows findGuest emplaces case-folded aliases into guestNames, which the earlier executable pass must not trigger; on Linux/macOS it is a pure lookup. Host-declared, undeclared and declared-and-exported imports keep their old paths, and the exported arm of the guest rename is untouched.

Red/green. With this PR's test file over the base GuestModuleBuilder.cpp (801d5b9), guest_symbol_names fails with AssertionError: ('other', ['ZZZZZZZZZZZ#guest', 'entry#guest']) — identical to the counter-check in the description; at a5e1bc5 it passes.

Tests (macOS, Release, ANYPS5_RELINKER_ONLY). ctest -R 'guest_|module|import|registry': 44/44, same set as the Windows run. Full suite: 361/361, matching the local main baseline (the two failures on your machine are Windows-specific).

Overlap. pr_overlap: no shared lines with any open PR that merges with main; #10861 is the only uncomparable. Hand-checked at 8a4fa9a: its GuestModuleBuilder.cpp hunks sit in the top-of-file helpers and the syscall scan (406-435), disjoint from both change sites here, and a trial merge conflicts only in CodeInstructionCollector.cpp with GuestModuleBuilder.cpp auto-merging — same conflict main itself has with #10861. Trial merge with #11479 (needed-name alias loop + a hunk below 489): clean.

@github-actions

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 10861 core/relinker/relinker/src/guest/GuestModuleBuilder.cpp

@adriadam10 adriadam10 self-assigned this Oct 28, 2026
@adriadam10
adriadam10 merged commit e4da19a into boykopovar:main Oct 28, 2026
13 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the declared-module condition against the builder's own provider choice (the executable rename at line 248 and the guest-module rename at line 336), the new test's control and missing-export cases for both the guest module and the executable, and the relinker jobs on Linux, macOS and Windows; merged.

@roboterspinne
roboterspinne deleted the fix/relinker-guest-declared-module-binding branch October 29, 2026 08:24
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.

3 participants