The DT_RELA tags were still guarded on the input module's relocation table, while the
new accessor relocations were appended to a copy of it. A module with an initialiser and
no imports therefore kept the argument stub but lost both relocations that fill its GOT
slots, so the stub called through a null pointer at load time.
Guard on the extended table instead, and drop the unused start parameter from the
displacement helper. The test now converts a module whose DT_RELASZ is zero and asserts
that the output carries both accessor imports and a 48-byte relocation table.
sceKernelLoadStartModule discarded its arguments, and the relinker's Linux guest writer
synthesized an initialisation trampoline that also cleared the argument registers before
jumping to the module entry point, so a converted module always reached its own start-up
code with args = 0 and argp = NULL.
On PPSA28997 two of the title's own modules validate that parameter block as their first
act and dereference it unconditionally: SaveData faults on `mov (%rsi),%eax` with rsi = 0
beside its `cmp $0x10` size checks, and PSN faults the same way at PSN+0x3ee7 with rbx = 0.
The Windows branch delivers the block, so identical code reports its parameter-version
error and continues instead of faulting.
sceKernelLoadStartModule now records the pair before dlopen and clears it afterwards, and
the emitted DT_INIT stub reads it through two accessors exported by libkernel. The
initialiser is called rather than tail-jumped so its return value can be reported through
*res as the PS5 API specifies. The state is thread_local, which makes the handoff per-call:
ld.so runs a module's initialiser on the thread that called dlopen. DT_FINI keeps a plain
call-through, so unloading neither reads stale arguments nor reports a result.
The accessor names use the _nid_no_patch suffix so the NID resolver leaves them alone and a
guest import can name them exactly.
Since #1156 a title's bundled sce_module/libc.prx is loaded for its
exports but its init is suppressed, while titles still call its malloc,
which then returns NULL (#1500). On Linux, titles that bundle libc crash
at launch: one segfaults within 0.2 s, another stops with bad_alloc.
This reverts commit 2d6aff7a1e, reversing
changes made to e82e03f11e.
SHA-1 and SHA-256 instructions with a [rip+disp32] operand, and
instructions with one that a short site absorbs, now move into the
stub. The stub records each displacement as a relocation relative to
the site, and the Linux, guest and Windows placements resolve it once
the stub address is known. Indirect branches through [rip+disp32]
still fail the relink.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux guest module writer emitted DT_HASH with a single bucket, so
every symbol lookup that reached a guest module walked its whole symbol
table. Guest modules come first in the global scope, so each host prx
lookup paid that walk at load and on every lazy PLT call. Build the
standard SysV table from the final symbol names with one bucket per
symbol, as the console's modules ship it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GuestModuleWriter::WriteLinux adds read permission to every PT_LOAD, including
the SCE dynlib data segment, which has no flags and is never mapped on the PS5.
When that segment starts inside the last page of the preceding RW segment, the
read-only mapping covers the tail of its bss and the module's initializer faults
on its first write there. The symbol and relocation tables it holds are already
copied into the appended segment, so it no longer needs to be mapped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ButHENCbXXt8h4EZuWUabJ