Skip to content

epic: dynamic libSystem-linked darwin executables (Apple Silicon rejects static images) #1718

Description

@octalide

Status (2026-06-28)

Scoped (see the investigation findings comment). The exec gate on Apple Silicon is
PIE, proven on macos-14 — not signing, load commands, or libSystem. Awaiting a
direction call: commit to the PIE work / x86_64-interim / shelve. #1680 aarch64 leg
and #1716 are blocked on this. Not started.

What

macOS on arm64 (Apple Silicon) refuses to exec a non-PIE main executable. Proven
on a macos-14 runner (#1680 / scope investigation): EVERY mach variant — static,
dynamic+libSystem+LC_UNIXTHREAD+raw-svc, +LC_MAIN, +LC_BUILD_VERSION, +LC_UUID,
+MH_PIE flag, +libSystem call — is Killed: 9, both mach-signed and after Apple
codesign -s -. System clang on the same runner: clang -nostdlib raw-svc with
zero dylibs runs (→42); clang -no_pie emits ld: warning: -no_pie ignored for arm64 — the linker forces PIE. mach emits a non-PIE fixed-absolute image (__TEXT at
0xfffff000, empty rebase stream), so the kernel kills it.

What this rules OUT (shrinks the epic): raw svc works → no libSystem syscall
routing; classic LC_DYLD_INFO works → no chained-fixups; a dyld-linked image with
zero dylibs runs → no libSystem dependency. The only missing hard requirement is
genuine PIE.

Goal: emit a position-independent darwin executable so arm64 binaries exec on Apple
Silicon and the compiler self-hosts there.

Sized scope (from the investigation)

  • mach (codegen/link) — LARGE. Emit a real PIE Mach-O: MH_PIE + a relocatable
    base/layout, LC_MAIN / LC_BUILD_VERSION / LC_UUID (all prototyped on the scope
    branch), and the dominant cost — a rebase stream (classic LC_DYLD_INFO rebase
    opcodes) for every in-image absolute pointer (globals, vtable fn-pointers, absolute
    data relocs). arm64 TEXT is already PC-relative; the work is in absolute DATA
    pointers and the linker collecting them as rebase records. Route the darwin
    self-host through this writer (no imports ⇒ zero dylibs/stubs/got/bind).
  • mach-std (os:darwin) — SMALL. Keep raw svc; only change the darwin arm64 entry
    from the LC_UNIXTHREAD stack convention ([sp]=argc) to the LC_MAIN register
    convention (x0=argc, x1=argv, x2=envp).

Children

Sequencing

mach PIE writer + mach-std LC_MAIN entry, validated together on a macos-14 runner.
Then #1680 aarch64. The #1717 static-writer header fix already landed (correct for
static x86_64-darwin + freestanding); x86_64-darwin tolerates non-PIE/static and is
unaffected by this epic.

Future follow-ons (enabled, NOT in scope here)

The BaseReloc set #1722 adds to the linker is format-neutral, so once it's proven on
macOS, two OPTIONAL ASLR-hardening follow-ons become small encoder issues consuming the
same set (to be filed against the proven interface, parallelizable with each other):

  • ELF PIE (linux ASLR): ET_DYN + R_*_RELATIVE in .rela.dyn. Caveat — mach's
    no-libc static linux binaries also need a self-relocation startup (apply RELATIVE
    relocs before main, since there's no ld.so), so this is more than the encoder.
  • PE/COFF ASLR (windows): .reloc base-relocation blocks + DYNAMIC_BASE. Closer
    to pure-encoder.

Neither is forced — linux/windows run fixed-address fine — so both are deferred until
ASLR hardening is wanted. Captured here so the generalization isn't lost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:codegenIR/middle-end, isel, regalloc, frame, encoders, inline asmarea:linklinker, object formats, target/ABI plumbingepicumbrella issue tracking a family of sub-issuesfeaturenew capability, or an extension of an existing onetarget:darwinmacOS-specific behavior or work

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions