Commit Graph
158 Commits
Author SHA1 Message Date
Josip Parlov 606fa2ef91 fix(relinker): take red-zone framing's function bounds from .eh_frame FDEs
Function starts were direct call targets plus 16-byte-aligned code after
int3 padding. A function reached only through a pointer and not preceded
by int3 then ended up inside the previous region, and its pop rbp; ret
exit could get the epilogue lea without a matching prologue.

The bounds now come from the executable's .eh_frame FDEs (the existing
EhFrameReader), each function is decoded from its FDE start to its FDE
end, and code outside every FDE is not framed. Conversion stops with an
error when the executable has no FDE. Sites added in one pass also count
as taken for the next function.
2026-10-23 05:19:10 +00:00
Yunus Shaikh 259bb69d65 feat(relinker): move rsp operands that cannot keep their encoding to stubs
A leaf whose rsp operand could not keep its encoding after the shift
was skipped: an 8-bit displacement that overflows, an operand at (%rsp)
without a displacement, or an indexed (%rsp,%idx,s) operand. Such an
instruction now moves to a stub, re-encoded with the moved displacement
in 8 bits when it fits and in 32 bits otherwise, followed by a jump
back.

The site covers the instruction and the sequential instructions after
it until it is 5 bytes long. It is refused when a branch lands inside
it, when it would have to cover a branch, ret or rip-relative
instruction (these are not relocated), or when it overlaps the
prologue, an exit, another operand site or an existing stub. Legacy,
REX and VEX forms are re-encoded; EVEX operands, whose 8-bit
displacement is scaled, are still refused. A leaf with no room for a
site is skipped as "rsp operand that cannot move and has no room for a
stub". Operands inside a site that could move in place are carried by
the stub instead, so the site's bytes stay as they were.

The report counts the operand stubs and the operands they carry.
2026-10-23 04:24:32 +00:00
Yunus Shaikh e0ba27fc60 test(relinker): run red-zone leaves under writes below rsp, framed and unframed
The framer's tests check its sites, stub bytes and moved displacements,
but nothing yet showed that framed code survives a write below rsp.

red_zone_framer_execution frames synthetic leaves with
FrameRedZoneFunctions in a writable and executable buffer, writes the
moved displacements and the stubs as Conversion.cpp and the trampoline
builders do, and calls each leaf with the trap flag set. A SIGTRAP
handler fills [rsp-0x80, rsp) of the interrupted context at every
instruction, as Windows exception dispatch may.

Five leaves each loop over slots they spilled, so a clobbered slot
changes the result: #5640's frameless shape, a leaf reaching 0x28 below
rsp, a leaf that also reads its saved rbx at +0x10 (moved in place), a
leaf that takes the address of a slot below rsp (shifted by the whole red
zone), and the inflate_fast-shaped leaf that allocates with sub after six
pushes. Each must return the right value unframed without the clobber
and a wrong one with it, and the right value framed with it.

