Skip to content

6l: modernise -H6 (LC_MAIN, PIE, libSystem), mirror of the 7l work#6

Open
rafael2knokia wants to merge 4 commits into
aryx:masterfrom
rafael2knokia:macho-modern-6l
Open

6l: modernise -H6 (LC_MAIN, PIE, libSystem), mirror of the 7l work#6
rafael2knokia wants to merge 4 commits into
aryx:masterfrom
rafael2knokia:macho-modern-6l

Conversation

@rafael2knokia

Copy link
Copy Markdown

Rebased subset of #5, per your comment there: keeps only the 6l /
amd64 pieces you said you'd take. The 7l / arm64 changes are dropped
since ba7132b7e (7a/7l macOS Mach-O) already covers them from a
cleaner starting point.

What's in here

Four commits on top of current master (51b21b872), all case '6' /
HEADTYPE==6 gated so ELF (-H7) and Plan 9 (-H2) outputs stay
byte-identical:

  1. 6l: emit modern Mach-O (LC_MAIN + libSystem) for -H6
    LC_MAIN entry (not LC_UNIXTHREAD), header flags
    MH_DYLDLINK|MH_TWOLEVEL, LC_LOAD_DYLIB(libSystem),
    LC_DYLD_INFO_ONLY, LC_BUILD_VERSION, LC_UUID. Raises the
    default INITTEXT from 4096+HEADR to 0x100000+HEADR so the
    image clears Linux mmap_min_addr and loads under darling.

  2. 6l: PIE/RIP-relative addressing for macOS Mach-O (-H6)
    MOV \$sym(SB) is encoded [rip+disp32] with a D_PCREL reloc
    (mod=00, rm=101) instead of the absolute disp32 SIB form; header
    sets MH_PIE. doasm() corrects the addend for any bytes emitted
    after the disp32 (RIP is relative to the end of the whole
    instruction).

  3. 6l: -I dynamic imports — call libSystem from Mach-O binaries
    New 6l -I got:remote:lib: the program defines an 8-byte GOT slot
    got in __DATA and calls through it; the linker emits an
    LC_DYLD_INFO non-lazy bind so dyld resolves remote@lib
    (libSystem, ordinal 1) into that slot at load. Lets hand-written
    amd64 binaries call libc functions -- required to run under darling,
    which emulates Darwin via libSystem and cannot translate raw amd64
    Darwin syscalls.

  4. docs: 6l -H6 modernisation notes + changes.txt entries
    Adds an amd64 section to docs/notes_macos.txt beside the existing
    arm64 one, and records the change under 0.4 in changes.txt
    referencing your ba7132b7e.

Verification

Ran locally on Linux:

$ (cd tests/s/mini && mk hello_macos_amd64.exe hello_macos_libc_amd64.exe hello_linux_amd64.exe)
$ file hello_macos_{amd64,libc_amd64}.exe
hello_macos_amd64.exe:      Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
hello_macos_libc_amd64.exe: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ darling shell ./hello_macos_libc_amd64.exe
Hello, world
$ ./hello_linux_amd64.exe                    # ELF, byte-identical to master
Hello, world

(cd tests/s/mini && mk clean && mk all) builds every existing target.
The full 7l/arm64 side (`hello_macos_arm64.exe`, ad-hoc code
signature, ADRP/ADD, linkers/lk/macho.c) comes from your master
untouched.

What's not here (versus #5)

Dropped:

  • 7l LC_MAIN / ad-hoc LC_CODE_SIGNATURE / sha256.c -- your
    ba7132b7e does the equivalent via linkers/lk/macho.c.
  • 7l -I dynimports and `hello_macos_libc_arm64.s` -- would land as
    a follow-up on your arm64 path.
  • 7l PIE via ADR -- your ba7132b7e uses ADRP/ADD, which is the
    right shape.
  • Debian packaging -- unrelated, will land separately if useful.
  • README.md prose changes -- your `57968451b` already covers this.

Known limitations still noted in the notes file:

  • __PAGEZERO is 1 MB, not Apple's 4 GB, because 6l's text layout uses
    int32 offsets. MH_PIE lets dyld slide the image regardless, so
    this is cosmetic.
  • No amd64 ad-hoc code signature. XNU still accepts unsigned amd64
    binaries; the LINKEDIT slack for codesign(1) is intentionally left
    reachable.

Original PR: #5.

