AnyPS5/docs/dev/debt/relinker.md

5.9 KiB

relinker technical debt

Functional

  • Runtime-loaded modules (--module-dir) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded.
  • The executable file that relinker generates opens a console when launched, unless it is relinked with --windows-gui, which selects the GUI subsystem. A GUI build started from Explorer has no standard output or error handle, so everything it prints is lost, including the entry stub's errors and crash reports (#3282).
  • SDK memory-management syscall traps under --trap-sdk-syscall-stubs recognize three exact instruction sequences from ps5-payload-dev SDK libc/mman.c, observed in Nuvio Maxd 0.5.15 (PPSA99288): syscall 74 and the two syscall 477 paths, with their argument preparation and carry-flag error handling. Matching requires a decoded instruction boundary and the complete sequence inside the scanned code range. Other SDK/compiler output remains rejected. The matched syscall becomes UD2; this does not implement mmap or mprotect and a call still faults.
  • --to-intel does not lower RDPRU/MCOMMIT; the matcher fails the relink instead.
  • The length-changing path of the instruction rewriter is not used by the converter: it does not adjust data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length.
  • A PT_SCE_RELRO segment that no PT_LOAD covers, the PS4 layout, is loaded as a read-write PT_LOAD and stays writable after relocation; FreeBSD rtld makes PT_GNU_RELRO read-only once relocation is done, and what the console does with PT_SCE_RELRO is not checked. The unused-filter=1 and unused-filter=2 readers parse the program headers themselves and do not load it.
  • In executables with a PT_SCE_DYNLIBDATA segment, the PS4 layout, the executable segment also holds read-only data after the code (CUSA00900: 37 MiB after its last function). The relinker pipeline therefore scans for syscalls and import call sites only up to the end of the PLT that follows the last function of the unwind table; code after the PLT without an unwind entry is not scanned. --to-intel and the unused-filter=1 and unused-filter=2 analyses are not limited this way.
  • 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). 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). 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).
  • The Linux placement of the --to-intel stubs in the ELF patcher is covered only by a synthetic test.
  • --to-rosetta lowers what Rosetta lacks on top of --to-intel (matcher). Rosetta runs AVX2, FMA, BMI1/2, F16C and MOVBE but reports none of them through CPUID, so code that checks CPUID takes its baseline path. RDSEED becomes RDRAND and CLWB a NOP. RDPID returns processor 0, because Rosetta's TSC_AUX is the constant 0xAAAAAAAB; the same value reaches RDTSCP's ECX, which is not converted. ADCX/ADOX fault under Rosetta and are not converted: compilers do not emit them, and code that checks CPUID sees no ADX. The conversions are covered by the converter tests; that the converted code runs under Rosetta was checked on an M5 Max only, outside this repository's CI.
  • --to-intel replaces the approximate legacy SSE and VEX.128 register forms of RSQRTPS/RCPPS and VRSQRTPS/VRCPPS with correctly rounded 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). Legacy SSE forms preserve the destination's upper YMM bits; VEX forms clear them. The 256-bit, scalar and memory forms keep the native instruction, as does a site shorter than 5 bytes with no movable instruction after it for the jump (5 sites in PPSA21564, counted before a following RIP-relative instruction could move into the stub; not recounted).
  • --trace-imports (entry stub) exists for Windows output only. Only imports bound through a PLT slot, or through a GOT slot whose symbol is a function, are traced; a function reached through any other relocation, or looked up at run time with sceKernelDlsym, is not, and neither are weak imports or the imports of modules loaded at run time