Skip to content

fix(relinker): reject --lazy-binding for macOS output - #10712

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
roboterspinne:fix/relinker-macos-reject-lazy-binding
Oct 27, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
roboterspinne:fix/relinker-macos-reject-lazy-binding

Conversation

@roboterspinne

@roboterspinne roboterspinne commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

What

MacOsMachOPatcher::Patch discarded its lazyBinding argument (static_cast<void>(lazyBinding);), so relinker --macos --lazy-binding --skip-sce-module on the import_fixture() guest of test_macos_output.py exited 0 without an error or warning and wrote the same eagerly bound Mach-O as the run without the flag: 77,920 bytes, byte-identical on a main build, while the flag changes the Windows (282,112 to 281,600 bytes) and Linux (25,906 to 25,890 bytes) outputs of the same fixture. Nothing earlier rejects the combination: CliArgs.cpp pairs --lazy-binding only with --trace-imports and --linux-fixed-base, the target checks in Conversion::Convert do not cover LazyBinding, and GuestModuleBuilder refuses it only when bundled modules are converted (Guest modules require eager binding; --lazy-binding is incompatible), so every module-free macOS conversion reached the discard.

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, relinked outputs not run.

  • Counter-check: macos_output with the new case on untouched d0e7fe7 fails with --macos with --lazy-binding: exit 0: Converting conflict.elf; with the change it passes in 0.6 s.
  • Manual runs on import_fixture(): --macos --lazy-binding, --lazy-binding --macos and --macos --lazy-binding --macos-fixed-base 0x400000 exit 2 with the message and no output; the eager --macos output is byte-identical to that of the main build (sha256 aed941e41b5ff6b0…).
  • ctest -R 'macos|lazy|linux_fixed_base|executable_tls_relocations|trace_imports|conversion|cli_args|windows_fixed_base|linux_gnu_segments': 17 tests passed in 12.7 s, macos_output (0.6 s), the other seven macos_* tests, relinker_conversion, conversion_diagnostics, cli_args, linux_fixed_base, linux_gnu_segments, windows_fixed_base, executable_tls_relocations and the Linux and Windows trace_imports tests.
  • ctest --test-dir build-relinker -j 4: 244 tests, 240 passed, long_paths skipped; three failures known on this machine on main as well: select_tests (FileExistsError, Windows symlink fixture), patch_advisor_unit (build path with spaces, fixed in open test(tools): compare quoted and resolved paths in the patch advisor test #10667) and shader_cache_runtime_sources (timeout at 180 s under load; passes alone in 112 s).
  • python tools/check_conventions.py --base upstream/main with this title and description passed.
  • 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)

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

Reviewed exact head 9a69e8b against current main. The macOS patcher rejects lazy binding before writing output, matching the documented unsupported Mach-O behavior; the fixture checks exit 2, diagnostic text, and absence of the output file. Local relinker Release build and focused macos_output CTest passed (1/1); git diff --check, current-main conventions and merge-tree pass. Hosted Linux, Windows, macOS x86_64, ASAN and policy checks pass; macOS arm64 remains pending. No blocking source issue found.

@MarcZX10

Copy link
Copy Markdown
Contributor

CI follow-up for reviewed head 9a69e8b6de5875722bba08feb5e6489af77ef739: the previously pending macOS arm64 relinker job has completed successfully. Linux, Windows, macOS arm64, macOS x86_64, and ASAN relinker jobs are now green; the product jobs remain skipped by policy.

@github-actions

github-actions Bot commented Oct 24, 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 3786 docs/user/USAGE.md
PR 5021 docs/user/USAGE.md
PR 5925 docs/user/USAGE.md
PR 6171 docs/user/USAGE.md
PR 6924 docs/user/USAGE.md
PR 7642 docs/user/USAGE.md
PR 9345 docs/user/USAGE.md
PR 9508 docs/user/USAGE.md
PR 10043 docs/user/USAGE.md
PR 10193 docs/user/USAGE.md
PR 10861 core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp
docs/user/USAGE.md

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

Approve: the fix is correct and the new check fails without it and passes with it on macOS. Two optional items below.

Tested on Apple M1 Pro, macOS 27.0.1 (x86_64 build under Rosetta 2)

Applied at 9a69e8b6de to mugurc's macOS series (27e968974) merged with main at 2742004ed2, with other open macOS fixes. I toggled only this PR, and the MacOsMachOPatcher.cpp hunk separately from the test and doc hunks.

macos_output the other 17 relinker tests (macos_*, relinker_conversion, cli_args, conversion_diagnostics, *_fixed_base, *_trace_imports, ...) relinker --macos --lazy-binding --skip-sce-module on import_fixture()
Without this PR Passed (old test) 17/17 passed exit 0, writes the same 77,920-byte Mach-O as --macos alone (sha256 aed941e41b5ff6b0…)
PR test, old patcher Failed: --macos with --lazy-binding: exit 0: Converting conflict.elf n/a n/a
With this PR Passed (3 runs) 17/17 passed in 3 runs (the C++ test binaries were relinked against the patched core before the third); relinker_game_patch also passed exit 2, FAIL: macOS target does not support --lazy-binding, no output; the eager --macos output is byte-identical to the old one

