The Windows writer accepted any image up to the 2 GiB RVA bound, so an
image with a SizeOfImage above 0x77000000 converted with exit 0 and then
failed to start with ERROR_BAD_EXE_FORMAT (193). WindowsPeWriter::Write
now refuses such an image, matching the loader's SizeOfImage >
MM_SIZE_OF_LARGEST_IMAGE (0x77000000) test, which applies to executables
and to bundled modules alike. Images with a SizeOfImage of at most
0x77000000 are written unchanged.
LinuxElfPatcher::Patch zeroed only e_ident bytes 7 and 8 of the output
executable, and ElfReader checks only bytes 0 to 6, so nonzero EI_PAD
bytes 9 to 15 of the input reached the output in every Linux mode. The
gABI reserves EI_PAD as zero, and glibc's open_verify refuses an object
with nonzero padding when ld.so maps the program itself, as under ldd
or an explicit ld-linux-x86-64.so.2 invocation. The patcher now zeroes
bytes 7 to 15, the rule that #11582 applies to guest modules. Only
reserved bytes change, and a direct exec is unaffected either way.
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.
MacOsMachOPatcher::Patch discarded its lazyBinding argument, so a
module-free `--macos --lazy-binding` conversion exited 0 and wrote the
same eagerly bound Mach-O as a run without the flag. The Mach-O writer
puts every import in the LC_DYLD_INFO_ONLY bind stream and leaves the
lazy_bind stream empty, with no __la_symbol_ptr or stub helper, so the
macOS target cannot honour the flag. The patcher now throws "macOS
target does not support --lazy-binding", in the form of its
--windows-diagnostics check, so such a conversion exits 2 and writes no
output. Conversions with bundled modules were already refused by the
guest module builder.
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]>
A relinked macOS game stops in dyld before any guest code runs: libc does not
export the guest entry and TLS functions the macOS relinker's executable
imports, and AppKit only allows windows on the process main thread.
- libc: add Aps5StartGuest, which the relinker's entry stub calls with the
guest entry and the FreeBSD initial block. It runs the guest on a second
thread and keeps the main thread serving the main dispatch queue.
- libc: add the guest TLS functions the relinked code imports
(Aps5GuestTlsRegister, Aps5GuestTlsAllocateBlock, Aps5GuestTlsGetAddr,
Aps5GuestTlsUseXsave).
- libc: MainThread.hpp's RunOnMainThread runs a call on the main thread,
waits for it and rethrows its exception. It runs on the calling thread
when the main thread does not serve the main queue, as in the tests.
- libSceVideoOut: create, resize, retitle and destroy the window, initialise
SDL video, create the Vulkan surface and pump events through
RunOnMainThread on macOS.
- libc: find the process parameters through dl_iterate_phdr on macOS too,
and link CoreFoundation.
- relinker: leave 0x400 bytes after the Mach-O load commands, like ld's
-headerpad, so codesign's LC_CODE_SIGNATURE no longer overwrites the
entry stub.
Linux and Windows compile the same libc and libSceVideoOut code as before.
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 executable export loop accepted names that are empty after
stripping '#', IFUNC, FILE, COMMON and SECTION types, bindings above
STB_WEAK, st_other bits above the visibility, and same-name exports
that differ, and Linux copied them into .dynsym. It now rejects them
before the location checks with the rule GuestImageReader applies to
non-local guest symbols, while identical aliases stay accepted. Every
target reports the same line at the symbol entry offset, and the macOS
attribute and empty-name checks, now unreachable, are removed.
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.