The relinker's repeatable --executable converts another executable of the
title beside the output, sharing its libs, app0 and guest modules.
sceSystemServiceLoadExec maps /app0 paths to those executables, passes the
argument list on the new command line and finds the main executable through
APS5_EBOOT. RetroArch (PPSA99169) starts its frontend picker this way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PS4 executables keep their relocated read-only data, GOT and init/fini
arrays in a PT_SCE_RELRO segment outside every PT_LOAD. The segment filter
dropped it, so Windows and macOS output stopped at the first relocation
into it, and Linux output placed the relinker's extra block over its
addresses without an error. The ELF reader now reports such a segment as a
read-write PT_LOAD. A PT_SCE_RELRO inside a PT_LOAD keeps being skipped,
and one that partially overlaps a PT_LOAD throws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Weak undefined imports in the input ELF became STB_GLOBAL in the relinked
output, so a title that weak-imports a function no module exports stopped
at startup instead of binding the import to null.
Keep STB_WEAK on the relinked dynamic symbols. On Windows, write 0 plus
the addend to the slot of a weak import that no searched module exports.
Fixes#4497
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A relocation whose dynamic symbol entry is all zero, like the null symbol,
now takes the value 0 plus its addend, as the gABI defines for STN_UNDEF.
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 instead of failing with "Empty import symbol name". Such
references are left out of the call registry. An unnamed symbol entry that
is not all zero still fails the relink.
OliSe Player (PPSA01153) has seven of these entries and calls through the
slots only when they are not 0 (#4437).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A short fs-prefixed TLS access is patched with a 5-byte jump by
relocating the following instructions into the stub, but any branch
there was rejected outright. NHL 27 hits this on every stack-protector
epilogue: fs:xor rax,[rbx] immediately followed by jne __stack_chk_fail.
Allow relative conditional branches and short jumps: re-encode rel8
forms as their rel32 equivalents, rebase the displacement when the
stub is emitted, and keep rejecting loop/jrcxz, calls, returns, and
segment-prefixed followers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rosetta 2 ends the process when an unaligned 256-bit store crosses into
a page the store may not write ("rosetta error: unexpectedly need to
EmulateForward on a synchronous exception") instead of raising the
fault. A store inside such a page, and a 128-bit store across one,
fault normally. The macOS write watch keeps watched pages read-only
between collections, so a guest that stores 32 bytes across the start
of a watched page ends the process.
--to-rosetta now lowers VMOVUPS, VMOVUPD and VMOVDQU with a 256-bit
memory destination to a stub that stores the low half with the same
instruction at VEX.L=0 and the high half with VEXTRACTF128 at the
operand plus 16, so each half faults like any other store. RIP-relative
operands are relocated from the instruction's place in the site. A
store whose upper half has no 32-bit displacement fails the conversion.
Aligned stores (VMOVAPS, VMOVAPD, VMOVDQA and the non-temporal forms)
cannot cross a page and are left alone.
Every store is lowered. A 4-byte store has no room for the jump to its
stub, so its stub also takes one neighbouring instruction. The
following one: a return ends the stub, a call pushes its original
return address and jumps, a jump or conditional branch is re-encoded
with a 32-bit displacement, and another 256-bit store is lowered in the
same stub. Otherwise the preceding one, unless it is a call, a jump, a
return or a trap. Register calls, LOOP, JRCXZ and branches with a
prefix that changes them do not move. When neither neighbour can move,
the store becomes a short jump to an island: a run of movable
instructions within reach is moved to its own stub, and the bytes it
frees hold the jump to the store's stub. A store with neither fails the
conversion.
The converter tests compare the halves with clang's encodings for base,
SIB, disp8 overflowing to disp32, disp32, RSP-relative, FS-absolute,
r13-based and RIP-relative operands, and check that aligned stores,
loads, register forms and masked stores are not split. They check the
stub of a short store followed by a return, a call (direct, with an
address-size prefix, and through a RIP-relative pointer), short, near
and RIP-relative jumps, short and near conditional branches and another
store; that a following register call, LOOP, JRCXZ, prefixed branch or
CLWB makes the store take the instruction before it; islands past
instructions already moved, a store that needs its own stub, a branch
target, a register call and the short-jump reach; and that a store with
no room fails.
PPSA12544: 55295 stores split in the executable, 278 of them in the
stub of the store before them; every module converts with none kept.
AI-assisted: yes
The Linux desktop entry was written for every non-Windows output, macOS
included since --macos landed. It is now written only for Linux output,
and its paths go through Io::NativePath/Utf8Path like the rest of the
relinker. An end-to-end test relinks for Linux, macOS and Windows.
Co-Authored-By: Claude <noreply@anthropic.com>
ELF has no icon resource, so when sce_sys/icon0.png sits beside the input
the relinker copies it next to the output and writes <output>.desktop
(freedesktop Desktop Entry) with that icon. Name is the param.json title
without trademark signs or line breaks, or the output name without
param.json. Exec is quoted per the spec and the entry is marked executable
so file managers launch it. A malformed icon0.png or param.json fails the
relink; without icon0.png nothing extra is written.
Co-Authored-By: Claude <noreply@anthropic.com>