--lazy-binding --macos and --macos --lazy-binding --macos-fixed-base 0x400000 are also rejected with exit 2 and no output. --lazy-binding without --macos still converts.

  1. Optional: reject the combination in the CLI as well (core/relinker/cli/src/CliArgs.cpp:211). The patcher throw at core/relinker/elfpatcher/src/macos/MacOsMachOPatcher.cpp:62 only runs at Conversion.cpp:178, after Relink() (:104) and the guest-module build (:146). On a real title the user waits through the whole relink before getting what is really a usage error. With bundled modules, the user first gets Guest modules require eager binding, and only after dropping the modules learns that macOS has no lazy binding at all. The other target conflicts are already checked in ParseArgs at CliArgs.cpp:202-211, before any input is read: --trace-imports cannot be used with --macos, --linux-fixed-base cannot be used with --windows or --macos, and --macos-fixed-base requires --macos. Fix:
    • add if (args.lazyBinding && args.toMacos) throw std::runtime_error("--lazy-binding cannot be used with --macos"); after CliArgs.cpp:211. That is below #9408's CliArgs hunk, which removes the --trace-imports/--macos check, so the two do not conflict;
    • add an expectThrows next to core/relinker/cli/tests/CliArgsTests.cpp:241;
    • keep the patcher throw as a backstop;
    • in test_macos_output.py:178, match the new message and check for a non-zero exit, as the --windows conflicts with --macos check two lines above does, because CLI errors exit 1 (main.cpp:254-256).
  2. Nit: docs/dev/debt/relinker.md:42 records that --trace-imports exists for Windows and Linux output, not macOS. A matching line for --lazy-binding would keep the debt file in step with the new USAGE.md wording. It is fine to skip because the flag is deprecated.

AI-assisted: yes (Claude Code applied the PR, ran the tests, read the code and wrote this review).

@roboterspinne

Copy link
Copy Markdown
Contributor Author

@maximilianfv Thanks for testing it on macOS. 1: This PR keeps the single check where the option is consumed, which covers every macOS conversion, including direct Conversion::Convert callers. With a parse-time check as well, the CLI could no longer reach the patcher throw, and test_macos_output.py would test only the parser. If a maintainer prefers the earlier usage error, I will add the CliArgs.cpp check as a new commit here. 2: Skipped, since --lazy-binding is deprecated and USAGE.md now names the targets it supports.

@wachi35676

Copy link
Copy Markdown
Contributor

Tested head 9a69e8b merged onto main aeff26b (clean) on Windows 11 (Ryzen 5 9600X), WinLibs GCC 15.2.0, Release.

  • macos_output passes. With main's MacOsMachOPatcher.cpp and the new case, it fails.
  • Four real homebrew titles converted without the flag to Windows, macOS (--to-rosetta) and Linux give output byte-identical to main's.

AI-assisted: yes (Claude Code); results above were run on this machine.

@adriadam10 adriadam10 self-assigned this Oct 26, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 26, 2026
MacOsMachOPatcher::Patch discarded its lazyBinding argument, so a
module-free `--macos --lazy-binding` conversion exited 0 and wrote the
same eagerly bound Mach-O as a run without the flag. The Mach-O writer
puts every import in the LC_DYLD_INFO_ONLY bind stream and leaves the
lazy_bind stream empty, with no __la_symbol_ptr or stub helper, so the
macOS target cannot honour the flag. The patcher now throws "macOS
target does not support --lazy-binding", in the form of its
--windows-diagnostics check, so such a conversion exits 2 and writes no
output. Conversions with bundled modules were already refused by the
guest module builder.
@adriadam10
adriadam10 force-pushed the fix/relinker-macos-reject-lazy-binding branch from 9a69e8b to 3123f92 Compare October 27, 2026 05:00
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 27, 2026
@adriadam10
adriadam10 merged commit f2f759a into boykopovar:main Oct 27, 2026
12 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main to resolve the docs/user/USAGE.md conflict with #10871: the --lazy-binding row now sits below its --frame-red-zone row, and its text is unchanged apart from "Windows and Linux output only.". Checked the macOS patcher throw, the check_options case (exit 2, message, no output file) and the relinker tests on the rebased head: 190 of 190 passed. The macOS and Windows runs by @maximilianfv and @wachi35676 cover the same code. Rebased and merged.

@roboterspinne
roboterspinne deleted the fix/relinker-macos-reject-lazy-binding branch October 27, 2026 15:00
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.

5 participants