Skip to content

fix(relinker): ignore direct branch targets inside an instruction past its prefixes - #7651

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
DavidSamir:fix/relinker-amd-only-branch-targets
Oct 20, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
DavidSamir:fix/relinker-amd-only-branch-targets

Conversation

@DavidSamir

Copy link
Copy Markdown
Contributor

What

With --macos --to-rosetta, SuperTuxKart (PPSA69421) and Mortalfin (PPSA99141) stop during instruction conversion:

FAIL: Branch enters an AMD-only instruction (offset 0xd9d517)    PPSA69421
FAIL: Branch enters an AMD-only instruction (offset 0x1245dd7)   PPSA99141

The branch is not real. Both titles link OpenSSL's x86-64 SHA-1 assembly, which keeps its signature string in the code segment, right before sha1_block_data_order_avx2:

0xd9d4a0  "SHA1 block transform for x86_64, CRYPTOGAMS by <appro@openssl.org>"
0xd9d4af  73 66                 "sf" read as jae 0xd9d517
0xd9d516  c5 fc 11 47 40        vmovups %ymm0, 0x40(%rdi)   (a 256-bit store --to-rosetta splits)

The converter collects branch targets from a linear scan of the segment, so the string's jae lands one byte into the store's VEX prefix and the store is refused. In both titles the only branch to that address is this one: a search of every rel8, rel32 and Jcc rel32 encoding in the file finds no other.

Jump-table entries already count as targets only where a scanned instruction starts. This applies a narrower form of that rule to direct branches. A target inside a scanned instruction still counts where every byte before it is a legacy prefix or REX, so a branch that skips a lock prefix, or the INSERTQ prefixes the existing test uses, still keeps the instruction in place. A target in a VEX prefix, opcode or operand bytes is ignored: executing from there does not run the instruction the scan decoded, and the conversion of that instruction already relies on that decode.

Three commits:

  1. fix(relinker): ignore direct branch targets inside an instruction past its prefixes
  2. test(relinker): a branch into a 256-bit store and into the instruction moved with it no longer keeps the store. The existing "branch into the compare keeps it native" case now uses the legacy cmpss with a branch past its F3 prefix, which still keeps it native; a new case shows that a branch past the VEX prefix of vcmpss no longer does (it would execute c2 ca 03, ret 0x3ca).
  3. docs(debt): the relinker debt file records the rule and what it gives up: a real branch into the middle of an instruction, as overlapping hand-written code can do, is not seen.

Tested

macOS 26 on an Apple M2 (MoltenVK 1.4.x, Vulkan SDK 1.4.363), x86-64 build under Rosetta 2, Release, relinker-only build (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON).

  • amd64_only_converter_tests: the new cases fail on main (No room for a jump to the stub of 256-bit store within its neighbours or a nearby island in the small fixture, where the false target also blocks the neighbour placement) and pass with this change. All other converter cases pass, the INSERTQ "Branch into an AMD-only instruction was accepted" check included.
  • Full relinker ctest: 192 of 192 pass. hardware_oracle_compare timed out at 20 s once under -j8 and passed alone in 5.6 s.
  • Titles, relinked with --macos --to-rosetta and their sce_module:
Title main This PR
SuperTuxKart PPSA69421 (catalog 00.001.000) Branch enters an AMD-only instruction conversion passes (46452 256-bit store sites, 49152 stubs, 0 kept); stops at the next check, Forbidden syscall instruction at code offset 0xe2c3a7
Mortalfin PPSA99141 (catalog 1.0.0-beta.5) Branch enters an AMD-only instruction conversion passes (53618 sites, 59714 stubs, 0 kept); stops at Unsupported macOS guest TLS instruction (bytes: 65 6e)

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: PPSA69421 and PPSA99141 instruction conversion under --to-rosetta (no bring-up discussion)
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

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 7822 docs/dev/debt/relinker.md L16

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 5021 docs/dev/debt/relinker.md
PR 6977 docs/dev/debt/relinker.md

@LeandroLP23

Copy link
Copy Markdown
Contributor

Windows host run, since the description covers macOS only. Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), Windows 11 26300, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON. Build: main e8ad642fe with #7651, #7650, #7647, #7655 and #7648 merged together (each merge clean, built once).

AI-assisted

ycarogabriel-lgtm added a commit to ycarogabriel-lgtm/AnyPS5 that referenced this pull request Oct 19, 2026
… inside an instruction past its prefixes (@DavidSamir)

This branch gathers the open macOS work, and this PR is part of it: it is merged here with its author credited, so the integration carries it now and merging it first in main only shrinks this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@DavidSamir

Copy link
Copy Markdown
Contributor Author

Windows check, main 0929be5e8d against main plus this head (clean merge). Windows 11 Pro 26300, WinLibs GCC 15.2.0, Ninja, Release.

  • Relinker tests: 151 of 151 pass on both, amd64_only_converter included.
  • Titles: SuperTuxKart (PPSA69421) and Mortalfin (PPSA99141), catalog releases. Each relinked with --trap-sdk-syscall-stubs and --windows, and again with --windows --to-intel. All eight relinks exit 0. The four app.exe pairs are byte-identical between main and this PR (md5 7a7a91a79d01, 4f4cc5fcf4ae, b514585484ac, e4fbb3035d04).
  • Intel substitutions: the --to-intel runs make the same ones on both: INSERTQ at 0x352688 in PPSA69421 and EXTRQ at 0x109d7e in PPSA99141.

AI-assisted: yes

@adriadam10
adriadam10 merged commit 34b1b77 into boykopovar:main Oct 20, 2026
13 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the branch-target rule against DecodedInstruction::OpcodeOffset() (legacy/REX prefixes only, so VEX/opcode/operand targets are ignored and prefix-skipping branches still keep the instruction native), the new and changed converter tests, the debt line and the relinker CI jobs (Linux, Windows, macOS incl. x86_64, asan); merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs relinker unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants