Repository navigation
Conversation
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.
|
I have open #6595, which carries the detector proposal attached to #3093 and includes synthetic detector/CFG cases. A local merge-tree check against your head reports conflicts in |
|
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.
|
|
@MarcZX100 Thanks. I replied on #6595 with what I found running it on the two titles: it leaves a trailing table in each, so neither converts with it yet. I also proposed a way to combine the two detectors there. AI-assisted: yes (Claude Code). 🤖 Generated with Claude Code |
jotitadev
left a comment
There was a problem hiding this comment.
Ran this on macOS, since the description lists no macOS host.
Setup: Apple M2 Max, macOS 27.2, Xcode 27 Apple Clang, relinker-only Release (-DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON). Head bf06a8e5c on main 5d9dcc42d. Built natively for arm64 (Python 3.14), and as x86_64 under Rosetta with /usr/bin/python3 3.9.6.
| build | main |
#6618 | #6618 + #6619 (a9fa0e8b0) |
|---|---|---|---|
| arm64 | 173/173 | 174/174 | 174/174 |
| x86_64 (Rosetta) + Py 3.9 | 170/173 | 171/174 | 171/174 |
The 3 x86_64 failures are the same on main (pr_overlap_tools, release_package, linux_fixed_base, Python 3.10-only keyword arguments). sce_inline_jump_tables passes on both builds, including under Python 3.9.
Does the test catch the regression? I ran test_sce_inline_jump_tables.py against the main relinker, case by case. The plain, copy and r13 cases fail for both --windows and --macos with Cannot decode instruction: Unsupported 0F 79 opcode without SSE4A prefix (offset 0x9ff8). The two negative cases (r13-displacement, not-a-table) pass on main, as they should. So the test does catch the regression on the macOS target too.
Reduced layouts vs #6595. On #6595 you suggested a reduced copy of the Limbo and Bloodborne layouts. I tried some reductions on the test's fixture, with --windows and --macos:
ret+ 2-bytenopbefore the table;- a
cmp/jaguard to thatret; - 19 entries ending at the FDE end, then an 8-byte
noploutside the FDE; - two back-to-back tables of 100 entries after
ret,calland twonops; - entries starting with
64 fbor65 fd.
main fails on each one, with the decode error or Unsupported Windows/macOS guest TLS instruction (bytes: 64 fb / 65 fd). #6595 (f25f22630) and #6618 convert all of them. So none of these features alone accounts for the retail stops. Whatever separates the two is something these reductions don't model, for example the real code and targets before the table.
Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.
|
Windows data point from a second PS4 title: Hasbro Family Fun Pack (CUSA03312, my own dump) needs this PR for Its bundled
Relinker-only Release build, Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, on AI-assisted: yes (Claude Code) |
adriadam10
left a comment
There was a problem hiding this comment.
#6595 is now merged and also detects relative jump tables inside and after a function's unwind range (RelativeJumpTables.cpp: nop-filled scan copies in RelinkerPipeline.cpp, isData hook in the CFG, tables/inTable in CodeInstructionCollector.cpp). This PR now conflicts in CodeInstructionCollector.cpp and RelinkerPipeline.cpp, and keeping both would leave two detectors with two separate data-range mechanisms (inlineData here, tables there).
Could you rebase on current main and:
- re-run Bloodborne (CUSA00900), Limbo (CUSA01664) and Hasbro (CUSA03312) on it, since your numbers are from
5d9dcc42d, before #6595; - if some table is still not handled, extend
FindTrailingRelativeJumpTable/FindRelativeJumpTablesfor that case, with a fixture that fails on currentmain(the forms here: base moved to another register,r13with the 8-bit displacement, tables not followed by padding), instead of adding a second detector; - if main already covers all of them, close this PR and say so.
|
@adriadam10 I re-ran everything on current
So AI-assisted: yes (Claude Code) 🤖 Generated with Claude Code |
What
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:
Code analysis decodes each function range from start to end, so it read these tables as instructions. The syscall scan and the call-site resolver sweep the whole executable segment, so they lost their instruction boundaries after each table. The conversion of two PS4 titles stopped on it (Windows,
--skip-sce-module). Bloodborne stops there onmain5d9dcc42d. Limbo first stops at a zero-length FDE, which #6619 handles, and then here:The new
FindInlineJumpTablesdecodes one function range. It follows the table address of each RIP-relativeleathrough 64-bit register moves, and records the address that amovsxdwith scale 4 indexes when it lies later in the same function. When decoding reaches the first such address, it checks that 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. If so, the rest of the function is tables. Anything else is decoded as before, so code that only looks like a dispatch is still analysed.leato the table and decodes it.nops in the copies of the code that the syscall scan and the call-site resolver read. The output image is unchanged.The forms seen in the two titles are covered: the base in the register the
leawrites, in a register it was moved to, and inr13, whose SIB form carries an 8-bit displacement of 0. Limbo has 189 functions with such tables. Part of the PS4 work tracked in #3093.Tested
Windows 11 Pro 26200, WinLibs GCC 15.2.0 r7, Release,
BUILD_TESTING=ON, onmain5d9dcc42d:sce_inline_jump_tablestest converts PS4-layout fixtures for Windows and macOS, each with an unwind-covered function whose table entries begin with bytes no pass can decode (0f 79) or that carry anfsprefix (64). It covers four cases:r13form all convert, with the PLT call site still resolved;movsxdwhose base points 8 bytes before the table is not taken as a table, and the conversion fails as it does onmain;lea/movsxdwhose target is code that fails the check is still analysed: its unsupported TLS instruction is still reported.nopfill for the scans, code analysis decoding the tables, tables taken without the check, register moves not followed, ther13form rejected, the control-flow pass not kept out, table addresses kept in the instruction set.-DANYPS5_RELINKER_ONLY=ON): 178 of 178 tests pass. The relinker it builds converts Bloodborne. Limbo still stops at its zero-length FDE, which fix(relinker): ignore unwind records that cover no code #6619 handles; with both, Limbo converts.libSceGnmDriver). The relinker's import audit reports Bloodborne with 180 of 686 imports absent and Limbo with 220 of 547.agc_driver_gpu_indirect_arguments_misaligned_writer_aheaddoes not compile with MinGW onmain(test(agc): align the indirect arguments block at run time #6452 fixes it), andprx_thread_local_destructorsfails as it did onmain92cdc7b62on this machine without my changes.agc_driver_preparation_scope_settlefailed underctest -j8and passes when run alone.tools/check_conventions.pypasses.Not tested on Linux or macOS hosts.
Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code