The Linux patcher writes its own PT_PHDR and PT_INTERP but also copied
the input's. glibc takes the load bias from the last PT_PHDR, which was
the input's entry with the pre-relink address, so such an executable
crashed at start. Both copies also took program header slots, so an
input with PT_PHDR and PT_INTERP and no spare slots failed to relink.
The MinGW CRT delivers argv to main as ANSI code page bytes, and every narrow string became a filesystem path through the same code page, so a game in a directory with characters outside the code page failed to open (#2692). Read the command line as Unicode (wmain via -municode), keep paths as UTF-8 strings, convert at the filesystem boundaries through Io::NativePath and Io::Utf8Path, and run the --autorun output through CreateProcessW.
(cherry picked from commit 30a5181f8a71b177cf2dc03334e39998938b3472)
The Windows TLS rewriter only patches FS-relative instructions that
CodeInstructionCollector decodes. Code that no symbol, unwind range or
root reaches is covered by a linear sweep that starts after int3 padding
and stops at the first instruction beginning with two zero bytes, so
zero-filled bytes are not read as code.
The .NET NativeAOT homebrew titles pad between functions with zero bytes
instead of int3 and have unwind entries for only part of their code. In
Prospero Vibrate (PPSA99920) the sweep ended at the padding after the
first function. Functions reached only through switch jump tables, which
the control-flow graph does not follow, or through pointers it cannot
resolve kept 105 of 670 FS loads unpatched: 84 `mov reg, fs:[0x28]`
stack-guard reads (65 into rax, 19 into rcx) and 21 `mov rax, fs:[0]`
thread-pointer reads. The title faulted reading address 0x28 within a
second of start.
The sweep now treats a zero run that follows a jump, call, return or
trap and ends at the next 16-byte boundary as alignment padding, and
continues at that boundary. A call counts because a function can end in
a call to a noreturn function. Zero fill after straight-line code, or a
whole zero 16-byte block, still ends the sweep as before.
All 670 FS loads in PPSA99920 are now patched, as are the 102 of 685 in
Prospero Explorer (PPSA99109) and the 103 of 718 in Prospero Multi Tools
(PPSA99110). New tls_function_coverage padding cases: loads behind zero
padding after a return, a call and a jump, with 13, 2, 1 and 5 zero
bytes, are patched; a load behind zero fill after a nop is left
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0306c677784b6c4272990ce65f6daf9fb2dbc3ca)
Generate guest PRX dependencies with adjacent and separated repeats, equal names at distinct string offsets, and invalid-name controls. Check acceptance for Linux and Windows conversion and first-seen dependency order in ELF outputs.
Depends on the production fix by Hannah Lyons (babyhuey), c1a6696872153b8e41608f4cc8ab9a0d36f1d206. Written with AI assistance.
WindowsPeWriter stored every PE section with SizeOfRawData equal to its
virtual size, so the zero-filled tail of a PT_LOAD segment (p_memsz above
p_filesz, the .bss) was written to the executable byte for byte. A segment
with 256 MiB of .bss produced a 269 MB .exe, while the Linux output of the
same image is under 5 KB.
Each section is now stored up to its last non-zero byte, aligned to the file
alignment, and VirtualSize keeps the full size. The PE loader zero-fills the
part of a section beyond SizeOfRawData (Microsoft PE format, section table:
SizeOfRawData). A section with no non-zero byte gets no raw data and a zero
PointerToRawData.
windows_bss relinks an image with 64 MiB of .bss, checks the section table
and the mapped image, and on Windows runs the executable, which reads the
last word of the .bss.
A bundled module whose file starts with a SELF container magic
(4f 15 3d 1d or 54 14 f5 ee) was skipped like any other non-ELF file,
so the relink succeeded without it and the module was missing at run
time. The guest module scan now throws, naming the module, as
ElfReader already does for a SELF executable.
Fixes#2599
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The executable is linked at a fixed base with a __PAGEZERO that fills the space below it, and
the guest assumes 16 KB pages. With MH_PIE set dyld slides the image by a multiple of 4 KB,
which breaks that assumption and ends in a SIGBUS (KERN_PROTECTION_FAILURE) in about half of
the launches under Rosetta 2. Clearing the flag keeps the image at its linked address.
test_macos_output.py now reads the header flags and checks that MH_PIE is clear.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The guest filesystem root and the save data directory are taken from the
current directory, so an executable started from another directory does
not find its title files and keeps saves elsewhere. Make the executable
directory the current directory in the Windows entry stub before the
system libraries are loaded.
The x86 decoder had no XOP (AMD 8F escape) support and read 8F E8.. as
POP r/m, losing instruction boundaries. Since #2238 roots code addresses
taken by RIP-relative LEA, code analysis reaches CPU-dispatched XOP
paths, such as the vpperm routines in Stremio Plus (PPSA74126), OpenNOW
(PPSA99082) and PPSA99420, and their Windows conversion failed with
"Code analysis: overlapping instruction boundaries". Decode 8F followed
by a map of 8 or more as a three-byte XOP prefix (8F can only be POP r/m
with ModRM.reg 0) with a ModRM operand, an 8-bit immediate in map 8 and
a 32-bit immediate in map 0Ah, in both the length and operand decoders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A directory opens and tellg reports an enormous size, so the relinker
tried to allocate it and failed with std::bad_alloc. Require a regular
file and a non-negative size before allocating the input buffer.
The unused-filter=1 entry point collector translated DT_INIT_ARRAY and
DT_FINI_ARRAY through PT_DYNAMIC and skipped arrays placed before it, which is
the usual linker layout, so constructors and destructors were not roots and
imports used only by them were dropped. Arrays are now translated through the
PT_LOAD that holds them, slot targets come from RELATIVE relocations when
present, and DT_PREINIT_ARRAY is collected as in the strict filter.
The CFG/GOT filter's entry point collector checked header and symbol
positions as `offset + size > file size`, which wraps for offsets
near 2^64. A section header or symbol table offset there passed the
check, read bytes before the input buffer, and could reach an
in-bounds table through the wrapped address, adding entry points that
keep relocations alive. Check every position by subtraction instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A jump slot whose r_offset is 0xFFFFFFFFFFFFFFF8 or higher makes
targetGotOrPltAddress + 8 wrap, so the upper lower_bound lands before
the lower one and the call-site loop walks past the end of the map
(SIGSEGV; ASan heap-buffer-overflow in ResolveCallSites).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The machine check compared against 'x86-64' (uname reports 'x86_64'), so the
native execution was always skipped. With it enabled the fixture itself was
wrong: the initialiser's rip-relative call used the wrong displacement, and
the pending block was set on a different copy of the host library than the
one the module binds to.
Co-Authored-By: Claude <noreply@anthropic.com>
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.
Add a hand-built guest module with an explicit DT_INIT that forwards rdi and rsi to a
host recorder without touching them and returns a known value. The test sets a known
argument count and argument pointer, loads the converted module, and asserts that the
initialiser received both values and that the stub forwarded its return value.
The host recorder also provides the two accessor symbols, so the test resolves them
without loading libkernel. The converted module must import both accessors, which is
what distinguishes the new behaviour from the previous stub that zeroed the arguments.
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.
The entry stubs called the title's entry with rbp left as the process
started it (0 on Linux) or as a loop counter left it (0 on Windows). The
title's start code pushes rbp, so the frame-pointer chain of its main
thread ended in a null frame pointer. Unreal Engine walks that chain to
capture call stacks (for an ensure, for example) and reads the return
address at [rbp + 8], so the walk read address 8 and crashed.
Both stubs now push a zero frame record (saved rbp 0, return address 0)
and point rbp at it before the call, so frame-pointer walks stop at the
entry. The Linux stub pushes it below the aligned stack pointer, keeping
the 16-byte alignment at the call. The Windows stub writes it into free
slots of its own frame at rsp+0x40.
PPSA15657 crashed in its stack walk at the title screen in about half of
its runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134RWCyiyydJBbfKz8g9Bwe
Palworld's eboot needs EOSSDK-PS5-Shipping.debug_prx while the dump ships
prx/eossdk-ps5-shipping.prx. On Windows, compare module stems without
ASCII case, and let a name that already resolves to a guest image (exact
or case-folded filename, or soname) win over a stem alias.
Reported and diagnosed by @Rohan03x in #1830.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title's executable can name a bundled module differently from the file it
ships: Dying Light 2 needs EOSSDK-PS5-Shipping.debug_prx and ships
EOSSDK-PS5-Shipping.prx. Needed names were matched by exact filename, so the
module was skipped without an error and start-up failed to load it. Renaming
the file does not help either: the title later loads the module at run time
by its shipped name (/app0//EOSSDK-PS5-Shipping.prx), and dlopen looks for
that name's .guest.prx (#1794).
When no file has the needed name, a module whose name differs only in its
.prx, .sprx or .debug_prx extension now satisfies it, in the module
directories or elsewhere under the input. The output keeps the shipped file
name, and the needed name is added as another name of that guest module.
Two such candidates for one needed name are ambiguous, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest image reader and the module builder already produce a
platform-neutral GuestImage; --macos adds a third writer next to the ELF
and PE ones. The executable and every bundled module become x86-64 Mach-O
files (modules as dylibs in app0/sce_module) that run under Rosetta on
Apple silicon.
FreeBSD code reaches its thread pointer through %fs, which macOS cannot
set, so the writer rewrites each %fs access into a stub that reads the
image's thread pointer from a pthread key and allocates the TLS block on
a thread's first access. Dynamic TLS goes through the image's descriptor
instead of __tls_get_addr. Imports bind by NID to the prx libraries
beside the output, exports and TLS imports use the dynamic symbol names,
and the image's metadata lives in __APS5DATA,__meta.
--macos conflicts with --windows. The macOS runtime that consumes the
output (TLS allocation, libraries built for macOS) comes in separate
changes.
--registry only described the imports of the executable, so a title whose
own modules import system functions (PSN.prx, libc.prx and others) could
not be audited completely. With --registry the relinker now also writes
<output-stem>.<module>.registry.json for every converted guest module.
An entry is written for each relocation against an undefined symbol that
no other bundled module exports, the same test the module builder uses to
decide that a dependency is satisfied by a sibling. Entries carry nid,
library and targetOffset; the symbol name no longer has the # suffix by the
time the module is read, so the NID is the bare 11 characters. The
executable's registry is unchanged.
The test converts two modules where one is provided by its sibling, for
Linux and Windows output, covers the DT_RELA and DT_JMPREL tables, checks
that the same module imports the sibling's function once the sibling is
absent, and that nothing is written without --registry. Registered in
CTest as guest_registry.
Functions referenced only through a REX.W RIP-relative LEA (constructors,
callbacks, thread entries called indirectly) were not reached by code
analysis, so their guest TLS accesses stayed unpatched on Windows.
Add LEA targets inside executable code as roots, both in the instruction
walk and when following the control-flow graph of unreached code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GUI missing-symbol and missing-module fixtures were started with
ShellExecuteExW. A process the shell starts gets the default error mode,
so the status the entry stub raises reaches Windows Error Reporting as an
APPCRASH. Collecting each report took about 2 s, sometimes over 10 s,
and under a parallel ctest run it could pass the 30 s wait, failing
windows_import_modules with "ShellExecuteExW child did not exit" while
WerFault still held the fixture's files.
The fixtures now start through CreateProcessW with STARTF_USESTDHANDLES
and no standard handles, so the stub's diagnostic still has nowhere to
write, and with the test's error mode, which keeps the report away.
The header-block PT_LOAD has the highest address but was the first
loadable entry in the table. Linux before 5.18 sizes the ET_DYN mapping
from the first and last PT_LOAD entries in table order, so the output
failed to exec with SIGSEGV. Write PT_PHDR and PT_INTERP first, sort the
kept PT_LOAD entries by address, and place the extra and header blocks
after them.
Use the decoded opcode offset when checking forbidden system calls.
Add synthetic regressions for prefixed forms, immediate decoys, and the skip option.
AI-assisted: Codex. The submitting author is responsible for every changed line.
A title built with -fstack-protector reads its guard from fs:0x28 even when
it has no thread-local data and its linker emits an empty PT_TLS. The
Windows TLS builder refused any guest TLS access in that case. The guard and
the thread pointer live in the thread control block, which the builder
already emits, so an empty PT_TLS now takes the normal path, where a
zero-sized TLS block still rejects every other displacement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
addGap and buildEdges each called isZeroPadding(address, end) at every
instruction boundary. isZeroPadding scanned from the current address to
the region end, so the total work for a region with N instructions and a
trailing-zero suffix of length Z was O(N*Z), quadratic in the worst case.
Replace with trailingZeroStart, which scans backwards from the region end
once to find the start of the trailing-zero suffix. The loop then stops at
that position, eliminating the per-instruction zero check entirely.
Restore full 64-bit RCX before 32-bit ALU operation so CMP and other non-writing/carry instructions do not zero-extend and clear upper 32 bits. Add test coverage for 32-bit CMP, ADC, and SBB.
Recognize read-direction ALU forms (add, or, adc, sbb, and, sub, xor, cmp) that read from fs:[mem] where mem has a base or index register (such as 64 48 33 03, xor rax, fs:[rbx], in #217). The stub loads the TLS value through the computed address, performs the ALU operation, and preserves scratch registers and flags.
Extend test_tls_function_coverage with execution and stub coverage cases for register-addressed ALU operations, including rax, rcx, rbx and r13 destinations, wide and 32-bit forms, and verification of register and flag preservation.
Refs #217
Co-Authored-By: Claude Opus 5.5 <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>