Addresses the #12591 review. Because the Windows image ORs page permissions,
refuse a congruent overlap when one segment is executable and the other
writable and the later segment does not start on a page boundary, which would
otherwise share a read-write-execute page; the rule is applied on both loaders
so their acceptance decision matches. Bound the congruence check to the
file-backed part of the overlap and compare spans rather than walking the
implicit zero bytes of a large bss overlap.
Expand test_executable_segments.py: byte-congruent tail and bss overlaps
accepted with their trimmed sizes and no output overlap; a write-execute
overlap refused at an unaligned boundary and accepted at an aligned one with
the Windows image carrying no write-execute section; differing bytes, an
overlap past the later end, a non-zero file byte over the later segment's bss,
and a shared start address refused; and a no-overlap control. Keep only the
PPSA ids in the relinker.md note.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Some homebrew toolchains emit an eboot whose read-only PT_LOAD runs over the
GNU_RELRO start of the following read-write PT_LOAD, with byte-identical content
over the overlap. The relinker refused every such title with "Overlapping
PT_LOAD memory ranges" on both the Linux and Windows paths (Photo Splat VR
PPSA99770 #12183, FoxHollow PPSA99573, WWE 13 PPSA92013).
When an earlier PT_LOAD's tail overlaps a later one, the overlapping bytes are
identical, and the overlap does not extend past the later segment, trim the
earlier segment to end where the later one starts so the later segment owns the
shared range. On Linux the preserved PT_GNU_RELRO makes that range read-only
after relocation, matching the earlier read-only mapping; the Windows image
builder has no RELRO step, so the shared range stays writable (noted in
docs/dev/debt/relinker.md).
Overlaps whose bytes differ (the #10564 case), that extend past the later
segment, or that share a start address with it are still refused on every
target; the last case would otherwise trim the earlier segment to zero length.
test_executable_segments.py covers the accepted tail overlap and all three
refused cases.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Family fallback entries appended for kernel-variant imports were marked
imported, so the startup stub hard-required every family member; a
partial libs/ without the unused sibling aborted with a load failure.
Track fallback positions separately so only the declared module and
canonical libkernel.prx stay required while siblings probe and skip.
Adds missing-sibling cases pinning expected providers (11 for the
declared variant, 22 for canonical libkernel.prx) and trims the debt
entry below the 1000-character limit.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The debt entry on pointer fixups in executable segments claimed that
dyld refuses them, which no run had shown. It now states what was
measured on an M1 Pro running macOS 27.0.1 under Rosetta 2: dyld_info
reports both cases, the PF_R|PF_W|PF_X case loads and runs, and a
fixup into a PF_R|PF_X segment ends with SIGBUS in dyld. The
interpreter_in_code fixture keeps a read-only data PT_LOAD for the
Windows runs, so PE output still covers a RELATIVE into it.
The macOS image writer emitted dyld rebase and bind opcodes for any
target inside a segment, so an R_X86_64_RELATIVE into an R-X PT_LOAD
became a rebase into a read-execute segment. dyld-1378 refuses fixups
in segments without write access (enforceFixupsInWritableSegments), so
Locate now throws when the target segment lacks VM_PROT_WRITE. Such
inputs now fail the macOS conversion instead of writing an output that
dyld would not load.
In a leaf without a frame pointer, --frame-red-zone lowers RSP after
the prologue and adds the shift to every RSP operand in the body,
assuming RSP does not move between the prologue and the exits. It
refused explicit RSP arithmetic but not opcode-encoded pushes and pops,
so after "push rax" in the body the read of the pushed slot at [rsp]
became [rsp+0x10], a red-zone slot, and "pushfq; and qword [rsp], -2;
popfq" changed that slot instead of the saved flags. Such a leaf now
keeps its red zone and is reported as "push or pop inside the body":
push and pop of a register, push imm, push r/m, pop r/m, pushf, popf
and push or pop of fs or gs outside the exits. The pops of an exit
still frame, and leaves with a frame pointer, whose RSP operands are
already refused, still frame when they push and pop.
The entry for the M5 running macOS 26.4 did not say what Rosetta does
with SHA or AVX-512, and the XSAVE entry was measured only on macOS
27.0.1. On that host sha1rnds4 and a 256-bit-wide vpaddd on zmm raise an
illegal instruction alongside RDSEED, CLWB, RDPID, ADCX and ADOX, while
XSAVE writes an XSTATE_BV of 0x7 and XGETBV returns the same x87, SSE
and AVX mask although CPUID leaf 1 still reports neither XSAVE nor
OSXSAVE.
The BMI1 and BMI2 results were compared with the Intel SDM definitions
over 200,000 random inputs each for PDEP, PEXT, BEXTR, BZHI, LZCNT and
TZCNT, so the entry can say they compute the documented result rather
than only that they avoid a fault.
A Windows conversion now fails when a code entry lies after the first
byte of a guest GS instruction that the TLS builder replaces with UD2.
The new debt entry names both messages, says that no option skips the
checks and that Linux output keeps the instruction, so a user who hits
the refusal knows which target to use.
Read the unwind functions of an image that only has PT_GNU_EH_FRAME so
inter-FDE data tables are still found and masked, but leave the syscall
and call-site scan ranges empty without PT_SCE_DYNLIBDATA as main does.
The fixture now emits standard dynamic tags for its SysV block, and the
debt entry stays within the 1000 character limit.
In PS5 Prospero PRX binaries, compilers place embedded constant data tables (such as the OpenSSL Whirlpool lookup table in ubiservices.prx) in inter-FDE gaps after function code, referenced via RIP-relative LEA instructions.
- In RelativeJumpTables, detect constant data tables in inter-FDE gaps that begin with padding, contain no direct branch targets, and contain undecodable non-code bytes, distinguishing them from valid unannotated code routines and multi-hop gap branches.
- In GuestModuleBuilder and RelinkerPipeline, extract unwind functions from PT_GNU_EH_FRAME as well as PT_SCE_DYNLIBDATA, and mask embedded data tables with 0x90 in scan buffers before SyscallScanner.
- In CodeInstructionCollector, populate table boundaries whenever unwind functions are present to prevent addAddressTakenRoot from adding data table addresses to CFG roots on Windows and macOS.
- Update relinker technical debt documentation and add regression tests.
Fixes#7547
Unblocks: PPSA28155 #7547
The debt entry said that shrink-wrapped leaves, which push later and pop in
balance, could be framed. They cannot as the framer works now: lowering RSP on
entry moves their later pushes onto the spills, which keep their addresses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Walk each macOS function import before the guest entry and pass it through TraceImport_nid_no_patch, as Linux and Windows already do. An x86-64 libc build records the call and prints it on SIGABRT.
The Linux output copied a nonzero r_addend of GLOB_DAT and JUMP_SLOT
relocations unchanged into DT_RELA and DT_JMPREL, while the Windows,
macOS and guest module paths refuse it. The x86-64 psABI computes both
types as S, but glibc before f8587a61892c applies S+A when binding
eagerly, so the result depended on the host glibc and, for JUMP_SLOT,
on the binding mode. The Linux patcher now refuses such an entry in all
three Linux modes with the Windows message and the unrebased r_offset.
Entries with a zero addend are emitted as before.
Windows output accepted an executable import relocation whose target
overlaps the PT_TLS initialization image, and every guest thread read 0
there because the .gtls template is copied before the entry stub binds
the import. In ELF, ld.so relocates the image before copying it into a
thread's TLS block. The PE patcher now refuses such a relocation after
the relocation pass, as the Windows guest module writer already does,
and reports its ELF target address. Linux and macOS outputs are
unchanged, and the gap is recorded in docs/dev/debt/relinker.md.
The check that refuses leaves without pushes that push or pop also refused
`push rbp; mov rbp, rsp` leaves without other pushes, whose `pop rbp` it
counted, since rbp is not in their list of pushes. A unit test and an
execution test now cover such a leaf with a 4-byte prologue and a 2-byte
exit. The debt entry also notes that the check refuses shrink-wrapped leaves
whose later pushes and pops balance.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
--frame-red-zone replaces a leaf's prologue and each exit with a 5-byte jump
to a stub, so it skipped leaves whose pushes or pop-and-ret exit were shorter
than that, and leaves with neither pushes nor an allocation. A short prologue
now takes in the straight-line instructions after it and a short exit those
before it, re-encoded in the stub with the moved displacement. Widening stops
at branches, rets, RIP-relative instructions, pushes and pops, and the leaf is
skipped when a branch targets the instructions taken in or the first
instruction of a widened prologue, or when a leaf without pushes pushes or
pops.
Co-Authored-By: Claude Opus 5.5 <[email protected]>