read.c (39627B)
1 /* Mach-O MH_OBJECT reader. Parses a 64-bit little-endian relocatable 2 * object back into a fresh ObjBuilder. The post-finalize ObjBuilder 3 * shape is the canonical superset of the writer's input: 4 * read_macho of an emit_macho output produces an ObjBuilder 5 * shape-equivalent to the writer's input, modulo the synthesized 6 * "__SEG,__sect"-form section names. 7 * 8 * Scope: AArch64 little-endian. MH_OBJECT parses to the section/symbol/ 9 * reloc view; MH_EXECUTE / MH_DYLIB additionally get the linked-image view 10 * (read_macho_image: segments, dylibs, entry, dynamic symbols + relocs). 11 * read_macho_dso remains the linker's DSO-only input path. Other archs / 12 * endianness produce a compiler_panic with a diagnostic. */ 13 14 #include <stdlib.h> 15 #include <string.h> 16 17 #include "core/arena.h" 18 #include "core/bytes.h" 19 #include "core/heap.h" 20 #include "core/pool.h" 21 #include "core/slice.h" 22 #include "core/util.h" 23 #include "obj/format.h" 24 #include "obj/macho/macho.h" 25 26 /* ---- mach-section scratch struct ---- */ 27 28 typedef struct MSecRec { 29 char segname[16]; 30 char sectname[16]; 31 u32 seg_len; 32 u32 sect_len; 33 u64 addr; 34 u64 size; 35 u32 fileoff; 36 u32 align_log2; 37 u32 reloff; 38 u32 nreloc; 39 u32 flags; 40 u32 reserved2; 41 ObjSecId obj_sec; /* assigned in pass 1 */ 42 } MSecRec; 43 44 typedef struct MAtomCand { 45 ObjSecId sec; 46 ObjSymId sym; 47 u32 offset; 48 u32 flags; 49 } MAtomCand; 50 51 static int matom_cand_cmp(const void* av, const void* bv) { 52 const MAtomCand* a = (const MAtomCand*)av; 53 const MAtomCand* b = (const MAtomCand*)bv; 54 if (a->sec < b->sec) return -1; 55 if (a->sec > b->sec) return 1; 56 if (a->offset < b->offset) return -1; 57 if (a->offset > b->offset) return 1; 58 if (a->sym < b->sym) return -1; 59 if (a->sym > b->sym) return 1; 60 return 0; 61 } 62 63 static u32 fixed16_len(const char* s) { 64 u32 n = 0; 65 while (n < 16 && s[n] != 0) ++n; 66 return n; 67 } 68 69 static u16 sec_kind_from_seg_sect(const char* segname, u32 seg_len, 70 const char* sectname, u32 sect_len, 71 u32 flags) { 72 u32 stype = flags & SECTION_TYPE; 73 if (stype == S_ZEROFILL || stype == S_THREAD_LOCAL_ZEROFILL) return SEC_BSS; 74 if (flags & S_ATTR_PURE_INSTRUCTIONS) return SEC_TEXT; 75 76 if (seg_len == 7 && memcmp(segname, "__DWARF", 7) == 0) return SEC_DEBUG; 77 if (seg_len == 6 && memcmp(segname, "__TEXT", 6) == 0) { 78 if (sect_len == 6 && memcmp(sectname, "__text", 6) == 0) return SEC_TEXT; 79 return SEC_RODATA; /* __const, __cstring, ... */ 80 } 81 if (seg_len == 6 && memcmp(segname, "__DATA", 6) == 0) { 82 if (sect_len == 5 && memcmp(sectname, "__bss", 5) == 0) return SEC_BSS; 83 return SEC_DATA; 84 } 85 return SEC_OTHER; 86 } 87 88 static u16 sec_flags_from(u32 mflags, u16 sec_kind) { 89 u16 f = 0; 90 if (sec_kind == SEC_TEXT || (mflags & S_ATTR_PURE_INSTRUCTIONS)) { 91 f |= SF_ALLOC | SF_EXEC; 92 } else if (sec_kind == SEC_RODATA) { 93 f |= SF_ALLOC; 94 } else if (sec_kind == SEC_DATA || sec_kind == SEC_BSS) { 95 f |= SF_ALLOC | SF_WRITE; 96 } 97 u32 stype = mflags & SECTION_TYPE; 98 if (stype == S_THREAD_LOCAL_REGULAR || stype == S_THREAD_LOCAL_ZEROFILL || 99 stype == S_THREAD_LOCAL_VARIABLES) { 100 f |= SF_TLS; 101 } 102 if (stype == S_CSTRING_LITERALS) { 103 f |= SF_MERGE | SF_STRINGS; 104 } 105 return f; 106 } 107 108 static u16 sec_sem_from(u32 mflags, u16 sec_kind) { 109 u32 stype = mflags & SECTION_TYPE; 110 if (stype == S_ZEROFILL || stype == S_THREAD_LOCAL_ZEROFILL || 111 sec_kind == SEC_BSS) { 112 return SSEM_NOBITS; 113 } 114 if (stype == S_MOD_INIT_FUNC_POINTERS) return SSEM_INIT_ARRAY; 115 if (stype == S_MOD_TERM_FUNC_POINTERS) return SSEM_FINI_ARRAY; 116 return SSEM_PROGBITS; 117 } 118 119 /* Intern a Mach-O lc_str (NUL-terminated string embedded inside a load 120 * command at `cmd_pos + str_off`, bounded by the command's cmdsize). 121 * Returns 0 if the offset/string is malformed. */ 122 static Sym macho_lc_str(Compiler* c, const u8* data, u64 cmd_pos, u32 cmdsize, 123 u32 str_off) { 124 if (str_off < 8 || str_off >= cmdsize) return 0; 125 const char* p = (const char*)(data + cmd_pos + str_off); 126 u32 maxlen = cmdsize - str_off; 127 u32 nlen = 0; 128 while (nlen < maxlen && p[nlen]) ++nlen; 129 if (!nlen) return 0; 130 return pool_intern_slice(c->global, (Slice){.s = p, .len = nlen}); 131 } 132 133 /* ---- read_macho_image ---- 134 * 135 * Linked-image (MH_EXECUTE / MH_DYLIB) view, the Mach-O peer of 136 * read_elf_image. Walks the load commands a second time to populate the 137 * ObjImage: LC_SEGMENT_64 -> segments (+ __TEXT base), LC_LOAD_DYLINKER -> 138 * interp, LC_ID_DYLIB -> soname, LC_LOAD_DYLIB/WEAK/REEXPORT -> deps, 139 * LC_RPATH -> rpaths, LC_MAIN/LC_UNIXTHREAD -> entry, the LC_SYMTAB external 140 * nlist entries -> dynamic symbols, and LC_DYLD_CHAINED_FIXUPS binds/rebases 141 * -> dynamic relocations. The section / symbol / reloc views are parsed by 142 * read_macho's normal passes; this adds the orthogonal image dimension. 143 * Lenient: a malformed sub-table is skipped rather than panicked, so a 144 * partially-damaged image still yields a useful inspection. 145 * 146 * `msecs`/`nmsecs` carry the section table read in read_macho's pass 1 so a 147 * defined dynamic symbol's n_sect maps back to its ObjSecId. */ 148 static void read_macho_image(Compiler* c, ObjBuilder* ob, const u8* data, 149 size_t len, u32 filetype, u32 cputype, 150 const MSecRec* msecs, u32 nmsecs) { 151 ObjImage* im = 152 obj_image_ensure(ob, filetype == MH_DYLIB ? OBJ_KIND_DYN : OBJ_KIND_EXEC); 153 if (!im) 154 compiler_panic(c, SRCLOC_NONE, "read_macho: obj_image_ensure failed"); 155 156 u32 ncmds = rd_u32_le(data + 16); 157 u32 sizeofcmds = rd_u32_le(data + 20); 158 159 /* Per-segment (vmaddr, file_off) recorded for chained-fixup vaddr 160 * resolution below; sized to ncmds (segments are a subset of commands). */ 161 u64* seg_vaddr = arena_array(c->scratch, u64, ncmds ? ncmds : 1); 162 u64* seg_fileoff = arena_array(c->scratch, u64, ncmds ? ncmds : 1); 163 u32 nseg = 0; 164 165 int have_text = 0; 166 u64 text_vmaddr = 0; 167 int have_main = 0; 168 u64 main_entryoff = 0; 169 u32 symoff = 0, nsyms = 0, stroff = 0, strsize = 0; 170 u32 cf_off = 0, cf_size = 0; 171 172 u64 pos = MACHO_HDR64_SIZE; 173 u64 end = pos + sizeofcmds; 174 for (u32 ci = 0; ci < ncmds && pos + 8 <= end; ++ci) { 175 u32 cmd = rd_u32_le(data + pos); 176 u32 cmdsize = rd_u32_le(data + pos + 4); 177 if (cmdsize < 8 || pos + cmdsize > end) break; 178 179 /* Raw load-command view (escape hatch): one entry per LC_* command, 180 * carrying its file offset and on-disk size. */ 181 { 182 ObjImageRaw r; 183 r.tag = cmd; 184 r.value = pos; 185 r.extra = cmdsize; 186 obj_image_add_raw(im, &r); 187 } 188 189 if (cmd == LC_SEGMENT_64 && cmdsize >= MACHO_SEGCMD64_SIZE) { 190 const char* segname = (const char*)(data + pos + 8); 191 u32 seg_len = fixed16_len(segname); 192 u64 vmaddr = rd_u64_le(data + pos + 24); 193 u64 vmsize = rd_u64_le(data + pos + 32); 194 u64 fileoff = rd_u64_le(data + pos + 40); 195 u64 filesize = rd_u64_le(data + pos + 48); 196 u32 initprot = rd_u32_le(data + pos + 60); 197 ObjSegment seg; 198 seg.name = seg_len ? pool_intern_slice( 199 c->global, (Slice){.s = segname, .len = seg_len}) 200 : 0; 201 seg.vaddr = vmaddr; 202 seg.paddr = vmaddr; 203 seg.vsize = vmsize; 204 seg.file_off = fileoff; 205 seg.file_size = filesize; 206 /* VM_PROT_* bits differ from OBJ_SEG_* — remap explicitly. */ 207 seg.perms = ((initprot & VM_PROT_READ) ? OBJ_SEG_R : 0) | 208 ((initprot & VM_PROT_WRITE) ? OBJ_SEG_W : 0) | 209 ((initprot & VM_PROT_EXECUTE) ? OBJ_SEG_X : 0); 210 seg.align = 1; /* Mach-O segments don't carry an explicit p_align */ 211 obj_image_add_segment(im, &seg); 212 213 seg_vaddr[nseg] = vmaddr; 214 seg_fileoff[nseg] = fileoff; 215 ++nseg; 216 if (!have_text && seg_len == 6 && memcmp(segname, "__TEXT", 6) == 0) { 217 have_text = 1; 218 text_vmaddr = vmaddr; 219 } 220 } else if (cmd == LC_LOAD_DYLINKER) { 221 Sym s = macho_lc_str(c, data, pos, cmdsize, rd_u32_le(data + pos + 8)); 222 if (s) obj_image_set_interp(im, s); 223 } else if (cmd == LC_ID_DYLIB) { 224 Sym s = macho_lc_str(c, data, pos, cmdsize, rd_u32_le(data + pos + 8)); 225 if (s) obj_image_set_soname(im, s); 226 } else if (cmd == LC_LOAD_DYLIB || cmd == LC_LOAD_WEAK_DYLIB || 227 cmd == LC_REEXPORT_DYLIB) { 228 Sym s = macho_lc_str(c, data, pos, cmdsize, rd_u32_le(data + pos + 8)); 229 if (s) { 230 ObjImageDep d; 231 d.name = s; 232 d.imports = NULL; 233 d.nimports = 0; 234 obj_image_add_dep(im, &d); 235 } 236 } else if (cmd == LC_RPATH) { 237 Sym s = macho_lc_str(c, data, pos, cmdsize, rd_u32_le(data + pos + 8)); 238 if (s) obj_image_add_rpath(im, s); 239 } else if (cmd == LC_MAIN && cmdsize >= 16) { 240 have_main = 1; 241 main_entryoff = rd_u64_le(data + pos + 8); 242 } else if (cmd == LC_UNIXTHREAD && cmdsize >= 16 && !have_main) { 243 /* thread_command: flavor (u32) + count (u32) + register state. Pull 244 * the program counter out of the arch's state. */ 245 u32 flavor = rd_u32_le(data + pos + 8); 246 u64 pc_off = 0; 247 int have_pc = 0; 248 if (cputype == CPU_TYPE_ARM64 && flavor == 6 /* ARM_THREAD_STATE64 */) { 249 pc_off = pos + 16 + 32u * 8u; /* x0..x28,fp,lr,sp,pc */ 250 have_pc = 1; 251 } else if (cputype == CPU_TYPE_X86_64 && 252 flavor == 4 /* x86_THREAD_STATE64 */) { 253 pc_off = pos + 16 + 16u * 8u; /* rax..r15, then rip */ 254 have_pc = 1; 255 } 256 if (have_pc && pc_off + 8 <= pos + cmdsize) 257 obj_image_set_entry(im, rd_u64_le(data + pc_off)); 258 } else if (cmd == LC_SYMTAB && cmdsize >= MACHO_SYMTAB_CMD_SIZE) { 259 symoff = rd_u32_le(data + pos + 8); 260 nsyms = rd_u32_le(data + pos + 12); 261 stroff = rd_u32_le(data + pos + 16); 262 strsize = rd_u32_le(data + pos + 20); 263 } else if (cmd == LC_DYLD_CHAINED_FIXUPS && cmdsize >= 16) { 264 cf_off = rd_u32_le(data + pos + 8); 265 cf_size = rd_u32_le(data + pos + 12); 266 } 267 pos += cmdsize; 268 } 269 270 if (have_text) obj_image_set_base(im, text_vmaddr); 271 /* LC_MAIN entryoff is a file offset within __TEXT (which maps file 0 to 272 * its vmaddr); the entry vaddr is __TEXT base + entryoff. */ 273 if (have_main && have_text) 274 obj_image_set_entry(im, text_vmaddr + main_entryoff); 275 276 /* LC_SYMTAB external nlist entries -> dynamic symbols (Mach-O's analog of 277 * .dynsym: the dynamically-visible exports and undefined imports). */ 278 if (nsyms && stroff + (u64)strsize <= len && 279 symoff + (u64)nsyms * MACHO_NLIST64_SIZE <= len) { 280 const u8* strtab = data + stroff; 281 const u8* sbase = data + symoff; 282 for (u32 i = 0; i < nsyms; ++i) { 283 const u8* p = sbase + (u64)i * MACHO_NLIST64_SIZE; 284 u32 strx = rd_u32_le(p + 0); 285 u8 n_type = p[4]; 286 u8 n_sect = p[5]; 287 u16 n_desc = rd_u16_le(p + 6); 288 u64 n_value = rd_u64_le(p + 8); 289 if (n_type & N_STAB) continue; /* debug stab, not dynamic */ 290 if (!(n_type & N_EXT)) continue; /* locals aren't dynamic */ 291 if (strx >= strsize) continue; 292 const char* nm = (const char*)(strtab + strx); 293 u32 nlen = 0; 294 while (strx + nlen < strsize && nm[nlen]) ++nlen; 295 if (!nlen) continue; 296 297 u8 type_field = (u8)(n_type & N_TYPE); 298 ObjImageSym ds; 299 memset(&ds, 0, sizeof ds); 300 ds.version = 0; /* Mach-O has no ELF-style symbol versioning */ 301 ds.name = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}); 302 ds.bind = (n_desc & (N_WEAK_DEF | N_WEAK_REF)) ? SB_WEAK : SB_GLOBAL; 303 ds.value = (type_field == N_SECT || type_field == N_ABS) ? n_value : 0; 304 ds.size = 0; 305 if (type_field == N_SECT && n_sect >= 1 && n_sect <= nmsecs) { 306 ds.section = msecs[n_sect - 1].obj_sec; 307 ds.kind = (msecs[n_sect - 1].flags & S_ATTR_PURE_INSTRUCTIONS) ? SK_FUNC 308 : SK_OBJ; 309 } else { 310 ds.section = OBJ_SEC_NONE; /* undefined import / absolute */ 311 ds.kind = SK_NOTYPE; 312 } 313 obj_image_add_dynsym(im, &ds); 314 } 315 } 316 317 /* LC_DYLD_CHAINED_FIXUPS binds/rebases -> dynamic relocations. */ 318 if (cf_size >= 28 && (u64)cf_off + cf_size <= len) { 319 const u8* cf = data + cf_off; 320 u32 starts_offset = rd_u32_le(cf + 4); 321 u32 imports_offset = rd_u32_le(cf + 8); 322 u32 symbols_offset = rd_u32_le(cf + 12); 323 u32 imports_count = rd_u32_le(cf + 16); 324 u32 imports_format = rd_u32_le(cf + 20); 325 u32 relative_kind = 326 (cputype == CPU_TYPE_X86_64) ? R_X64_RELATIVE : R_AARCH64_RELATIVE; 327 328 /* Import symbol names, indexed by 0-based bind ordinal. */ 329 Sym* imp_names = 330 arena_zarray(c->scratch, Sym, imports_count ? imports_count : 1); 331 if (imports_format == DYLD_CHAINED_IMPORT && 332 (u64)imports_offset + (u64)imports_count * 4u <= cf_size) { 333 for (u32 i = 0; i < imports_count; ++i) { 334 u32 packed = rd_u32_le(cf + imports_offset + i * 4u); 335 u32 name_off = (packed >> 9) & 0x7fffffu; 336 u64 so = (u64)symbols_offset + name_off; 337 if (so >= cf_size) continue; 338 const char* nm = (const char*)(cf + so); 339 u32 maxn = (u32)(cf_size - so); 340 u32 nlen = 0; 341 while (nlen < maxn && nm[nlen]) ++nlen; 342 if (nlen) 343 imp_names[i] = 344 pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}); 345 } 346 } 347 348 if ((u64)starts_offset + 4u <= cf_size) { 349 const u8* sib = cf + starts_offset; 350 u32 seg_count = rd_u32_le(sib + 0); 351 for (u32 si = 0; si < seg_count; ++si) { 352 if ((u64)starts_offset + 4u + (u64)si * 4u + 4u > cf_size) break; 353 u32 seg_info_offset = rd_u32_le(sib + 4 + si * 4u); 354 if (!seg_info_offset) continue; 355 if ((u64)starts_offset + seg_info_offset + 22u > cf_size) continue; 356 const u8* sis = cf + starts_offset + seg_info_offset; 357 u16 pointer_format = rd_u16_le(sis + 6); 358 u64 segment_offset = rd_u64_le(sis + 8); /* file offset of segment */ 359 u16 page_count = rd_u16_le(sis + 20); 360 /* Only the DYLD_CHAINED_PTR_64 family shares the bit layout below. */ 361 if (pointer_format != DYLD_CHAINED_PTR_64 && pointer_format != 6u) 362 continue; 363 u16 page_size = rd_u16_le(sis + 4); 364 if (!page_size) continue; 365 /* Resolve this segment's vmaddr from its file offset. */ 366 u64 seg_va = 0; 367 int found_seg = 0; 368 for (u32 k = 0; k < nseg; ++k) { 369 if (seg_fileoff[k] == segment_offset) { 370 seg_va = seg_vaddr[k]; 371 found_seg = 1; 372 break; 373 } 374 } 375 if (!found_seg) continue; 376 for (u32 pg = 0; pg < page_count; ++pg) { 377 u64 ps_pos = (u64)starts_offset + seg_info_offset + 22u + pg * 2u; 378 if (ps_pos + 2u > cf_size) break; 379 u16 ps = rd_u16_le(cf + ps_pos); 380 if (ps == 0xFFFFu) continue; 381 u32 cur = ps; 382 for (;;) { 383 u64 file_loc = segment_offset + (u64)pg * page_size + cur; 384 if (file_loc + 8u > len) break; 385 u64 v = rd_u64_le(data + file_loc); 386 u64 vaddr = seg_va + (u64)pg * page_size + cur; 387 int is_bind = (int)((v >> 63) & 1u); 388 ObjImageReloc dr; 389 dr.section = OBJ_SEC_NONE; 390 dr.offset = vaddr; 391 if (is_bind) { 392 u32 ordinal = (u32)(v & 0xffffffu); 393 dr.sym_name = (ordinal < imports_count) ? imp_names[ordinal] : 0; 394 dr.addend = (i64)((v >> 24) & 0xffu); 395 dr.kind = R_ABS64; 396 } else { 397 dr.sym_name = 0; 398 dr.addend = (i64)(v & (((u64)1 << 36) - 1u)); 399 dr.kind = (RelocKind)relative_kind; 400 } 401 obj_image_add_dynreloc(im, &dr); 402 u32 next = (u32)((v >> 51) & 0xfffu); 403 if (!next) break; 404 cur += next * 4u; 405 if (cur >= page_size) break; 406 } 407 } 408 } 409 } 410 } 411 } 412 413 ObjBuilder* read_macho(Compiler* c, const char* name, const u8* data, 414 size_t len) { 415 (void)name; 416 if (len < MACHO_HDR64_SIZE) 417 compiler_panic(c, SRCLOC_NONE, "read_macho: input shorter than header"); 418 419 u32 magic = rd_u32_le(data + 0); 420 if (magic != MH_MAGIC_64) 421 compiler_panic(c, SRCLOC_NONE, "read_macho: bad magic 0x%x", magic); 422 423 u32 cputype = rd_u32_le(data + 4); 424 const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_MACHO); 425 const ObjMachoArchOps* macho = 426 fmt && fmt->macho_cputype ? fmt->macho_cputype(cputype) : NULL; 427 u32 filetype = rd_u32_le(data + 12); 428 u32 ncmds = rd_u32_le(data + 16); 429 u32 sizeofcmds = rd_u32_le(data + 20); 430 u32 mh_flags = rd_u32_le(data + 24); 431 432 if (!macho || !macho->reloc_decode) 433 compiler_panic(c, SRCLOC_NONE, "read_macho: unsupported cputype 0x%x", 434 cputype); 435 /* MH_OBJECT parses to the section/symbol/reloc view only. MH_EXECUTE / 436 * MH_DYLIB additionally get the linked-image view (read_macho_image, at 437 * the end); their sections still parse through the same passes. */ 438 if (filetype != MH_OBJECT && filetype != MH_EXECUTE && filetype != MH_DYLIB) 439 compiler_panic(c, SRCLOC_NONE, 440 "read_macho: unsupported filetype %u (expected MH_OBJECT, " 441 "MH_EXECUTE, or MH_DYLIB)", 442 filetype); 443 444 if ((u64)MACHO_HDR64_SIZE + sizeofcmds > len) 445 compiler_panic(c, SRCLOC_NONE, "read_macho: load commands exceed file"); 446 447 /* ---- pass 1: walk load commands, collect sections, symtab cmd. */ 448 MSecRec* msecs = NULL; 449 u32 nmsecs = 0; 450 u32 symoff = 0, nsyms = 0, stroff = 0, strsize = 0; 451 452 u64 pos = MACHO_HDR64_SIZE; 453 u64 end = pos + sizeofcmds; 454 for (u32 ci = 0; ci < ncmds && pos + 8 <= end; ++ci) { 455 u32 cmd = rd_u32_le(data + pos); 456 u32 cmdsize = rd_u32_le(data + pos + 4); 457 if (cmdsize < 8 || pos + cmdsize > end) 458 compiler_panic(c, SRCLOC_NONE, "read_macho: malformed load command"); 459 460 if (cmd == LC_SEGMENT_64) { 461 u32 nsects = rd_u32_le(data + pos + 64); 462 if (MACHO_SEGCMD64_SIZE + (u64)nsects * MACHO_SECT64_SIZE > cmdsize) 463 compiler_panic(c, SRCLOC_NONE, "read_macho: segment cmd truncated"); 464 MSecRec* extra = arena_array(c->scratch, MSecRec, nmsecs + nsects); 465 if (msecs && nmsecs) memcpy(extra, msecs, sizeof(MSecRec) * nmsecs); 466 msecs = extra; 467 const u8* sp = data + pos + MACHO_SEGCMD64_SIZE; 468 for (u32 si = 0; si < nsects; ++si, sp += MACHO_SECT64_SIZE) { 469 MSecRec* m = &msecs[nmsecs++]; 470 memset(m, 0, sizeof *m); 471 memcpy(m->sectname, sp + 0, 16); 472 memcpy(m->segname, sp + 16, 16); 473 m->seg_len = fixed16_len(m->segname); 474 m->sect_len = fixed16_len(m->sectname); 475 m->addr = rd_u64_le(sp + 32); 476 m->size = rd_u64_le(sp + 40); 477 m->fileoff = rd_u32_le(sp + 48); 478 m->align_log2 = rd_u32_le(sp + 52); 479 m->reloff = rd_u32_le(sp + 56); 480 m->nreloc = rd_u32_le(sp + 60); 481 m->flags = rd_u32_le(sp + 64); 482 m->reserved2 = rd_u32_le(sp + 72); 483 } 484 } else if (cmd == LC_SYMTAB) { 485 symoff = rd_u32_le(data + pos + 8); 486 nsyms = rd_u32_le(data + pos + 12); 487 stroff = rd_u32_le(data + pos + 16); 488 strsize = rd_u32_le(data + pos + 20); 489 } 490 pos += cmdsize; 491 } 492 493 if (stroff + (u64)strsize > len) 494 compiler_panic(c, SRCLOC_NONE, "read_macho: string table out of range"); 495 if (symoff + (u64)nsyms * MACHO_NLIST64_SIZE > len) 496 compiler_panic(c, SRCLOC_NONE, "read_macho: symbol table out of range"); 497 const u8* strtab = data + stroff; 498 499 ObjBuilder* ob = obj_new(c); 500 if (!ob) compiler_panic(c, SRCLOC_NONE, "read_macho: obj_new failed"); 501 obj_reserve_symbols(ob, nsyms); /* skip the 256->nsyms resize cascade */ 502 503 /* ---- pass 2: create ObjSecs and copy bytes. */ 504 for (u32 i = 0; i < nmsecs; ++i) { 505 MSecRec* m = &msecs[i]; 506 /* Build "__SEG,__sect"-form name; matches what emit_macho would 507 * round-trip back out. */ 508 char nmbuf[34]; 509 u32 nlen = 0; 510 memcpy(nmbuf + nlen, m->segname, m->seg_len); 511 nlen += m->seg_len; 512 nmbuf[nlen++] = ','; 513 memcpy(nmbuf + nlen, m->sectname, m->sect_len); 514 nlen += m->sect_len; 515 Sym sn = pool_intern_slice(c->global, (Slice){.s = nmbuf, .len = nlen}); 516 517 u16 kind = sec_kind_from_seg_sect(m->segname, m->seg_len, m->sectname, 518 m->sect_len, m->flags); 519 u16 flags = sec_flags_from(m->flags, kind); 520 u16 sem = sec_sem_from(m->flags, kind); 521 u32 align = 1u << (m->align_log2 & 31); 522 523 ObjSecId id = obj_section_ex(ob, sn, (SecKind)kind, (SecSem)sem, flags, 524 align, m->reserved2, 0, 0); 525 if (id == OBJ_SEC_NONE) 526 compiler_panic(c, SRCLOC_NONE, "read_macho: obj_section_ex failed"); 527 528 /* Preserve the raw mach section.flags so emit_macho can write back 529 * the same S_TYPE / S_ATTR_* bits. */ 530 obj_section_set_ext(ob, id, OBJ_EXT_MACHO, m->flags, 0); 531 532 if (sem == SSEM_NOBITS) { 533 obj_reserve_bss(ob, id, (u32)m->size, align); 534 } else if (m->size) { 535 if (m->fileoff + m->size > len) 536 compiler_panic(c, SRCLOC_NONE, 537 "read_macho: section bytes out of range"); 538 obj_write(ob, id, data + m->fileoff, (size_t)m->size); 539 } 540 m->obj_sec = id; 541 } 542 543 /* ---- pass 3: parse symbol table. Two-pass strategy: first pass 544 * creates undefs (so relocations can refer to them), second 545 * pass creates defined locals/extdefs. Both write into 546 * mach_idx -> ObjSymId so reloc resolution works. */ 547 ObjSymId* sym_macho_to_obj = 548 arena_zarray(c->scratch, ObjSymId, nsyms ? nsyms : 1); 549 MAtomCand* atom_cands = 550 arena_zarray(c->scratch, MAtomCand, nsyms ? nsyms : 1); 551 u32 natom_cands = 0; 552 553 const u8* sbase = data + symoff; 554 for (u32 i = 0; i < nsyms; ++i) { 555 const u8* p = sbase + (u64)i * MACHO_NLIST64_SIZE; 556 u32 strx = rd_u32_le(p + 0); 557 u8 n_type = p[4]; 558 u8 n_sect = p[5]; 559 u16 n_desc = rd_u16_le(p + 6); 560 u64 n_value = rd_u64_le(p + 8); 561 562 const char* nm = ""; 563 u32 nlen = 0; 564 if (strx < strsize) { 565 nm = (const char*)(strtab + strx); 566 while (strx + nlen < strsize && nm[nlen]) ++nlen; 567 } 568 /* Mach-O names round-trip verbatim — the leading `_` Apple 569 * toolchains apply to C symbols is part of the on-disk name as 570 * far as ObjBuilder is concerned. Name-canonicalization (the 571 * `test_main` ↔ `_test_main` mapping for API callers) happens 572 * one layer up at the linker API boundary (link_c_name_intern 573 * in link.c); the on-disk shape stays byte-for-byte stable. */ 574 Sym sn = 575 nlen ? pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}) : 0; 576 577 u8 type_field = (u8)(n_type & N_TYPE); 578 u8 ext = (u8)(n_type & N_EXT); 579 u8 pext = (u8)(n_type & N_PEXT); 580 581 u16 bind = ext ? SB_GLOBAL : SB_LOCAL; 582 /* Weak DEFs (defined symbols) carry N_WEAK_DEF; weak REFs (undef 583 * `__attribute__((weak))` references) carry N_WEAK_REF. Either 584 * one collapses to SB_WEAK in the kit model. */ 585 if (ext && (n_desc & (N_WEAK_DEF | N_WEAK_REF))) bind = SB_WEAK; 586 u8 vis = pext ? SV_HIDDEN : SV_DEFAULT; 587 588 u16 kind; 589 ObjSecId sec_id = OBJ_SEC_NONE; 590 u64 value = 0; 591 u64 size = 0; 592 u64 cmnalign = 0; 593 594 if (type_field == N_UNDF) { 595 if (ext && n_value != 0) { 596 /* Common: n_value is size, n_desc encodes log2(align) in 597 * GET_COMM_ALIGN bits. */ 598 kind = SK_COMMON; 599 value = 0; 600 size = n_value; 601 u32 la = (u32)((n_desc >> 8) & 0xf); 602 cmnalign = 1u << la; 603 } else { 604 kind = SK_UNDEF; 605 } 606 } else if (type_field == N_ABS) { 607 kind = SK_ABS; 608 value = n_value; 609 } else if (type_field == N_SECT) { 610 if (n_sect == 0 || n_sect > nmsecs) { 611 kind = SK_NOTYPE; 612 } else { 613 sec_id = msecs[n_sect - 1].obj_sec; 614 /* MH_OBJECT: the obj model and the linker treat an input 615 * symbol's value as a section-local offset, and a relocatable 616 * .o's sections carry non-zero layout addrs, so subtract the 617 * section base. Linked images (MH_EXECUTE/MH_DYLIB) keep the 618 * absolute n_value so nm / objdump -t / size / addr2line report 619 * real vaddrs — matching the ELF reader, whose st_value is 620 * already absolute for images. */ 621 if (filetype == MH_OBJECT) { 622 u64 base = msecs[n_sect - 1].addr; 623 value = (n_value >= base) ? (n_value - base) : 0; 624 } else { 625 value = n_value; 626 } 627 kind = (msecs[n_sect - 1].flags & S_ATTR_PURE_INSTRUCTIONS) ? SK_FUNC 628 : SK_OBJ; 629 } 630 } else { 631 kind = SK_NOTYPE; 632 } 633 634 ObjSymId id = obj_symbol_ex(ob, sn, (SymBind)bind, (SymVis)vis, 635 (SymKind)kind, sec_id, value, size, cmnalign); 636 obj_sym_mark_referenced(ob, id); 637 if ((mh_flags & MH_SUBSECTIONS_VIA_SYMBOLS) && type_field == N_SECT && 638 sec_id != OBJ_SEC_NONE && !(n_desc & N_ALT_ENTRY)) { 639 MAtomCand* ac = &atom_cands[natom_cands++]; 640 ac->sec = sec_id; 641 ac->sym = id; 642 ac->offset = (u32)value; 643 if ((n_desc & N_NO_DEAD_STRIP) || 644 (n_sect != 0 && n_sect <= nmsecs && 645 (msecs[n_sect - 1].flags & S_ATTR_NO_DEAD_STRIP))) { 646 ac->flags |= OBJ_ATOM_RETAIN; 647 } 648 } 649 /* n_desc carries Mach-O attribute bits beyond what bind/vis/kind 650 * model — N_NO_DEAD_STRIP, N_REF_TO_WEAK, N_ARM_THUMB_DEF, etc. 651 * Mask off the bits we already round-trip via bind (N_WEAK_DEF / 652 * N_WEAK_REF) and the alignment field for commons (which lives 653 * in cmnalign), then stash the remainder so emit_macho can OR it 654 * back in. */ 655 u16 desc_pass = n_desc; 656 desc_pass &= (u16) ~(N_WEAK_DEF | N_WEAK_REF); 657 if (type_field == N_SECT) desc_pass &= (u16)~N_ALT_ENTRY; 658 if (kind == SK_COMMON) desc_pass &= 0x00ff; /* drop align field */ 659 if (desc_pass) obj_symbol_set_flags(ob, id, desc_pass); 660 if (type_field == N_SECT && (n_desc & N_ALT_ENTRY)) 661 obj_symbol_set_atom_subordinate(ob, id, 1); 662 sym_macho_to_obj[i] = id; 663 } 664 665 if (mh_flags & MH_SUBSECTIONS_VIA_SYMBOLS) { 666 if (natom_cands > 1u) 667 qsort(atom_cands, natom_cands, sizeof(*atom_cands), matom_cand_cmp); 668 for (u32 i = 0; i < natom_cands; ++i) { 669 MAtomCand* ac = &atom_cands[i]; 670 const Section* sec = obj_section_get(ob, ac->sec); 671 u32 end = sec ? ((sec->sem == SSEM_NOBITS || sec->kind == SEC_BSS) 672 ? sec->bss_size 673 : sec->bytes.total) 674 : ac->offset; 675 if (i + 1u < natom_cands && atom_cands[i + 1u].sec == ac->sec) 676 end = atom_cands[i + 1u].offset; 677 if (end >= ac->offset) 678 obj_atom_define(ob, ac->sec, ac->offset, end - ac->offset, ac->sym, 679 ac->flags); 680 } 681 } 682 683 /* ---- pass 4: parse per-section relocations into ObjBuilder relocs. 684 * Mach-O encodes addends out-of-band as a leading 685 * ARM64_RELOC_ADDEND followed by the real reloc; the 686 * reader collapses the pair on the way in. */ 687 /* Lazily-populated section-start local symbols, for clang-emitted 688 * non-extern (section-relative) relocations. See the r_extern==0 689 * branch below for the encoding. */ 690 ObjSymId* sec_start_sym = 691 arena_zarray(c->scratch, ObjSymId, nmsecs ? nmsecs : 1); 692 for (u32 i = 0; i < nmsecs; ++i) sec_start_sym[i] = OBJ_SYM_NONE; 693 for (u32 i = 0; i < nmsecs; ++i) { 694 MSecRec* m = &msecs[i]; 695 if (!m->nreloc) continue; 696 if (m->reloff + (u64)m->nreloc * MACHO_RELOC_SIZE > len) 697 compiler_panic(c, SRCLOC_NONE, 698 "read_macho: relocation table out of range"); 699 const u8* rp = data + m->reloff; 700 i64 pending_addend = 0; 701 int have_pending = 0; 702 int pending_subtractor = 0; 703 u32 pending_subtractor_offset = 0; 704 u32 pending_subtractor_length = 0; 705 for (u32 j = 0; j < m->nreloc; ++j) { 706 u32 r_address = rd_u32_le(rp + j * MACHO_RELOC_SIZE); 707 u32 packed = rd_u32_le(rp + j * MACHO_RELOC_SIZE + 4); 708 u32 r_symbolnum = packed & 0x00ffffffu; 709 u32 r_pcrel = (packed >> 24) & 1u; 710 u32 r_length = (packed >> 25) & 3u; 711 u32 r_extern = (packed >> 27) & 1u; 712 u32 r_type = (packed >> 28) & 0xfu; 713 714 /* Decode the entry per-arch: r_type's 4 bits are not arch-unique (e.g. 715 * value 2 is both X86_64_RELOC_BRANCH and ARM64_RELOC_BRANCH26), so the 716 * arch hook resolves the kind from (r_type, r_pcrel, r_length) and the 717 * patched instruction, and classifies the entry's structural role. */ 718 MachoRelocEntry rin; 719 MachoRelocDecoded rdc; 720 rin.r_type = r_type; 721 rin.r_pcrel = r_pcrel; 722 rin.r_length = r_length; 723 rin.r_extern = r_extern; 724 rin.r_symbolnum = r_symbolnum; 725 rin.r_address = r_address; 726 if ((u64)m->fileoff + r_address + 4u <= len) { 727 rin.insn = data + m->fileoff + r_address; 728 rin.insn_len = 4u; 729 } else { 730 rin.insn = NULL; 731 rin.insn_len = 0u; 732 } 733 if (!macho->reloc_decode(&rin, &rdc)) 734 compiler_panic(c, SRCLOC_NONE, "read_macho: unsupported reloc type %u", 735 r_type); 736 737 if (rdc.role == MACHO_RELOC_ADDEND) { 738 pending_addend = rdc.addend; 739 have_pending = 1; 740 continue; 741 } 742 743 u32 kind = rdc.kind; 744 /* An absolute datum at the same offset/width as a just-seen SUBTRACTOR 745 * is the addend half of a symbol-difference pair: SUB* + ADD*. */ 746 if (rdc.role == MACHO_RELOC_ABSOLUTE && pending_subtractor && 747 pending_subtractor_offset == r_address && 748 pending_subtractor_length == r_length) { 749 kind = (r_length == 3) ? R_ADD64 750 : (r_length == 2) ? R_ADD32 751 : (r_length == 1) ? R_ADD16 752 : R_ADD8; 753 pending_subtractor = 0; 754 } 755 756 ObjSymId target = OBJ_SYM_NONE; 757 i64 inplace_addend_override = 0; 758 int use_inplace_addend = 0; 759 if (r_extern) { 760 if (r_symbolnum < nsyms) target = sym_macho_to_obj[r_symbolnum]; 761 if (!have_pending && rdc.inplace_addend) { 762 u32 rsz = 1u << r_length; 763 if ((u64)m->fileoff + r_address + rsz > len) 764 compiler_panic(c, SRCLOC_NONE, 765 "read_macho: extern reloc r_address out of range"); 766 const u8* pv = data + m->fileoff + r_address; 767 i64 inplace; 768 /* A PC-relative displacement is signed (rdc.inplace_signed); an 769 * absolute datum reads as written. The arch decoder's bias undoes any 770 * PC adjustment baked into the field (x86_64: +width). */ 771 if (r_length == 3) 772 inplace = (i64)rd_u64_le(pv); 773 else if (r_length == 2) 774 inplace = rdc.inplace_signed ? (i64)(i32)rd_u32_le(pv) 775 : (i64)(u64)rd_u32_le(pv); 776 else if (r_length == 1) 777 inplace = rdc.inplace_signed ? (i64)(i16)rd_u16_le(pv) 778 : (i64)(u64)rd_u16_le(pv); 779 else 780 inplace = rdc.inplace_signed ? (i64)(i8)pv[0] : (i64)(u64)pv[0]; 781 inplace_addend_override = inplace + rdc.inplace_bias; 782 use_inplace_addend = 1; 783 } 784 } else { 785 /* Section-relative reloc — clang emits these for compact unwind, 786 * EH frame, and DWARF debug info. r_symbolnum is the 1-based 787 * section index; the in-place value at r_address is the absolute 788 * .o virtual address of the referent. Synthesize a local 789 * symbol pointing to the target section's start (lazily, once 790 * per section) and re-express the reloc as 791 * target = sec_start_sym, addend = inplace - section.addr. */ 792 if (r_symbolnum == 0 || r_symbolnum > nmsecs) 793 compiler_panic(c, SRCLOC_NONE, 794 "read_macho: section-relative reloc references " 795 "invalid section index %u", 796 r_symbolnum); 797 u32 sec_idx = r_symbolnum - 1u; 798 MSecRec* tm = &msecs[sec_idx]; 799 if (sec_start_sym[sec_idx] == OBJ_SYM_NONE) { 800 /* Build ".Lkit.macho_secstart.<sec_idx>" without snprintf 801 * (the freestanding build doesn't pull in stdio). */ 802 static const char prefix[] = ".Lkit.macho_secstart."; 803 char nmbuf[sizeof(prefix) + 10]; 804 u32 nlen = (u32)(sizeof(prefix) - 1); 805 memcpy(nmbuf, prefix, nlen); 806 char dec[10]; 807 u32 dn = 0; 808 u32 v = sec_idx; 809 do { 810 dec[dn++] = (char)('0' + (v % 10u)); 811 v /= 10u; 812 } while (v); 813 for (u32 k = 0; k < dn; ++k) nmbuf[nlen + k] = dec[dn - 1 - k]; 814 nlen += dn; 815 Sym sn = 816 pool_intern_slice(c->global, (Slice){.s = nmbuf, .len = nlen}); 817 u16 sk = (tm->flags & S_ATTR_PURE_INSTRUCTIONS) ? SK_FUNC : SK_OBJ; 818 sec_start_sym[sec_idx] = 819 obj_symbol(ob, sn, SB_LOCAL, (SymKind)sk, tm->obj_sec, 0, 0); 820 } 821 target = sec_start_sym[sec_idx]; 822 u32 rsz = 1u << r_length; 823 if ((u64)m->fileoff + r_address + rsz > len) 824 compiler_panic(c, SRCLOC_NONE, 825 "read_macho: non-extern reloc r_address out of range"); 826 u64 inplace; 827 const u8* pv = data + m->fileoff + r_address; 828 if (r_length == 3) 829 inplace = rd_u64_le(pv); 830 else if (r_length == 2) 831 inplace = (u64)rd_u32_le(pv); 832 else if (r_length == 1) 833 inplace = (u64)rd_u16_le(pv); 834 else 835 inplace = (u64)pv[0]; 836 inplace_addend_override = (i64)inplace - (i64)tm->addr; 837 use_inplace_addend = 1; 838 } 839 840 i64 addend = have_pending 841 ? pending_addend 842 : (use_inplace_addend ? inplace_addend_override : 0); 843 int has_explicit = have_pending || use_inplace_addend || addend != 0; 844 have_pending = 0; 845 pending_addend = 0; 846 847 obj_reloc_ex(ob, m->obj_sec, r_address, (RelocKind)kind, target, addend, 848 has_explicit, 0); 849 if (rdc.role == MACHO_RELOC_SUBTRACTOR) { 850 pending_subtractor = 1; 851 pending_subtractor_offset = r_address; 852 pending_subtractor_length = r_length; 853 } 854 } 855 } 856 857 /* MH_EXECUTE / MH_DYLIB: attach the linked-image view (segments, dylibs, 858 * entry, dynamic symbols + relocations). */ 859 if (filetype != MH_OBJECT) 860 read_macho_image(c, ob, data, len, filetype, cputype, msecs, nmsecs); 861 862 obj_finalize(ob); 863 return ob; 864 } 865 866 /* ---- read_macho_dso ---- 867 * 868 * MH_DYLIB reader. Walks load commands once to find LC_ID_DYLIB 869 * (install-name) and LC_SYMTAB (symbol table + string table), then 870 * emits one defined ObjSym per externally-visible nlist entry. 871 * 872 * Like read_elf_dso, the produced ObjBuilder carries no sections / 873 * relocations / groups — only symbol definitions in OBJ_SEC_NONE. The 874 * consumer's resolve_undefs sees these as defined globals and marks the 875 * matching consumer-side undef as `imported`. The dylib's own undefs 876 * (its imports of other dylibs) are filtered: they don't satisfy any 877 * undef in the consumer. */ 878 879 ObjBuilder* read_macho_dso(Compiler* c, const char* name, const u8* data, 880 size_t len, Sym* install_name_out) { 881 (void)name; 882 if (install_name_out) *install_name_out = 0; 883 if (len < MACHO_HDR64_SIZE) 884 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: input shorter than header"); 885 886 u32 magic = rd_u32_le(data + 0); 887 if (magic != MH_MAGIC_64) 888 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: bad magic 0x%x", magic); 889 890 u32 cputype = rd_u32_le(data + 4); 891 u32 filetype = rd_u32_le(data + 12); 892 u32 ncmds = rd_u32_le(data + 16); 893 u32 sizeofcmds = rd_u32_le(data + 20); 894 895 { 896 const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_MACHO); 897 const ObjMachoArchOps* macho = 898 fmt && fmt->macho_cputype ? fmt->macho_cputype(cputype) : NULL; 899 if (!macho) 900 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: unsupported cputype 0x%x", 901 cputype); 902 } 903 if (filetype != MH_DYLIB && filetype != MH_BUNDLE) 904 compiler_panic(c, SRCLOC_NONE, 905 "read_macho_dso: not MH_DYLIB/MH_BUNDLE (filetype=%u)", 906 filetype); 907 if ((u64)MACHO_HDR64_SIZE + sizeofcmds > len) 908 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: load commands exceed file"); 909 910 u32 symoff = 0, nsyms = 0, stroff = 0, strsize = 0; 911 Sym install_name = 0; 912 913 u64 pos = MACHO_HDR64_SIZE; 914 u64 end = pos + sizeofcmds; 915 for (u32 ci = 0; ci < ncmds && pos + 8 <= end; ++ci) { 916 u32 cmd = rd_u32_le(data + pos); 917 u32 cmdsize = rd_u32_le(data + pos + 4); 918 if (cmdsize < 8 || pos + cmdsize > end) 919 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: malformed load command"); 920 if (cmd == LC_ID_DYLIB) { 921 /* dylib_command: cmd, cmdsize, name(lc_str: 4-byte offset within 922 * the cmd), timestamp, current_version, compat_version. */ 923 if (cmdsize < 24) goto next; 924 u32 nm_off = rd_u32_le(data + pos + 8); 925 if (nm_off >= cmdsize) goto next; 926 const char* p = (const char*)(data + pos + nm_off); 927 u32 maxlen = cmdsize - nm_off; 928 u32 nlen = 0; 929 while (nlen < maxlen && p[nlen]) ++nlen; 930 if (nlen) 931 install_name = 932 pool_intern_slice(c->global, (Slice){.s = p, .len = nlen}); 933 } else if (cmd == LC_SYMTAB) { 934 symoff = rd_u32_le(data + pos + 8); 935 nsyms = rd_u32_le(data + pos + 12); 936 stroff = rd_u32_le(data + pos + 16); 937 strsize = rd_u32_le(data + pos + 20); 938 } 939 next: 940 pos += cmdsize; 941 } 942 if (install_name_out) *install_name_out = install_name; 943 944 if (stroff + (u64)strsize > len) 945 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: string table out of range"); 946 if (symoff + (u64)nsyms * MACHO_NLIST64_SIZE > len) 947 compiler_panic(c, SRCLOC_NONE, "read_macho_dso: symbol table out of range"); 948 949 ObjBuilder* ob = obj_new(c); 950 if (!ob) compiler_panic(c, SRCLOC_NONE, "read_macho_dso: obj_new failed"); 951 952 const u8* strtab = data + stroff; 953 const u8* sbase = data + symoff; 954 for (u32 i = 0; i < nsyms; ++i) { 955 const u8* p = sbase + (u64)i * MACHO_NLIST64_SIZE; 956 u32 strx = rd_u32_le(p + 0); 957 u8 n_type = p[4]; 958 u16 n_desc = rd_u16_le(p + 6); 959 960 u8 type_field = (u8)(n_type & N_TYPE); 961 u8 ext = (u8)(n_type & N_EXT); 962 /* Skip non-external (locals) and undef refs (the dylib's own imports). */ 963 if (!ext) continue; 964 if (type_field == N_UNDF) continue; 965 /* N_INDR / N_PBUD / N_STAB: skip — not interesting for static link. */ 966 if (n_type & N_STAB) continue; 967 968 if (strx >= strsize) continue; 969 const char* nm = (const char*)(strtab + strx); 970 u32 nlen = 0; 971 while (strx + nlen < strsize && nm[nlen]) ++nlen; 972 if (!nlen) continue; 973 Sym sn = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen}); 974 975 SymBind bind = (n_desc & (N_WEAK_DEF | N_WEAK_REF)) ? SB_WEAK : SB_GLOBAL; 976 SymKind kind = SK_NOTYPE; 977 /* Mach-O dylib nlist doesn't carry STT_FUNC / STT_OBJECT cleanly — 978 * default to NOTYPE. The consuming linker uses dso_export_is_func 979 * to peek at this for ELF; for Mach-O the `imported` decision flows 980 * through synthetic __got / __stubs regardless of kind. */ 981 { 982 ObjSymId did = 983 obj_symbol_ex(ob, sn, bind, SV_DEFAULT, kind, OBJ_SEC_NONE, 0, 0, 0); 984 obj_sym_mark_referenced(ob, did); 985 } 986 } 987 988 obj_finalize(ob); 989 return ob; 990 }