Cli::ParseArgs had no test coverage: its only sibling test
(AutorunExitTests) exercises the autorun launcher, not the parser.
Add cli_args_tests covering every option and its side effects, the
--rpath and --exclude-sce-module value consumption (including that the
following token is taken verbatim rather than re-parsed as a flag), the
unused-filter value and single-specification validation, the
--exclude-sce-module/--skip-sce-module conflict in both orders, the
order-independent --windows-diagnostics/--windows-gui requirement on
--windows, unknown-option and extra-positional rejection, and the usage
message when an input or output path is missing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The segment filter dropped every header in the PT_LOOS..PT_HIOS range,
which includes PT_GNU_EH_FRAME. The unwinders find the executable's
unwind tables through that header, so no C++ exception thrown in the
main executable could be caught on Linux.
The header is kept only when the output program header table still fits
in the input's table; otherwise it is dropped as before, with a warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeC9bA3n74TzJRYv9AgBE8
DT_JMPREL and DT_PLTRELSZ were never range-checked against the input
file. With DT_OS_JMPREL the offset is used raw, so an offset outside the
image reached BuildDynamicSection and CompactPlt. When DT_PLTRELSZ is
zero the per-entry loops never run, so nothing rejected the table and
the relink produced an output executable.
Validate the declared range with the subtraction-based check already
used for DT_STRTAB and DT_STRSZ, next to it and before the table is
read.
A static branch into a skipped function tail is accepted when an
instruction decodes there within its executable segment, so a range that
ends inside an instruction no longer stops code analysis.
The Windows TLS rewriter skips a non-FS segment-prefixed instruction that
starts inside an earlier collected instruction: it is an alternative
decoding from a range that begins mid-instruction, not a TLS access.
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.
ClassifyInstruction assumed a ModRM byte right after every opcode that
was not a near branch. A length-changing rewrite therefore adjusted
immediates such as MOVABS imm64 that looked like a RIP-relative ModRM,
and missed RIP-relative operands of VEX and 0F 38 / 0F 3A instructions
and the rel8 targets of LOOP and JRCXZ. Take the RIP-relative
displacement and branch target from DecodeInstruction instead.
UD1 (0F B9 /r) always raises #UD, like UD2, but DecodeInstruction
reported it as sequential. Reachability analysis then followed the
bytes after it, so an import reachable only through that fallthrough
was kept by the strict unused NID filter. UD1 keeps its ModRM, so a
RIP-relative operand is still reported.
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>
glibc 2.41+ refuses to dlopen a module without PT_GNU_STACK, so the native
part of guest_lifecycle_ownership failed on current distributions.
Co-Authored-By: Claude <noreply@anthropic.com>
PUSH GS, POP GS and RSM (0F A8, 0F A9, 0F AA) have no ModRM byte, but fell in the 0F A3-AB ModRM range, so the decoder read the next byte as a ModRM and sized its displacement. MOV to and from control and debug registers (0F 20-23) ignore the ModRM mod field and always name a register, but were sized and reported as memory forms, including a RIP-relative disp32 for mod 00 r/m 101.
ElfReader::ReadHeader names 0x1D3D154F as a SELF container, but dumps also
carry a second container magic, 0xEEF51454 (bytes 54 14 f5 ee), which fell
through to "Invalid ELF magic number: 54 14 f5 ee". That message reads as a
corrupt input rather than an encrypted one, so a user cannot tell that
decryption is what stands between them and a relink.
Both magics now report the SELF message. Measured on two retail dumps:
PPSA03671's eboot.bin and libcohtml.Prospero.prx are 54 14 f5 ee; PPSA28997's
eboot and modules are 4f 15 3d 1d.
Tested: Linux (CachyOS, GCC, Release, BUILD_TESTING=ON), relinker rebuilt. The
new case fails on the revert of the reader change with the exact message above
and passes with it; elf_header, elf_offsets, dynamic_segment,
string_table_bounds, guest_needed_modules, guest_module_directories and
linux_load_alignment still pass.
The decoder adds the TEST immediate of opcodes F6 and F7 when ModRM.reg
is 0 or 1. The check excluded VEX and two-byte opcodes but not EVEX, so
an EVEX.0F F6 (VPSADBW) whose ModRM.reg is 0 or 1 was given an extra
imm8 and decoded one byte too long. VPSADBW has no immediate (Intel SDM
Vol. 2B, VPSADBW).