Skip to content

fix(relinker): skip the input executable when scanning modules for --executable - #5512

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
zaryabzip:fix/module-dir-executable-input
Oct 14, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
zaryabzip:fix/module-dir-executable-input

Conversation

@zaryabzip

@zaryabzip zaryabzip commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What

--executable cannot be combined with a module directory that holds the input executable, for example --module-dir .: the run fails when the other executable is converted (input.elf: Invalid string or symbol table with the test fixtures).

The module scan skips the input executable (#3850). Since #4786 Convert() runs once per executable and passes the executable it is converting as the file to skip. While an --executable is converted, the scan therefore reads the title's input executable as a guest module. An --executable cannot be in a module directory itself (--executable is in a guest module directory), so the input is the only executable the scan can meet.

Convert() now passes the input executable to Cli::ReadModules for every executable, so the scan skips the same file as before #4786. Runs without --executable pass the same argument as before.

Tested

Relinker-only build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Release):

  • macOS 27.0.1 (arm64), Apple clang 21.0.0, Python 3.14.7: 146/146 tests pass.
  • Ubuntu 24.04 container (x86-64 under Rosetta), GCC 13.3.0, Python 3.12.3: 150/155 pass, guest_module_dir and relinker_executables among them. The five that fail there fail the same way on main: amd64_only_converter, tls_function_coverage_register_address_loads, linux_entry_argv, linux_executable_exports and linux_input_program_headers.
  • guest_module_dir gains one case, run for Linux and Windows output: --module-dir . --executable picker/picker.bin with a module next to the input. It checks that the conversion succeeds, that picker.picker.bin.elf / picker.picker.bin.exe is written, and that app0/root.prx.guest.prx is the only guest module.
  • On both hosts the new case fails on main at the return code check and passes with the fix.
  • tools/check_conventions.py --base origin/main passes with this title and body.

Not tested: a Windows host, or a title.

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
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@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 source change keeps the original input executable excluded while each selected executable is converted, and the added regression exercises the reported case for both Linux and Windows output formats. The author reports the relinker-only suite passing (146/146); the current PR checks also pass. I found no issue in the changed path.

@MarcZX10

Copy link
Copy Markdown
Contributor

For clarity, the regression uses --module-dir . --executable picker/picker.bin and checks that picker.bin is not emitted as a guest module. The preceding review body omitted those literal arguments due to formatting.

@jotitadev

Copy link
Copy Markdown
Contributor

macOS data point plus one edge case on the guard this fix relies on.

Apple M2 Max, macOS 27.2 (26B5091g), AppleClang, Ninja Release, relinker-only (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON). Head 9954e58af merged without conflicts into main e34c47921.

  • arm64: ctest --parallel 8 --timeout 30 156/156, including guest_module_dir.
  • x86_64 under Rosetta 2 (-DCMAKE_OSX_ARCHITECTURES=x86_64): guest_module_dir passes; the remaining failures are the 6 that fail identically on unmodified main on this machine (Python 3.9 incompatibilities in tools tests, and the guest_module_args fixture test(relinker): keep the guest_module_args fixture's GOT out of executable memory #5362 fixes).
  • The PR's --module-dir . --executable picker/picker.bin case with --macos output also succeeds: exit 0, output and picker.picker.bin are x86_64 Mach-O executables, and app0/root.prx.guest.prx is the only guest module.

Edge case (pre-existing, same result on main): the description notes that an --executable cannot be in a module directory, so the input executable is the only one the scan can meet. That check (main.cpp, std::find over moduleDirectories) compares paths lexically, while the default APFS volume, like NTFS, is case-insensitive. Spelling the directory with a different case bypasses it, and the scan then reads the other executable as a guest module:

Arguments Result
--module-dir picker --executable picker/picker.bin --executable is in a guest module directory: picker/picker.bin
--module-dir Picker --executable picker/picker.bin .../Picker/picker.bin: Invalid string or symbol table
--executable SCE_MODULE/picker.bin (directory is sce_module) .../sce_module/picker.bin: Invalid string or symbol table

Both bypassed cases still fail, but with the cryptic error this PR removes, not the explicit one. Comparing with std::filesystem::equivalent (the module scan already does this for the input executable) would make the check hold on case-insensitive volumes. The Duplicate guest module directory check in NativeModules.cpp has the same lexical compare. Out of scope here; mentioning it because the fix's reasoning depends on that check.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@adriadam10 adriadam10 self-assigned this Oct 14, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on the PR merged onto current main: relinker build + ctest 132/132 green. The new guest_module_dir case fails without the main.cpp change and passes with it. Passing args.inputPath restores the pre-#4786 skip and leaves runs without --executable unchanged. Merging once the full Build is green.

@adriadam10
adriadam10 merged commit 8f9240f into boykopovar:main Oct 14, 2026
9 checks passed
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