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 <noreply@anthropic.com>
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.
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.
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.
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.
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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.
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.
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 <noreply@anthropic.com>
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.
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TFMtUC4L4wMS4eeEqaNSR
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.
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.
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 <noreply@anthropic.com>
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.
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.
The AMD-only converter moves the instructions next to a short AMD-only
instruction into its stub, and refuses to move one that a branch
enters. It knew only direct jump and call targets, so a switch case
entered through a jump table could land inside the jump written over
the moved instructions.
The converter now also reads relative jump tables: a RIP-relative LEA
whose address lies in a loaded segment starts a table of 32-bit
offsets from that address, read while each target starts an
instruction of the segment being converted. To read them, Convert
takes the program headers and converts their executable load
segments; the relinker passes all headers, and
ElfReader::ReadCodeSegments, now unused, is removed.
The converter test builds a dispatch through such a table whose second
case enters the instruction after a 4-byte 256-bit store, and checks
that the store takes the instruction before it instead. It also checks
that reading stops at an entry that does not start an instruction,
that only a LEA forms a table address, that tables are read only
inside loaded segments, that only executable load segments are
converted, and that a segment past the end of the file fails the
conversion.
PPSA12544: one 256-bit store in Il2cppUserAssemblies moved the
instruction after it, which is case 1 of a switch; it now goes through
an island. The other relinked files are unchanged.
AI-assisted: yes
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
DecodeInstruction did not treat 0F 0F as having a ModRM byte, so a
RIP-relative 3DNow! operand was not reported to the analyses and the
--to-intel stub relocation that read HasRipRelativeDisp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
0F 0F was decoded as a two-byte instruction. 3DNow! instructions are
0F 0F ModRM [SIB] [displacement] imm8, the trailing byte selecting the
operation (AMD64 Architecture Programmer's Manual Volume 3, Appendix A,
3DNow! opcodes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb