fix(relinker): follow fs loads and report pointers taken as data between PS4 unwind functions

A relocation or lea target outside the unwind functions of a PS4 image
is now decoded up to its first jump, return or trap, or to the start of
the next unwind function, which also bounds the transfer check of the
tail rule. A target whose run holds an fs:[disp32] access, the form the
linear TLS sweep roots, is followed as code. The other targets taken as
data are counted in one message and kept as entry points that the
Windows TLS rewrite may not overwrite. The analysis merges nested
unwind ranges like the scans.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Savvanis SpyrosandClaude Opus 5.5 committed 2026-10-20 15:48:42 +03:00
1 parent 1ea49c741f
commit 58a2a4962f
17 files changed
+154 -47

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@
- The [syscall scan of `sce_module` modules](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) with a `PT_SCE_DYNLIBDATA` segment stops at the same end, or, when the PLT comes before the code, at the end of the last function of the unwind table; code after it is not scanned.
- In the same executables and modules, the bytes between unwind functions are 16-byte slots of function tails and padding, and a tail can begin inside an instruction. The [instruction scanner](../../../core/relinker/codegen/src/InstructionScanner.cpp) of the syscall and call-site scans therefore resumes at the next byte when bytes outside every unwind function do not decode, and ends a decoding outside them at the start of the next unwind function. Code without an unwind entry that does not decode from its first byte is skipped by these scans instead of failing the relink; inside an unwind function a decode failure still fails it.
- Read-only data can also sit between the unwind functions of such images, with whole tables in a gap. There, a `syscall`, `sysenter`, `int 0x80` or `sysret` pattern fails the [syscall scan](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) only when a constant syscall number is set before it; the others, and every `sysret` pattern, are skipped and counted in one message. The number is looked for in the decoded instructions and, whatever alignment the scan's decoding took, in the bytes in front of the pattern: a `mov eax, imm32`, `mov rax, imm32` or `xor eax, eax` that ends at the pattern or before at most seven register moves or zeroings that leave `rax` alone. A real syscall without an unwind entry whose number is set in another way, such as from memory or from another register, is therefore not refused, and runs on the host.
- In the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs for such images, a relocation target or a `lea` target outside every unwind function is followed as code only when it is 16-byte aligned, starts less than 256 bytes after the end of an unwind function, and decodes to a jump, return or trap. That is where the thunks and tails without unwind entries of the measured PS4 titles sit; every other such target is taken as data. Code without an unwind entry that is reached only through a pointer and sits elsewhere is not analyzed, so a TLS access in it is not rewritten.
- In the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs for such images, a relocation or `lea` target outside every unwind function is followed as code only when its linear decoding, up to the first jump, return or trap, the next unwind function or 256 instructions, holds an `fs:[disp32]` access, or when it is 16-byte aligned, starts less than 256 bytes after an unwind function and ends in a jump, return or trap. Every other such target is taken as data and counted in one message, and the Windows TLS rewrite fails instead of overwriting one. A TLS access of another form, such as `mov rax, fs:[rax]`, or a `gs` instruction in code reached only through such a target is therefore not rewritten or trapped. Other fs and gs instructions do not tell code from data there: string bytes decode as these prefixes, and in two measured titles about 2,500 of about 5,500 such targets decode to one before their first jump.
- In executables and guest modules with a `PT_SCE_DYNLIBDATA` segment, the [code analysis](../../../core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp) of the Windows and macOS outputs follows code only from the end of `PT_INTERP`, or of the `/libexec/ld-elf.so.1` string that begins the segment when no `PT_INTERP` covers it, as in OpenOrbis modules, to the end of the PLT after the last function of the unwind table, or to the end of that function when the PLT comes before the code, as in the `libSceFios2.prx` and `libc.prx` that CUSA02636 bundles (PLT at 0xc8); the pipeline's scans of executables above stop at this same end, also when the PLT comes before the code. A guest module with PLT relocations and no PLT before or after its unwind functions, or with a PLT that overlaps one, fails as the executable does. Code after that end is not analyzed, such as the `DT_FINI` routine without an unwind entry that follows the last unwind function in both modules and that the Windows and macOS outputs call when the program exits: a TLS access in it is not rewritten, the ELF entry point, `DT_INIT`, `DT_FINI`, a branch or a pointer into it is ignored, and a function symbol or init/fini array entry pointing into it fails the conversion.
- A relocation against a dynamic symbol entry that is all zero, like the null symbol, takes the value 0 plus its addend, as the gABI defines for `STN_UNDEF` ([pipeline](../../../core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp)). The Linux output imports such an entry as an unnamed weak symbol, which the host loader resolves to 0; the Windows and macOS outputs write the value into the slot ([relocation builder](../../../core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp)). What the console's loader does with such an entry is not measured. OliSe Player (PPSA01153) has seven of them, and its code calls through those slots only when they are not 0 ([#4437](https://github.com/boykopovar/AnyPS5/issues/4437)).
- In macOS output, a guest instruction with a GS segment prefix, including one with no memory operand (`65 90`, `65 48 89 c8`), which does not touch GS, becomes `UD2` and NOP padding of its own length ([macOS image writer](../../../core/relinker/elfpatcher/src/macos/MacOsImage.cpp)): the host owns GS, which holds the pthread TSD base the guest TLS stubs read, and the guest GS base that FreeBSD's `sysarch(AMD64_SET_GSBASE)` would set is not emulated. A converted module runs until such an instruction is reached, which then faults. Prospero Win (PPSA99995) has six in its Wine `ntdll`.