kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

link.c (102226B)


      1 /* link_emit_macho — write a dyld-loadable arm64 MH_EXECUTE.
      2  *
      3  * Mach-O peer of link_emit_elf.  Produces a position-independent
      4  * MH_EXECUTE that links against libSystem.B.dylib (or any other
      5  * dylib/.tbd input) via LC_LOAD_DYLIB + LC_DYLD_CHAINED_FIXUPS.  The
      6  * binary is ad-hoc codesigned at the tail so the kernel will exec it
      7  * on macOS 11+.
      8  *
      9  * Layout (Apple's stock arm64 layout):
     10  *
     11  *   __PAGEZERO  vmaddr 0, vmsize 0x100000000, no file bytes
     12  *   __TEXT  (R-X)
     13  *     mach_header_64
     14  *     load commands
     15  *     [SF_EXEC sections — .text]
     16  *     [SF_ALLOC R-only sections — .rodata, init/fini_array, etc.]
     17  *     __stubs (12B per import-func)
     18  *   __DATA_CONST  (RW initially, dyld marks R-only after fixups)
     19  *     __got    (8B per import — both data and func imports)
     20  *   __DATA  (R-W)
     21  *     [SF_WRITE sections — .data, .bss]
     22  *   __LINKEDIT  (R)
     23  *     dyld_chained_fixups blob
     24  *     dyld_exports_trie blob
     25  *     function starts (empty)
     26  *     data in code (empty)
     27  *     symtab
     28  *     indirect symbol table (one entry per __stubs and __got slot)
     29  *     strtab
     30  *     code signature
     31  *
     32  * Imports are routed:
     33  *   CALL26/JUMP26 against an imported function -> __stubs entry
     34  *   GOT_LOAD_PAGE21/PAGEOFF12 against any import -> __got slot
     35  *   ABS64 against an imported symbol            -> chained-bind at site
     36  *   ABS64 against a defined internal symbol     -> chained-rebase at site
     37  *
     38  * arm64-only.  x86_64-macos arrives with x64 codegen. */
     39 
     40 #include "link/link.h"
     41 
     42 #include <string.h>
     43 
     44 #include "core/bytes.h"
     45 #include "core/heap.h"
     46 #include "core/pool.h"
     47 #include "core/sha256.h"
     48 #include "core/slice.h"
     49 #include "core/util.h"
     50 #include "core/vec.h"
     51 #include "link/link_arch.h"
     52 #include "link/link_internal.h"
     53 #include "link/link_reloc_desc.h"
     54 #include "obj/bytebuf.h"
     55 #include "obj/format.h"
     56 #include "obj/macho/macho.h"
     57 
     58 /* ---- constants ---- */
     59 #define MZ_PAGEZERO 0x100000000ULL
     60 #define MZ_PAGE 0x4000ULL
     61 #define MZ_GOT_SIZE 8u
     62 /* __DATA,__thread_ptrs slot size — one pointer per unique TLV referenced
     63  * via TLVP_LOAD_PAGE21/PAGEOFF12. Each slot holds the address of the
     64  * matching TLV descriptor in __DATA,__thread_vars. */
     65 #define MZ_TLVP_SIZE 8u
     66 
     67 #define DYLD_CHAINED_PTR_64 2u
     68 #define DYLD_CHAINED_IMPORT 1u
     69 
     70 #define VM_PROT_READ 0x1u
     71 #define VM_PROT_WRITE 0x2u
     72 #define VM_PROT_EXECUTE 0x4u
     73 
     74 #define CS_MAGIC_EMBEDDED_SIGNATURE 0xfade0cc0u
     75 #define CS_MAGIC_CODEDIRECTORY 0xfade0c02u
     76 #define CSSLOT_CODEDIRECTORY 0u
     77 #define CS_HASHTYPE_SHA256 2u
     78 #define CS_SHA256_LEN SHA256_DIGEST_LEN
     79 /* Default code-signing hash page size (log2). Apple's tools sign with a page
     80  * size that matches the target's native VM page: 4 KiB (12) on x86_64, but
     81  * 16 KiB (14) on Apple-Silicon arm64. The kernel's loader validates the main
     82  * executable against its own (16 KiB) pages, so a 4 KiB-page signature is
     83  * rejected as invalid on arm64 once the image is large enough to span the
     84  * mismatch — small images can slip through, large ones SIGKILL with "invalid
     85  * signature". MCtx.cs_page_log2 carries the per-target choice; this is the
     86  * x86_64 fallback. */
     87 #define CS_PAGE_SIZE_LOG2 12u
     88 #define CS_EXECSEG_MAIN_BINARY 1u
     89 
     90 /* extra LC ids */
     91 #define LC_DYLD_INFO_ONLY (0x22u | 0x80000000u)
     92 #define LC_FUNCTION_STARTS_C 0x26u
     93 #define LC_DATA_IN_CODE_C 0x29u
     94 #define LC_CODE_SIGNATURE_C 0x1du
     95 
     96 /* ---- byte buffer ----
     97  * Linkedit tables / load commands / the output file are staged in the shared
     98  * ObjByteBuf (objbb_*). */
     99 
    100 /* ---- imports + dylibs ---- */
    101 
    102 typedef struct MachImp {
    103   LinkSymId sym;
    104   Sym name;
    105   u32 dylib_ord;    /* 1-based ordinal into LC_LOAD_DYLIB list */
    106   u32 stub_idx;     /* 1-based index into __stubs (0 if data import) */
    107   u32 got_idx;      /* 1-based index into __got */
    108   u32 imports_strx; /* offset into chained-fixups symbol pool */
    109   u8 is_func;
    110   u8 weak;
    111   /* internal=1 means this entry is an in-image symbol that's referenced
    112    * via GOT_LOAD_PAGE21 / LD64_GOT_LO12_NC (clang emits these for any
    113    * extern global so a single static-link can later become PIC).  The
    114    * GOT slot stores the symbol's image-relative vaddr and gets a
    115    * chained-fixup rebase entry (or no entry at all for a weak-undef
    116    * resolving to NULL).  No dylib_ord / stub_idx / chained-fixup bind. */
    117   u8 internal;
    118 } MachImp;
    119 
    120 typedef struct MachDylib {
    121   Sym install;
    122 } MachDylib;
    123 
    124 /* One slot in the synthetic __DATA,__thread_ptrs section per unique TLV
    125  * descriptor referenced via TLVP_LOAD_PAGE21/PAGEOFF12.  Modeled after
    126  * MachImp's internal-GOT entries: the slot holds the descriptor address
    127  * (REBASE for internal-to-image descriptors, BIND for dylib-imported
    128  * ones).  The descriptor itself is laid out in __DATA,__thread_vars by
    129  * either the input objects (internal) or the providing dylib (imported). */
    130 typedef struct MachTlv {
    131   LinkSymId sym; /* canonical descriptor LinkSymId */
    132   u32 tlv_idx;   /* 1-based slot index in __thread_ptrs */
    133   u8 imported;   /* 1 == descriptor lives in a dylib (BIND), 0 == internal
    134                     (REBASE) */
    135   u8 pad[3];
    136   u32 import_idx; /* 1-based MachImp index when imported (for chained-bind
    137                      ordinal) */
    138 } MachTlv;
    139 
    140 /* ---- planned section ---- */
    141 
    142 typedef struct MSec {
    143   /* Source: either a LinkSection (link_sec_id != 0) or a synthetic
    144    * pre-built byte buffer (data + size). */
    145   LinkSectionId link_sec_id;
    146   const u8* synth_data;
    147   u32 synth_size;
    148   /* Mach-O placement */
    149   const char* segname;
    150   const char* sectname;
    151   /* Inline storage for segname/sectname when split from a Mach-O
    152    * `__SEG,__sect`-form LinkSection name.  Names from string literals
    153    * (synth sections, derived-from-flags defaults) point at .rodata
    154    * and don't use these.  17 bytes: the on-disk field is a fixed 16
    155    * (no NUL needed there), but these are read as C strings, so a full
    156    * 16-char name (e.g. __debug_line_str) needs the extra NUL slot. */
    157   char segname_buf[17];
    158   char sectname_buf[17];
    159   u64 vaddr;
    160   u64 file_offset;
    161   u64 size;
    162   u32 align;
    163   u32 flags; /* S_TYPE | S_ATTR_* */
    164   u32 reserved1;
    165   u32 reserved2;
    166   u8 segidx; /* 1=__TEXT, 2=__DATA_CONST, 3=__DATA */
    167   u8 is_zerofill;
    168   u8 pad[6];
    169 } MSec;
    170 
    171 static void msec_repair_name_ptrs(MSec* m) {
    172   if (m->segname_buf[0]) m->segname = m->segname_buf;
    173   if (m->sectname_buf[0]) m->sectname = m->sectname_buf;
    174 }
    175 
    176 /* Segment slot indices in MCtx.segs[].  __DWARF carries the file-only
    177  * .debug_* sections (debug-info retention); it sits before __LINKEDIT so
    178  * the ad-hoc code signature stays the last bytes of the file. */
    179 enum {
    180   MSEG_PAGEZERO = 0,
    181   MSEG_TEXT = 1,
    182   MSEG_DATA_CONST = 2,
    183   MSEG_DATA = 3,
    184   MSEG_DWARF = 4,
    185   MSEG_LINKEDIT = 5,
    186   MSEG_COUNT = 6,
    187 };
    188 
    189 typedef struct MSeg {
    190   const char* name;
    191   u32 maxprot;
    192   u32 initprot;
    193   u64 vmaddr;
    194   u64 vmsize;
    195   u64 fileoff;
    196   u64 filesize;
    197   u32 nsects;    /* MSec count in segment — internal layout */
    198   u32 first_sec; /* first index into MSec[] */
    199   u32 nouts;     /* OutSec count in segment — what hits the file */
    200   u32 first_out; /* first index into OutSec[] */
    201 } MSeg;
    202 
    203 /* On-disk section view: one record per (segname, sectname) within a
    204  * segment.  Mach-O requires this — emitting one section_64 per input
    205  * MSec yields sibling __TEXT,__text records that violate the spec.
    206  * Built from MSec[] after vaddr placement; reloc-apply still uses
    207  * MSec[] for byte-buffer addressing. */
    208 typedef struct OutSec {
    209   const char* segname;
    210   const char* sectname;
    211   u64 vaddr;
    212   u64 file_offset;
    213   u64 size;
    214   u32 align;
    215   u32 flags;
    216   u32 reserved1;
    217   u32 reserved2;
    218   u8 segidx;
    219   u8 is_zerofill;
    220 } OutSec;
    221 
    222 /* ---- main context ---- */
    223 
    224 typedef struct MCtx {
    225   LinkImage* img;
    226   Compiler* c;
    227   Heap* h;
    228   Writer* w;
    229   Linker* linker;
    230   const LinkArchDesc* link_arch;
    231   const ObjMachoArchOps* macho;
    232 
    233   /* imports */
    234   MachImp* imports;
    235   u32 nimports;
    236   u32 nimports_real; /* count of imports with internal=0 (== prefix length;
    237                       * collect_imports appends internal=1 entries last) */
    238   u32 nimport_funcs;
    239   MachDylib* dylibs;
    240   u32 ndylibs;
    241   /* sym->import index, 1-based, 0 = not an import. Sized to LinkSymId space
    242    * + 1. */
    243   u32* sym_to_imp;
    244   u32 sym_to_imp_size;
    245 
    246   /* sections + segments */
    247   MSec* secs;
    248   u32 nsecs;
    249   /* link_sec_id (1-based) -> backing MSec, NULL where none. Built once after
    250    * plan_layout so shift_sections / patch_ptr map a section id to its MSec in
    251    * O(1) instead of a linear scan of secs per reloc (was O(nrelocs*nsecs)). */
    252   MSec** by_link_sec;
    253   u32 by_link_sec_cap; /* img->nsections + 1 */
    254   OutSec* outs;
    255   u32 nouts;
    256   MSeg segs[MSEG_COUNT]; /* PAGEZERO, TEXT, DATA_CONST, DATA, DWARF, LINKEDIT */
    257   u32 nsegs;
    258 
    259   /* Synthetic byte buffers, owned. */
    260   u8* stubs_bytes;
    261   u32 stubs_size;
    262   u8* got_bytes;
    263   u32 got_size;
    264   /* TLV pointer slots — one entry in __DATA,__thread_ptrs per unique
    265    * descriptor referenced via TLVP_LOAD_PAGE21/PAGEOFF12.  sym_to_tlv
    266    * maps LinkSymId → 1-based slot index (parallel to sym_to_imp).  Slot
    267    * bytes are populated at apply_relocs time once shift_sections has
    268    * pinned descriptor vaddrs. */
    269   MachTlv* tlv_slots;
    270   u32 ntlv;
    271   u32* sym_to_tlv;
    272   u32 sym_to_tlv_size;
    273   u8* tlv_ptrs_bytes;
    274   u32 tlv_ptrs_size;
    275   u64 tlv_ptrs_vaddr;
    276   /* Vaddr of the first thread-local-storage section
    277    * (__thread_data / __thread_bss).  Each TLV descriptor's word 2
    278    * stores the symbol's offset within this image rather than an
    279    * absolute address — see apply_relocs's S_THREAD_LOCAL_VARIABLES
    280    * ABS64 special case. */
    281   u64 tls_image_vaddr;
    282   u8 has_tls_image;
    283 
    284   /* Final layout (computed during plan) */
    285   u64 text_vaddr;
    286   u64 stubs_vaddr;
    287   u64 got_vaddr;
    288   u64 linkedit_vaddr;
    289   u64 linkedit_fileoff;
    290   u32 entry_offset; /* offset of entry within __TEXT segment */
    291 
    292   u64 headers_size; /* header + loadcmds */
    293 
    294   /* LINKEDIT contents */
    295   ObjByteBuf chained_fixups;
    296   ObjByteBuf exports_trie;
    297   ObjByteBuf symtab; /* binary nlist_64 array */
    298   ObjByteBuf strtab;
    299   ObjByteBuf indirect; /* u32 array */
    300   ObjByteBuf fn_starts;
    301   ObjByteBuf data_in_code;
    302   ObjByteBuf codesig;
    303 
    304   u32 chained_fixups_off;
    305   u32 exports_trie_off;
    306   u32 fn_starts_off;
    307   u32 data_in_code_off;
    308   u32 symtab_off;
    309   u32 indirect_off;
    310   u32 strtab_off;
    311   u32 codesig_off;
    312   u32 codesig_size;
    313   u8 cs_page_log2; /* code-signing hash page size, log2 (12=4K x64, 14=16K arm64) */
    314   u32 nsyms;
    315 
    316   u8 uuid[16];
    317 } MCtx;
    318 
    319 /* ---- helpers for finding LinkSymbol vaddr ---- */
    320 
    321 static LinkSymbol* sym_at(LinkImage* img, LinkSymId id) {
    322   if (id == LINK_SYM_NONE || id > LinkSyms_count(&img->syms)) return NULL;
    323   return LinkSyms_at(&img->syms, id - 1);
    324 }
    325 
    326 /* ---- pass: collect imports ---- */
    327 
    328 static u32 dylib_ordinal_of(MCtx* x, Sym install) {
    329   for (u32 j = 0; j < x->ndylibs; ++j)
    330     if (x->dylibs[j].install == install) return j + 1u;
    331   return 0;
    332 }
    333 
    334 static void collect_imports(MCtx* x) {
    335   LinkImage* img = x->img;
    336   Heap* h = x->h;
    337 
    338   x->sym_to_imp_size = LinkSyms_count(&img->syms) + 1u;
    339   x->sym_to_imp =
    340       (u32*)h->alloc(h, sizeof(u32) * x->sym_to_imp_size, _Alignof(u32));
    341   if (!x->sym_to_imp)
    342     compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on sym_to_imp");
    343   memset(x->sym_to_imp, 0, sizeof(u32) * x->sym_to_imp_size);
    344 
    345   u32 cap = 0, cap_d = 0;
    346   for (u32 i = 0; i < LinkSyms_count(&img->syms); ++i) {
    347     LinkSymbol* s = LinkSyms_at(&img->syms, i);
    348     if (!s->imported) continue;
    349     if (s->name == 0) continue;
    350     LinkSymId canon = symhash_get(&img->globals, s->name);
    351     if (canon != LINK_SYM_NONE && canon != s->id) continue;
    352     if (VEC_GROW(h, x->imports, cap, x->nimports + 1u))
    353       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on imports");
    354     MachImp* mi = &x->imports[x->nimports++];
    355     memset(mi, 0, sizeof(*mi));
    356     mi->sym = s->id;
    357     mi->name = s->name;
    358     mi->is_func = (s->kind == SK_FUNC || s->kind == SK_IFUNC) ? 1 : 0;
    359     mi->weak = (s->bind == SB_WEAK) ? 1 : 0;
    360     x->sym_to_imp[s->id] = x->nimports;
    361   }
    362 
    363   /* Back-classify: any CALL26/JUMP26 reloc target -> function. */
    364   for (u32 i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
    365     LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
    366     if (!reloc_kind_is_branch(x->c, r->kind)) continue;
    367     if (r->target == LINK_SYM_NONE || r->target >= x->sym_to_imp_size) continue;
    368     u32 idx = x->sym_to_imp[r->target];
    369     if (!idx) {
    370       /* Resolve through canonical. */
    371       LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1);
    372       if (tgt->name == 0) continue;
    373       LinkSymId canon = symhash_get(&img->globals, tgt->name);
    374       if (canon == LINK_SYM_NONE || canon >= x->sym_to_imp_size) continue;
    375       idx = x->sym_to_imp[canon];
    376       if (!idx) continue;
    377       /* Stash so future lookups skip this loop. */
    378       x->sym_to_imp[r->target] = idx;
    379     }
    380     x->imports[idx - 1].is_func = 1;
    381   }
    382 
    383   /* Build dylib ordinal table.  Pull soname from the providing DSO. */
    384   for (u32 i = 0; i < x->nimports; ++i) {
    385     MachImp* mi = &x->imports[i];
    386     LinkSymbol* s = sym_at(img, mi->sym);
    387     LinkInputId dso_id = s ? s->dso_input_id : LINK_INPUT_NONE;
    388     Sym install = 0;
    389     if (dso_id != LINK_INPUT_NONE && x->linker &&
    390         dso_id - 1u < LinkInputs_count(&x->linker->inputs)) {
    391       LinkInput* in = LinkInputs_at(&x->linker->inputs, dso_id - 1u);
    392       if (in->kind == LINK_INPUT_DSO_BYTES) install = in->soname;
    393     }
    394     if (install == 0)
    395       install = pool_intern_slice(x->c->global,
    396                                   SLICE_LIT("/usr/lib/libSystem.B.dylib"));
    397     u32 ord = dylib_ordinal_of(x, install);
    398     if (!ord) {
    399       if (VEC_GROW(h, x->dylibs, cap_d, x->ndylibs + 1u))
    400         compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on dylibs");
    401       x->dylibs[x->ndylibs].install = install;
    402       ++x->ndylibs;
    403       ord = x->ndylibs;
    404     }
    405     mi->dylib_ord = ord;
    406   }
    407 
    408   /* Always include every DSO input's install-name. */
    409   if (x->linker) {
    410     for (u32 ii = 0; ii < LinkInputs_count(&x->linker->inputs); ++ii) {
    411       LinkInput* in = LinkInputs_at(&x->linker->inputs, ii);
    412       if (in->kind != LINK_INPUT_DSO_BYTES) continue;
    413       if (in->soname == 0) continue;
    414       if (dylib_ordinal_of(x, in->soname)) continue;
    415       if (VEC_GROW(h, x->dylibs, cap_d, x->ndylibs + 1u))
    416         compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on dylibs");
    417       x->dylibs[x->ndylibs].install = in->soname;
    418       ++x->ndylibs;
    419     }
    420   }
    421 
    422   /* All entries so far are real imports; remember the partition point
    423    * so import/symtab table emit loops can skip the appended internals. */
    424   x->nimports_real = x->nimports;
    425 
    426   /* Internal GOT pass.  clang on Mach-O routes every extern-global
    427    * reference through the GOT (GOT_LOAD_PAGE21 / LD64_GOT_LO12_NC), so
    428    * even a common symbol or weak-undef that ends up resolved within the
    429    * image still needs a __got slot.  For each such reloc whose target
    430    * isn't an existing import, materialize a MachImp with internal=1.
    431    * The slot's contents are filled at write time and a chained-fixup
    432    * REBASE entry (or none, for weak undef → NULL) keeps it valid
    433    * post-ASLR. */
    434   for (u32 i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
    435     LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
    436     if (!reloc_kind_is_got_load(x->c, r->kind)) continue;
    437     if (r->target == LINK_SYM_NONE || r->target >= x->sym_to_imp_size) continue;
    438     if (x->sym_to_imp[r->target]) continue;
    439     LinkSymbol* t = sym_at(img, r->target);
    440     if (!t) continue;
    441     /* Resolve through canonical so we share a single slot per symbol. */
    442     LinkSymId canon = r->target;
    443     if (t->name != 0) {
    444       LinkSymId hit = symhash_get(&img->globals, t->name);
    445       if (hit != LINK_SYM_NONE) {
    446         canon = hit;
    447         if (x->sym_to_imp[canon]) {
    448           x->sym_to_imp[r->target] = x->sym_to_imp[canon];
    449           continue;
    450         }
    451         t = sym_at(img, canon);
    452         if (!t) continue;
    453       }
    454     }
    455     if (VEC_GROW(h, x->imports, cap, x->nimports + 1u))
    456       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on internal got");
    457     MachImp* mi = &x->imports[x->nimports++];
    458     memset(mi, 0, sizeof(*mi));
    459     mi->sym = canon;
    460     mi->name = t->name;
    461     mi->is_func = (t->kind == SK_FUNC || t->kind == SK_IFUNC) ? 1 : 0;
    462     mi->weak = (t->bind == SB_WEAK) ? 1 : 0;
    463     mi->internal = 1;
    464     x->sym_to_imp[canon] = x->nimports;
    465     if (canon != r->target) x->sym_to_imp[r->target] = x->nimports;
    466   }
    467 
    468   /* Assign stub_idx + got_idx.  Internal entries get a slot but no stub:
    469    * the call site (CALL26) on internal funcs goes direct, not via stub. */
    470   u32 stub_run = 0;
    471   for (u32 i = 0; i < x->nimports; ++i) {
    472     MachImp* mi = &x->imports[i];
    473     mi->got_idx = i + 1u;
    474     if (mi->is_func && !mi->internal) mi->stub_idx = ++stub_run;
    475   }
    476   x->nimport_funcs = stub_run;
    477 }
    478 
    479 /* ---- pass: collect TLV pointer slots ----
    480  *
    481  * Mirror of collect_imports' internal-GOT pass, but for TLV descriptors:
    482  * each unique descriptor referenced via ARM64_RELOC_TLVP_LOAD_PAGE21 /
    483  * PAGEOFF12 gets one slot in the synthetic __DATA,__thread_ptrs section.
    484  * The slot's runtime value is the descriptor's address; we patch it at
    485  * apply_relocs time (REBASE for in-image descriptors, BIND for ones in
    486  * a dylib).
    487  *
    488  * Slots are deduplicated by canonical LinkSymId so a single descriptor
    489  * referenced from N call sites shares one __thread_ptrs entry. */
    490 static void collect_tlv(MCtx* x) {
    491   LinkImage* img = x->img;
    492   Heap* h = x->h;
    493   x->sym_to_tlv_size = LinkSyms_count(&img->syms) + 1u;
    494   x->sym_to_tlv =
    495       (u32*)h->alloc(h, sizeof(u32) * x->sym_to_tlv_size, _Alignof(u32));
    496   if (!x->sym_to_tlv)
    497     compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on sym_to_tlv");
    498   memset(x->sym_to_tlv, 0, sizeof(u32) * x->sym_to_tlv_size);
    499 
    500   u32 cap = 0;
    501   for (u32 i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
    502     LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
    503     if (!reloc_kind_is_tlvp(x->c, r->kind)) continue;
    504     if (r->target == LINK_SYM_NONE || r->target >= x->sym_to_tlv_size) continue;
    505     /* Resolve through canonical so multiple per-input duplicate undefs
    506      * collapse onto one __thread_ptrs slot. */
    507     LinkSymId canon = r->target;
    508     LinkSymbol* t = sym_at(img, r->target);
    509     if (!t) continue;
    510     if (t->name != 0) {
    511       LinkSymId hit = symhash_get(&img->globals, t->name);
    512       if (hit != LINK_SYM_NONE) {
    513         canon = hit;
    514         t = sym_at(img, canon);
    515         if (!t) continue;
    516       }
    517     }
    518     if (x->sym_to_tlv[canon]) {
    519       if (canon != r->target) x->sym_to_tlv[r->target] = x->sym_to_tlv[canon];
    520       continue;
    521     }
    522     if (VEC_GROW(h, x->tlv_slots, cap, x->ntlv + 1u))
    523       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on tlv_slots");
    524     MachTlv* ts = &x->tlv_slots[x->ntlv++];
    525     memset(ts, 0, sizeof(*ts));
    526     ts->sym = canon;
    527     ts->tlv_idx = x->ntlv;
    528     ts->imported = t->imported ? 1u : 0u;
    529     /* If the descriptor is imported we route the bind through the
    530      * symbol's MachImp slot — that's where dyld's chained-import index
    531      * comes from.  When this loop fires the imp pass has already
    532      * materialized the entry (real imports were processed first); the
    533      * lookup may also have stashed an alias for non-canonical ids. */
    534     if (ts->imported) {
    535       u32 idx = (canon < x->sym_to_imp_size) ? x->sym_to_imp[canon] : 0u;
    536       if (!idx && t->name != 0) {
    537         LinkSymId hit2 = symhash_get(&img->globals, t->name);
    538         if (hit2 != LINK_SYM_NONE && hit2 < x->sym_to_imp_size)
    539           idx = x->sym_to_imp[hit2];
    540       }
    541       ts->import_idx = idx;
    542     }
    543     x->sym_to_tlv[canon] = x->ntlv;
    544     if (canon != r->target) x->sym_to_tlv[r->target] = x->ntlv;
    545   }
    546 }
    547 
    548 /* ---- pass: plan Mach-O sections ----
    549  *
    550  * Walks LinkImage sections.  Each non-zero-size LinkSection becomes one
    551  * MSec.  Synthetic __stubs and __got are appended at the right segment
    552  * boundaries.  Vaddr and file_offset are assigned in a single forward
    553  * pass starting at __TEXT base; __PAGEZERO and __LINKEDIT are special. */
    554 
    555 static void seg_init(MSeg* s, const char* name, u32 maxp, u32 initp) {
    556   memset(s, 0, sizeof(*s));
    557   s->name = name;
    558   s->maxprot = maxp;
    559   s->initprot = initp;
    560 }
    561 
    562 static int sec_is_writable(const LinkSection* ls) {
    563   return (ls->flags & SF_WRITE) != 0u;
    564 }
    565 static int sec_is_exec(const LinkSection* ls) {
    566   return (ls->flags & SF_EXEC) != 0u;
    567 }
    568 static int sec_is_zerofill(const LinkSection* ls) {
    569   return ls->sem == SSEM_NOBITS;
    570 }
    571 
    572 static int section_has_abs64_reloc(const LinkImage* img, LinkSectionId id) {
    573   for (u32 i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
    574     const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
    575     if (r->link_section_id == id && r->kind == R_ABS64) return 1;
    576   }
    577   return 0;
    578 }
    579 
    580 static int sec_needs_data_const(const LinkImage* img, const LinkSection* ls) {
    581   if (!ls || !ls->size || sec_is_exec(ls) || sec_is_writable(ls) ||
    582       sec_is_zerofill(ls)) {
    583     return 0;
    584   }
    585   return section_has_abs64_reloc(img, ls->id);
    586 }
    587 
    588 /* Pick (segname, sectname) for a LinkSection.  Comma-form Mach-O names
    589  * round-trip into MSec's inline 16-byte buffers; literal defaults point
    590  * at .rodata strings.  Caller passes the MSec for per-section storage —
    591  * a previous version used a shared static buffer which aliased all
    592  * sections to whichever name was set last. */
    593 static void pick_macho_names(const LinkSection* ls, Compiler* c, MSec* m) {
    594   Slice nm_s = pool_slice(c->global, ls->name);
    595   const char* nm = nm_s.s;
    596   size_t nlen = nm_s.len;
    597   if (nm) {
    598     /* Comma-form: "__SEG,__sect" round-tripped from a Mach-O input. */
    599     for (size_t i = 0; i < nlen; ++i) {
    600       if (nm[i] == ',') {
    601         u32 seg_n = (u32)(i > 16 ? 16 : i);
    602         memcpy(m->segname_buf, nm, seg_n);
    603         m->segname_buf[seg_n] = 0;
    604         u32 sect_n = (u32)((nlen - i - 1) > 16 ? 16 : (nlen - i - 1));
    605         memcpy(m->sectname_buf, nm + i + 1, sect_n);
    606         m->sectname_buf[sect_n] = 0;
    607         m->segname = m->segname_buf;
    608         m->sectname = m->sectname_buf;
    609         return;
    610       }
    611     }
    612   }
    613   /* Derive from flags. */
    614   if (sec_is_exec(ls)) {
    615     m->segname = "__TEXT";
    616     m->sectname = "__text";
    617   } else if (sec_is_writable(ls)) {
    618     m->segname = "__DATA";
    619     m->sectname = sec_is_zerofill(ls) ? "__bss" : "__data";
    620   } else {
    621     m->segname = "__TEXT";
    622     m->sectname = "__const";
    623   }
    624 }
    625 
    626 static void plan_layout(MCtx* x) {
    627   LinkImage* img = x->img;
    628   Heap* h = x->h;
    629 
    630   /* PAGEZERO */
    631   seg_init(&x->segs[0], "__PAGEZERO", 0, 0);
    632   x->segs[0].vmaddr = 0;
    633   x->segs[0].vmsize = MZ_PAGEZERO;
    634   x->segs[0].fileoff = 0;
    635   x->segs[0].filesize = 0;
    636   x->segs[0].nsects = 0;
    637   x->segs[0].first_sec = 0;
    638 
    639   /* Segments 1..4 */
    640   seg_init(&x->segs[1], "__TEXT", VM_PROT_READ | VM_PROT_EXECUTE,
    641            VM_PROT_READ | VM_PROT_EXECUTE);
    642   seg_init(&x->segs[2], "__DATA_CONST", VM_PROT_READ | VM_PROT_WRITE,
    643            VM_PROT_READ | VM_PROT_WRITE);
    644   seg_init(&x->segs[3], "__DATA", VM_PROT_READ | VM_PROT_WRITE,
    645            VM_PROT_READ | VM_PROT_WRITE);
    646   /* __DWARF holds the file-only .debug_* sections; mapped R but never
    647    * referenced at runtime. Empty (nsects 0) when there's no debug info. */
    648   seg_init(&x->segs[MSEG_DWARF], "__DWARF", VM_PROT_READ, VM_PROT_READ);
    649   seg_init(&x->segs[MSEG_LINKEDIT], "__LINKEDIT", VM_PROT_READ, VM_PROT_READ);
    650   x->nsegs = MSEG_COUNT;
    651 
    652   /* Pre-allocate MSec capacity: every LinkSection + 2 synth (__stubs,
    653    * __got).  (LinkSections from the dynamic-link layer — .dynsym / .plt
    654    * etc. — were synthesized by layout_dyn for ELF; we won't have them
    655    * since pie wasn't set on this Linker.  Still, oversize by a few.) */
    656   u32 cap = LinkRelocs_count(&img->relocs) + img->nsections + 4u;
    657   x->secs = (MSec*)h->alloc(h, sizeof(MSec) * cap, _Alignof(MSec));
    658   if (!x->secs) compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on MSec");
    659   memset(x->secs, 0, sizeof(MSec) * cap);
    660   x->nsecs = 0;
    661 
    662   /* Pass 1: __TEXT segment.  Header + loadcmds reserve front. */
    663   /* We need the exact header_size to set first sec's file_offset.  We'll
    664    * compute it later, but reserve a placeholder; for now use 0 and patch
    665    * in pass 4 (offsets get bumped). */
    666 
    667   u64 text_vaddr = MZ_PAGEZERO;
    668   /* We'll compute headers_size after plan; stash starting vaddr only. */
    669   x->segs[1].vmaddr = text_vaddr;
    670   x->segs[1].fileoff = 0;
    671   x->text_vaddr = text_vaddr;
    672 
    673   /* Collect: (a) exec sections, (b) read-only allocatable sections. */
    674   /* (cursor advances per-segment in pass 2; nothing to track here) */
    675 
    676   /* We don't know the header size yet; walk sections first to enumerate
    677    * MSec entries, then back-fill file_offset/vaddr after we know the
    678    * load-command count. */
    679 
    680   u32 first_text_sec = x->nsecs;
    681 
    682   for (u32 i = 0; i < img->nsections; ++i) {
    683     LinkSection* ls = &img->sections[i];
    684     if (!ls->size) continue;
    685     if (ls->file_only) continue; /* .debug_* → __DWARF segment below */
    686     if (sec_is_writable(ls)) continue;
    687     if (sec_is_zerofill(ls)) continue; /* placed in __DATA */
    688     if (sec_needs_data_const(img, ls)) continue;
    689     MSec* m = &x->secs[x->nsecs++];
    690     memset(m, 0, sizeof(*m));
    691     m->link_sec_id = ls->id;
    692     pick_macho_names(ls, x->c, m);
    693     /* Force into __TEXT. */
    694     if (!slice_eq_cstr(slice_from_cstr(m->segname), "__TEXT"))
    695       m->segname = "__TEXT";
    696     m->align = ls->align ? ls->align : 1u;
    697     m->size = ls->size;
    698     m->segidx = 1;
    699     m->flags = sec_is_exec(ls) ? (0x80000000u /*S_ATTR_PURE_INSTRUCTIONS*/ |
    700                                   0x00000400u /*S_ATTR_SOME_INSTRUCTIONS*/)
    701                                : 0u;
    702   }
    703 
    704   /* __stubs synthetic */
    705   if (x->nimport_funcs) {
    706     x->stubs_size = x->nimport_funcs * x->macho->stub_size;
    707     x->stubs_bytes = (u8*)h->alloc(h, x->stubs_size, 4);
    708     if (!x->stubs_bytes)
    709       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on stubs");
    710     memset(x->stubs_bytes, 0, x->stubs_size);
    711     MSec* m = &x->secs[x->nsecs++];
    712     memset(m, 0, sizeof(*m));
    713     m->synth_data = x->stubs_bytes;
    714     m->synth_size = x->stubs_size;
    715     m->segname = "__TEXT";
    716     m->sectname = "__stubs";
    717     m->align = 4u;
    718     m->size = x->stubs_size;
    719     m->segidx = 1;
    720     m->flags = 0x80000000u | 0x00000400u | 0x00000008u /*S_SYMBOL_STUBS*/;
    721     m->reserved1 = 0; /* fill in later: indirect-symtab base */
    722     m->reserved2 = x->macho->stub_size;
    723   }
    724   x->segs[1].nsects = x->nsecs - first_text_sec;
    725   x->segs[1].first_sec = first_text_sec;
    726 
    727   /* __DATA_CONST: __got synth */
    728   u32 first_dc = x->nsecs;
    729   if (x->nimports) {
    730     x->got_size = x->nimports * MZ_GOT_SIZE;
    731     x->got_bytes = (u8*)h->alloc(h, x->got_size, 8);
    732     if (!x->got_bytes)
    733       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on got");
    734     memset(x->got_bytes, 0, x->got_size);
    735     MSec* m = &x->secs[x->nsecs++];
    736     memset(m, 0, sizeof(*m));
    737     m->synth_data = x->got_bytes;
    738     m->synth_size = x->got_size;
    739     m->segname = "__DATA_CONST";
    740     m->sectname = "__got";
    741     m->align = 8u;
    742     m->size = x->got_size;
    743     m->segidx = 2;
    744     m->flags = 0x00000006u /*S_NON_LAZY_SYMBOL_POINTERS*/;
    745     m->reserved1 = 0; /* indirect-symtab base */
    746   }
    747   for (u32 i = 0; i < img->nsections; ++i) {
    748     LinkSection* ls = &img->sections[i];
    749     if (ls->file_only) continue; /* .debug_* → __DWARF (has ABS64 relocs) */
    750     if (!sec_needs_data_const(img, ls)) continue;
    751     MSec* m = &x->secs[x->nsecs++];
    752     memset(m, 0, sizeof(*m));
    753     m->link_sec_id = ls->id;
    754     pick_macho_names(ls, x->c, m);
    755     m->segname = "__DATA_CONST";
    756     m->align = ls->align ? ls->align : 1u;
    757     m->size = ls->size;
    758     m->segidx = 2;
    759     m->flags = 0;
    760   }
    761   x->segs[2].nsects = x->nsecs - first_dc;
    762   x->segs[2].first_sec = first_dc;
    763 
    764   /* __DATA segment: writable sections + zerofill. */
    765   u32 first_d = x->nsecs;
    766   for (u32 i = 0; i < img->nsections; ++i) {
    767     LinkSection* ls = &img->sections[i];
    768     if (ls->file_only) continue; /* .debug_* → __DWARF */
    769     if (!ls->size && !sec_is_zerofill(ls)) continue;
    770     if (!sec_is_writable(ls)) continue;
    771     MSec* m = &x->secs[x->nsecs++];
    772     memset(m, 0, sizeof(*m));
    773     m->link_sec_id = ls->id;
    774     pick_macho_names(ls, x->c, m);
    775     if (!slice_eq_cstr(slice_from_cstr(m->segname), "__DATA"))
    776       m->segname = "__DATA";
    777     m->align = ls->align ? ls->align : 1u;
    778     m->size = ls->size;
    779     m->segidx = 3;
    780     m->is_zerofill = sec_is_zerofill(ls) ? 1 : 0;
    781     m->flags = m->is_zerofill ? 0x00000001u /*S_ZEROFILL*/ : 0;
    782     /* dyld dispatches on the section type byte (low 8 bits of flags).
    783      * __mod_init_func / __mod_term_func sections must carry the
    784      * S_MOD_INIT_FUNC_POINTERS / S_MOD_TERM_FUNC_POINTERS type or dyld
    785      * skips them entirely — leaving constructors unrun at startup. */
    786     if (slice_eq_cstr(slice_from_cstr(m->sectname), "__mod_init_func"))
    787       m->flags = 0x00000009u /*S_MOD_INIT_FUNC_POINTERS*/;
    788     else if (slice_eq_cstr(slice_from_cstr(m->sectname), "__mod_term_func"))
    789       m->flags = 0x0000000au /*S_MOD_TERM_FUNC_POINTERS*/;
    790     else if (ls->flags & SF_TLS) {
    791       /* TLV sections: dyld dispatches by section type, not name.  Map
    792        * __thread_vars → S_THREAD_LOCAL_VARIABLES (descriptor records),
    793        * __thread_data → S_THREAD_LOCAL_REGULAR (initial data),
    794        * __thread_bss → S_THREAD_LOCAL_ZEROFILL (zero-init data).  Done
    795        * by sectname so per-TU inputs without a Mach-O ext_type still
    796        * get the right section type. */
    797       if (slice_eq_cstr(slice_from_cstr(m->sectname), "__thread_vars")) {
    798         m->flags = S_THREAD_LOCAL_VARIABLES;
    799         /* Each descriptor is three pointers (24B) whose first word is
    800          * dyld's _tlv_bootstrap thunk pointer.  Clang/llvm emit
    801          * __thread_vars with on-disk alignment 1 (relying on layout to
    802          * land it on 8); force 8-alignment here so the descriptor
    803          * pointers fall on 8-byte boundaries — dyld's chained-fixup
    804          * processing assumes that. */
    805         if (m->align < 8u) m->align = 8u;
    806       } else if (m->is_zerofill)
    807         m->flags = S_THREAD_LOCAL_ZEROFILL;
    808       else
    809         m->flags = S_THREAD_LOCAL_REGULAR;
    810     }
    811   }
    812   /* __thread_ptrs synthetic (TLV pointer slots).  Emitted into __DATA
    813    * after the user's TLV input sections so descriptors and their
    814    * pointers share the same segment.  Each slot's runtime initial
    815    * value (= TLV descriptor address) is patched during apply_relocs. */
    816   if (x->ntlv) {
    817     x->tlv_ptrs_size = x->ntlv * MZ_TLVP_SIZE;
    818     x->tlv_ptrs_bytes = (u8*)h->alloc(h, x->tlv_ptrs_size, 8);
    819     if (!x->tlv_ptrs_bytes)
    820       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on tlv_ptrs");
    821     memset(x->tlv_ptrs_bytes, 0, x->tlv_ptrs_size);
    822     MSec* m = &x->secs[x->nsecs++];
    823     memset(m, 0, sizeof(*m));
    824     m->synth_data = x->tlv_ptrs_bytes;
    825     m->synth_size = x->tlv_ptrs_size;
    826     m->segname = "__DATA";
    827     m->sectname = "__thread_ptrs";
    828     m->align = 8u;
    829     m->size = x->tlv_ptrs_size;
    830     m->segidx = 3;
    831     m->flags = S_THREAD_LOCAL_VARIABLE_POINTERS;
    832   }
    833   x->segs[3].nsects = x->nsecs - first_d;
    834   x->segs[3].first_sec = first_d;
    835 
    836   /* __DWARF: file-only .debug_* sections (debug-info retention). Each
    837    * contribution becomes a synth MSec whose bytes are the per-image
    838    * debug registry buffer (relocs applied in place at apply_relocs).
    839    * Iterated in registry order (= input order) so same-name runs land
    840    * adjacent and the per-input DWARF-relative bases line up with the
    841    * coalesced section's byte layout. */
    842   u32 first_dw = x->nsecs;
    843   for (u32 i = 0; i < img->nsections; ++i) {
    844     LinkSection* ls = &img->sections[i];
    845     u8* dbg;
    846     if (!ls->file_only || !ls->size) continue;
    847     dbg = link_fileonly_bytes(img, ls->id);
    848     if (!dbg) continue;
    849     MSec* m = &x->secs[x->nsecs++];
    850     memset(m, 0, sizeof(*m));
    851     m->synth_data = dbg;
    852     m->synth_size = (u32)ls->size;
    853     /* Section name: a Mach-O input already carries "__DWARF,__debug_*";
    854      * an in-process .debug_* maps via obj_macho_debug_sectname. */
    855     {
    856       Slice nm = pool_slice(x->c->global, ls->name);
    857       const char* comma = nm.s ? memchr(nm.s, ',', nm.len) : NULL;
    858       char sect[17];
    859       if (comma) {
    860         u32 sgn = (u32)(comma - nm.s);
    861         if (sgn > 16u) sgn = 16u;
    862         memcpy(m->segname_buf, nm.s, sgn);
    863         m->segname_buf[sgn] = 0;
    864         u32 stn = (u32)(nm.len - (comma - nm.s) - 1);
    865         if (stn > 16u) stn = 16u;
    866         memcpy(m->sectname_buf, comma + 1, stn);
    867         m->sectname_buf[stn] = 0;
    868       } else if (obj_macho_debug_sectname(nm.s, nm.len, sect)) {
    869         memcpy(m->segname_buf, "__DWARF", 8);
    870         memcpy(m->sectname_buf, sect, slice_from_cstr(sect).len + 1);
    871       } else {
    872         memcpy(m->segname_buf, "__DWARF", 8);
    873         u32 stn = nm.len > 16u ? 16u : (u32)nm.len;
    874         memcpy(m->sectname_buf, nm.s, stn);
    875         m->sectname_buf[stn] = 0;
    876       }
    877       m->segname = m->segname_buf;
    878       m->sectname = m->sectname_buf;
    879     }
    880     /* align 1: contributions concatenate gap-free so the DWARF-relative
    881      * bases (assigned without padding in link_layout_debug) stay valid. */
    882     m->align = 1u;
    883     m->size = ls->size;
    884     m->segidx = MSEG_DWARF;
    885     m->flags = 0; /* S_REGULAR */
    886   }
    887   x->segs[MSEG_DWARF].nsects = x->nsecs - first_dw;
    888   x->segs[MSEG_DWARF].first_sec = first_dw;
    889 
    890   /* Group MSecs by (segname, sectname) within each segment so vaddr
    891    * placement keeps same-named runs contiguous.  Otherwise Phase B's
    892    * adjacency-based coalescing splits a single Mach-O section into
    893    * multiple OutSecs (e.g. `.text` from an in-memory ObjBuilder and
    894    * `__TEXT,__text` from a Mach-O .o input both map to `__TEXT,__text`
    895    * but arrive in separate link_layout groups, interleaved with other
    896    * sections from each input).  Stable insertion sort preserves input
    897    * order within a name, which matters for synth __stubs/__thread_ptrs
    898    * order relative to peers. */
    899   for (u32 i = 0; i < x->nsegs; ++i) {
    900     MSeg* sg = &x->segs[i];
    901     if (sg->nsects < 2) continue;
    902     u32 base = sg->first_sec;
    903     u32 n = sg->nsects;
    904     for (u32 a = 1; a < n; ++a) {
    905       MSec key = x->secs[base + a];
    906       msec_repair_name_ptrs(&key);
    907       u32 j = a;
    908       while (j > 0) {
    909         MSec* prev = &x->secs[base + j - 1];
    910         /* Ordering compare for stable sort: slices don't order, keep strcmp. */
    911         int cmp = strcmp(prev->segname, key.segname);             /* ordering */
    912         if (cmp == 0) cmp = strcmp(prev->sectname, key.sectname); /* ordering */
    913         if (cmp <= 0) break;
    914         x->secs[base + j] = x->secs[base + j - 1];
    915         msec_repair_name_ptrs(&x->secs[base + j]);
    916         --j;
    917       }
    918       x->secs[base + j] = key;
    919       msec_repair_name_ptrs(&x->secs[base + j]);
    920     }
    921   }
    922 
    923   /* Phase A: count OutSecs per segment (distinct sectnames) so we can
    924    * size the load commands before placing vaddrs.  Phase B builds the
    925    * actual OutSec[] after placement, when vaddrs are final. */
    926   for (u32 i = 0; i < x->nsegs; ++i) {
    927     MSeg* sg = &x->segs[i];
    928     u32 cnt = 0;
    929     for (u32 a = sg->first_sec; a < sg->first_sec + sg->nsects; ++a) {
    930       int seen = 0;
    931       for (u32 b = sg->first_sec; b < a; ++b) {
    932         if (slice_eq_cstr(slice_from_cstr(x->secs[a].sectname),
    933                           x->secs[b].sectname) &&
    934             slice_eq_cstr(slice_from_cstr(x->secs[a].segname),
    935                           x->secs[b].segname)) {
    936           seen = 1;
    937           break;
    938         }
    939       }
    940       if (!seen) ++cnt;
    941     }
    942     sg->nouts = cnt;
    943     sg->first_out = 0; /* assigned in Phase B */
    944   }
    945 
    946   /* Compute load-command count + sizeofcmds, then back-fill section
    947    * offsets.  Layout pass 2. */
    948   u32 nseg_real = 0;
    949   for (u32 i = 0; i < x->nsegs; ++i) {
    950     /* Skip __DATA_CONST or __DATA if no sections (edge case). */
    951     if (i == 0) {
    952       ++nseg_real;
    953       continue;
    954     } /* PAGEZERO */
    955     if (i == MSEG_LINKEDIT) {
    956       ++nseg_real;
    957       continue;
    958     } /* LINKEDIT always */
    959     if (x->segs[i].nsects > 0) ++nseg_real; /* incl. __DWARF when present */
    960   }
    961   /* Each LC_SEGMENT_64 carries 72 + 80*nouts bytes (one section_64
    962    * record per coalesced (segname,sectname), not per MSec). */
    963   u32 sizeofcmds = 0;
    964   for (u32 i = 0; i < x->nsegs; ++i) {
    965     if (i == 0 || i == MSEG_LINKEDIT) {
    966       sizeofcmds += MACHO_SEGCMD64_SIZE; /* no sections */
    967       continue;
    968     }
    969     if (x->segs[i].nsects == 0) continue;
    970     sizeofcmds += MACHO_SEGCMD64_SIZE + x->segs[i].nouts * MACHO_SECT64_SIZE;
    971   }
    972   (void)nseg_real;
    973   /* LC_DYLD_CHAINED_FIXUPS / LC_DYLD_EXPORTS_TRIE */
    974   sizeofcmds += 16u + 16u;
    975   /* LC_SYMTAB / LC_DYSYMTAB */
    976   sizeofcmds += MACHO_SYMTAB_CMD_SIZE + MACHO_DYSYMTAB_CMD_SIZE;
    977   /* LC_LOAD_DYLINKER */
    978   {
    979     u32 ld_size = 12u + (u32)(sizeof("/usr/lib/dyld") - 1u) + 1u;
    980     sizeofcmds += (u32)ALIGN_UP((u64)ld_size, 8u);
    981   }
    982   /* LC_UUID + LC_BUILD_VERSION + LC_MAIN */
    983   sizeofcmds += 24u + 24u + 24u;
    984   /* LC_LOAD_DYLIB per dylib */
    985   for (u32 i = 0; i < x->ndylibs; ++i) {
    986     size_t nl = pool_slice(x->c->global, x->dylibs[i].install).len;
    987     u32 sz = 24u + (u32)nl + 1u;
    988     sizeofcmds += (u32)ALIGN_UP((u64)sz, 8u);
    989   }
    990   /* LC_FUNCTION_STARTS / LC_DATA_IN_CODE / LC_CODE_SIGNATURE */
    991   sizeofcmds += 16u + 16u + 16u;
    992 
    993   x->headers_size = MACHO_HDR64_SIZE + sizeofcmds;
    994 
    995   /* Now place sections in __TEXT, __DATA_CONST, __DATA. */
    996   u64 vaddr = MZ_PAGEZERO + x->headers_size;
    997   u64 fileoff = x->headers_size;
    998   /* Pad __TEXT sections to natural alignment. */
    999   for (u32 i = 0; i < x->nsegs; ++i) {
   1000     if (i == 0 || i == MSEG_LINKEDIT) continue; /* DWARF placed here too */
   1001     MSeg* sg = &x->segs[i];
   1002     if (i > 1) {
   1003       /* page-align the start of __DATA_CONST and __DATA */
   1004       vaddr = ALIGN_UP(vaddr, MZ_PAGE);
   1005       fileoff = ALIGN_UP(fileoff, MZ_PAGE);
   1006     }
   1007     sg->vmaddr = (i == 1) ? MZ_PAGEZERO : vaddr;
   1008     sg->fileoff = (i == 1) ? 0 : fileoff;
   1009     /* __TEXT carries the headers_size + sections. */
   1010     u64 seg_start_v = sg->vmaddr;
   1011     u64 seg_start_f = sg->fileoff;
   1012     /* For __TEXT, sections begin after the header area. */
   1013     u64 cur_v = (i == 1) ? (seg_start_v + x->headers_size) : seg_start_v;
   1014     u64 cur_f = (i == 1) ? (seg_start_f + x->headers_size) : seg_start_f;
   1015     u64 first_zerofill_v = 0;
   1016     int seen_zerofill = 0;
   1017     /* Non-zerofill first */
   1018     for (u32 j = 0; j < sg->nsects; ++j) {
   1019       MSec* m = &x->secs[sg->first_sec + j];
   1020       if (m->is_zerofill) continue;
   1021       cur_v = ALIGN_UP(cur_v, (u64)m->align);
   1022       cur_f = ALIGN_UP(cur_f, (u64)m->align);
   1023       m->vaddr = cur_v;
   1024       m->file_offset = cur_f;
   1025       cur_v += m->size;
   1026       cur_f += m->size;
   1027     }
   1028     first_zerofill_v = cur_v;
   1029     /* zerofill last (no file bytes) */
   1030     for (u32 j = 0; j < sg->nsects; ++j) {
   1031       MSec* m = &x->secs[sg->first_sec + j];
   1032       if (!m->is_zerofill) continue;
   1033       cur_v = ALIGN_UP(cur_v, (u64)m->align);
   1034       m->vaddr = cur_v;
   1035       m->file_offset = 0;
   1036       cur_v += m->size;
   1037       seen_zerofill = 1;
   1038     }
   1039     sg->filesize = (i == 1)
   1040                        ? (cur_f - seg_start_f)
   1041                        : (first_zerofill_v ? (first_zerofill_v - seg_start_v)
   1042                                            : (cur_v - seg_start_v));
   1043     sg->vmsize = ALIGN_UP(cur_v - seg_start_v, MZ_PAGE);
   1044     if (sg->vmsize == 0 && sg->nsects > 0) sg->vmsize = MZ_PAGE;
   1045     if (i == 1) {
   1046       x->stubs_vaddr = 0;
   1047       for (u32 j = 0; j < sg->nsects; ++j) {
   1048         MSec* m = &x->secs[sg->first_sec + j];
   1049         if (slice_eq_cstr(slice_from_cstr(m->sectname), "__stubs"))
   1050           x->stubs_vaddr = m->vaddr;
   1051       }
   1052     }
   1053     if (i == 2) {
   1054       for (u32 j = 0; j < sg->nsects; ++j) {
   1055         MSec* m = &x->secs[sg->first_sec + j];
   1056         if (slice_eq_cstr(slice_from_cstr(m->sectname), "__got"))
   1057           x->got_vaddr = m->vaddr;
   1058       }
   1059     }
   1060     if (i == 3) {
   1061       for (u32 j = 0; j < sg->nsects; ++j) {
   1062         MSec* m = &x->secs[sg->first_sec + j];
   1063         if (slice_eq_cstr(slice_from_cstr(m->sectname), "__thread_ptrs"))
   1064           x->tlv_ptrs_vaddr = m->vaddr;
   1065         /* TLS storage image base: min vaddr across __thread_data and
   1066          * __thread_bss sections.  __thread_vars is excluded — it holds
   1067          * the descriptors, not the data that maps into the per-thread
   1068          * block. */
   1069         if ((slice_eq_cstr(slice_from_cstr(m->sectname), "__thread_data") ||
   1070              slice_eq_cstr(slice_from_cstr(m->sectname), "__thread_bss")) &&
   1071             (!x->has_tls_image || m->vaddr < x->tls_image_vaddr)) {
   1072           x->tls_image_vaddr = m->vaddr;
   1073           x->has_tls_image = 1;
   1074         }
   1075       }
   1076     }
   1077     vaddr = sg->vmaddr + sg->vmsize;
   1078     /* Mach-O segments are mapped in page units.  If a segment's memory
   1079      * image extends past its initialized file bytes (for example
   1080      * __DATA,__bss), the following segment's fileoff must not reuse those
   1081      * pages or the kernel can map later file contents into the zero-fill
   1082      * tail. */
   1083     fileoff = sg->fileoff + ((sg->vmsize > ALIGN_UP(sg->filesize, MZ_PAGE))
   1084                                  ? sg->vmsize
   1085                                  : sg->filesize);
   1086     (void)seen_zerofill;
   1087   }
   1088   /* LINKEDIT placeholder; size is filled after blob assembly. */
   1089   vaddr = ALIGN_UP(vaddr, MZ_PAGE);
   1090   fileoff = ALIGN_UP(fileoff, MZ_PAGE);
   1091   x->segs[MSEG_LINKEDIT].vmaddr = vaddr;
   1092   x->segs[MSEG_LINKEDIT].fileoff = fileoff;
   1093   x->linkedit_vaddr = vaddr;
   1094   x->linkedit_fileoff = fileoff;
   1095 
   1096   /* Encode __stubs bytes now that vaddrs are settled.  Internal-GOT
   1097    * entries have stub_idx=0 (direct CALL26, no stub) and must be
   1098    * skipped so the (stub_idx - 1u) arithmetic doesn't wrap. */
   1099   for (u32 i = 0; i < x->nimports; ++i) {
   1100     MachImp* mi = &x->imports[i];
   1101     if (!mi->is_func || !mi->stub_idx) continue;
   1102     u64 stub_v = x->stubs_vaddr + (mi->stub_idx - 1u) * x->macho->stub_size;
   1103     u64 got_v = x->got_vaddr + (mi->got_idx - 1u) * MZ_GOT_SIZE;
   1104     x->macho->emit_stub(
   1105         x->stubs_bytes + (mi->stub_idx - 1u) * x->macho->stub_size, stub_v,
   1106         got_v);
   1107   }
   1108 
   1109   /* Phase B: build OutSec[] now that all MSec vaddrs are final.  Walk
   1110    * MSecs sorted by (segidx, vaddr) and coalesce adjacent same-name
   1111    * runs.  Mirrors link_elf.c's OutShdr build at link_elf.c:879. */
   1112   {
   1113     u32* order =
   1114         (u32*)h->alloc(h, sizeof(u32) * (x->nsecs + 1u), _Alignof(u32));
   1115     if (!order && x->nsecs)
   1116       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on outsec sort");
   1117     for (u32 i = 0; i < x->nsecs; ++i) order[i] = i;
   1118     /* Insertion sort — section count is small. */
   1119     for (u32 i = 1; i < x->nsecs; ++i) {
   1120       u32 cur = order[i];
   1121       MSec* a = &x->secs[cur];
   1122       u32 j = i;
   1123       while (j > 0) {
   1124         MSec* b = &x->secs[order[j - 1]];
   1125         if ((b->segidx < a->segidx) ||
   1126             (b->segidx == a->segidx && b->vaddr <= a->vaddr))
   1127           break;
   1128         order[j] = order[j - 1];
   1129         --j;
   1130       }
   1131       order[j] = cur;
   1132     }
   1133     u32 cap = x->nsecs + 1u;
   1134     x->outs = (OutSec*)h->alloc(h, sizeof(OutSec) * cap, _Alignof(OutSec));
   1135     if (!x->outs)
   1136       compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on OutSec");
   1137     memset(x->outs, 0, sizeof(OutSec) * cap);
   1138     x->nouts = 0;
   1139     for (u32 i = 0; i < x->nsecs; ++i) {
   1140       MSec* m = &x->secs[order[i]];
   1141       OutSec* tail = x->nouts ? &x->outs[x->nouts - 1] : NULL;
   1142       int merge = tail && tail->segidx == m->segidx &&
   1143                   slice_eq_cstr(slice_from_cstr(tail->sectname), m->sectname) &&
   1144                   slice_eq_cstr(slice_from_cstr(tail->segname), m->segname);
   1145       if (merge) {
   1146         if (tail->flags != m->flags || tail->is_zerofill != m->is_zerofill)
   1147           compiler_panic(
   1148               x->c, SRCLOC_NONE,
   1149               "link_macho: coalesce mismatch on %.*s,%.*s (flags/zerofill)",
   1150               SLICE_ARG(slice_from_cstr(m->segname)),
   1151               SLICE_ARG(slice_from_cstr(m->sectname)));
   1152         u64 end = m->vaddr + m->size;
   1153         u64 prev_end = tail->vaddr + tail->size;
   1154         if (end > prev_end) tail->size = end - tail->vaddr;
   1155         if (m->align > tail->align) tail->align = m->align;
   1156       } else {
   1157         OutSec* o = &x->outs[x->nouts++];
   1158         o->segname = m->segname;
   1159         o->sectname = m->sectname;
   1160         o->vaddr = m->vaddr;
   1161         o->file_offset = m->file_offset;
   1162         o->size = m->size;
   1163         o->align = m->align;
   1164         o->flags = m->flags;
   1165         o->reserved1 = m->reserved1;
   1166         o->reserved2 = m->reserved2;
   1167         o->segidx = m->segidx;
   1168         o->is_zerofill = m->is_zerofill;
   1169       }
   1170     }
   1171     h->free(h, order, sizeof(u32) * (x->nsecs + 1u));
   1172     /* Recompute per-segment OutSec span; Phase A's count was for
   1173      * sizeofcmds sizing — recompute it here as the source of truth and
   1174      * assert agreement. */
   1175     for (u32 i = 0; i < x->nsegs; ++i) {
   1176       x->segs[i].first_out = 0;
   1177     }
   1178     u32 prev_nouts[MSEG_COUNT];
   1179     for (u32 i = 0; i < x->nsegs; ++i) prev_nouts[i] = x->segs[i].nouts;
   1180     for (u32 i = 0; i < x->nsegs; ++i) x->segs[i].nouts = 0;
   1181     for (u32 i = 0; i < x->nouts; ++i) {
   1182       u8 sx = x->outs[i].segidx;
   1183       if (x->segs[sx].nouts == 0) x->segs[sx].first_out = i;
   1184       ++x->segs[sx].nouts;
   1185     }
   1186     for (u32 i = 0; i < x->nsegs; ++i) {
   1187       if (prev_nouts[i] != x->segs[i].nouts)
   1188         compiler_panic(x->c, SRCLOC_NONE,
   1189                        "link_macho: OutSec count drift seg %u (%u vs %u)",
   1190                        (u32)i, prev_nouts[i], x->segs[i].nouts);
   1191     }
   1192   }
   1193 }
   1194 
   1195 /* ---- pass: shift LinkImage into final vaddrs/file_offsets ----
   1196  *
   1197  * The sections in img->sections are still in their original
   1198  * link_layout coordinates.  Map each LinkSection -> its MSec and copy
   1199  * the final vaddr/file_offset so reloc-apply walks correctly. */
   1200 
   1201 /* link_sec_id -> backing MSec index, built once. */
   1202 static void build_section_index(MCtx* x) {
   1203   u32 n = x->img->nsections;
   1204   x->by_link_sec_cap = n + 1u;
   1205   x->by_link_sec =
   1206       (MSec**)x->h->alloc(x->h, sizeof(MSec*) * x->by_link_sec_cap,
   1207                           _Alignof(MSec*));
   1208   if (!x->by_link_sec)
   1209     compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on section index");
   1210   for (u32 i = 0; i < x->by_link_sec_cap; ++i) x->by_link_sec[i] = NULL;
   1211   for (u32 i = 0; i < x->nsecs; ++i) {
   1212     MSec* m = &x->secs[i];
   1213     if (m->link_sec_id && m->link_sec_id < x->by_link_sec_cap)
   1214       x->by_link_sec[m->link_sec_id] = m;
   1215   }
   1216 }
   1217 
   1218 /* Re-base every section to its final Mach-O vaddr/file_offset, then shift the
   1219  * relocs and defined symbols anchored to each section by its per-section delta.
   1220  * A per-section delta table + three single passes (O(nsecs + nrelocs + nsyms))
   1221  * replaces the former per-section rescan of the whole reloc + sym vectors
   1222  * (O(nsecs * (nrelocs + nsyms)) — the dominant obj-count superlinearity). The
   1223  * ELF emitter (src/obj/elf/link.c) is the reference; this generalizes it to
   1224  * per-section deltas because Mach-O segments get different bases. */
   1225 static void shift_sections(MCtx* x) {
   1226   LinkImage* img = x->img;
   1227   u32 n = img->nsections;
   1228   i64* dv = (i64*)x->h->alloc(x->h, sizeof(i64) * (n + 1u), _Alignof(i64));
   1229   i64* df = (i64*)x->h->alloc(x->h, sizeof(i64) * (n + 1u), _Alignof(i64));
   1230   if (!dv || !df)
   1231     compiler_panic(x->c, SRCLOC_NONE, "link_macho: oom on shift deltas");
   1232   for (u32 i = 0; i <= n; ++i) {
   1233     dv[i] = 0;
   1234     df[i] = 0;
   1235   }
   1236   /* Per-section deltas; re-base each LinkSection. Sections keyed by id (the
   1237    * same id relocs/syms carry), matching the former ls->id match. */
   1238   for (u32 i = 0; i < x->nsecs; ++i) {
   1239     MSec* m = &x->secs[i];
   1240     LinkSection* ls;
   1241     u32 id;
   1242     if (!m->link_sec_id) continue;
   1243     ls = &img->sections[m->link_sec_id - 1u];
   1244     id = ls->id;
   1245     if (id == 0 || id > n) continue;
   1246     dv[id] = (i64)m->vaddr - (i64)ls->vaddr;
   1247     df[id] = (i64)m->file_offset - (i64)ls->file_offset;
   1248     ls->vaddr = m->vaddr;
   1249     ls->file_offset = m->file_offset;
   1250   }
   1251   /* Shift relocs by their section's delta (0 == no shift, was the early-out). */
   1252   for (u32 ri = 0; ri < LinkRelocs_count(&img->relocs); ++ri) {
   1253     LinkRelocApply* r = LinkRelocs_at(&img->relocs, ri);
   1254     u32 id = r->link_section_id;
   1255     if (id == LINK_SEC_NONE || id > n) continue;
   1256     r->write_vaddr = (u64)((i64)r->write_vaddr + dv[id]);
   1257     r->write_file_offset = (u64)((i64)r->write_file_offset + df[id]);
   1258   }
   1259   /* Shift defined, non-absolute symbols. Match by section_id (multiple input
   1260    * sections may share a pre-shift vaddr; the bucket each starts at offset 0). */
   1261   for (u32 si = 0; si < LinkSyms_count(&img->syms); ++si) {
   1262     LinkSymbol* s = LinkSyms_at(&img->syms, si);
   1263     u32 id;
   1264     if (!s->defined || s->kind == SK_ABS) continue;
   1265     id = s->section_id;
   1266     if (id == LINK_SEC_NONE || id > n) continue;
   1267     s->vaddr = (u64)((i64)s->vaddr + dv[id]);
   1268   }
   1269   x->h->free(x->h, dv, sizeof(i64) * (n + 1u));
   1270   x->h->free(x->h, df, sizeof(i64) * (n + 1u));
   1271 }
   1272 
   1273 /* ---- pass: apply relocations + collect chained-fixup sites ----
   1274  *
   1275  * Reloc dispatch:
   1276  *   target=imported func + CALL26/JUMP26 -> S = stub vaddr
   1277  *   target=import + GOT_LOAD_PAGE21/PAGEOFF12 -> S = got slot vaddr
   1278  *   target=import + ABS64 -> write 0; collect bind site
   1279  *   target=internal + ABS64 -> write target VA; collect rebase site
   1280  *   everything else -> standard apply
   1281  *
   1282  * Patch sites for chained fixups are 8-byte slots; for ABS32 we do not
   1283  * support fixups (no chained-fixup format for 32-bit pointers in
   1284  * standard arm64 — would need DYLD_CHAINED_PTR_32).  Internal R_ABS32
   1285  * still works (no slide adjustment is wrong technically, but for
   1286  * compile-time-known offsets it suffices).
   1287  */
   1288 
   1289 typedef struct FixSite {
   1290   u8 segidx;  /* 2 = __DATA_CONST, 3 = __DATA */
   1291   u8 is_bind; /* 0 = rebase, 1 = bind */
   1292   u8 pad[2];
   1293   u32 import_idx;    /* 1-based import index for binds, 0 for rebases */
   1294   u64 vaddr;         /* absolute VA of the slot */
   1295   u64 rebase_target; /* unslid target VA; only used for rebases */
   1296 } FixSite;
   1297 
   1298 typedef struct FixList {
   1299   Heap* heap;
   1300   FixSite* a;
   1301   u32 n;
   1302   u32 cap;
   1303 } FixList;
   1304 
   1305 static void fix_init(FixList* fl, Heap* h) {
   1306   fl->heap = h;
   1307   fl->a = NULL;
   1308   fl->n = 0;
   1309   fl->cap = 0;
   1310 }
   1311 static void fix_fini(FixList* fl) {
   1312   if (fl->a) fl->heap->free(fl->heap, fl->a, sizeof(*fl->a) * fl->cap);
   1313   fl->a = NULL;
   1314   fl->n = fl->cap = 0;
   1315 }
   1316 static void fix_push(FixList* fl, const FixSite* s) {
   1317   if (VEC_GROW(fl->heap, fl->a, fl->cap, fl->n + 1u)) return;
   1318   fl->a[fl->n++] = *s;
   1319 }
   1320 
   1321 /* find MSec covering an absolute vaddr */
   1322 static MSec* msec_for_vaddr(MCtx* x, u64 v) {
   1323   for (u32 i = 0; i < x->nsecs; ++i) {
   1324     MSec* m = &x->secs[i];
   1325     if (v >= m->vaddr && v < m->vaddr + m->size) return m;
   1326   }
   1327   return NULL;
   1328 }
   1329 
   1330 static u8* bytes_for_section(MCtx* x, MSec* m, LinkImage* img) {
   1331   if (m->synth_data) {
   1332     /* Synthetic — caller reads/writes via x->stubs_bytes / x->got_bytes. */
   1333     if (m->synth_data == x->stubs_bytes) return x->stubs_bytes;
   1334     if (m->synth_data == x->got_bytes) return x->got_bytes;
   1335     return NULL;
   1336   }
   1337   /* Backed by a LinkSection: find the LinkSegment buffer that section
   1338    * sits in (link_layout.c stored input section bytes there). */
   1339   LinkSection* ls = &img->sections[m->link_sec_id - 1u];
   1340   u32 segid = ls->segment_id;
   1341   if (segid == LINK_SEG_NONE) return NULL;
   1342   return img->segment_bytes[segid - 1u];
   1343 }
   1344 
   1345 /* Map the LinkSection that backs a write_vaddr to an MSec, then to the
   1346  * underlying byte buffer. */
   1347 static u8* patch_ptr(MCtx* x, LinkImage* img, const LinkRelocApply* r,
   1348                      MSec** out_msec) {
   1349   /* Look up via the LinkSection.  After shift_sections the section
   1350    * vaddr is the Mach-O vaddr; the corresponding MSec backs it. */
   1351   if (r->link_section_id == LINK_SEC_NONE) return NULL;
   1352   LinkSection* ls = &img->sections[r->link_section_id - 1u];
   1353   /* MSec by link_sec_id via the prebuilt index (O(1)); fall back to a scan if
   1354    * the index was not built (it always is, post plan_layout). */
   1355   MSec* m = NULL;
   1356   if (x->by_link_sec && ls->id < x->by_link_sec_cap) {
   1357     m = x->by_link_sec[ls->id];
   1358   } else {
   1359     for (u32 i = 0; i < x->nsecs; ++i) {
   1360       if (x->secs[i].link_sec_id == ls->id) {
   1361         m = &x->secs[i];
   1362         break;
   1363       }
   1364     }
   1365   }
   1366   if (!m) return NULL;
   1367   /* The LinkSegment's bytes are valid (not shifted), but the offset
   1368    * within them is the original input_offset.  Use input_offset for
   1369    * the byte offset, since the LinkSegment buffer wasn't reshuffled. */
   1370   /* link_layout.c set ls->file_offset = seg.file_offset + input_offset
   1371    * originally.  ls->vaddr similarly.  After our shift, they're new.
   1372    * The byte offset within the segment buffer is still input_offset. */
   1373   u8* base = bytes_for_section(x, m, img);
   1374   if (!base) return NULL;
   1375   u32 within_section = (u32)(r->write_vaddr - m->vaddr);
   1376   /* The segment buffer's first byte corresponds to ls->input_offset==0
   1377    * for the FIRST section in the segment.  But that's a complication.
   1378    * For simplicity we recompute the segment-relative byte offset by
   1379    * (file_offset - segment.file_offset) where segment.file_offset is
   1380    * unchanged.  Wait: the original layout produced `ls->file_offset =
   1381    * seg.file_offset + input_offset`, and we may have changed
   1382    * ls->file_offset.  Let's just use input_offset stored on the
   1383    * LinkSection. */
   1384   u32 in_off = (u32)(ls->input_offset + within_section);
   1385   if (out_msec) *out_msec = m;
   1386   return base + in_off;
   1387 }
   1388 
   1389 /* Symbol-relative resolved-address S, accounting for imports. */
   1390 static int sym_S(MCtx* x, LinkImage* img, LinkSymId id, u64* out_S,
   1391                  int* out_imp_idx) {
   1392   *out_S = 0;
   1393   *out_imp_idx = 0;
   1394   if (id == LINK_SYM_NONE) return 0;
   1395   LinkSymbol* s = sym_at(img, id);
   1396   if (!s) return 0;
   1397   /* Look up the import index — real imports plus internal-GOT entries
   1398    * the collect_imports pass materialized for GOT-routed internal refs. */
   1399   u32 idx = 0;
   1400   if (id < x->sym_to_imp_size) idx = x->sym_to_imp[id];
   1401   if (!idx && s->name != 0) {
   1402     LinkSymId canon = symhash_get(&img->globals, s->name);
   1403     if (canon != LINK_SYM_NONE && canon < x->sym_to_imp_size)
   1404       idx = x->sym_to_imp[canon];
   1405   }
   1406   if (s->imported) {
   1407     *out_imp_idx = (int)idx;
   1408     return 1;
   1409   }
   1410   /* Internal symbol that has a GOT slot — surface the import index so
   1411    * the GOT_LOAD reloc paths in apply_relocs find it, but also expose
   1412    * S=vaddr so non-GOT relocs (CALL26 etc.) still apply directly. */
   1413   *out_imp_idx = (int)idx;
   1414   *out_S = s->vaddr;
   1415   return 0;
   1416 }
   1417 
   1418 static void apply_relocs(MCtx* x, FixList* fl) {
   1419   LinkImage* img = x->img;
   1420   for (u32 i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
   1421     LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
   1422     if (r->target == LINK_SYM_NONE) continue;
   1423     /* File-only .debug_* section: patch the registry buffer in place (no
   1424      * __got/stub/chained-fixup — debug bytes aren't loaded or slid). A
   1425      * SK_SECTION target resolves to its DWARF-section-relative base; a
   1426      * code/data symbol to its final (absolute) vaddr for low_pc. Mach-O
   1427      * vaddrs are already absolute, so there's no extra image base. */
   1428     {
   1429       const LinkSection* sec = &img->sections[r->link_section_id - 1u];
   1430       if (sec->file_only) {
   1431         u8* dbg = link_fileonly_bytes(img, r->link_section_id);
   1432         const LinkSymbol* tgt = sym_at(img, r->target);
   1433         if (dbg && tgt)
   1434           link_reloc_apply(x->c, r->kind, dbg + r->offset, tgt->vaddr,
   1435                            r->addend, 0);
   1436         continue;
   1437       }
   1438     }
   1439     MSec* msec = NULL;
   1440     u8* P_bytes = patch_ptr(x, img, r, &msec);
   1441     if (!P_bytes) continue;
   1442     u64 P = r->write_vaddr;
   1443 
   1444     u64 S;
   1445     int imp_idx;
   1446     int is_imp = sym_S(x, img, r->target, &S, &imp_idx);
   1447 
   1448     /* TLVP relocs route through a __thread_ptrs slot regardless of
   1449      * whether the descriptor target is in-image or imported.  Resolved
   1450      * before the import / internal split because an imported TLV
   1451      * descriptor doesn't use the __got slot (its address lives in
   1452      * __thread_ptrs with its own chained bind). */
   1453     if (reloc_kind_is_tlvp(x->c, r->kind)) {
   1454       u32 tlv_idx =
   1455           (r->target < x->sym_to_tlv_size) ? x->sym_to_tlv[r->target] : 0u;
   1456       if (!tlv_idx)
   1457         compiler_panic(x->c, SRCLOC_NONE,
   1458                        "link_macho: TLVP reloc has no __thread_ptrs slot");
   1459       u64 slot_v = x->tlv_ptrs_vaddr + (tlv_idx - 1u) * MZ_TLVP_SIZE;
   1460       link_reloc_apply(x->c, r->kind, P_bytes, slot_v, r->addend, P);
   1461       continue;
   1462     }
   1463 
   1464     if (is_imp) {
   1465       MachImp* mi = (imp_idx > 0) ? &x->imports[imp_idx - 1] : NULL;
   1466       if (reloc_kind_is_branch(x->c, r->kind)) {
   1467         if (!mi || !mi->stub_idx)
   1468           compiler_panic(x->c, SRCLOC_NONE,
   1469                          "link_macho: import has no stub for branch");
   1470         u64 stub_v = x->stubs_vaddr + (mi->stub_idx - 1u) * x->macho->stub_size;
   1471         link_reloc_apply(x->c, r->kind, P_bytes, stub_v, r->addend, P);
   1472         continue;
   1473       }
   1474       if (reloc_kind_is_got_load(x->c, r->kind)) {
   1475         if (!mi)
   1476           compiler_panic(x->c, SRCLOC_NONE,
   1477                          "link_macho: GOT reloc for unknown import");
   1478         u64 got_v = x->got_vaddr + (mi->got_idx - 1u) * MZ_GOT_SIZE;
   1479         link_reloc_apply(x->c, r->kind, P_bytes, got_v, r->addend, P);
   1480         continue;
   1481       }
   1482       if (reloc_kind_is_direct_page(x->c, r->kind)) {
   1483         /* Direct page/lo12 against an import: route through __got. */
   1484         if (!mi)
   1485           compiler_panic(x->c, SRCLOC_NONE,
   1486                          "link_macho: PAGE/LO12 against unknown import");
   1487         u64 got_v = x->got_vaddr + (mi->got_idx - 1u) * MZ_GOT_SIZE;
   1488         link_reloc_apply(x->c, r->kind, P_bytes, got_v, r->addend, P);
   1489         continue;
   1490       }
   1491       if (r->kind == R_ABS64) {
   1492         /* Direct 8-byte absolute against an import: bind the slot. */
   1493         wr_u64_le(P_bytes, 0);
   1494         FixSite fs = {(u8)msec->segidx, 1, {0}, (u32)imp_idx, P, 0};
   1495         fix_push(fl, &fs);
   1496         continue;
   1497       }
   1498       compiler_panic(x->c, SRCLOC_NONE,
   1499                      "link_macho: unhandled reloc kind %u against imported "
   1500                      "symbol",
   1501                      (u32)r->kind);
   1502     }
   1503 
   1504     /* Internal relocs. */
   1505     if (r->kind == R_ABS64) {
   1506       /* Special case: ABS64 reloc inside a TLV descriptor record
   1507        * (__thread_vars section) targeting in-image TLS storage.  This
   1508        * is the descriptor's word-2 "offset" field — dyld interprets it
   1509        * as the per-thread offset of the storage within the TLS image,
   1510        * NOT as an absolute address.  Apple's ld writes the literal
   1511        * offset and emits no chained-fixup entry; replicate that so the
   1512        * chain skips over this slot (chained_fixups already does the
   1513        * right thing: no fixsite -> no chain link). */
   1514       if (msec && (msec->flags & SECTION_TYPE) == S_THREAD_LOCAL_VARIABLES &&
   1515           x->has_tls_image) {
   1516         u64 offset = (S + (u64)r->addend) - x->tls_image_vaddr;
   1517         wr_u64_le(P_bytes, offset);
   1518         continue;
   1519       }
   1520       /* Rebase site. */
   1521       wr_u64_le(P_bytes, S + (u64)r->addend);
   1522       FixSite fs = {(u8)msec->segidx, 0, {0}, 0, P, S + (u64)r->addend};
   1523       fix_push(fl, &fs);
   1524       continue;
   1525     }
   1526     /* Internal symbol routed through __got (clang emits GOT_LOAD_PAGE21
   1527      * for any extern global, even if the def is in-image).  imp_idx
   1528      * was populated by collect_imports' internal-GOT pass; redirect
   1529      * the page/lo12 reloc to the GOT slot's vaddr. */
   1530     if (imp_idx > 0 && reloc_kind_is_got_load(x->c, r->kind)) {
   1531       MachImp* mi = &x->imports[imp_idx - 1];
   1532       u64 got_v = x->got_vaddr + (mi->got_idx - 1u) * MZ_GOT_SIZE;
   1533       link_reloc_apply(x->c, r->kind, P_bytes, got_v, r->addend, P);
   1534       continue;
   1535     }
   1536     /* Generic apply. */
   1537     link_reloc_apply(x->c, r->kind, P_bytes, S, r->addend, P);
   1538   }
   1539 
   1540   /* Per-slot chained fixup.  Real imports → bind (dyld resolves at
   1541    * load).  Internal GOT entries → rebase pointing at the symbol's
   1542    * image-relative vaddr; a target vaddr of 0 (weak undef → NULL) gets
   1543    * no fixup, just a literal zero slot — chained fixups treat 0 as a
   1544    * gap and won't disturb it. */
   1545   for (u32 i = 0; i < x->nimports; ++i) {
   1546     MachImp* mi = &x->imports[i];
   1547     u64 slot_v = x->got_vaddr + (mi->got_idx - 1u) * MZ_GOT_SIZE;
   1548     if (mi->internal) {
   1549       /* Re-read the symbol's final vaddr now that shift_sections has
   1550        * rebased every defined symbol into the Mach-O image layout
   1551        * (collect_imports snapshotted too early). */
   1552       LinkSymbol* s = sym_at(img, mi->sym);
   1553       u64 tgt_v = s ? s->vaddr : 0;
   1554       u8* slot = x->got_bytes + (mi->got_idx - 1u) * MZ_GOT_SIZE;
   1555       wr_u64_le(slot, tgt_v);
   1556       if (tgt_v == 0) continue; /* weak-undef → NULL */
   1557       FixSite fs = {2u, 0, {0}, 0, slot_v, tgt_v};
   1558       fix_push(fl, &fs);
   1559     } else {
   1560       /* clear slot bytes (already zero) — dyld writes via chain */
   1561       FixSite fs = {2u, 1, {0}, i + 1u, slot_v, 0};
   1562       fix_push(fl, &fs);
   1563     }
   1564   }
   1565 
   1566   /* Per-slot TLV pointer fixups.  Mirror of the __got loop above: each
   1567    * __thread_ptrs slot points at the descriptor record.  When the
   1568    * descriptor is in-image (internal) we REBASE to its final vaddr; when
   1569    * it lives in a dylib we BIND through the descriptor's MachImp.  The
   1570    * slot itself lives in __DATA (segidx=3), distinct from __got's
   1571    * __DATA_CONST (segidx=2). */
   1572   for (u32 i = 0; i < x->ntlv; ++i) {
   1573     MachTlv* ts = &x->tlv_slots[i];
   1574     u64 slot_v = x->tlv_ptrs_vaddr + (ts->tlv_idx - 1u) * MZ_TLVP_SIZE;
   1575     u8* slot = x->tlv_ptrs_bytes + (ts->tlv_idx - 1u) * MZ_TLVP_SIZE;
   1576     if (ts->imported) {
   1577       if (!ts->import_idx)
   1578         compiler_panic(x->c, SRCLOC_NONE,
   1579                        "link_macho: imported TLV without matching import slot");
   1580       wr_u64_le(slot, 0);
   1581       FixSite fs = {3u, 1, {0}, ts->import_idx, slot_v, 0};
   1582       fix_push(fl, &fs);
   1583     } else {
   1584       LinkSymbol* s = sym_at(img, ts->sym);
   1585       u64 tgt_v = s ? s->vaddr : 0;
   1586       wr_u64_le(slot, tgt_v);
   1587       if (tgt_v == 0) continue; /* weak-undef descriptor → NULL */
   1588       FixSite fs = {3u, 0, {0}, 0, slot_v, tgt_v};
   1589       fix_push(fl, &fs);
   1590     }
   1591   }
   1592 }
   1593 
   1594 /* ---- chained fixups blob assembler ----
   1595  *
   1596  * For each segment that has fixups, build a dyld_chained_starts_in_segment
   1597  * with one chain per page (MZ_PAGE).  Within a page, sort sites by
   1598  * offset, encode each as DYLD_CHAINED_PTR_64, and link via the `next`
   1599  * field (4-byte units, 0 = end of chain).
   1600  */
   1601 
   1602 static int site_cmp_by_vaddr(const void* a, const void* b) {
   1603   const FixSite* x = a;
   1604   const FixSite* y = b;
   1605   if (x->vaddr < y->vaddr) return -1;
   1606   if (x->vaddr > y->vaddr) return 1;
   1607   return 0;
   1608 }
   1609 
   1610 /* Map each MCtx segment slot to its index in the emitted LC_SEGMENT_64
   1611  * sequence and return the emitted-segment count.  Empty middle segments
   1612  * (__DATA_CONST / __DWARF with no sections) are omitted from the load
   1613  * commands (see emit_load_command_segment calls), so the emitted order is
   1614  * a compaction of the slot order: PAGEZERO, TEXT, [DATA_CONST], [DATA],
   1615  * [DWARF], LINKEDIT.  Must mirror that emit logic exactly.  dyld indexes
   1616  * dyld_chained_starts_in_image.seg_info_offset[] by this emitted order, so
   1617  * the chained-fixups seg table uses it rather than the raw slot index —
   1618  * indexing by slot leaves a non-emitted hole that x86_64 dyld's
   1619  * applyFixupsGeneric resolves to the wrong segment (SIGBUS at load).
   1620  * Slots that are not emitted get emit_idx[slot] = 0xff. */
   1621 static u32 macho_emit_seg_indices(const MCtx* x, u8 emit_idx[MSEG_COUNT]) {
   1622   u32 n = 0;
   1623   for (u32 i = 0; i < x->nsegs; ++i) {
   1624     int emitted = (i == MSEG_PAGEZERO) || (i == MSEG_LINKEDIT) ||
   1625                   (x->segs[i].nsects > 0);
   1626     emit_idx[i] = emitted ? (u8)n : 0xffu;
   1627     if (emitted) ++n;
   1628   }
   1629   return n;
   1630 }
   1631 
   1632 /* tiny insertion sort to avoid pulling qsort */
   1633 static void sort_sites(FixSite* a, u32 n) {
   1634   for (u32 i = 1; i < n; ++i) {
   1635     FixSite tmp = a[i];
   1636     u32 j = i;
   1637     while (j > 0 && site_cmp_by_vaddr(&a[j - 1], &tmp) > 0) {
   1638       a[j] = a[j - 1];
   1639       --j;
   1640     }
   1641     a[j] = tmp;
   1642   }
   1643 }
   1644 
   1645 static void emit_pointer(u8* slot, int is_bind, u32 ord_or_target_lo,
   1646                          u32 high_or_target_hi, u32 next4) {
   1647   /* DYLD_CHAINED_PTR_64:
   1648    *   bind  : ordinal:24, addend:8, reserved:19, next:12, bind:1=1
   1649    *   rebase: target:36 (vmaddr), high8:8, reserved:7, next:12, bind:1=0
   1650    */
   1651   u64 v = 0;
   1652   if (is_bind) {
   1653     u64 ordinal = (u64)ord_or_target_lo & 0xffffffull; /* 24 bits */
   1654     u64 addend = 0;
   1655     u64 next = (u64)next4 & 0xfffull;
   1656     v = ordinal | (addend << 24) | (0ull /* reserved */ << 32) | (next << 51) |
   1657         ((u64)1 << 63);
   1658   } else {
   1659     /* rebase: target is full vmaddr; we get hi:lo split. */
   1660     u64 target = ((u64)high_or_target_hi << 32) | (u64)ord_or_target_lo;
   1661     target &= ((u64)1 << 36) - 1u; /* 36 bits */
   1662     u64 high8 = 0;
   1663     u64 next = (u64)next4 & 0xfffull;
   1664     v = target | (high8 << 36) | (0ull /* reserved */ << 44) | (next << 51) |
   1665         ((u64)0 << 63);
   1666   }
   1667   wr_u64_le(slot, v);
   1668 }
   1669 
   1670 static void build_chained_fixups(MCtx* x, FixList* fl) {
   1671   Heap* h = x->h;
   1672   ObjByteBuf* out = &x->chained_fixups;
   1673   objbb_init(out, h);
   1674 
   1675   /* Header (32 B):
   1676    *   uint32 fixups_version (=0)
   1677    *   uint32 starts_offset
   1678    *   uint32 imports_offset
   1679    *   uint32 symbols_offset
   1680    *   uint32 imports_count
   1681    *   uint32 imports_format (=1)
   1682    *   uint32 symbols_format (=0)
   1683    */
   1684   u32 hdr_pos = objbb_u32(out, 0); /* fixups_version */
   1685   (void)hdr_pos;
   1686   u32 starts_offset_pos = objbb_u32(out, 0);
   1687   u32 imports_offset_pos = objbb_u32(out, 0);
   1688   u32 symbols_offset_pos = objbb_u32(out, 0);
   1689   objbb_u32(out, x->nimports_real);
   1690   objbb_u32(out, DYLD_CHAINED_IMPORT);
   1691   objbb_u32(out, 0); /* symbols uncompressed */
   1692   /* dyld expects 8-byte alignment of the starts table. */
   1693   objbb_align(out, 4);
   1694 
   1695   /* dyld_chained_starts_in_image:
   1696    *   uint32 seg_count
   1697    *   uint32 seg_info_offset[seg_count]
   1698    *
   1699    * seg_count must equal mach-O segment count (5).
   1700    * seg_info_offset[i] = 0 means no fixups in that segment.
   1701    */
   1702   u32 starts_off = out->len;
   1703   wr_u32_le(out->data + starts_offset_pos, starts_off);
   1704   /* seg_count + seg_info_offset[] are indexed by emitted LC_SEGMENT_64
   1705    * order, not MCtx slot — empty middle segments are dropped from the load
   1706    * commands. */
   1707   u8 emit_seg[MSEG_COUNT];
   1708   u32 emit_nsegs = macho_emit_seg_indices(x, emit_seg);
   1709   objbb_u32(out, emit_nsegs);
   1710   /* Reserve seg_info_offset[]. */
   1711   u32 seg_info_offsets_pos = out->len;
   1712   for (u32 i = 0; i < emit_nsegs; ++i) objbb_u32(out, 0);
   1713 
   1714   /* Sort fixsites by vaddr globally. */
   1715   sort_sites(fl->a, fl->n);
   1716 
   1717   /* Per segment, emit dyld_chained_starts_in_segment when fixups present. */
   1718   for (u32 si = 0; si < x->nsegs; ++si) {
   1719     /* count sites in this segment */
   1720     u32 first = (u32)-1, count = 0;
   1721     for (u32 k = 0; k < fl->n; ++k) {
   1722       if (fl->a[k].segidx == si) {
   1723         if (first == (u32)-1) first = k;
   1724         ++count;
   1725       }
   1726     }
   1727     if (!count) continue;
   1728     if (emit_seg[si] == 0xffu)
   1729       compiler_panic(x->c, SRCLOC_NONE,
   1730                      "link_macho: chained fixups in non-emitted segment %u",
   1731                      (unsigned)si);
   1732     /* Page-align this struct to 4. */
   1733     objbb_align(out, 4);
   1734     u32 sis_off = out->len;
   1735     /* Patch seg_info_offset[emitted index] to (sis_off - starts_off). */
   1736     wr_u32_le(out->data + seg_info_offsets_pos + emit_seg[si] * 4u,
   1737               sis_off - starts_off);
   1738 
   1739     /* Compute page count for this segment. */
   1740     u64 seg_va = x->segs[si].vmaddr;
   1741     u64 seg_size = x->segs[si].vmsize ? x->segs[si].vmsize : MZ_PAGE;
   1742     u32 page_count = (u32)((seg_size + MZ_PAGE - 1u) / MZ_PAGE);
   1743 
   1744     /* dyld_chained_starts_in_segment:
   1745      *   uint32 size
   1746      *   uint16 page_size
   1747      *   uint16 pointer_format
   1748      *   uint64 segment_offset    (offset of segment's first byte from
   1749      *                             mach_header)
   1750      *   uint32 max_valid_pointer (0 for 64-bit)
   1751      *   uint16 page_count
   1752      *   uint16 page_start[page_count]  (0xFFFF = no fixups in page)
   1753      */
   1754     u32 sis_size_pos = objbb_u32(out, 0); /* fill below */
   1755     objbb_u16(out, (u16)MZ_PAGE);
   1756     objbb_u16(out, (u16)DYLD_CHAINED_PTR_64);
   1757     objbb_u64(out, (u64)x->segs[si].fileoff); /* segment file offset */
   1758     objbb_u32(out, 0);
   1759     objbb_u16(out, (u16)page_count);
   1760     u32 page_starts_pos = out->len;
   1761     for (u32 p = 0; p < page_count; ++p) objbb_u16(out, 0xFFFFu);
   1762     /* size includes the page_start array */
   1763     u32 sis_size = out->len - sis_size_pos + 4u;
   1764     /* Hmm, the `size` field is the size of *this* struct. We measure
   1765      * from sis_off through end of page_starts. */
   1766     sis_size = out->len - sis_off;
   1767     wr_u32_le(out->data + sis_size_pos, sis_size);
   1768 
   1769     /* Now: walk sites in this segment, group by page, write
   1770      * page_start[i] = offset_in_page of first site, and chain via
   1771      * next-field in the actual segment's bytes. */
   1772     /* Sites are sorted globally; collect contiguous run for this seg. */
   1773     u32 cur = first;
   1774     while (cur < first + count) {
   1775       u32 page_idx = (u32)((fl->a[cur].vaddr - seg_va) / MZ_PAGE);
   1776       u32 offset_in_page = (u32)((fl->a[cur].vaddr - seg_va) % MZ_PAGE);
   1777       wr_u16_le(out->data + page_starts_pos + page_idx * 2u,
   1778                 (u16)offset_in_page);
   1779       /* Walk this page's chain. */
   1780       u32 next_in_page = cur;
   1781       while (next_in_page + 1 < first + count) {
   1782         u64 nv = fl->a[next_in_page + 1].vaddr;
   1783         if (nv >= seg_va + (u64)(page_idx + 1) * MZ_PAGE) break;
   1784         ++next_in_page;
   1785       }
   1786       /* Encode chain pointers. */
   1787       for (u32 k = cur; k <= next_in_page; ++k) {
   1788         FixSite* s = &fl->a[k];
   1789         u32 next4 = 0;
   1790         if (k < next_in_page) {
   1791           u64 dist = fl->a[k + 1].vaddr - s->vaddr;
   1792           next4 = (u32)(dist / 4u);
   1793         }
   1794         /* Find segment bytes.  Synthetic pointer sections have private
   1795          * buffers; file-backed sections can live in any segment, including
   1796          * pointer-bearing read-only constants in __TEXT. */
   1797         u8* slot = NULL;
   1798         if (s->segidx == 2 && x->got_bytes && s->vaddr >= x->got_vaddr &&
   1799             s->vaddr < x->got_vaddr + x->got_size) {
   1800           /* __DATA_CONST: __got slot. */
   1801           slot = x->got_bytes + (s->vaddr - x->got_vaddr);
   1802         } else if (x->tlv_ptrs_bytes && s->vaddr >= x->tlv_ptrs_vaddr &&
   1803                    s->vaddr < x->tlv_ptrs_vaddr + x->tlv_ptrs_size) {
   1804           slot = x->tlv_ptrs_bytes + (s->vaddr - x->tlv_ptrs_vaddr);
   1805         } else {
   1806           MSec* m = msec_for_vaddr(x, s->vaddr);
   1807           if (m && m->link_sec_id) {
   1808             u8* base = bytes_for_section(x, m, x->img);
   1809             if (base) {
   1810               LinkSection* ls = &x->img->sections[m->link_sec_id - 1u];
   1811               u32 in_off = (u32)(ls->input_offset + (s->vaddr - m->vaddr));
   1812               slot = base + in_off;
   1813             }
   1814           }
   1815         }
   1816         if (!slot)
   1817           compiler_panic(x->c, SRCLOC_NONE,
   1818                          "link_macho: chained-fixup slot for vaddr 0x%llx not "
   1819                          "in any segment buffer",
   1820                          (unsigned long long)s->vaddr);
   1821         if (s->is_bind) {
   1822           /* ordinal is import index (1-based) - 1; chained-import format
   1823            * uses 0-based. */
   1824           if (s->import_idx == 0 || s->import_idx > x->nimports_real) {
   1825             compiler_panic(
   1826                 x->c, SRCLOC_NONE,
   1827                 "link_macho: chained bind for vaddr 0x%llx uses import index "
   1828                 "%u outside real import table size %u",
   1829                 (unsigned long long)s->vaddr, (unsigned)s->import_idx,
   1830                 (unsigned)x->nimports_real);
   1831           }
   1832           u32 ord = s->import_idx - 1u;
   1833           emit_pointer(slot, 1, ord, 0, next4);
   1834         } else {
   1835           /* rebase target = unslid vmaddr */
   1836           u32 lo = (u32)(s->rebase_target & 0xffffffffu);
   1837           u32 hi = (u32)(s->rebase_target >> 32);
   1838           emit_pointer(slot, 0, lo, hi, next4);
   1839         }
   1840       }
   1841       cur = next_in_page + 1u;
   1842     }
   1843   }
   1844 
   1845   /* Imports table: one dyld_chained_import (4B) per real import.
   1846    * Layout: lib_ordinal:8, weak:1, name_offset:23.  Internal-GOT
   1847    * entries are not bound by dyld so they're omitted here. */
   1848   objbb_align(out, 4);
   1849   u32 imports_off = out->len;
   1850   wr_u32_le(out->data + imports_offset_pos, imports_off);
   1851   /* We need to first build the symbol pool to know name offsets. */
   1852   u32 symbols_off = imports_off + x->nimports_real * 4u;
   1853   /* Reserve imports area. */
   1854   for (u32 i = 0; i < x->nimports_real; ++i) objbb_u32(out, 0);
   1855   /* Emit symbols (each NUL-terminated). Set name_offset on each import. */
   1856   wr_u32_le(out->data + symbols_offset_pos, out->len);
   1857   /* Leading NUL for offset 0. */
   1858   objbb_u8(out, 0);
   1859   for (u32 i = 0; i < x->nimports_real; ++i) {
   1860     MachImp* mi = &x->imports[i];
   1861     Slice nm_s = pool_slice(x->c->global, mi->name);
   1862     const char* nm = nm_s.s;
   1863     size_t nl = nm_s.len;
   1864     if (!nm || !nl || mi->dylib_ord == 0 || mi->dylib_ord > x->ndylibs) {
   1865       compiler_panic(x->c, SRCLOC_NONE,
   1866                      "link_macho: invalid chained import %u "
   1867                      "(name=%u dylib_ord=%u ndylibs=%u)",
   1868                      (unsigned)i, (unsigned)mi->name, (unsigned)mi->dylib_ord,
   1869                      (unsigned)x->ndylibs);
   1870     }
   1871     u32 off = out->len - symbols_off;
   1872     objbb_str(out, nm, (u32)nl);
   1873     /* Patch the import slot. */
   1874     u32 packed = ((u32)mi->dylib_ord & 0xffu) |
   1875                  ((u32)(mi->weak ? 1u : 0u) << 8) | ((off & 0x7fffffu) << 9);
   1876     wr_u32_le(out->data + imports_off + i * 4u, packed);
   1877   }
   1878   (void)symbols_off;
   1879 }
   1880 
   1881 /* ---- exports trie ---- *
   1882  *
   1883  * Minimal trie: one node carrying a single export "_main" with the
   1884  * entry symbol's VA-relative offset.  This is enough for dyld; binaries
   1885  * with a real exports trie include more data but we don't need it. */
   1886 
   1887 static void uleb128(ObjByteBuf* out, u64 v) {
   1888   do {
   1889     u8 byte = v & 0x7fu;
   1890     v >>= 7;
   1891     if (v) byte |= 0x80u;
   1892     objbb_u8(out, byte);
   1893   } while (v);
   1894 }
   1895 
   1896 static u32 uleb128_size(u64 v) {
   1897   u32 n = 0;
   1898   do {
   1899     ++n;
   1900     v >>= 7;
   1901   } while (v);
   1902   return n;
   1903 }
   1904 
   1905 static void build_exports_trie(MCtx* x) {
   1906   /* Format:
   1907    *   node = (terminal_size: uleb128) (export_data)? (children_count: u8)
   1908    *          (children: [(label NUL) (offset uleb128)]*)
   1909    *
   1910    * We emit a trie with a single leaf at "_main" with offset
   1911    * entry_offset (from __TEXT base).
   1912    *
   1913    * Easiest: single root node with children_count=1, child label = "_main",
   1914    * child offset points to a leaf node.
   1915    */
   1916   ObjByteBuf* out = &x->exports_trie;
   1917   objbb_init(out, x->h);
   1918 
   1919   LinkImage* img = x->img;
   1920   LinkSymbol* esym = sym_at(img, img->entry_sym);
   1921   if (!esym || !esym->defined) {
   1922     /* No entry — emit a single empty terminal trie. */
   1923     objbb_u8(out, 0); /* terminal_size 0 */
   1924     objbb_u8(out, 0); /* children 0 */
   1925     return;
   1926   }
   1927   Slice nm_s = pool_slice(x->c->global, esym->name);
   1928   const char* nm = nm_s.s;
   1929   size_t nl = nm_s.len;
   1930   if (!nm || nl == 0) {
   1931     objbb_u8(out, 0);
   1932     objbb_u8(out, 0);
   1933     return;
   1934   }
   1935   /* leaf node: terminal_size = sizeof(uleb(flags)+uleb(offset))
   1936    * flags = 0 (regular export); offset = vaddr - __TEXT.vmaddr */
   1937   u64 entry_off = esym->vaddr - x->text_vaddr;
   1938 
   1939   /* Compute leaf-node bytes length: uleb(flags=0) + uleb(offset). */
   1940   u32 flags = 0;
   1941   u32 leaf_payload_len = uleb128_size(flags) + uleb128_size(entry_off);
   1942   /* Layout: root node first, then leaf.  The root node's child entry
   1943    * carries the absolute offset of the leaf within the trie. */
   1944 
   1945   /* root: terminal_size=0, children_count=1, "_main"\0, child_offset=
   1946    *        (leaf-position uleb).
   1947    *
   1948    * The child offset's own ULEB width contributes to the leaf position, so
   1949    * solve for the fixed point before emitting. */
   1950   u32 leaf_pos = 2u + (u32)nl + 1u + 1u;
   1951   for (;;) {
   1952     u32 n = uleb128_size(leaf_pos);
   1953     u32 next = 2u + (u32)nl + 1u + n;
   1954     if (next == leaf_pos) break;
   1955     leaf_pos = next;
   1956   }
   1957 
   1958   objbb_u8(out, 0); /* root terminal size */
   1959   objbb_u8(out, 1); /* children_count */
   1960   objbb_str(out, nm, (u32)nl);
   1961   uleb128(out, leaf_pos);
   1962   /* leaf node */
   1963   if (out->len != leaf_pos)
   1964     compiler_panic(x->c, SRCLOC_NONE,
   1965                    "macho: exports trie leaf offset mismatch");
   1966   /* terminal_size byte then payload */
   1967   objbb_u8(out, (u8)leaf_payload_len);
   1968   uleb128(out, flags);
   1969   uleb128(out, entry_off);
   1970   objbb_u8(out, 0); /* children_count */
   1971   /* Pad trie to 8 bytes. */
   1972   objbb_align(out, 8);
   1973 }
   1974 
   1975 /* ---- symtab + strtab + indirect symtab ---- */
   1976 
   1977 typedef struct NlistRec {
   1978   u32 strx;
   1979   u8 type;
   1980   u8 sect; /* 1-based section index (Mach-O) */
   1981   u16 desc;
   1982   u64 value;
   1983 } NlistRec;
   1984 
   1985 static void build_symtab(MCtx* x) {
   1986   Heap* h = x->h;
   1987   LinkImage* img = x->img;
   1988   objbb_init(&x->symtab, h);
   1989   objbb_init(&x->strtab, h);
   1990   objbb_init(&x->indirect, h);
   1991 
   1992   /* strtab leading NUL */
   1993   objbb_u8(&x->strtab, 0);
   1994 
   1995   /* Approach:
   1996    * - Add one local nlist per defined LinkSymbol (locals + non-imported
   1997    *   externs) — but to keep things simple we only emit external defined
   1998    *   syms (mainly _main), plus all imports as N_UNDF|N_EXT.
   1999    *
   2000    * Mach-O dyld requires the symtab order: locals first, ext-defs next,
   2001    * undef last (matched by LC_DYSYMTAB ranges).
   2002    */
   2003 
   2004   /* Pass A: defined externals. */
   2005   u32 n_local = 0;
   2006   u32 n_extdef = 0;
   2007   u32 n_undef = 0;
   2008 
   2009   /* For now we emit only externals + imports.  No locals. */
   2010   /* extdef pass */
   2011   for (u32 i = 0; i < LinkSyms_count(&img->syms); ++i) {
   2012     LinkSymbol* s = LinkSyms_at(&img->syms, i);
   2013     if (!s->defined) continue;
   2014     if (s->bind != SB_GLOBAL && s->bind != SB_WEAK) continue;
   2015     if (s->name == 0) continue;
   2016     if (!link_symbol_is_canonical_global(img, s)) continue;
   2017     if (s->kind == SK_ABS) continue; /* skip abs externs */
   2018     /* Locate which OutSec contains this vaddr to figure out n_sect.
   2019      * n_sect is the 1-based index into the flat section_64 table the
   2020      * file actually contains (post-coalesce), matching what we emit
   2021      * in emit_load_command_segment. */
   2022     u8 n_sect = 0;
   2023     /* Prefer the section whose half-open [vaddr, vaddr+size) range contains
   2024      * the symbol. This must win over the end-boundary fallback below: when
   2025      * two sections abut (A ends exactly where B begins), a symbol at the
   2026      * boundary is the *start* of B, not the end of A. */
   2027     for (u32 k = 0; k < x->nouts; ++k) {
   2028       OutSec* o = &x->outs[k];
   2029       if (s->vaddr >= o->vaddr && s->vaddr < o->vaddr + o->size) {
   2030         n_sect = (u8)(k + 1u);
   2031         break;
   2032       }
   2033     }
   2034     /* Fallback: a symbol sitting exactly one-past-the-end of a section with
   2035      * no following section covering it (e.g. an end-of-section marker) is
   2036      * attributed to the section that ends there. */
   2037     if (n_sect == 0) {
   2038       for (u32 k = 0; k < x->nouts; ++k) {
   2039         OutSec* o = &x->outs[k];
   2040         if (s->vaddr == o->vaddr + o->size) {
   2041           n_sect = (u8)(k + 1u);
   2042           break;
   2043         }
   2044       }
   2045     }
   2046     if (n_sect == 0) continue;
   2047     Slice nm_s = pool_slice(x->c->global, s->name);
   2048     const char* nm = nm_s.s;
   2049     size_t nl = nm_s.len;
   2050     u32 strx = x->strtab.len;
   2051     if (nm && nl) objbb_str(&x->strtab, nm, (u32)nl);
   2052 
   2053     u8 t[16];
   2054     u8 nt = N_SECT | N_EXT;
   2055     if (s->bind == SB_WEAK) {
   2056       /* N_WEAK_DEF in n_desc (not a flag in n_type) */
   2057     }
   2058     wr_u32_le(t + 0, strx);
   2059     t[4] = nt;
   2060     t[5] = n_sect;
   2061     wr_u16_le(t + 6, s->bind == SB_WEAK ? N_WEAK_DEF : 0);
   2062     wr_u64_le(t + 8, s->vaddr);
   2063     objbb_append(&x->symtab, t, 16);
   2064     ++n_extdef;
   2065   }
   2066 
   2067   /* undef imports — real imports only.  Internal-GOT entries don't get
   2068    * N_UNDF nlist records since they're defined in the image. */
   2069   u32 imp_first_symtab_idx = n_extdef;
   2070   for (u32 i = 0; i < x->nimports_real; ++i) {
   2071     MachImp* mi = &x->imports[i];
   2072     Slice nm_s = pool_slice(x->c->global, mi->name);
   2073     const char* nm = nm_s.s;
   2074     size_t nl = nm_s.len;
   2075     u32 strx = x->strtab.len;
   2076     if (nm && nl) objbb_str(&x->strtab, nm, (u32)nl);
   2077 
   2078     u8 t[16];
   2079     wr_u32_le(t + 0, strx);
   2080     t[4] = N_UNDF | N_EXT;
   2081     t[5] = 0;
   2082     /* n_desc carries dylib ordinal in high byte (REFERENCED_DYNAMICALLY etc.)
   2083      */
   2084     u16 desc = (u16)(((u16)mi->dylib_ord & 0xff) << 8);
   2085     if (mi->weak) desc |= N_WEAK_REF;
   2086     wr_u16_le(t + 6, desc);
   2087     wr_u64_le(t + 8, 0);
   2088     objbb_append(&x->symtab, t, 16);
   2089     ++n_undef;
   2090   }
   2091 
   2092   /* indirect symtab: one entry per __stubs slot, then one per __got
   2093    * slot.  Internal-GOT slots use INDIRECT_SYMBOL_LOCAL (0x80000000)
   2094    * since they have no nlist entry. */
   2095   u32 indirect_start = 0;
   2096   /* Patch reserved1 of each synth OutSec.  __stubs and __got are each
   2097    * singleton OutSecs (synth sections never coalesce with user input),
   2098    * so a sectname match identifies them unambiguously. */
   2099   for (u32 i = 0; i < x->nouts; ++i) {
   2100     OutSec* o = &x->outs[i];
   2101     if (slice_eq_cstr(slice_from_cstr(o->sectname), "__stubs") && o->size) {
   2102       o->reserved1 = indirect_start;
   2103       for (u32 k = 0; k < x->nimports; ++k) {
   2104         MachImp* mi = &x->imports[k];
   2105         if (!mi->stub_idx) continue;
   2106         u32 sym_idx = imp_first_symtab_idx + k;
   2107         objbb_u32(&x->indirect, sym_idx);
   2108         ++indirect_start;
   2109       }
   2110     }
   2111   }
   2112   for (u32 i = 0; i < x->nouts; ++i) {
   2113     OutSec* o = &x->outs[i];
   2114     if (slice_eq_cstr(slice_from_cstr(o->sectname), "__got") && o->size) {
   2115       o->reserved1 = indirect_start;
   2116       for (u32 k = 0; k < x->nimports; ++k) {
   2117         MachImp* mi = &x->imports[k];
   2118         u32 sym_idx = mi->internal ? 0x80000000u /* INDIRECT_SYMBOL_LOCAL */
   2119                                    : (imp_first_symtab_idx + k);
   2120         objbb_u32(&x->indirect, sym_idx);
   2121         ++indirect_start;
   2122       }
   2123     }
   2124   }
   2125 
   2126   x->nsyms = n_local + n_extdef + n_undef;
   2127   (void)n_local;
   2128   (void)imp_first_symtab_idx;
   2129 }
   2130 
   2131 /* ---- LINKEDIT layout assembly ----
   2132  *
   2133  * Place blobs in the order Apple prefers:
   2134  *   chained_fixups, exports_trie, fn_starts, data_in_code,
   2135  *   symtab, indirect, strtab, codesig
   2136  */
   2137 
   2138 static void layout_linkedit(MCtx* x) {
   2139   /* LC_FUNCTION_STARTS is a ULEB128 stream terminated by a zero byte.  Keep a
   2140    * real empty table here so tools that rewrite LINKEDIT preserve the
   2141    * canonical blob order between exports and the symbol table. */
   2142   objbb_init(&x->fn_starts, x->h);
   2143   objbb_u8(&x->fn_starts, 0);
   2144   objbb_init(&x->data_in_code, x->h);
   2145   objbb_init(&x->codesig, x->h);
   2146 
   2147   u64 cur = x->linkedit_fileoff;
   2148   /* chained fixups */
   2149   cur = ALIGN_UP(cur, 8u);
   2150   x->chained_fixups_off = (u32)cur;
   2151   cur += x->chained_fixups.len;
   2152   /* exports trie. Keep LINKEDIT data blobs contiguous; Apple strip rejects
   2153    * padding between chained fixups and the exports trie. */
   2154   x->exports_trie_off = (u32)cur;
   2155   cur += x->exports_trie.len;
   2156   /* function starts */
   2157   x->fn_starts_off = (u32)cur;
   2158   cur += x->fn_starts.len;
   2159   /* data in code */
   2160   cur = ALIGN_UP(cur, 8u);
   2161   x->data_in_code_off = (u32)cur;
   2162   /* symtab */
   2163   cur = ALIGN_UP(cur, 8u);
   2164   x->symtab_off = (u32)cur;
   2165   cur += x->symtab.len;
   2166   /* indirect symtab */
   2167   cur = ALIGN_UP(cur, 4u);
   2168   x->indirect_off = (u32)cur;
   2169   cur += x->indirect.len;
   2170   /* strtab */
   2171   cur = ALIGN_UP(cur, 8u);
   2172   x->strtab_off = (u32)cur;
   2173   cur += x->strtab.len;
   2174   /* code signature: end-aligned to 16 */
   2175   cur = ALIGN_UP(cur, 16u);
   2176   x->codesig_off = (u32)cur;
   2177 
   2178   /* Linkedit segment file_size includes everything up to (but not yet
   2179    * including) codesig.  Codesig is computed below. */
   2180   u64 le_size = cur - x->linkedit_fileoff;
   2181   /* Set linkedit segment size; will be increased after codesig. */
   2182   x->segs[MSEG_LINKEDIT].filesize = le_size;
   2183   x->segs[MSEG_LINKEDIT].vmsize = ALIGN_UP(le_size, MZ_PAGE);
   2184   if (!x->segs[MSEG_LINKEDIT].vmsize) x->segs[MSEG_LINKEDIT].vmsize = MZ_PAGE;
   2185 }
   2186 
   2187 /* ---- ad-hoc code signature (CodeDirectory + SuperBlob) ----
   2188  *
   2189  * Produces a minimal embedded SuperBlob with a single CodeDirectory.
   2190  * The CD is sha256-hashed over CS_PAGE_SIZE_LOG2 = 4096-byte pages of
   2191  * the file (excluding the codesig itself).  The kernel verifies the
   2192  * CD's hash chain on exec.
   2193  *
   2194  * Output format (in big-endian for SuperBlob/CodeDirectory headers):
   2195  *   [SuperBlob]
   2196  *     u32 magic    (0xfade0cc0)
   2197  *     u32 length
   2198  *     u32 count    (=1)
   2199  *     [Slot]
   2200  *       u32 type (=0 CSSLOT_CODEDIRECTORY)
   2201  *       u32 offset (=20)  -- relative to start of SuperBlob
   2202  *   [CodeDirectory]
   2203  *     u32 magic    (0xfade0c02)
   2204  *     u32 length   (bytes including all hashes)
   2205  *     u32 version  (>=0x20400 for execSeg fields)
   2206  *     u32 flags    (=0 ad-hoc — actually flags must include 0x2
   2207  * (kSecCodeSignatureAdhoc)) u32 hashOffset  (offset of first slot hash) u32
   2208  * identOffset (offset of identifier string) u32 nSpecialSlots (=0) u32
   2209  * nCodeSlots u32 codeLimit  (file bytes covered) u8  hashSize   (=32) u8
   2210  * hashType   (=2 sha256) u8  platform   (=0) u8  pageSize   (=12 for 4096) u32
   2211  * spare2     (=0) u32 scatterOffset (=0) u32 teamOffset    (=0) u32 spare3 (=0)
   2212  *     u64 codeLimit64   (=0)
   2213  *     u64 execSegBase   (=__TEXT.fileoff)
   2214  *     u64 execSegLimit  (=__TEXT.filesize)
   2215  *     u64 execSegFlags  (=1 main binary)
   2216  *     [identifier bytes "a.out\0"]
   2217  *     [codeslot hashes  nCodeSlots * 32 B]
   2218  *
   2219  * Hashes computed AFTER everything else is final — including the codesig
   2220  * blob's own offset in the file (the hash range stops just before
   2221  * codeLimit). */
   2222 
   2223 static void wr_u64_be(u8* p, u64 v) {
   2224   for (u32 i = 0; i < 8; ++i) p[7 - i] = (u8)(v >> (i * 8));
   2225 }
   2226 
   2227 /* Build the codesig blob with placeholder hashes; size is precise so
   2228  * file layout is final after this. */
   2229 static void build_codesig_skeleton(MCtx* x, u32 code_limit, const char* ident) {
   2230   u32 page_log2 = x->cs_page_log2 ? x->cs_page_log2 : CS_PAGE_SIZE_LOG2;
   2231   u32 code_page = 1u << page_log2;
   2232   u32 nslots = (code_limit + code_page - 1u) / code_page;
   2233 
   2234   /* CodeDirectory size:
   2235    *   header 88 bytes through execSegFlags
   2236    *   identifier (ident_len + 1)
   2237    *   hashes (nslots * 32)
   2238    */
   2239   u32 ident_len = (u32)slice_from_cstr(ident).len + 1u;
   2240   u32 cd_hdr = 88u;
   2241   u32 cd_size = cd_hdr + ident_len + nslots * CS_SHA256_LEN;
   2242   /* SuperBlob: 12 hdr + 8 slot + cd. */
   2243   u32 sb_size = 12u + 8u + cd_size;
   2244 
   2245   ObjByteBuf* out = &x->codesig;
   2246   objbb_init(out, x->h);
   2247   objbb_reserve(out, sb_size);
   2248   memset(out->data, 0, sb_size);
   2249   out->len = sb_size;
   2250 
   2251   u8* sb = out->data;
   2252   /* SuperBlob header */
   2253   wr_u32_be(sb + 0, CS_MAGIC_EMBEDDED_SIGNATURE);
   2254   wr_u32_be(sb + 4, sb_size);
   2255   wr_u32_be(sb + 8, 1); /* count */
   2256   /* slot 0: type=CSSLOT_CODEDIRECTORY, offset=20 */
   2257   wr_u32_be(sb + 12, CSSLOT_CODEDIRECTORY);
   2258   wr_u32_be(sb + 16, 20u);
   2259 
   2260   /* CodeDirectory */
   2261   u8* cd = sb + 20;
   2262   wr_u32_be(cd + 0, CS_MAGIC_CODEDIRECTORY);
   2263   wr_u32_be(cd + 4, cd_size);
   2264   wr_u32_be(cd + 8, 0x20400u);            /* version with execSeg */
   2265   wr_u32_be(cd + 12, 0x2u);               /* flags = adhoc */
   2266   wr_u32_be(cd + 16, cd_hdr + ident_len); /* hashOffset */
   2267   wr_u32_be(cd + 20, cd_hdr);             /* identOffset */
   2268   wr_u32_be(cd + 24, 0);                  /* nSpecialSlots */
   2269   wr_u32_be(cd + 28, nslots);
   2270   wr_u32_be(cd + 32, code_limit);
   2271   cd[36] = (u8)CS_SHA256_LEN;
   2272   cd[37] = (u8)CS_HASHTYPE_SHA256;
   2273   cd[38] = 0; /* platform */
   2274   cd[39] = (u8)page_log2;
   2275   wr_u32_be(cd + 40, 0);                   /* spare2 */
   2276   wr_u32_be(cd + 44, 0);                   /* scatterOffset */
   2277   wr_u32_be(cd + 48, 0);                   /* teamOffset */
   2278   wr_u32_be(cd + 52, 0);                   /* spare3 */
   2279   wr_u64_be(cd + 56, 0);                   /* codeLimit64 */
   2280   wr_u64_be(cd + 64, x->segs[1].fileoff);  /* execSegBase */
   2281   wr_u64_be(cd + 72, x->segs[1].filesize); /* execSegLimit */
   2282   wr_u64_be(cd + 80, CS_EXECSEG_MAIN_BINARY);
   2283 
   2284   /* identifier */
   2285   memcpy(cd + cd_hdr, ident, ident_len);
   2286 
   2287   x->codesig_size = sb_size;
   2288 }
   2289 
   2290 static void compute_codesig(MCtx* x, const u8* full_file, u32 file_len_excl_cs,
   2291                             const char* ident) {
   2292   u32 page_log2 = x->cs_page_log2 ? x->cs_page_log2 : CS_PAGE_SIZE_LOG2;
   2293   u32 code_page = 1u << page_log2;
   2294   u32 nslots = (file_len_excl_cs + code_page - 1u) / code_page;
   2295   u32 ident_len = (u32)slice_from_cstr(ident).len + 1u;
   2296   u8* cd = x->codesig.data + 12 + 8;
   2297   u8* hashes = cd + 88u + ident_len;
   2298 
   2299   for (u32 i = 0; i < nslots; ++i) {
   2300     u32 off = i * code_page;
   2301     u32 take = (off + code_page <= file_len_excl_cs) ? code_page
   2302                                                      : (file_len_excl_cs - off);
   2303     Sha256 s;
   2304     sha256_init(&s);
   2305     sha256_update(&s, full_file + off, take);
   2306     /* Pages shorter than code_page get the standard SHA over the
   2307      * partial bytes — Apple's tools do exactly this (no zero padding
   2308      * on the tail). */
   2309     sha256_final(&s, hashes + i * CS_SHA256_LEN);
   2310   }
   2311 }
   2312 
   2313 /* ---- final emission ---- */
   2314 
   2315 static void emit_load_command_segment(ObjByteBuf* lc, MCtx* x, u32 segidx) {
   2316   MSeg* sg = &x->segs[segidx];
   2317   u32 seg_cmd_size = MACHO_SEGCMD64_SIZE + sg->nouts * MACHO_SECT64_SIZE;
   2318   u32 base = lc->len;
   2319   objbb_u32(lc, LC_SEGMENT_64);
   2320   objbb_u32(lc, seg_cmd_size);
   2321   /* segname: 16 bytes zero-padded */
   2322   u8 nm[16];
   2323   memset(nm, 0, 16);
   2324   size_t nlen = slice_from_cstr(sg->name).len;
   2325   if (nlen > 16) nlen = 16;
   2326   memcpy(nm, sg->name, nlen);
   2327   objbb_append(lc, nm, 16);
   2328   objbb_u64(lc, sg->vmaddr);
   2329   objbb_u64(lc, sg->vmsize);
   2330   objbb_u64(lc, sg->fileoff);
   2331   objbb_u64(lc, sg->filesize);
   2332   objbb_u32(lc, sg->maxprot);
   2333   objbb_u32(lc, sg->initprot);
   2334   objbb_u32(lc, sg->nouts);
   2335   objbb_u32(lc, 0); /* flags */
   2336 
   2337   for (u32 j = 0; j < sg->nouts; ++j) {
   2338     OutSec* o = &x->outs[sg->first_out + j];
   2339     u8 sname[16], gname[16];
   2340     memset(sname, 0, 16);
   2341     memset(gname, 0, 16);
   2342     size_t sl = o->sectname ? slice_from_cstr(o->sectname).len : 0;
   2343     if (sl > 16) sl = 16;
   2344     if (sl) memcpy(sname, o->sectname, sl);
   2345     size_t gl = slice_from_cstr(sg->name).len; /* segname must match */
   2346     if (gl > 16) gl = 16;
   2347     memcpy(gname, sg->name, gl);
   2348     objbb_append(lc, sname, 16);
   2349     objbb_append(lc, gname, 16);
   2350     objbb_u64(lc, o->vaddr);
   2351     objbb_u64(lc, o->size);
   2352     objbb_u32(lc, (u32)o->file_offset);
   2353     /* align is power of 2; encode as log2. */
   2354     u32 a = o->align ? o->align : 1u;
   2355     u32 al = 0;
   2356     while ((1u << al) < a) ++al;
   2357     objbb_u32(lc, al);
   2358     objbb_u32(lc, 0); /* reloff */
   2359     objbb_u32(lc, 0); /* nreloc */
   2360     objbb_u32(lc, o->flags);
   2361     objbb_u32(lc, o->reserved1);
   2362     objbb_u32(lc, o->reserved2);
   2363     objbb_u32(lc, 0); /* reserved3 */
   2364   }
   2365   (void)base;
   2366 }
   2367 
   2368 void link_emit_macho(LinkImage* img, Writer* w);
   2369 
   2370 void link_emit_macho(LinkImage* img, Writer* w) {
   2371   MCtx x;
   2372   memset(&x, 0, sizeof(x));
   2373   x.img = img;
   2374   x.c = img->c;
   2375   x.h = img->heap;
   2376   x.w = w;
   2377   x.linker = img->linker;
   2378   x.link_arch = link_arch_desc_for(img->c);
   2379   /* Apple-Silicon arm64 mains must be signed with 16 KiB hash pages (the native
   2380    * VM page); x86_64 uses 4 KiB. See CS_PAGE_SIZE_LOG2. */
   2381   x.cs_page_log2 = (img->c->target.arch == KIT_ARCH_ARM_64) ? 14u : 12u;
   2382   {
   2383     const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_MACHO);
   2384     x.macho =
   2385         fmt && fmt->macho_arch ? fmt->macho_arch(img->c->target.arch) : NULL;
   2386   }
   2387 
   2388   if (!x.link_arch || !x.macho || !x.macho->cputype || !x.macho->emit_stub ||
   2389       !x.macho->stub_size)
   2390     compiler_panic(x.c, SRCLOC_NONE,
   2391                    "link_emit_macho: no Mach-O descriptor for target");
   2392   if (img->entry_sym == LINK_SYM_NONE)
   2393     compiler_panic(x.c, SRCLOC_NONE, "link_emit_macho: no resolved entry");
   2394 
   2395   collect_imports(&x);
   2396   collect_tlv(&x);
   2397   plan_layout(&x);
   2398   build_section_index(&x);
   2399   shift_sections(&x);
   2400 
   2401   /* entry offset within __TEXT segment. */
   2402   LinkSymbol* esym = sym_at(img, img->entry_sym);
   2403   if (!esym || !esym->defined)
   2404     compiler_panic(x.c, SRCLOC_NONE, "link_emit_macho: entry symbol undefined");
   2405   if (esym->vaddr < x.text_vaddr)
   2406     compiler_panic(x.c, SRCLOC_NONE,
   2407                    "link_emit_macho: entry symbol below __TEXT base");
   2408   x.entry_offset = (u32)(esym->vaddr - x.text_vaddr);
   2409 
   2410   /* image-id UUID. */
   2411   u8 image_id[LINK_IMAGE_ID_BYTES];
   2412   link_image_id_compute(img, image_id);
   2413   memcpy(x.uuid, image_id, 16);
   2414 
   2415   /* Reloc apply collects fixsites. */
   2416   FixList fl;
   2417   fix_init(&fl, x.h);
   2418   apply_relocs(&x, &fl);
   2419 
   2420   /* Build LINKEDIT contents. */
   2421   build_chained_fixups(&x, &fl);
   2422   build_exports_trie(&x);
   2423   build_symtab(&x);
   2424   layout_linkedit(&x);
   2425 
   2426   /* Compute code-sig skeleton sized to file bytes excluding sig. */
   2427   u32 code_limit = x.codesig_off;
   2428   build_codesig_skeleton(&x, code_limit, "a.out");
   2429   /* Now extend linkedit segment to include codesig. */
   2430   u64 le_size = (u64)x.codesig_off + (u64)x.codesig_size - x.linkedit_fileoff;
   2431   x.segs[MSEG_LINKEDIT].filesize = le_size;
   2432   x.segs[MSEG_LINKEDIT].vmsize = ALIGN_UP(le_size, MZ_PAGE);
   2433 
   2434   /* Build load commands buffer. */
   2435   ObjByteBuf lc;
   2436   objbb_init(&lc, x.h);
   2437 
   2438   /* LC_SEGMENT_64 for each segment with sections (and PAGEZERO/LINKEDIT). */
   2439   emit_load_command_segment(&lc, &x, 0); /* PAGEZERO */
   2440   emit_load_command_segment(&lc, &x, 1); /* TEXT */
   2441   if (x.segs[2].nsects > 0)
   2442     emit_load_command_segment(&lc, &x, 2); /* DATA_CONST */
   2443   if (x.segs[3].nsects > 0) emit_load_command_segment(&lc, &x, 3); /* DATA */
   2444   if (x.segs[MSEG_DWARF].nsects > 0)
   2445     emit_load_command_segment(&lc, &x, MSEG_DWARF);  /* DWARF (debug info) */
   2446   emit_load_command_segment(&lc, &x, MSEG_LINKEDIT); /* LINKEDIT */
   2447 
   2448   /* LC_DYLD_CHAINED_FIXUPS  (linkedit_data_command: 16B) */
   2449   objbb_u32(&lc, LC_DYLD_CHAINED_FIXUPS);
   2450   objbb_u32(&lc, 16);
   2451   objbb_u32(&lc, x.chained_fixups_off);
   2452   objbb_u32(&lc, x.chained_fixups.len);
   2453 
   2454   /* LC_DYLD_EXPORTS_TRIE */
   2455   objbb_u32(&lc, LC_DYLD_EXPORTS_TRIE);
   2456   objbb_u32(&lc, 16);
   2457   objbb_u32(&lc, x.exports_trie_off);
   2458   objbb_u32(&lc, x.exports_trie.len);
   2459 
   2460   /* LC_SYMTAB */
   2461   objbb_u32(&lc, LC_SYMTAB);
   2462   objbb_u32(&lc, MACHO_SYMTAB_CMD_SIZE);
   2463   objbb_u32(&lc, x.symtab_off);
   2464   objbb_u32(&lc, x.nsyms);
   2465   objbb_u32(&lc, x.strtab_off);
   2466   objbb_u32(&lc, x.strtab.len);
   2467 
   2468   /* LC_DYSYMTAB */
   2469   /* nlocal=0, nextdef=#defined-globals, nundef=#imports.  We tracked
   2470    * those during build_symtab; recompute by inspecting strtab... easier
   2471    * to recount: defined globals are total - imports. */
   2472   u32 nlocal = 0;
   2473   u32 nundef = x.nimports_real;
   2474   u32 nextdef = (x.nsyms > nundef) ? x.nsyms - nundef - nlocal : 0;
   2475   objbb_u32(&lc, LC_DYSYMTAB);
   2476   objbb_u32(&lc, MACHO_DYSYMTAB_CMD_SIZE);
   2477   objbb_u32(&lc, 0); /* ilocalsym */
   2478   objbb_u32(&lc, nlocal);
   2479   objbb_u32(&lc, nlocal);
   2480   objbb_u32(&lc, nextdef);
   2481   objbb_u32(&lc, nlocal + nextdef);
   2482   objbb_u32(&lc, nundef);
   2483   objbb_u32(&lc, 0);
   2484   objbb_u32(&lc, 0); /* tocoff, ntoc */
   2485   objbb_u32(&lc, 0);
   2486   objbb_u32(&lc, 0); /* modtaboff, nmodtab */
   2487   objbb_u32(&lc, 0);
   2488   objbb_u32(&lc, 0); /* extrefsymoff, nextrefsyms */
   2489   objbb_u32(&lc, x.indirect_off);
   2490   objbb_u32(&lc, x.indirect.len / 4u);
   2491   objbb_u32(&lc, 0);
   2492   objbb_u32(&lc, 0); /* extreloff, nextrel */
   2493   objbb_u32(&lc, 0);
   2494   objbb_u32(&lc, 0); /* locreloff, nlocrel */
   2495 
   2496   /* LC_LOAD_DYLINKER */
   2497   {
   2498     const char* dyld = "/usr/lib/dyld";
   2499     u32 dyld_len = (u32)slice_from_cstr(dyld).len;
   2500     u32 cmd_size = (u32)ALIGN_UP((u64)(12u + dyld_len + 1u), 8u);
   2501     objbb_u32(&lc, LC_LOAD_DYLINKER);
   2502     objbb_u32(&lc, cmd_size);
   2503     objbb_u32(&lc, 12u); /* name offset within cmd */
   2504     u32 wrote = objbb_str(&lc, dyld, dyld_len);
   2505     (void)wrote;
   2506     /* Pad to cmd_size. */
   2507     while (lc.len < (u32)((u64)objbb_align(&lc, 1) + 0)) {
   2508       /* no-op */
   2509       break;
   2510     }
   2511     /* Re-align to cmd_size. */
   2512     u32 want = (u32)(lc.len);
   2513     /* Walk back: lc grew by 12 + (strlen+1).  Pad to cmd_size. */
   2514     u32 cmd_start_back = lc.len - (12u + dyld_len + 1u);
   2515     u32 pad_needed = cmd_size - (lc.len - cmd_start_back);
   2516     while (pad_needed-- > 0) objbb_u8(&lc, 0);
   2517     (void)want;
   2518   }
   2519 
   2520   /* LC_UUID */
   2521   objbb_u32(&lc, LC_UUID);
   2522   objbb_u32(&lc, 24);
   2523   objbb_append(&lc, x.uuid, 16);
   2524 
   2525   /* LC_BUILD_VERSION */
   2526   objbb_u32(&lc, LC_BUILD_VERSION);
   2527   objbb_u32(&lc, 24);
   2528   objbb_u32(&lc, macho_platform_for_target(img->c->target));
   2529   objbb_u32(&lc, (12u << 16) | 0); /* minos 12.0.0 */
   2530   objbb_u32(&lc, (12u << 16) | 0); /* sdk   12.0.0 */
   2531   objbb_u32(&lc, 0);               /* ntools */
   2532 
   2533   /* LC_MAIN — entryoff is offset within __TEXT segment from its file
   2534    * start (0). */
   2535   objbb_u32(&lc, LC_MAIN);
   2536   objbb_u32(&lc, 24);
   2537   objbb_u64(&lc, (u64)x.entry_offset); /* entryoff = vaddr - __TEXT.vmaddr */
   2538   objbb_u64(&lc, 0);                   /* stacksize */
   2539 
   2540   /* LC_LOAD_DYLIB per dylib. */
   2541   for (u32 i = 0; i < x.ndylibs; ++i) {
   2542     Slice nm_s = pool_slice(x.c->global, x.dylibs[i].install);
   2543     const char* nm = nm_s.s;
   2544     size_t nl = nm_s.len;
   2545     u32 cmd_size = (u32)ALIGN_UP((u64)(24u + (u32)nl + 1u), 8u);
   2546     u32 cmd_start = lc.len;
   2547     objbb_u32(&lc, LC_LOAD_DYLIB);
   2548     objbb_u32(&lc, cmd_size);
   2549     objbb_u32(&lc, 24u);        /* name offset */
   2550     objbb_u32(&lc, 0);          /* timestamp */
   2551     objbb_u32(&lc, (1u << 16)); /* current_version 1.0 */
   2552     objbb_u32(&lc, (1u << 16)); /* compat_version 1.0 */
   2553     objbb_str(&lc, nm ? nm : "", (u32)nl);
   2554     while (lc.len - cmd_start < cmd_size) objbb_u8(&lc, 0);
   2555   }
   2556 
   2557   /* LC_FUNCTION_STARTS / LC_DATA_IN_CODE */
   2558   objbb_u32(&lc, LC_FUNCTION_STARTS_C);
   2559   objbb_u32(&lc, 16);
   2560   objbb_u32(&lc, x.fn_starts_off);
   2561   objbb_u32(&lc, x.fn_starts.len);
   2562 
   2563   objbb_u32(&lc, LC_DATA_IN_CODE_C);
   2564   objbb_u32(&lc, 16);
   2565   objbb_u32(&lc, x.data_in_code_off);
   2566   objbb_u32(&lc, 0);
   2567 
   2568   /* LC_CODE_SIGNATURE */
   2569   objbb_u32(&lc, LC_CODE_SIGNATURE_C);
   2570   objbb_u32(&lc, 16);
   2571   objbb_u32(&lc, x.codesig_off);
   2572   objbb_u32(&lc, x.codesig_size);
   2573 
   2574   /* Sanity: lc.len + MACHO_HDR64_SIZE must equal headers_size we
   2575    * predicted in plan_layout.  If not, we mis-sized — panic. */
   2576   if ((u64)lc.len + MACHO_HDR64_SIZE != x.headers_size) {
   2577     compiler_panic(x.c, SRCLOC_NONE,
   2578                    "link_macho: load-cmd size mismatch: predicted %llu got %u",
   2579                    (unsigned long long)(x.headers_size - MACHO_HDR64_SIZE),
   2580                    lc.len);
   2581   }
   2582 
   2583   /* ---- now stream the file ---- */
   2584   /* The Writer in kit allows seek; we'll write a flat buffer first
   2585    * (so we can hash it for codesig) and flush at the end. */
   2586   ObjByteBuf file;
   2587   objbb_init(&file, x.h);
   2588 
   2589   /* mach_header_64 */
   2590   u32 ncmds = 0;
   2591   /* Recount: PAGEZERO + TEXT + maybe DATA_CONST + maybe DATA + LINKEDIT
   2592    * + chained + exports_trie + symtab + dysymtab + dyld + uuid +
   2593    * build_version + main + nDylibs + fn_starts + data_in_code +
   2594    * codesig. */
   2595   ncmds += 2; /* PAGEZERO + TEXT */
   2596   if (x.segs[2].nsects > 0) ncmds++;
   2597   if (x.segs[3].nsects > 0) ncmds++;
   2598   if (x.segs[MSEG_DWARF].nsects > 0) ncmds++; /* __DWARF (debug info) */
   2599   ncmds++;                                    /* LINKEDIT */
   2600   ncmds += 11 + x.ndylibs;
   2601   /* (chained, exports_trie, symtab, dysymtab, dyld, uuid, build_version,
   2602    *  main, fn_starts, data_in_code, codesig) = 11 */
   2603 
   2604   objbb_u32(&file, MH_MAGIC_64);
   2605   objbb_u32(&file, x.macho->cputype);
   2606   objbb_u32(&file, x.macho->cpusubtype);
   2607   objbb_u32(&file, MH_EXECUTE);
   2608   objbb_u32(&file, ncmds);
   2609   objbb_u32(&file, lc.len);
   2610   {
   2611     u32 mh_flags = MH_DYLDLINK | MH_TWOLEVEL | MH_NOUNDEFS | MH_PIE;
   2612     /* dyld scans __thread_vars and allocates a pthread_key for each
   2613      * descriptor only when this flag is set; without it the descriptor's
   2614      * thunk pointer is silently patched to _tlv_bootstrap_error.  Apple's
   2615      * ld sets it whenever the image contains S_THREAD_LOCAL_* sections. */
   2616     if (x.ntlv) mh_flags |= MH_HAS_TLV_DESCRIPTORS;
   2617     objbb_u32(&file, mh_flags);
   2618   }
   2619   objbb_u32(&file, 0); /* reserved */
   2620   objbb_append(&file, lc.data, lc.len);
   2621 
   2622   /* Pad to first section's file offset. */
   2623   /* __TEXT first section begins at headers_size; we wrote header+lc =
   2624    * headers_size, so no pad needed.  Then each MSec's file_offset
   2625    * tells us where to write its bytes. */
   2626 
   2627   /* Now emit segment payload bytes per MSec. */
   2628   for (u32 i = 0; i < x.nsecs; ++i) {
   2629     MSec* m = &x.secs[i];
   2630     if (m->is_zerofill || m->size == 0) continue;
   2631     /* Pad up to m->file_offset. */
   2632     while (file.len < m->file_offset) objbb_u8(&file, 0);
   2633     if (m->synth_data) {
   2634       objbb_append(&file, m->synth_data, m->synth_size);
   2635     } else {
   2636       LinkSection* ls = &img->sections[m->link_sec_id - 1u];
   2637       u32 segid = ls->segment_id;
   2638       u8* base =
   2639           (segid != LINK_SEG_NONE) ? img->segment_bytes[segid - 1u] : NULL;
   2640       if (base && ls->size) {
   2641         objbb_append(&file, base + ls->input_offset, (u32)ls->size);
   2642       } else if (ls->size) {
   2643         for (u64 k = 0; k < ls->size; ++k) objbb_u8(&file, 0);
   2644       }
   2645     }
   2646   }
   2647 
   2648   /* Pad to LINKEDIT start. */
   2649   while (file.len < x.linkedit_fileoff) objbb_u8(&file, 0);
   2650 
   2651   /* LINKEDIT contents in declared order. */
   2652   while (file.len < x.chained_fixups_off) objbb_u8(&file, 0);
   2653   objbb_append(&file, x.chained_fixups.data, x.chained_fixups.len);
   2654   while (file.len < x.exports_trie_off) objbb_u8(&file, 0);
   2655   objbb_append(&file, x.exports_trie.data, x.exports_trie.len);
   2656   while (file.len < x.fn_starts_off) objbb_u8(&file, 0);
   2657   objbb_append(&file, x.fn_starts.data, x.fn_starts.len);
   2658   while (file.len < x.data_in_code_off) objbb_u8(&file, 0);
   2659   /* empty */
   2660   while (file.len < x.symtab_off) objbb_u8(&file, 0);
   2661   objbb_append(&file, x.symtab.data, x.symtab.len);
   2662   while (file.len < x.indirect_off) objbb_u8(&file, 0);
   2663   objbb_append(&file, x.indirect.data, x.indirect.len);
   2664   while (file.len < x.strtab_off) objbb_u8(&file, 0);
   2665   objbb_append(&file, x.strtab.data, x.strtab.len);
   2666   while (file.len < x.codesig_off) objbb_u8(&file, 0);
   2667 
   2668   /* Compute codesig hashes over file bytes [0, codesig_off). */
   2669   /* The codesig blob currently has zero hashes; hash now. */
   2670   compute_codesig(&x, file.data, x.codesig_off, "a.out");
   2671   /* Append codesig. */
   2672   objbb_append(&file, x.codesig.data, x.codesig.len);
   2673 
   2674   /* Stream out. */
   2675   kit_writer_seek(w, 0);
   2676   kit_writer_write(w, file.data, file.len);
   2677 
   2678   /* Cleanup. */
   2679   fix_fini(&fl);
   2680   objbb_fini(&lc);
   2681   objbb_fini(&file);
   2682   objbb_fini(&x.chained_fixups);
   2683   objbb_fini(&x.exports_trie);
   2684   objbb_fini(&x.symtab);
   2685   objbb_fini(&x.strtab);
   2686   objbb_fini(&x.indirect);
   2687   objbb_fini(&x.fn_starts);
   2688   objbb_fini(&x.data_in_code);
   2689   objbb_fini(&x.codesig);
   2690   if (x.imports) x.h->free(x.h, x.imports, 0); /* VEC_GROW: cap unknown */
   2691   if (x.dylibs) x.h->free(x.h, x.dylibs, 0);
   2692   if (x.sym_to_imp)
   2693     x.h->free(x.h, x.sym_to_imp, sizeof(u32) * x.sym_to_imp_size);
   2694   if (x.secs) x.h->free(x.h, x.secs, 0);
   2695   if (x.by_link_sec)
   2696     x.h->free(x.h, x.by_link_sec, sizeof(MSec*) * x.by_link_sec_cap);
   2697   if (x.stubs_bytes) x.h->free(x.h, x.stubs_bytes, x.stubs_size);
   2698   if (x.got_bytes) x.h->free(x.h, x.got_bytes, x.got_size);
   2699   if (x.tlv_ptrs_bytes) x.h->free(x.h, x.tlv_ptrs_bytes, x.tlv_ptrs_size);
   2700   if (x.tlv_slots) x.h->free(x.h, x.tlv_slots, 0);
   2701   if (x.sym_to_tlv)
   2702     x.h->free(x.h, x.sym_to_tlv, sizeof(u32) * x.sym_to_tlv_size);
   2703 }