The test is built and registered on Linux x86-64 only; elsewhere its main
returns 77.
2026-10-23 04:14:20 +00:00
Yunus Shaikh 132597c6e1 feat(relinker): frame red-zone leaves that allocate with sub after their pushes 2026-10-23 03:37:41 +00:00
Yunus Shaikh 1a262894ad feat(relinker): lower rsp only as far as a frameless red-zone leaf reaches below it 2026-10-23 03:34:55 +00:00
Yunus Shaikh ff16118b63 Merge branch 'main' into feat/relinker-frame-frameless-leaves-next 2026-10-23 03:13:53 +00:00
aasishdairel 8b86ca3984 fix(relinker): leave GS-prefixed LEA and NOP unchanged
LEA computes an effective address and the multi-byte NOP ignores its operand, so neither accesses memory and a GS prefix on them is ignored. The Windows TLS builder and macOS image writer treated every ModRM memory form as an access and replaced these instructions with UD2 and NOP padding.
2026-10-22 21:01:29 +05:30
Kang, Harry Zixuan 4e9854ea2b test(relinker): report SHA-1 preservation failures precisely 2026-10-21 20:29:55 -07:00
Dean 46bfbe4e25 Merge pull request #8063 from AasishDairelSahayaGrinspan/fix/relinker-gs-memory-only-traps
fix(relinker): trap only GS memory accesses in Windows and macOS output
2026-10-21 16:35:25 +13:00
javierUDP 5702e1f938 test(relinker): include algorithm for the converter test std::all_of
The new checks call std::all_of, which the file reached only through
another header.
2026-10-20 18:25:33 -03:00
javierUDP eab9b23a52 test(relinker): execute the split 256-bit store
The stub that splits an unaligned 256-bit store is checked through its
bytes but never run, so nothing confirms that the two halves land where
the original store would, including across a page boundary, which is the
case the split exists for. The x86-64 execution section now runs the
stub at three operand offsets and checks the 32 bytes it writes and the
pages around them.
2026-10-20 14:54:02 -03:00
Dean b9731811ba Merge pull request #8001 from mopga/fix/relinker-amd-only-claimed-followers
fix(relinker): do not take instructions another AMD-only stub moved as a short instruction's followers
2026-10-21 06:30:39 +13:00
Dean 989b6ea794 Merge pull request #7999 from mopga/fix/relinker-amd-only-sequence-in-place
fix(relinker): move in-place AMD-only replacements into a short instruction's stub
2026-10-21 06:19:51 +13:00
Savvanis SpyrosandClaude Opus 5.5 1ea49c741f fix(relinker): tighten the syscall skip between PS4 unwind functions
The skip of syscall patterns outside the unwind functions let some real
syscalls through. The scan now merges nested and overlapping unwind
ranges with the instruction scanner's MergeCodeRanges, so a pattern
inside an outer function is refused. A constant syscall number is also
looked for in the bytes in front of the pattern, whatever alignment the
decoding took: a mov eax, mov rax or xor eax, eax that ends at the
pattern or before register moves and zeroings that leave rax alone. The
decoded lookback requires each instruction to end where the next one
starts. Guest modules without PT_SCE_DYNLIBDATA get no unwind ranges,
so their scan stays strict even when their unwind table is read.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-20 15:48:42 +03:00
Adrià 8afd6ea478 Merge pull request #7660 from savvaniss/fix/sce-code-gaps
fix(relinker): resume scans after undecodable bytes between PS4 unwind functions
2026-10-20 13:17:15 +02:00
aasishdairel a0e591c932 fix(relinker): trap CLZERO GS overrides
CLZERO (0F 01 FC) clears the cache line at the logical address in rAX and applies a GS override, so mark its register-form encoding as a memory operand. RDPRU (0F 01 FD) has no memory operand and stays unchanged.
2026-10-20 10:53:42 +05:30
javierUDP d5886fce88 test(relinker): execute the in-place Rosetta replacements
RDPID, RDSEED and CLWB are checked through their replacement bytes but
never run, although all three raise an illegal instruction on a Rosetta
host, which is what the replacements exist for. The x86-64 execution
section now calls the RDPID stub and the RDSEED and CLWB replacements
and checks the value, the carry flag and the registers they leave.
2026-10-20 02:22:14 -03:00
aasishdairel a58d560c9b fix(relinker): trap UMONITOR GS overrides
UMONITOR (F3 0F AE /6) takes its effective address from the register operand and uses DS with segment overrides, so mark its register-form encoding as a memory operand. UMWAIT and TPAUSE use the same opcode with F2/66 and have no memory operand.
2026-10-20 10:19:04 +05:30
aasishdairel cde44070e0 fix(relinker): trap GS overrides on implicit memory operands
MASKMOVQ, MASKMOVDQU, VMASKMOVDQU, MONITOR and MONITORX use a register-form ModRM byte but still access memory at DS:rDI or DS:rAX. Mark them as memory operands and cover the encodings in the Windows and macOS GS trap tests.
2026-10-20 10:13:34 +05:30
aasishdairel 570c3297d3 fix(relinker): trap only GS memory accesses in Windows and macOS output 2026-10-20 10:13:34 +05:30
Dean d85b7c759a Merge pull request #2780 from SuarezPM/ai/relinker-hardening
feat(relinker): lower MCOMMIT and RDPRU for Intel with execution tests
2026-10-20 16:22:49 +13:00
Adrià 34b1b77abf Merge pull request #7651 from DavidSamir/fix/relinker-amd-only-branch-targets
fix(relinker): ignore direct branch targets inside an instruction past its prefixes
2026-10-20 02:13:47 +02:00
Karim Masmoudi 3cae921a66 fix(relinker): probe masked store elements before the store under --to-rosetta
Rosetta ends the process instead of raising SIGSEGV or SIGBUS when a
VMASKMOVPS, VMASKMOVPD, VPMASKMOVD or VPMASKMOVQ store faults on any
element other than an active first element ("rosetta error: unexpectedly
need to EmulateForward on a synchronous exception"). Measured on an M5
with macOS 27.0.1 for all eight 128- and 256-bit store forms. A masked
store into a page the macOS write watch keeps read-only therefore ends
the title.

--to-rosetta now moves each masked store to a stub that saves RAX, RCX
and RFLAGS below the red zone, computes the operand address with LEA,
reads the mask sign bits with VMOVMSKPS or VMOVMSKPD, and writes to the
first and last byte of every active element with LOCK OR of 0. Those
writes fault normally, so the write watch sees the page and unprotects
it, and they leave memory unchanged. The stub then restores the
registers and runs the original store. Segment and address-size
prefixes are kept on the probes, RSP-based operands skip the spill
frame, and RIP-relative operands are relocated. Masked loads, the
Intel target and a plain relink are unchanged.

masked_store_lowering decodes every form, register and extension,
checks the stub bytes, and on x86-64 Linux and macOS runs 1728 stubs
across all forms, six addressing modes, six offsets around a read-only
page and six mask patterns. It checks the stored bytes, that exactly one
fault reaches the handler when an active element touches the page,
that no register, flag or red-zone byte changes, and that RSP is
restored. Without the probes the x86-64 build aborts under Rosetta.
test_to_rosetta.py checks that only --to-rosetta lowers a VMASKMOVPS.
2026-10-20 01:12:24 +01:00
SuarezPM bf47697ec0 feat(relinker): lower MCOMMIT and RDPRU for Intel with execution tests
MCOMMIT becomes STC plus NOP padding and RDPRU goes through a
runtime-checked stub returning the host TSC for MPERF/APERF while
other selectors yield zero, with converter and execution tests.

docs/dev/debt/relinker.md records the new lowering and its unverified
assumptions.
2026-10-19 19:19:31 -03:00
mopgobopotandClaude Opus 5.5 e3e05d9171 fix(relinker): do not take instructions another AMD-only stub moved as a short instruction's followers
A short AMD-only instruction takes the following instructions into its stub until its site fits a jump. The loop did not check which instructions an earlier 256-bit store island had already claimed, so with --to-rosetta two trampolines covered the same bytes: for je; jmp; vmovups [rdi],ymm0; rcpps xmm1,xmm2 followed by adds, the RCPPS site at +8 overlapped the island at +11, the patched code crashed, and the real patchers failed with 'AMD-only site bytes changed before patching'. A claimed follower now ends the attempt: an optional instruction such as RCPPS or RDPID is kept, and any other instruction fails with an error naming the moved follower.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 20:07:03 +03:00
mopgobopotandClaude Opus 5.5 d37221a00a fix(relinker): move in-place AMD-only replacements into a short instruction's stub
A short AMD-only instruction lowered through a stub takes the following instructions with it. When one of them is an AMD-only instruction that is replaced in place (MOVNTSS, MOVNTSD, MONITORX, MWAITX, and RDSEED and CLWB for Rosetta), MatchSequence found no out-of-line lowering for it and the conversion failed with 'AMD-only instruction sequence has no out-of-line lowering', for example on extrq xmm0,xmm1 followed by movntss [rdi],xmm0. A following instruction with an in-place replacement now has that replacement moved into the stub, with its RIP-relative displacement relocated. Out-of-line emission for 256-bit store windows is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 20:01:41 +03:00
plokmklo c1866af79b fix(relinker): synthesize PT_GNU_STACK for Linux outputs without one
Outputs whose input carries no PT_GNU_STACK get one appended with
PF_R|PF_W and alignment 16 so the kernel does not fall back to an
executable stack marking.

RequiredProgramHeaderCount and WriteLayout now derive from one shared
header list, so the reserved slot and the written header cannot
disagree at any PhNum boundary, when a free input slot should be
reused, or when the RELRO coverage guard reduces the kept count.
Space exhaustion beyond the 16-bit header limit fails loudly instead
of silently skipping the header.
2026-10-19 07:30:56 +00:00
plokmklo d4cdfe0639 test(relinker): locate Linux stub segments via e_phoff
The program header table moves when the layout needs more slots than
the input provides, so the stub lookup must follow the output e_phoff
instead of assuming the table stays at file offset 64.
2026-10-19 07:30:56 +00:00
Savvanis Spyros c3d1363df3 Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into fix/sce-code-gaps
# Conflicts:
#	docs/dev/debt/relinker.md
2026-10-19 02:35:44 +03:00
Adrià cf72c02064 Merge pull request #6325 from amirerfantim/refactor/relinker-remove-dead-code
refactor(relinker): remove functions nothing calls
2026-10-19 01:17:30 +02:00
Savvanis SpyrosandClaude Opus 5.5 45a2e3dd4a fix(relinker): resume scans after undecodable bytes between PS4 unwind functions
In PS4 executables and modules the bytes between unwind functions are
16-byte slots of function tails and padding, and a tail can begin inside
an instruction, such as a lone REX byte before a 15-byte NOP. The
syscall and call-site scans decode the executable segment linearly, so
such a slot failed the relink with "Cannot decode instruction".

For images with a PT_SCE_DYNLIBDATA segment the scans now receive the
unwind function ranges. Outside every range an undecodable byte is
skipped and decoding resumes at the next byte, and a decoding that would
cross into a range ends at its start, so gap bytes cannot change how a
function is decoded. Inside a range a decode failure still fails the
relink, and images without unwind ranges are scanned as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 23:45:49 +03:00
David 5c2062cc0a test(relinker): cover branches into converted instructions past their prefixes 2026-10-19 00:10:07 +04:00
David cb9329f0e4 fix(relinker): ignore direct branch targets inside an instruction past its prefixes 2026-10-19 00:10:07 +04:00
Karim Masmoudi 57e52c1f03 fix(relinker): report processor 0 from RDTSCP under --to-rosetta
Rosetta sets RDTSCP's ECX to its constant TSC_AUX, 0xAAAAAAAB, which is
not a processor index. --to-rosetta already makes RDPID return processor
0 for the same reason, so code reading the processor through RDTSCP saw
a different value from code reading it through RDPID.

--to-rosetta now moves RDTSCP into an optional stub that runs the
instruction unchanged and then loads 0 into ECX with a MOV, which
leaves the counter in EDX:EAX and the flags as they were. A site with no
movable instruction after it for the jump is kept, as for RDPID. The
Intel target and a plain relink are unchanged.
2026-10-18 18:27:15 +02:00
Dean 5b7ec4a9bd Merge pull request #7272 from OskarLGS/fix/relinker-decoder-osize-branches
fix(relinker): decode near branches with an operand-size prefix as AM…
2026-10-18 12:56:40 +13:00
Morten HolmandClaude Sonnet 5.5 cf9e5a2567 test(relinker): keep an FS or GS access out of the window of a 256-bit store
Covers a thread-pointer read (FS and GS form) after the store with a movable
instruction before it, and the fallback to the following access when there is
no other neighbour.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-18 00:40:41 +02:00
Morten HolmandClaude Sonnet 5.5 513b2e181b fix(relinker): move a neighbour without an FS or GS prefix with a 256-bit store first
A 256-bit store is 4 bytes, so its stub window takes a neighbouring
instruction to make room for the jump. The window took the following
instruction first even when it was a guest TLS access (an FS or GS prefix).
The macOS writer cannot rewrite a TLS access inside a stub and refuses the
module with "AMD-only instruction is also a guest TLS access".

The window now tries the following and the preceding instruction without a
segment prefix first, and falls back to the old order only when neither
works, so outputs that can keep the access in the stub do not change.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-18 00:40:41 +02:00
oskarlgsandClaude Opus 5.5 206aaa56cb fix(relinker): decode near branches with an operand-size prefix as AMD does
66 E8, 66 E9 and 66 0F 80-8F without REX.W take a 16-bit displacement on
AMD processors, the console included. Every near relative branch with that
prefix truncates its target to 16 bits, so the decoder reports no branch
target for it and stubs refuse to move it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016TFMtUC4L4wMS4eeEqaNSR
2026-10-17 20:58:29 +00:00
javierUDP d67e7b7389 test(relinker): link the converter tests with a small macOS zero page
The CLZERO case that needs a 32-bit address found no mapping because a
64-bit Mach-O executable reserves 4 GiB as `__PAGEZERO`, not because
Rosetta withholds low memory: the same `mmap` succeeds once the
executable is linked with a smaller zero page. The Apple x86-64 build
links the test that way and the case runs with the other three, so the
skip it had is gone and the Linux mapping failure stays fatal. The arm64
build keeps the 4 GiB zero page, which it requires to start.
2026-10-15 17:10:59 -03:00
javierUDP 6f100d3f61 test(relinker): execute the AMD64-only conversion stubs on macOS
The execution part of the AMD64-only converter tests was compiled for
Linux x86-64 only, so the SSE4A, SHA-1, SHA-256, CLZERO and reciprocal
stubs were never run on a macOS x86-64 build, unlike the ANDN lowering
tests. Rosetta maps no memory below 4 GiB, so the CLZERO case that needs
a 32-bit address is left out when that mapping is unavailable.
2026-10-15 15:40:26 -03:00
Adrià 15a60e1455 Merge pull request #5762 from AlexOfTheForest/fix/rosetta-legacy-compare
fix(relinker): move legacy CMPSS and CMPSD out of line for Rosetta
2026-10-15 20:34:12 +02:00
Adrià 3bb409b603 Merge pull request #5784 from gguerra26/fix/macos-m1-andn
fix(relinker): lower ANDN in Rosetta conversion
2026-10-15 19:25:06 +02:00
Adrià 5c0cc1bba4 Merge pull request #5755 from AlexOfTheForest/fix/decoder-rip-far-indirect
fix(relinker): report RIP-relative operands of far indirect CALL and JMP
2026-10-15 15:58:16 +02:00
gguerra26 9b92c55edf fix(relinker): lower ANDN in Rosetta conversion 2026-10-15 15:00:43 +02:00
Adrià 2b8a685dfe Merge pull request #5683 from MarcZX100/codex/rosetta-scalar-compare-5642
fix(relinker): isolate Rosetta scalar compare hazards
2026-10-15 14:57:14 +02:00
Amirerfan TeimooriandClaude Opus 5.5 d4b40a37f1 refactor(relinker): remove functions nothing calls
GuestImageReader::ReadModuleNames lost its only caller in 41126bfa5.
EmitShiftImm and ElfReader::ReadSection have no callers.
ValidationPolicy's NID, SCE structure size, dynamic field and syscall
instruction checks are never called, ValidateSyscallAbsence is empty,
and RegisterLibraryImport only fills the library set that the unused
NID check reads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 15:16:49 +03:30
Alexej Szpevák ba4b2db238 fix(relinker): move legacy CMPSS and CMPSD out of line for Rosetta
The Rosetta split covered only VEX scalar compares. #5642 measured the
same lost load after legacy CMPSS and CMPSD, and the decoder already
hands them to the matcher with the right length and RIP offset.

Recognise F3 or F2, segment overrides and an optional REX before
0F C2 /r ib, with the same predicate rule as the VEX forms. A 66, 67 or
LOCK prefix, or both F2 and F3, keeps the instruction inline as before.
2026-10-14 11:48:11 +02:00
Alexej Szpevák 8e812f4b73 fix(relinker): report RIP-relative operands of far indirect CALL and JMP
DecodeInstruction returned before reading the ModRM of FF /3 and FF /5,
so a far CALL or JMP through a RIP-relative pointer had no RIP-relative
displacement. A stub that moved such a JMP copied the displacement
without relocating it.

The decoder was compared with iced-x86 (AMD decoding) over 3.99 million
encodings: every opcode of the legacy, 0F, 0F38 and 0F3A maps under 34
prefix combinations and the VEX 0F, 0F38 and 0F3A maps, each with 48
ModRM forms. Apart from this, it agrees on length, RIP-relative offset,
control flow kind and branch target for every valid Zen 2 instruction
except near branches with a 66 prefix, recorded as technical debt, and
kernel-only returns, which it treats as sequential.

Add the comparison's metadata as cases: one RIP-relative encoding per
opcode, map and immediate size, every FF /r form, and the branch forms
with their flow kind and target. The existing cases check lengths only;
a loop or jrcxz decoded as an unconditional jump passed the whole suite.
2026-10-14 11:33:59 +02:00
Leandro Soares 8b400afc5d fix(relinker): move hazard scalar compares out of line for Rosetta 2026-10-14 06:19:58 +00:00
Sanxchep Sharma 94ae3f1bfe perf(relinker): stream scans and reuse executable buffers 2026-10-14 07:40:54 +02:00