Skip to content

fix(relinker): skip jump tables placed right after a function's unwind range - #7620

Closed
DealPotato wants to merge 2 commits into
boykopovar:mainfrom
DealPotato:fix/relinker-jump-table-after-unwind-range
Closed

DealPotato wants to merge 2 commits into
boykopovar:mainfrom
DealPotato:fix/relinker-jump-table-after-unwind-range

Conversation

@DealPotato

Copy link
Copy Markdown
Contributor

What

Some PS4 executables put a switch jump table right after the FDE of the function that reads it. #6618 only finds tables inside the function's unwind range, so code analysis decodes such a table's entries as instructions and the conversion stops.

inFAMOUS Second Son (CUSA00004) does this:

  • The function at 0x64c870 has an FDE ending at 0x64d0c3. It ends with a call to a noreturn function (call 0x984360) and a nop.
  • Its switch loads a table at 0x64d0c4: lea rcx, [rip+0x6dd] at 0x64c9e0, then movslq (%rcx,%rax,4),%rax.
  • The lea adds 0x64d0c4 as a code root, and the conversion fails:
FAIL: Unsupported Windows guest TLS instruction (bytes: 65 ff ff) (offset 0x6510cc)

The change:

  • FindTrailingJumpTables tracks the same lea and movsxd table loads as FindInlineJumpTables, now shared through one helper. It does this for table bases between the function's end and the next function, or the end of the code segment.
  • A table ends at its first entry whose target falls outside the function.
  • CodeInstructionCollector adds these tables to inlineData, so the CFG neither enters them as roots nor reaches them by fall-through.
  • RelinkerPipeline fills them with nop before the syscall scan, as it already does for tables inside the function.

test_sce_inline_jump_tables.py gains a trailing fixture: the plain table with the FDE ending at the table. Without the change, it fails with Cannot decode instruction at the table.

Tested

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 (none added)
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: fix(relinker): skip jump tables placed after a function's code #6618 (its commit is the first one on this branch)
  • Fixes: none
  • Unblocks: none (CUSA00004 conversion; reported in feat: run PS4 games on the AGC driver — Limbo (CUSA01664) in game (integration branch) #6977, no bring-up discussion yet)
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

Get-fix and others added 2 commits October 18, 2026 20:22
PS4 titles built with Sony's toolchain place a switch statement's jump
table in the code segment, after the last instruction of the function
that indexes it and inside the function's unwind range:

    lea    rcx, [rip + table]
    movsxd rax, dword ptr [rcx + rax*4]
    add    rax, rcx
    jmp    rax

Code analysis decodes each function range from start to end, so it read
the table as instructions; the syscall scan and the call-site resolver
sweep the whole segment and lost their instruction boundaries there.
Limbo (CUSA01664) stopped at "Unsupported Windows guest TLS instruction
(bytes: 65 f9)" and Bloodborne (CUSA00900) at "Instruction exceeds 15
bytes".

FindInlineJumpTables decodes a function range, follows the table
address of each RIP-relative lea through 64-bit register moves, and
records the address a movsxd with scale 4 indexes when it lies later
in the same function. When decoding reaches the first such address and
every dword from there to the end of the function is an offset, from
the nearest table before it, to code before the first table, the rest
of the function is taken as tables. Anything else is decoded as before.
Code analysis stops each function there and keeps the control-flow
pass out of the tables; the pipeline fills them with nops in the copies
the syscall scan and the call-site resolver read.
…d range

Some PS4 executables place a switch table after the FDE of the function
that reads it. The function ends in a call to a noreturn function, and
the table follows immediately. inFAMOUS Second Son (CUSA00004) does this
at 0x64d0c4, right after a function whose unwind range ends at
0x64d0c3. The function's lea of the table base was added as a code root,
so code analysis decoded the table entries, and the conversion stopped
with "Unsupported Windows guest TLS instruction (bytes: 65 ff ff)".

FindTrailingJumpTables tracks the same lea and movsxd table loads as
FindInlineJumpTables, but for bases between the function's end and the
next function. A table ends at the first entry whose target falls
outside the function. The code collector treats such tables as inline
data, and the pipeline fills them with nops before the syscall scan, as
it already does for tables inside the function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 6595 core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp L4, L116-117
core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp L6, L366
PR 6977 core/relinker/relinker/include/relinker/analysis/InlineJumpTables.hpp
core/relinker/relinker/src/analysis/InlineJumpTables.cpp
core/relinker/relinker/tests/test_sce_inline_jump_tables.py

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 2780 core/relinker/CMakeLists.txt
PR 5021 core/relinker/CMakeLists.txt
PR 6044 core/relinker/CMakeLists.txt
PR 6879 core/relinker/CMakeLists.txt
PR 6974 core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp
PR 6977 core/relinker/CMakeLists.txt
core/relinker/relinker/include/relinker/analysis/InlineJumpTables.hpp
core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp
core/relinker/relinker/src/analysis/InlineJumpTables.cpp
core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp
core/relinker/relinker/tests/test_sce_inline_jump_tables.py
PR 7173 core/relinker/CMakeLists.txt
PR 7332 core/relinker/CMakeLists.txt
PR 7531 core/relinker/CMakeLists.txt

@DealPotato

DealPotato commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Closing in favour of #6595, which handles the same case and converts the Second Son eboot that motivated this PR.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants