A RELA entry repeated with the same target, type and addend writes the
same value twice, so applying it once is equivalent. The Windows
relocation builder refused it as overlapping and code analysis as an
invalid relative relocation; the Linux output already passed it through.
Forever Launcher's game-child.bin (PPSA99030) repeats one RELATIVE entry
at 0x2df70 and could not be converted. Distinct entries at one target
are still refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Save r12 in the entry walk so TraceImport_nid_no_patch is called with an aligned stack, record only executable Mach-O segments in the module map, and make the test hook check the alignment.
Co-Authored-By: Claude Sonnet 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.
With --windows-fixed-base the PE writer bounded the end of a fixed
image by 0x7fffffffffff, so an image ending in the top 64 KiB relinked
with exit code 0 and then failed to load with
STATUS_CONFLICTING_ADDRESSES. On x64 Windows 8.1 and later the highest
user address is 0x7ffffffeffff (lpMaximumApplicationAddress), so the
exclusive end of a fixed image must not exceed 0x7fffffff0000. Such
images are now rejected with the existing user address space error;
every image that ends at or below the limit is written unchanged.
--windows replaced a guest GS memory instruction with UD2 and NOPs even
when a symbol, a relocation pointer, a lea target or a DT_INIT_ARRAY
entry started inside it, so that entry ran padding instead of the
original code. A trapped instruction that holds a collector entry root
after its first byte now fails with "GS trap would overwrite an
executable entry point" at the root's file offset, the rule that TLS
rewrites already follow. The branch check runs first, so a relative
branch into the instruction keeps its message, and outputs without such
a root are unchanged.
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.
WriteMacOsImage rejected a TLS export whose value equals PT_TLS
p_memsz with "Guest TLS export is outside the TLS block", although
Linux and Windows output convert the same guest module or executable.
The bound is now the one GuestImageReader and MacOsMachOPatcher already
apply before this point: value <= p_memsz and size <= p_memsz - value,
so the deleted check could only reject that boundary case. Such an
export now gets the __APS5DATA entry (TLS descriptor, p_memsz) in
macOS output, also at offset 0 of an empty PT_TLS that code accesses.
With --lazy-binding, guest code calls the stub of an unresolved Windows
import under the SysV ABI, so the stub starts at RSP = 8 mod 16, and it
called lstrlenA, GetStdHandle, WriteFile and RaiseException without
adjusting RSP. RaiseException then faulted with an access violation
instead of raising STATUS_ENTRYPOINT_NOT_FOUND. The Win64 calling
convention requires a 16-byte aligned RSP and 32 bytes of shadow space
at every call. Each stub now starts with and rsp,-16; sub rsp,0x40, so
after the FAIL line the program exits with 0xC0000139.
The Linux output maps a PT_LOAD whose p_flags is 0 as RWX, so the
e_entry check now accepts an entry inside such a segment instead of
rejecting it. A new test case relinks that input in both Linux modes
and checks that the output maps the entry's segment RWX. The
R_X86_64_NONE skip in the relocation offset loop is removed, together
with its none-outside case, because extractRela drops NONE before the
relocation tables are rebuilt.
Windows output replaced each guest GS memory instruction with UD2 and
NOP fill without checking the decoded direct branch targets, so a branch
past the first byte of such an instruction was converted and landed in
the second UD2 byte or the NOP fill. A branch target strictly inside a
trapped GS instruction is now refused with "Branch enters a guest GS
instruction", the rule macOS applies since #7706 and this builder
already applies to its FS rewrites. The branch targets are sorted before
the trap loop, which runs before the PT_TLS early returns, so images
without PT_TLS are covered; guest modules go through the same builder. A
branch to the first byte still converts and reaches the UD2.
RelocationWidth took the width of a COPY relocation from the output
symbol table, where every import has st_size 0, so the width was
always one byte and the two copy-* boundary cases passed for that
reason alone. #10658 rejects R_X86_64_COPY in validation, so the type
no longer reaches this check; until it merges, COPY takes the 8-byte
default like every other type. The three copy-* cases go with it.
The Linux output writes every import symbol with st_size 0 and copies
a COPY relocation into the output unchanged, so ld.so copies
MIN(st_size of the definition, 0) = 0 bytes and the variable stays
zero. Windows and macOS already rejected the type in the relocation
builder. ValidationPolicy now rejects type 5 before any target code,
so every target reports the same error, and the COPY case of the
fixed-base rebasing, which validation makes unreachable, is gone.
LinuxElfPatcher wrote --rpath into DT_RUNPATH unchanged, so an empty
entry (an empty value, a leading or trailing colon, or a double colon)
reached the output, where glibc searches the working directory for it,
while the macOS and Windows outputs reject empty run paths. The guest
module rule that each entry is absolute or begins with $ORIGIN only
looked at the start of the whole value, so "$ORIGIN/libs:" or
"$ORIGIN:relative/libs" passed. The Linux patcher now rejects an empty
entry with the message the macOS patcher uses, the guest rule checks
every colon-separated entry, and USAGE.md says so.
The Linux patcher sorted the PT_LOAD segments by address but never
checked that their memory ranges are disjoint, while the Windows and
macOS images and guest modules reject overlapping loads. The Linux
output kept both headers, the kernel mapped the later segment over the
earlier one, and the program crashed at start. Adjacent loads whose
ranges overlap now fail with "Overlapping PT_LOAD memory ranges", the
message the other targets use.