Modernizes the amd64 Mach-O output of 6l, taking -H6 from an "old Mac
OS X" image (LC_UNIXTHREAD, unsigned, static) to a current-macOS one
that dyld will actually load: LC_MAIN entry point instead of the
UNIXTHREAD register-state entry, MH_DYLDLINK|MH_TWOLEVEL in the header,
a libSystem LC_LOAD_DYLIB, and LC_DYLD_INFO_ONLY, LC_BUILD_VERSION,
LC_UUID load commands.

The amd64 output stays non-PIE here (a follow-up switches 6l to
RIP-relative addressing and sets MH_PIE); the change is entirely gated
in src/cmd/ld/macho.c by `case '6'`, so 8l and other users of the same
file are unaffected.

Also raises 6l's default INITTEXT from 4096+HEADR to 0x100000+HEADR:
Linux's mmap_min_addr is 64K by default and refuses lower mappings, so
a __TEXT at 0x1088 could not be loaded by darling; 1MB clears it while
staying < 2GB, which the amd64 32-bit-absolute addressing still needs.
(Apple uses a 4GB __PAGEZERO; going that high would need a wider
INITTEXT type in 6l's text layout -- separate work.)

Mirrors the 7l/arm64 macOS support added in ba7132b ("7a/7l: produce
macOS arm64 Mach-O executables (-H6)").

Test: tests/s/mini/hello_macos_amd64.exe rebuilt with `6l -s -H6`
loads and runs under darling; ELF (-H7) and Plan 9 (-H2) outputs are
byte-identical.
Make the amd64 macho output position-independent, mirroring the arm64
work in ba7132b. A symbol memory reference (LEAQ name(SB), ...) is
now encoded RIP-relative ([rip+disp32] via a D_PCREL reloc) instead of
an absolute disp32 SIB form, and the Mach-O header sets MH_PIE. The
change is gated on HEADTYPE==6, so ELF (-H7) and Plan 9 (-H2) keep the
proven absolute encoding (verified: Linux output is byte-identical).

- span.c asmandsz(): for HEADTYPE==6, emit mod=00,rm=101 (RIP-relative)
  and mark the reloc D_PCREL. doasm() then corrects the addend for any
  immediate emitted after the disp32 (RIP is relative to the end of the
  whole instruction), via the new riprelfix.
- macho.c: OR MH_PIE into the header flags.

This is code-only PIE: a program with absolute pointers in __DATA would
still need rebase opcodes (not emitted). __PAGEZERO stays 1MB rather
than Apple's 4GB because INITTEXT>=4GB hits a separate 32-bit
truncation in 6l's text layout (no code emitted); MH_PIE lets the
loader slide the image regardless.

Verified: macOS entry disassembles to lea rax,[rip+0x3f5] resolving to
the real __DATA address; a no-syscall PIE binary (mov eax,42; ret)
loads and runs under darling and exits 42 via dyld's LC_MAIN
return->exit path.
Add "6l -I got:remote:lib" to declare a Mach-O dynamic import. The
program defines a GOT pointer slot `got` (8 bytes) in __DATA and calls
through it; the linker emits an LC_DYLD_INFO non-lazy bind opcode
stream so dyld resolves `remote` from `lib` (libSystem, ordinal 1)
into that slot at load. This lets hand-written Mach-O binaries call
libc functions instead of issuing raw Darwin syscalls -- required to
run under darling, which emulates Darwin in userspace via libSystem
and cannot translate the raw amd64 Darwin syscalls.

- src/cmd/ld/macho.c: adddynimp() + binduleb(); build the bind stream
  in asmbmacho, append it at the end of __LINKEDIT, and point
  LC_DYLD_INFO_ONLY bind_off/size at it (cflush before the direct
  write so it doesn't race machowrite's buffer).
- src/cmd/6l/obj.c: -I flag -> adddynimp().
- src/cmd/ld/macho.h: prototype.
- tests/s/mini/hello_macos_libc_amd64.s: call _write via the GOT slot.

Verified: hello_macos_libc_amd64.exe runs under darling and prints
"Hello, world". ELF and Plan 9 outputs are unaffected (dynimp entries
are only consumed by the '6' arm of asmbmacho). 7l on arm64 has its
own path added by aryx in a separate track.
Add the amd64 counterpart to the arm64 section already in
docs/notes_macos.txt: what changes 6l -H6 makes (LC_MAIN, MH_PIE,
RIP-relative addressing, -I dynamic imports), why (darling refuses
non-PIE / static / raw-syscall Mach-Os), and how to verify with
`darling shell ./hello_macos_libc_amd64.exe`.

Also record the 6l change under 0.4 in changes.txt, referencing the
7l/arm64 work in ba7132b that this mirrors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant