macOS Mach-O: modern format (LC_MAIN, PIE, ad-hoc signing, libSystem) for amd64 and 6l/7l#5
Open
rafael2knokia wants to merge 6 commits into
Open
macOS Mach-O: modern format (LC_MAIN, PIE, ad-hoc signing, libSystem) for amd64 and 6l/7l#5rafael2knokia wants to merge 6 commits into
rafael2knokia wants to merge 6 commits into
Conversation
Ships the full toolchain (compilers, assemblers, linkers, mk, rc, yacc, acid, emulators) under /usr/lib/goken9cc. The install prefix is baked into the binaries at build time via -DGOROOT=/usr/lib/goken9cc so the runtime does not need to export GOROOT, which would otherwise clash with the Go toolchain. debian/rules patches lib_core/lib9/mkfile in place to drop the unconditional ROOTDIR= assignment (mk's parser and recursive MKFLAGS do not let us override it cleanly from the command line), restores it on clean, and keeps a .debian-orig backup; both the backup pattern and the dh build artifacts are gitignored. /etc/profile.d/goken9cc.sh only appends the host arch's bin/ to PATH (so Plan 9 cat/ls/grep/... do not shadow the system ones) and sets MKSHELL, INCLUDE, ccroot to point under the install prefix. Built and tested on amd64; arm64 is declared but not yet exercised.
Add arm64 Mach-O output to 7l (-H6, previously stubbed out) and modernize
the macOS format produced by both linkers so it targets current macOS
instead of old Mac OS X.
- 6l (amd64) and 7l (arm64): use an LC_MAIN entry instead of LC_UNIXTHREAD,
flag the binary MH_DYLDLINK|MH_TWOLEVEL, link /usr/lib/libSystem.B.dylib,
and add LC_DYLD_INFO_ONLY, LC_BUILD_VERSION and LC_UUID.
- 7l: emit an ad-hoc LC_CODE_SIGNATURE (embedded SuperBlob + SHA-256
CodeDirectory over the whole image, computed in a second pass), since
Apple Silicon's kernel requires every binary to be signed. Adds a
self-contained sha256.c. The signing identifier is the output basename.
- Load at a 1MB __PAGEZERO (__TEXT at 0x100000), non-PIE: high enough to
clear Linux's mmap_min_addr so the image can be mapped by Linux-based
loaders (verified loading+executing under darling), low enough (< 2GB) for
the amd64 32-bit-absolute addressing and the classic linker's int32
INITTEXT. A real 0x100000000 __PAGEZERO would need 64-bit address types.
- tests/s/mini: add hello_macos_arm64.s and build hello_macos_{amd64,arm64}
with -H6 (without it a non-darwin host emitted ELF, not Mach-O).
Verified structurally with a Mach-O parser; the arm64 CodeDirectory page
hashes match the file image (AMFI would accept it). darling's loader maps
and runs the amd64 binary; full execution there needs libSystem calls
rather than the raw Darwin syscalls these hello-world tests use.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aff065e to
8b0a611
Compare
Make the amd64 macho output position-independent. A symbol memory reference (LEAQ name(SB), ...) is now encoded RIP-relative ([rip+disp32] via a D_PCREL reloc) instead of an absolute disp32, 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 SIB 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 (now 0x200085). 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. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add "6l/7l -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 rather than issuing raw Darwin
syscalls.
- src/cmd/ld/macho.c (6l): adddynimp() + binduleb(); build the bind stream in
asmbmacho, append it to __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).
- linkers/7l/asm.c (7l): same, placed before the ad-hoc code signature so the
bind data is covered by the CodeDirectory hashes.
- obj.c (both): -I flag -> adddynimp().
- tests/s/mini/hello_macos_libc_{amd64,arm64}.s: call _write via the GOT slot.
Verified: the amd64 binary runs under darling and prints "Hello, world" (darling
emulates Darwin via libSystem and cannot run the raw-syscall variant). The arm64
binary is structurally complete — bind opcodes well-formed, ad-hoc signature
still valid over the image including the binds — but is runtime-untested here
(no arm64 execution on an x86 host) and still non-PIE (7l uses a literal pool),
so real Apple Silicon would likely need ADRP-based PIE codegen first.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make arm64 macho output position-independent, the analog of 6l's RIP-relative change. Under HEADTYPE==6, omovlit() materialises a symbol address with a PC-relative ADR (dr <- pc + (symaddr-pc), ±1MB) instead of loading its absolute value from the read-only literal pool (which dyld can't rebase). This covers both MOV $sym(SB),R (ADR dr,sym) and the address half of MOV sym(SB),R (ADR REGTMP,sym; LDR R,[REGTMP]); the static base in R28/SB is itself set via ADR, so SB-relative loads are slide-independent too. The header now sets MH_PIE. Verified by disassembly: hello_macos_libc_arm64 materialises setSB, msg and writep entirely PC-relative, and the ad-hoc code signature is still valid over the image. Still runtime-untested here (no arm64 execution on an x86 host) - needs Apple Silicon. ELF/Plan 9 are unaffected (gated on HEADTYPE==6). Also removes a stray debug fprint left in omovlit(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Modernizes the macOS Mach-O output of the amd64 (
6l) and arm64 (7l)linkers, taking
-H6from an "old Mac OS X" image (LC_UNIXTHREAD,raw syscalls, unsigned, absolute addressing) to a current-macOS one:
LC_MAINentry, position-independent (PIE), code-signed on arm64, and ableto call libSystem instead of issuing raw syscalls.
The amd64 output is verified running end-to-end under
darling (
darling shell ./helloprintsHello, world); the arm64 output is structurally verified (it can't beexecuted on the x86 CI host).
What changed
1. Modern load commands + ad-hoc code signing (
8b0a61186)6l/7l: emitLC_MAINinstead ofLC_UNIXTHREAD; flagMH_DYLDLINK|MH_TWOLEVEL; link/usr/lib/libSystem.B.dylib; addLC_DYLD_INFO_ONLY,LC_BUILD_VERSION,LC_UUID.7l: emit an ad-hocLC_CODE_SIGNATURE— an embedded SuperBlob with aSHA-256
CodeDirectoryover the whole image (new self-containedsha256.c,two-pass write), since Apple Silicon's kernel (AMFI) requires every binary
to be signed.
2. PIE / RIP-relative addressing — amd64 (
d5e9d2389)MOV $sym(SB)is encoded RIP-relative ([rip+disp32], aD_PCRELreloc)instead of an absolute
disp32; header setsMH_PIE. Gated onHEADTYPE==6, so ELF (-H7) and Plan 9 (-H2) keep the proven absoluteencoding (Linux output is byte-identical).
3.
-Idynamic imports — call libSystem instead of raw syscalls (21020ce6c)6l/7l -I got:remote:libflag: the program defines an 8-byte GOT slotgotin__DATAand calls through it; the linker emits anLC_DYLD_INFOnon-lazy bind so dyld resolves
remotefromlib(libSystem, ordinal 1)into that slot at load.
tests/s/mini/hello_macos_libc_{amd64,arm64}.scall_writethis way.4. PIE / ADR addressing — arm64 (
6c256ad67)omovlit()materialises a symbol address with a PC-relativeADR(±1 MB)instead of loading its absolute value from the read-only literal pool, so
MOV $sym(SB),Rand the address half ofMOV sym(SB),Rbecomeslide-independent (the
R28/SB base is set viaADRtoo); header setsMH_PIE.Verification
raw-syscall and the libSystem variants load; the libSystem one prints
Hello, worldand exits 0. Disassembly confirms RIP-relative addressing;filereportsMach-O 64-bit x86_64 executable, flags:<…|PIE>.ADR); thead-hoc
CodeDirectorypage hashes match the file image (AMFI would acceptit);
LC_DYLD_INFObind opcodes are well-formed.tests/s/minisuite (mk all) builds clean; Linux ELF and Plan 9targets are unaffected.
Known limitations
emulation. It's structurally complete but should be confirmed on Apple
Silicon.
__PAGEZEROis 1 MB, not Apple's 4 GB.6l's text layout 32-bit-truncates at a 4 GB load address (a separate bug), and
7l'sINITTEXTis
int32. WithMH_PIEthe loader can slide the image regardless, butit's a deviation from a stock macOS binary.
__DATAwould stillneed rebase opcodes, which aren't emitted yet (the hello-world tests have
none).
which emulates Darwin in userspace via libSystem — hence the
-I/libSystemvariant for darling.