kit

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

read.c (50428B)


      1 /* ELF reader. Parses a 64-bit little-endian ELF object back into a fresh
      2  * ObjBuilder. ET_REL produces the section/symbol/reloc view; the
      3  * post-finalize shape is the canonical superset doc/DESIGN.md §5.5
      4  * promises: read_elf of an emit_elf output produces an ObjBuilder
      5  * equivalent to the writer's input, modulo (a) section ordering and
      6  * (b) STT_SECTION symbols synthesized by the writer.
      7  *
      8  * ET_EXEC / ET_DYN additionally attach the linked-image view via
      9  * read_elf_image (program-header segments, .dynamic dependencies,
     10  * .dynsym dynamic symbols, and allocatable dynamic relocations) — see
     11  * doc/OBJ.md. Their section tables still parse through the same
     12  * passes. The standalone read_elf_dso (below) remains the linker's
     13  * exports-only DSO-input path.
     14  *
     15  * Scope: AArch64 little-endian. Other archs / endianness produce a
     16  * compiler_panic with a diagnostic. */
     17 
     18 #include <string.h>
     19 
     20 #include "core/heap.h"
     21 #include "core/pool.h"
     22 #include "core/slice.h"
     23 #include "obj/elf/elf.h"
     24 #include "obj/format.h"
     25 
     26 /* ---- shdr scratch struct ---- */
     27 
     28 typedef struct ShdrRec {
     29   u32 sh_name;
     30   u32 sh_type;
     31   u64 sh_flags;
     32   u64 sh_addr;
     33   u64 sh_offset;
     34   u64 sh_size;
     35   u32 sh_link;
     36   u32 sh_info;
     37   u64 sh_addralign;
     38   u64 sh_entsize;
     39 } ShdrRec;
     40 
     41 static void parse_shdr(const u8* p, int is32, ShdrRec* out) {
     42   /* Elf32_Shdr (40B) shares field order with Elf64_Shdr (64B); only the
     43    * flags/addr/offset/size/addralign/entsize fields narrow to u32 and
     44    * shift the following offsets. The ShdrRec stays u64-wide. */
     45   if (is32) {
     46     out->sh_name = rd_u32_le(p + 0);
     47     out->sh_type = rd_u32_le(p + 4);
     48     out->sh_flags = rd_u32_le(p + 8);
     49     out->sh_addr = rd_u32_le(p + 12);
     50     out->sh_offset = rd_u32_le(p + 16);
     51     out->sh_size = rd_u32_le(p + 20);
     52     out->sh_link = rd_u32_le(p + 24);
     53     out->sh_info = rd_u32_le(p + 28);
     54     out->sh_addralign = rd_u32_le(p + 32);
     55     out->sh_entsize = rd_u32_le(p + 36);
     56   } else {
     57     out->sh_name = rd_u32_le(p + 0);
     58     out->sh_type = rd_u32_le(p + 4);
     59     out->sh_flags = rd_u64_le(p + 8);
     60     out->sh_addr = rd_u64_le(p + 16);
     61     out->sh_offset = rd_u64_le(p + 24);
     62     out->sh_size = rd_u64_le(p + 32);
     63     out->sh_link = rd_u32_le(p + 40);
     64     out->sh_info = rd_u32_le(p + 44);
     65     out->sh_addralign = rd_u64_le(p + 48);
     66     out->sh_entsize = rd_u64_le(p + 56);
     67   }
     68 }
     69 
     70 /* ---- mappers ---- */
     71 
     72 /* The bits this function maps to SecFlag — anything outside this mask is
     73  * treated as opaque and stashed in Section.ext_flags by the caller so the
     74  * emitter can write it back unchanged.  Examples of bits left over:
     75  * SHF_EXCLUDE (0x80000000) on .llvm_addrsig, SHF_COMPRESSED (0x800) on
     76  * compressed .debug_*, SHF_INFO_LINK (0x40) on .rela.* sections. */
     77 #define ELF_KNOWN_FLAGS_MASK                                           \
     78   ((u64)(SHF_ALLOC | SHF_EXECINSTR | SHF_WRITE | SHF_TLS | SHF_MERGE | \
     79          SHF_STRINGS | SHF_GROUP | SHF_LINK_ORDER | SHF_GNU_RETAIN))
     80 
     81 static u16 elf_flags_to_obj(u64 f) {
     82   u16 r = 0;
     83   if (f & SHF_ALLOC) r |= SF_ALLOC;
     84   if (f & SHF_EXECINSTR) r |= SF_EXEC;
     85   if (f & SHF_WRITE) r |= SF_WRITE;
     86   if (f & SHF_TLS) r |= SF_TLS;
     87   if (f & SHF_MERGE) r |= SF_MERGE;
     88   if (f & SHF_STRINGS) r |= SF_STRINGS;
     89   if (f & SHF_GROUP) r |= SF_GROUP;
     90   if (f & SHF_LINK_ORDER) r |= SF_LINK_ORDER;
     91   if (f & SHF_GNU_RETAIN) r |= SF_RETAIN;
     92   return r;
     93 }
     94 
     95 /* Map ELF sh_type -> SecSem.  Sets *known to 1 if the value is one of
     96  * the canonical types the kit model knows about; 0 means the caller
     97  * fell through to the SSEM_PROGBITS fallback and should preserve the
     98  * raw sh_type via Section.ext_type so emit_elf can write it back. */
     99 static u16 elf_type_to_sem(u32 t, int* known) {
    100   *known = 1;
    101   switch (t) {
    102     case SHT_PROGBITS:
    103       return SSEM_PROGBITS;
    104     case SHT_NOBITS:
    105       return SSEM_NOBITS;
    106     case SHT_SYMTAB:
    107       return SSEM_SYMTAB;
    108     case SHT_STRTAB:
    109       return SSEM_STRTAB;
    110     case SHT_RELA:
    111       return SSEM_RELA;
    112     case SHT_REL:
    113       return SSEM_REL;
    114     case SHT_NOTE:
    115       return SSEM_NOTE;
    116     case SHT_INIT_ARRAY:
    117       return SSEM_INIT_ARRAY;
    118     case SHT_FINI_ARRAY:
    119       return SSEM_FINI_ARRAY;
    120     case SHT_PREINIT_ARRAY:
    121       return SSEM_PREINIT_ARRAY;
    122     case SHT_GROUP:
    123       return SSEM_GROUP;
    124     default:
    125       *known = 0;
    126       return SSEM_PROGBITS;
    127   }
    128 }
    129 
    130 static u16 elf_kind_from_name(const char* name, u32 nlen, u64 sh_flags,
    131                               u32 sh_type) {
    132   if (sh_type == SHT_NOBITS) return SEC_BSS;
    133   if (nlen >= 5 && memcmp(name, ".text", 5) == 0) return SEC_TEXT;
    134   if (nlen >= 7 && memcmp(name, ".rodata", 7) == 0) return SEC_RODATA;
    135   if (nlen >= 5 && memcmp(name, ".data", 5) == 0) return SEC_DATA;
    136   if (nlen >= 4 && memcmp(name, ".bss", 4) == 0) return SEC_BSS;
    137   if (nlen >= 7 && memcmp(name, ".debug_", 7) == 0) return SEC_DEBUG;
    138   /* Fallback: classify by flags. */
    139   if (sh_flags & SHF_EXECINSTR) return SEC_TEXT;
    140   if (sh_flags & SHF_WRITE) return SEC_DATA;
    141   if (sh_flags & SHF_ALLOC) return SEC_RODATA;
    142   return SEC_OTHER;
    143 }
    144 
    145 static u16 elf_bind_to_obj(u32 b) {
    146   switch (b) {
    147     case STB_GLOBAL:
    148     case STB_GNU_UNIQUE:
    149       /* GNU-unique is a global with extra runtime uniqueness semantics; for
    150        * link-time resolution it is an ordinary global definition. FreeBSD's
    151        * crt1.o brands the binary with a GNU-unique `.freebsd.note*` symbol. */
    152       return SB_GLOBAL;
    153     case STB_WEAK:
    154       return SB_WEAK;
    155     default:
    156       return SB_LOCAL;
    157   }
    158 }
    159 
    160 static u16 elf_type_to_kind(u32 t, u16 shndx) {
    161   if (shndx == SHN_UNDEF) return SK_UNDEF;
    162   if (shndx == SHN_COMMON) return SK_COMMON;
    163   /* SHN_ABS is the convention for STT_FILE and a few other defined
    164    * symbols whose value is not an address. Don't smother the type
    165    * with SK_ABS when the type field carries real information — only
    166    * fall through to SK_ABS for STT_NOTYPE-at-SHN_ABS. */
    167   if (shndx == SHN_ABS && t == STT_NOTYPE) return SK_ABS;
    168   switch (t) {
    169     case STT_FUNC:
    170       return SK_FUNC;
    171     case STT_OBJECT:
    172       return SK_OBJ;
    173     case STT_SECTION:
    174       return SK_SECTION;
    175     case STT_FILE:
    176       return SK_FILE;
    177     case STT_TLS:
    178       return SK_TLS;
    179     case STT_COMMON:
    180       return SK_COMMON;
    181     case STT_GNU_IFUNC:
    182       return SK_IFUNC;
    183     default:
    184       /* STT_NOTYPE on a defined symbol (e.g. AArch64 mapping symbols
    185        * `$x` / `$d`, or assembly labels) round-trips as SK_NOTYPE.
    186        * The linker keeps definedness keyed on SK_UNDEF; SK_NOTYPE is
    187        * "defined but typeless". */
    188       return SK_NOTYPE;
    189   }
    190 }
    191 
    192 static u8 elf_other_to_vis(u32 other) {
    193   switch (other & 3) {
    194     case STV_HIDDEN:
    195       return SV_HIDDEN;
    196     case STV_PROTECTED:
    197       return SV_PROTECTED;
    198     case STV_INTERNAL:
    199       return SV_INTERNAL;
    200     default:
    201       return SV_DEFAULT;
    202   }
    203 }
    204 
    205 /* Bounds-checked C-string slice from a strtab section. Returns "" on
    206  * out-of-range so callers don't have to special-case it. `len_out` is
    207  * set to the result's byte length. */
    208 static const char* strtab_lookup(const u8* tab, u64 tab_size, u32 off,
    209                                  u32* len_out) {
    210   if (off >= tab_size) {
    211     *len_out = 0;
    212     return "";
    213   }
    214   const char* s = (const char*)(tab + off);
    215   u32 max = (u32)(tab_size - off);
    216   u32 n = 0;
    217   while (n < max && s[n] != '\0') ++n;
    218   *len_out = n;
    219   return s;
    220 }
    221 
    222 static const char* pt_type_name(u32 t) {
    223   switch (t) {
    224     case PT_NULL:
    225       return "NULL";
    226     case PT_LOAD:
    227       return "LOAD";
    228     case PT_DYNAMIC:
    229       return "DYNAMIC";
    230     case PT_INTERP:
    231       return "INTERP";
    232     case PT_NOTE:
    233       return "NOTE";
    234     case PT_PHDR:
    235       return "PHDR";
    236     case PT_TLS:
    237       return "TLS";
    238     case PT_GNU_EH_FRAME:
    239       return "GNU_EH_FRAME";
    240     case PT_GNU_STACK:
    241       return "GNU_STACK";
    242     case PT_GNU_RELRO:
    243       return "GNU_RELRO";
    244     default:
    245       return "UNKNOWN";
    246   }
    247 }
    248 
    249 static Sym intern_cstr(Compiler* c, const char* s) {
    250   return pool_intern_slice(c->global, (Slice){.s = s, .len = (u32)strlen(s)});
    251 }
    252 
    253 /* ELF default-version normalization. A symbol "base@@VERSION" is the *default*
    254  * version of `base`: an unversioned reference binds to it. GNU as emits the
    255  * literal "@@" into a relocatable object's .symtab string (e.g. FreeBSD
    256  * libc.a's openat@@FBSD_1.2 / setcontext / swapcontext). Trim to the base so
    257  * kit's name-based resolution matches plain references. A single-'@'
    258  * (non-default) version is left intact -- those are inert compatibility
    259  * aliases (e.g. fstat@FBSD_1.0) that must NOT shadow the modern base symbol.
    260  * Shared-library exports keep their version in .gnu.version_d rather than the
    261  * name string, so this only fires for relocatable .symtab reads. Returns the
    262  * length of the base name (== nlen when there is no "@@"). */
    263 static u32 elf_default_version_namelen(const char* nm, u32 nlen) {
    264   u32 i;
    265   if (!nm) return nlen;
    266   for (i = 1; i + 1 < nlen; ++i)
    267     if (nm[i] == '@' && nm[i + 1] == '@') return i;
    268   return nlen;
    269 }
    270 
    271 /* Parse a DSO's .gnu.version_d (SHT_GNU_VERDEF) into an index->version-name
    272  * table so .dynsym entries (whose version lives in the parallel .gnu.version)
    273  * can be labelled. Returns an arena table indexed by version index (0/1 unused,
    274  * matching VER_NDX_LOCAL/GLOBAL) and sets *out_max to the highest index seen;
    275  * NULL when the input has no verdef. The Verdef/Verdaux wire layout is
    276  * identical on ELFCLASS32/64 (all Half/Word fields), so this is width-agnostic.
    277  */
    278 static Sym* read_elf_verdefs(Compiler* c, const u8* data, size_t len,
    279                              const ShdrRec* shdrs, u16 e_shnum, u32* out_max) {
    280   u32 i, verdef_idx = 0, max_ndx = 0;
    281   const ShdrRec* sh;
    282   const ShdrRec* str_sh;
    283   const u8* strtab;
    284   const u8* base;
    285   u64 strtab_sz, size, off;
    286   Sym* tbl;
    287   *out_max = 0;
    288   for (i = 1; i < e_shnum; ++i)
    289     if (shdrs[i].sh_type == SHT_GNU_VERDEF) {
    290       verdef_idx = i;
    291       break;
    292     }
    293   if (!verdef_idx) return NULL;
    294   sh = &shdrs[verdef_idx];
    295   if (sh->sh_link >= e_shnum) return NULL;
    296   str_sh = &shdrs[sh->sh_link];
    297   if (sh->sh_offset + sh->sh_size > len ||
    298       str_sh->sh_offset + str_sh->sh_size > len)
    299     return NULL;
    300   strtab = data + str_sh->sh_offset;
    301   strtab_sz = str_sh->sh_size;
    302   base = data + sh->sh_offset;
    303   size = sh->sh_size;
    304 
    305   /* Pass 1: highest version index, to size the table. */
    306   off = 0;
    307   while (off + ELF_VERDEF_SIZE <= size) {
    308     u32 ndx = (u32)(rd_u16_le(base + off + 4) & VERSYM_VERSION);
    309     u32 vd_next = rd_u32_le(base + off + 16);
    310     if (ndx > max_ndx) max_ndx = ndx;
    311     if (!vd_next) break;
    312     off += vd_next;
    313   }
    314   tbl = arena_zarray(c->scratch, Sym, (size_t)max_ndx + 1u);
    315 
    316   /* Pass 2: record each non-base version's name (its first Verdaux). */
    317   off = 0;
    318   while (off + ELF_VERDEF_SIZE <= size) {
    319     u16 vd_flags = rd_u16_le(base + off + 2);
    320     u32 ndx = (u32)(rd_u16_le(base + off + 4) & VERSYM_VERSION);
    321     u32 vd_aux = rd_u32_le(base + off + 12);
    322     u32 vd_next = rd_u32_le(base + off + 16);
    323     if (!(vd_flags & VER_FLG_BASE) && ndx <= max_ndx &&
    324         off + vd_aux + ELF_VERDAUX_SIZE <= size) {
    325       u32 nlen;
    326       const char* nm = strtab_lookup(strtab, strtab_sz,
    327                                      rd_u32_le(base + off + vd_aux), &nlen);
    328       if (nlen)
    329         tbl[ndx] = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen});
    330     }
    331     if (!vd_next) break;
    332     off += vd_next;
    333   }
    334   *out_max = max_ndx;
    335   return tbl;
    336 }
    337 
    338 /* Populate the builder's ObjImage from an ET_EXEC / ET_DYN input: the
    339  * program-header segment table (+ interp + image base), the .dynamic
    340  * dependency view (DT_NEEDED / DT_SONAME / DT_RPATH / DT_RUNPATH), the
    341  * .dynsym dynamic symbols, and the allocatable .rela.* / .rel.* dynamic
    342  * relocations. The section / symbol tables are parsed by read_elf's normal
    343  * passes; this adds the orthogonal image dimension. Lenient where a
    344  * malformed sub-table would otherwise abort a useful inspection: a bad
    345  * .dynamic / .dynsym / dyn-reloc table is skipped rather than panicked. */
    346 static void read_elf_image(Compiler* c, ObjBuilder* ob, const u8* data,
    347                            size_t len, u16 e_type, int is32,
    348                            const ShdrRec* shdrs, u16 e_shnum,
    349                            const u32* elf_to_obj, u32 (*reloc_from)(u32)) {
    350   u32 phdr_size = is32 ? ELF32_PHDR_SIZE : ELF64_PHDR_SIZE;
    351   u32 sym_size = is32 ? ELF32_SYM_SIZE : ELF64_SYM_SIZE;
    352   u32 rela_size = is32 ? ELF32_RELA_SIZE : ELF64_RELA_SIZE;
    353   u32 rel_size = is32 ? 8u : 16u;
    354   u32 dyn_size = is32 ? ELF32_DYN_SIZE : ELF64_DYN_SIZE;
    355   ObjImage* im =
    356       obj_image_ensure(ob, e_type == ET_DYN ? OBJ_KIND_DYN : OBJ_KIND_EXEC);
    357   if (!im) compiler_panic(c, SRCLOC_NONE, "read_elf: obj_image_ensure failed");
    358 
    359   /* e_entry is at offset 24 in both Ehdr32/Ehdr64, native width. */
    360   obj_image_set_entry(im, elf_rd_addr(data + 24, is32));
    361 
    362   /* Program headers -> segments (+ PT_INTERP string, image base). */
    363   {
    364     /* e_phoff: 4B@28 on ELF32, 8B@32 on ELF64. e_phentsize/e_phnum
    365      * shift accordingly (42/44 vs 54/56). */
    366     u64 e_phoff = is32 ? (u64)rd_u32_le(data + 28) : rd_u64_le(data + 32);
    367     u16 e_phentsize = rd_u16_le(data + (is32 ? 42 : 54));
    368     u16 e_phnum = rd_u16_le(data + (is32 ? 44 : 56));
    369     int have_base = 0;
    370     u64 image_base = 0;
    371     if (e_phnum) {
    372       if (e_phentsize != phdr_size)
    373         compiler_panic(c, SRCLOC_NONE, "read_elf: unexpected e_phentsize %u",
    374                        (u32)e_phentsize);
    375       if (e_phoff + (u64)e_phnum * phdr_size > len)
    376         compiler_panic(c, SRCLOC_NONE,
    377                        "read_elf: program header table out of range");
    378       for (u16 i = 0; i < e_phnum; ++i) {
    379         const u8* p = data + e_phoff + (u64)i * phdr_size;
    380         /* Elf32_Phdr REORDERS p_flags AFTER the sizes:
    381          *   p_type@0,p_offset@4,p_vaddr@8,p_paddr@12,p_filesz@16,
    382          *   p_memsz@20,p_flags@24,p_align@28 (all u32).
    383          * Elf64_Phdr: p_type@0,p_flags@4,p_offset@8,p_vaddr@16,
    384          *   p_filesz@32,p_memsz@40,p_align@48. */
    385         u32 p_type = rd_u32_le(p + 0);
    386         u32 p_flags = is32 ? rd_u32_le(p + 24) : rd_u32_le(p + 4);
    387         u64 p_offset = is32 ? (u64)rd_u32_le(p + 4) : rd_u64_le(p + 8);
    388         u64 p_vaddr = is32 ? (u64)rd_u32_le(p + 8) : rd_u64_le(p + 16);
    389         u64 p_paddr = is32 ? (u64)rd_u32_le(p + 12) : rd_u64_le(p + 24);
    390         u64 p_filesz = is32 ? (u64)rd_u32_le(p + 16) : rd_u64_le(p + 32);
    391         u64 p_memsz = is32 ? (u64)rd_u32_le(p + 20) : rd_u64_le(p + 40);
    392         u64 p_align = is32 ? (u64)rd_u32_le(p + 28) : rd_u64_le(p + 48);
    393         ObjSegment seg;
    394         seg.name = intern_cstr(c, pt_type_name(p_type));
    395         seg.vaddr = p_vaddr;
    396         seg.paddr = p_paddr;
    397         seg.vsize = p_memsz;
    398         seg.file_off = p_offset;
    399         seg.file_size = p_filesz;
    400         /* PF_R/W/X share bit values with OBJ_SEG_R/W/X. */
    401         seg.perms = p_flags & (PF_R | PF_W | PF_X);
    402         seg.align = (u32)(p_align ? p_align : 1);
    403         obj_image_add_segment(im, &seg);
    404 
    405         if (p_type == PT_LOAD && (!have_base || p_vaddr < image_base)) {
    406           image_base = p_vaddr;
    407           have_base = 1;
    408         }
    409         if (p_type == PT_INTERP && p_filesz && p_offset + p_filesz <= len) {
    410           u32 ilen = (u32)p_filesz;
    411           while (ilen && data[p_offset + ilen - 1] == '\0') --ilen;
    412           if (ilen)
    413             obj_image_set_interp(
    414                 im, pool_intern_slice(
    415                         c->global, (Slice){.s = (const char*)(data + p_offset),
    416                                            .len = ilen}));
    417         }
    418       }
    419     }
    420     if (have_base) obj_image_set_base(im, image_base);
    421   }
    422 
    423   /* Locate .dynamic and .dynsym. */
    424   u32 dynamic_idx = 0, dynsym_idx = 0;
    425   for (u16 i = 1; i < e_shnum; ++i) {
    426     if (shdrs[i].sh_type == SHT_DYNAMIC && !dynamic_idx) dynamic_idx = i;
    427     if (shdrs[i].sh_type == SHT_DYNSYM && !dynsym_idx) dynsym_idx = i;
    428   }
    429 
    430   /* .dynamic -> dependency view. */
    431   if (dynamic_idx) {
    432     const ShdrRec* dsh = &shdrs[dynamic_idx];
    433     if (dsh->sh_link < e_shnum) {
    434       const ShdrRec* str_sh = &shdrs[dsh->sh_link];
    435       if (str_sh->sh_offset + str_sh->sh_size <= len &&
    436           dsh->sh_offset + dsh->sh_size <= len) {
    437         const u8* dynstr = data + str_sh->sh_offset;
    438         u64 dynstr_sz = str_sh->sh_size;
    439         const u8* dynp = data + dsh->sh_offset;
    440         u64 dynsz = dsh->sh_size;
    441         /* ELF32 DT entries are 8B (d_tag:u32, d_un:u32); ELF64 16B. */
    442         for (u64 off = 0; off + dyn_size <= dynsz; off += dyn_size) {
    443           u64 tag = elf_rd_addr(dynp + off, is32);
    444           u64 val = elf_rd_addr(dynp + off + (is32 ? 4 : 8), is32);
    445           /* Raw .dynamic view (escape hatch): one entry per DT_* tag, the
    446            * terminating DT_NULL included, before the NEEDED/SONAME/RPATH
    447            * filtering below. */
    448           {
    449             ObjImageRaw r;
    450             r.tag = (u32)tag;
    451             r.value = val;
    452             r.extra = 0;
    453             obj_image_add_raw(im, &r);
    454           }
    455           if (tag == DT_NULL) break;
    456           if (tag != DT_NEEDED && tag != DT_SONAME && tag != DT_RPATH &&
    457               tag != DT_RUNPATH)
    458             continue;
    459           {
    460             u32 nlen;
    461             const char* nm = strtab_lookup(dynstr, dynstr_sz, (u32)val, &nlen);
    462             Sym s = nlen ? pool_intern_slice(c->global,
    463                                              (Slice){.s = nm, .len = nlen})
    464                          : 0;
    465             if (!s) continue;
    466             if (tag == DT_NEEDED) {
    467               ObjImageDep d;
    468               d.name = s;
    469               d.imports = NULL;
    470               d.nimports = 0;
    471               obj_image_add_dep(im, &d);
    472             } else if (tag == DT_SONAME) {
    473               obj_image_set_soname(im, s);
    474             } else {
    475               obj_image_add_rpath(im, s);
    476             }
    477           }
    478         }
    479       }
    480     }
    481   }
    482 
    483   /* .dynsym -> dynamic symbols, plus an index->name table for dyn relocs. */
    484   Sym* dynsym_names = NULL;
    485   u32 ndynsym = 0;
    486   if (dynsym_idx) {
    487     const ShdrRec* sh = &shdrs[dynsym_idx];
    488     if (sh->sh_entsize == sym_size && (sh->sh_size % sym_size) == 0 &&
    489         sh->sh_link < e_shnum && sh->sh_offset + sh->sh_size <= len) {
    490       const ShdrRec* str_sh = &shdrs[sh->sh_link];
    491       if (str_sh->sh_offset + str_sh->sh_size <= len) {
    492         const u8* strtab = data + str_sh->sh_offset;
    493         u64 strtab_sz = str_sh->sh_size;
    494         const u8* base = data + sh->sh_offset;
    495         ndynsym = (u32)(sh->sh_size / sym_size);
    496         dynsym_names = arena_zarray(c->scratch, Sym, ndynsym ? ndynsym : 1);
    497         /* Parallel symbol-version tables: .gnu.version_d names indexed by
    498          * version index, and .gnu.version (one u16 per dynsym entry). A
    499          * defined entry whose versym lacks VERSYM_HIDDEN is the *default*
    500          * version of its name — the version a plain reference should bind. */
    501         u32 verdef_max = 0;
    502         Sym* verdef_tbl =
    503             read_elf_verdefs(c, data, len, shdrs, e_shnum, &verdef_max);
    504         const u8* versym = NULL;
    505         u32 nversym = 0;
    506         for (u16 vi = 1; vi < e_shnum; ++vi) {
    507           if (shdrs[vi].sh_type != SHT_GNU_VERSYM) continue;
    508           if (shdrs[vi].sh_offset + shdrs[vi].sh_size <= len &&
    509               shdrs[vi].sh_entsize == 2)
    510             versym = data + shdrs[vi].sh_offset,
    511             nversym = (u32)(shdrs[vi].sh_size / 2u);
    512           break;
    513         }
    514         for (u32 i = 1; i < ndynsym; ++i) {
    515           const u8* p = base + (u64)i * sym_size;
    516           /* Elf32_Sym REORDERS: st_name@0, st_value@4, st_size@8,
    517            * st_info@12, st_other@13, st_shndx@14. Elf64_Sym:
    518            * st_name@0, st_info@4, st_other@5, st_shndx@6,
    519            * st_value@8, st_size@16. */
    520           u32 st_name = rd_u32_le(p + 0);
    521           u8 st_info = is32 ? p[12] : p[4];
    522           u16 st_shndx = is32 ? rd_u16_le(p + 14) : rd_u16_le(p + 6);
    523           u64 st_value = is32 ? (u64)rd_u32_le(p + 4) : rd_u64_le(p + 8);
    524           u64 st_size = is32 ? (u64)rd_u32_le(p + 8) : rd_u64_le(p + 16);
    525           u32 nlen;
    526           const char* nm = strtab_lookup(strtab, strtab_sz, st_name, &nlen);
    527           Sym sn =
    528               nlen ? pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen})
    529                    : 0;
    530           ObjImageSym ds;
    531           dynsym_names[i] = sn;
    532           ds.name = sn;
    533           ds.bind = (SymBind)elf_bind_to_obj(ELF64_ST_BIND(st_info));
    534           ds.kind = (SymKind)elf_type_to_kind(ELF64_ST_TYPE(st_info), st_shndx);
    535           ds.section = (st_shndx == SHN_UNDEF || st_shndx == SHN_ABS ||
    536                         st_shndx == SHN_COMMON || st_shndx >= e_shnum)
    537                            ? OBJ_SEC_NONE
    538                            : elf_to_obj[st_shndx];
    539           ds.value = st_value;
    540           ds.size = st_size;
    541           ds.version = 0;
    542           ds.version_hidden = 0;
    543           if (versym && verdef_tbl && i < nversym && st_shndx != SHN_UNDEF) {
    544             u16 v = rd_u16_le(versym + (u64)i * 2u);
    545             u32 ndx = (u32)(v & VERSYM_VERSION);
    546             if (ndx >= 2u && ndx <= verdef_max) {
    547               ds.version = verdef_tbl[ndx];
    548               ds.version_hidden = (u8)((v & VERSYM_HIDDEN) != 0);
    549             }
    550           }
    551           obj_image_add_dynsym(im, &ds);
    552         }
    553       }
    554     }
    555   }
    556 
    557   /* Allocatable .rela.* / .rel.* -> dynamic relocations. */
    558   for (u16 i = 1; i < e_shnum; ++i) {
    559     const ShdrRec* sh = &shdrs[i];
    560     int is_rela = (sh->sh_type == SHT_RELA);
    561     int is_rel = (sh->sh_type == SHT_REL);
    562     u32 entsize, nrec, j;
    563     const u8* base;
    564     if (!is_rela && !is_rel) continue;
    565     if (!(sh->sh_flags & SHF_ALLOC))
    566       continue; /* link-time relocs: not dynamic */
    567     entsize = is_rela ? rela_size : rel_size;
    568     if (sh->sh_entsize != entsize || (sh->sh_size % entsize) != 0) continue;
    569     if (sh->sh_offset + sh->sh_size > len) continue;
    570     nrec = (u32)(sh->sh_size / entsize);
    571     base = data + sh->sh_offset;
    572     for (j = 0; j < nrec; ++j) {
    573       /* Elf32_Rela (12B): r_offset@0, r_info@4 (ELF32 packing),
    574        * r_addend@8. Elf64_Rela (24B): r_offset@0, r_info@8, r_addend@16. */
    575       const u8* p = base + (u64)j * entsize;
    576       u64 r_offset = elf_rd_addr(p + 0, is32);
    577       u64 r_info = is32 ? (u64)rd_u32_le(p + 4) : rd_u64_le(p + 8);
    578       i64 r_addend =
    579           is_rela ? (is32 ? (i64)(i32)rd_u32_le(p + 8) : (i64)rd_u64_le(p + 16))
    580                   : 0;
    581       u32 esym = is32 ? ELF32_R_SYM(r_info) : ELF64_R_SYM(r_info);
    582       u32 kind = reloc_from(is32 ? ELF32_R_TYPE(r_info) : ELF64_R_TYPE(r_info));
    583       ObjImageReloc dr;
    584       if (kind == (u32)-1) continue; /* unmodeled dyn reloc type: skip */
    585       dr.section = OBJ_SEC_NONE; /* offset is a vaddr, not section-relative */
    586       dr.offset = r_offset;
    587       dr.sym_name = (dynsym_names && esym < ndynsym) ? dynsym_names[esym] : 0;
    588       dr.addend = r_addend;
    589       dr.kind = (RelocKind)kind;
    590       obj_image_add_dynreloc(im, &dr);
    591     }
    592   }
    593 }
    594 
    595 ObjBuilder* read_elf(Compiler* c, const char* name, const u8* data,
    596                      size_t len) {
    597   (void)name;
    598 
    599   /* Need at least the e_ident to read EI_CLASS; the full min-length
    600    * check below uses the class-selected ehdr size. */
    601   if (len < EI_NIDENT)
    602     compiler_panic(c, SRCLOC_NONE, "read_elf: input shorter than ELF header");
    603 
    604   if (data[EI_MAG0] != ELFMAG0 || data[EI_MAG1] != ELFMAG1 ||
    605       data[EI_MAG2] != ELFMAG2 || data[EI_MAG3] != ELFMAG3)
    606     compiler_panic(c, SRCLOC_NONE, "read_elf: bad ELF magic");
    607 
    608   /* Accept both classes; is32 (EI_CLASS==ELFCLASS32) drives every
    609    * stride/offset/field-order decision below. RV32 and RV64 share
    610    * EM_RISCV — the reader cannot tell them apart by e_machine, only by
    611    * EI_CLASS, so is32 is the single source of truth here. */
    612   if (data[EI_CLASS] != ELFCLASS64 && data[EI_CLASS] != ELFCLASS32)
    613     compiler_panic(c, SRCLOC_NONE, "read_elf: not ELFCLASS32/64 (got %u)",
    614                    data[EI_CLASS]);
    615   if (data[EI_DATA] != ELFDATA2LSB)
    616     compiler_panic(c, SRCLOC_NONE, "read_elf: not ELFDATA2LSB (got %u)",
    617                    data[EI_DATA]);
    618 
    619   int is32 = (data[EI_CLASS] == ELFCLASS32);
    620   u32 ehdr_size = is32 ? ELF32_EHDR_SIZE : ELF64_EHDR_SIZE;
    621   u32 shdr_size = is32 ? ELF32_SHDR_SIZE : ELF64_SHDR_SIZE;
    622   u32 sym_size = is32 ? ELF32_SYM_SIZE : ELF64_SYM_SIZE;
    623   u32 rela_size = is32 ? ELF32_RELA_SIZE : ELF64_RELA_SIZE;
    624   u32 rel_size = is32 ? 8u : 16u;
    625   if (len < ehdr_size)
    626     compiler_panic(c, SRCLOC_NONE, "read_elf: input shorter than ELF header");
    627 
    628   u16 e_type = rd_u16_le(data + 16);
    629   /* ET_REL parses to the section/symbol/reloc view only. ET_EXEC/ET_DYN
    630    * additionally get the linked-image view (read_elf_image, below); their
    631    * section tables still parse through the same passes. ET_CORE and other
    632    * types are out of scope (see doc/plan/IMAGE_INSPECT.md). */
    633   if (e_type != ET_REL && e_type != ET_EXEC && e_type != ET_DYN)
    634     compiler_panic(c, SRCLOC_NONE,
    635                    "read_elf: unsupported e_type=%u (expected ET_REL, "
    636                    "ET_EXEC, or ET_DYN)",
    637                    (u32)e_type);
    638 
    639   u16 e_machine = rd_u16_le(data + 18);
    640   /* EM_RISCV is shared by RV32/RV64; disambiguate by EI_CLASS via
    641    * obj_elf_machine_class (obj_elf_machine keys on e_machine alone). */
    642   const ObjElfArchOps* arch = obj_elf_machine_class(e_machine, data[EI_CLASS]);
    643   u32 (*reloc_from)(u32);
    644   if (!arch || !arch->reloc_from) {
    645     compiler_panic(c, SRCLOC_NONE, "read_elf: unsupported e_machine 0x%x",
    646                    (u32)e_machine);
    647   }
    648   reloc_from = arch->reloc_from;
    649 
    650   /* Post-e_version Ehdr fields narrow + shift under ELF32: e_entry/
    651    * e_phoff/e_shoff are 4B (vs 8B), so e_flags@36, e_phentsize@42,
    652    * e_phnum@44, e_shentsize@46, e_shnum@48, e_shstrndx@50 (vs 48/54/
    653    * 56/58/60/62 on ELF64). */
    654   u64 e_shoff = is32 ? (u64)rd_u32_le(data + 32) : rd_u64_le(data + 40);
    655   u32 e_flags = rd_u32_le(data + (is32 ? 36 : 48));
    656   u16 e_shentsize = rd_u16_le(data + (is32 ? 46 : 58));
    657   u16 e_shnum = rd_u16_le(data + (is32 ? 48 : 60));
    658   u16 e_shstrndx = rd_u16_le(data + (is32 ? 50 : 62));
    659 
    660   /* A fully section-stripped image (objcopy --strip-sections, packers,
    661    * some release binaries) sets e_shoff/e_shnum to zero: the section
    662    * header table is gone, but the load segments still describe the file.
    663    * That's valid for ET_EXEC/ET_DYN — parse the image view (segments +
    664    * dynamic) and present an empty section view, matching GNU/LLVM. An
    665    * ET_REL with no sections carries no model state, so still reject it. */
    666   int has_sht = (e_shoff != 0 && e_shnum != 0);
    667   if (has_sht) {
    668     if (e_shentsize != shdr_size)
    669       compiler_panic(c, SRCLOC_NONE, "read_elf: unexpected e_shentsize %u",
    670                      (u32)e_shentsize);
    671     if (e_shoff + (u64)e_shnum * shdr_size > len)
    672       compiler_panic(c, SRCLOC_NONE,
    673                      "read_elf: section header table out of range");
    674     if (e_shstrndx >= e_shnum)
    675       compiler_panic(c, SRCLOC_NONE, "read_elf: e_shstrndx %u >= e_shnum %u",
    676                      (u32)e_shstrndx, (u32)e_shnum);
    677   } else {
    678     if (e_type == ET_REL)
    679       compiler_panic(c, SRCLOC_NONE,
    680                      "read_elf: ET_REL with no section header table");
    681     e_shnum = 0; /* normalize so the section/symbol/reloc passes are no-ops */
    682   }
    683 
    684   /* Parse all shdrs into scratch. NULL when the table is absent. */
    685   ShdrRec* shdrs = NULL;
    686   const u8* shstrtab = NULL;
    687   u64 shstrtab_sz = 0;
    688   if (has_sht) {
    689     shdrs = arena_array(c->scratch, ShdrRec, e_shnum);
    690     for (u32 i = 0; i < e_shnum; ++i)
    691       parse_shdr(data + e_shoff + (u64)i * shdr_size, is32, &shdrs[i]);
    692 
    693     const ShdrRec* shstr_sh = &shdrs[e_shstrndx];
    694     if (shstr_sh->sh_offset + shstr_sh->sh_size > len)
    695       compiler_panic(c, SRCLOC_NONE, "read_elf: .shstrtab out of range");
    696     shstrtab = data + shstr_sh->sh_offset;
    697     shstrtab_sz = shstr_sh->sh_size;
    698   }
    699 
    700   /* Build the ObjBuilder. */
    701   ObjBuilder* ob = obj_new(c);
    702   if (!ob) compiler_panic(c, SRCLOC_NONE, "read_elf: obj_new failed");
    703   obj_set_elf_e_flags(ob, e_flags);
    704 
    705   /* elf_to_obj[shndx] -> ObjSecId, OBJ_SEC_NONE for skipped sections. */
    706   u32* elf_to_obj = arena_zarray(c->scratch, u32, e_shnum ? e_shnum : 1);
    707 
    708   /* Pass 1: create obj sections for every non-NULL shdr that carries
    709    * load-bearing model state. SYMTAB / STRTAB / RELA / REL are
    710    * consumed below for symbols and relocations and do NOT round-trip
    711    * as obj sections — emit_elf re-synthesizes them from the
    712    * ObjBuilder's symbols / strtab / relocs. The shstrtab is a STRTAB
    713    * too, so it falls out the same way. */
    714   for (u32 i = 1; i < e_shnum; ++i) {
    715     const ShdrRec* sh = &shdrs[i];
    716     if (sh->sh_type == SHT_NULL) continue;
    717     if (sh->sh_type == SHT_SYMTAB) continue;
    718     if (sh->sh_type == SHT_STRTAB) continue;
    719     if (sh->sh_type == SHT_RELA) continue;
    720     if (sh->sh_type == SHT_REL) continue;
    721     /* SHT_GROUP is consumed below into an ObjGroup record (signature
    722      * symbol + member ObjSecIds). emit_elf re-synthesizes the group
    723      * section bytes from the ObjGroup, using current section indices
    724      * — so the original section's raw body would be stale anyway. */
    725     if (sh->sh_type == SHT_GROUP) continue;
    726 
    727     u32 nlen;
    728     const char* nm = strtab_lookup(shstrtab, shstrtab_sz, sh->sh_name, &nlen);
    729     Sym sym = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen});
    730 
    731     u16 sec_kind = elf_kind_from_name(nm, nlen, sh->sh_flags, sh->sh_type);
    732     int type_known;
    733     u16 sec_sem = elf_type_to_sem(sh->sh_type, &type_known);
    734     u16 flags = elf_flags_to_obj(sh->sh_flags);
    735     u32 align = sh->sh_addralign ? (u32)sh->sh_addralign : 1;
    736 
    737     ObjSecId id =
    738         obj_section_ex(ob, sym, (SecKind)sec_kind, (SecSem)sec_sem, flags,
    739                        align, (u32)sh->sh_entsize, sh->sh_link, sh->sh_info);
    740     if (id == OBJ_SEC_NONE)
    741       compiler_panic(c, SRCLOC_NONE,
    742                      "read_elf: obj_section_ex failed for '%.*s'",
    743                      SLICE_ARG(((Slice){.s = nm, .len = nlen})));
    744     elf_to_obj[i] = id;
    745 
    746     /* Load address: 0 for ET_REL, the assigned vaddr for linked images.
    747      * Lets the section view carry the load picture for execs/DSOs. */
    748     if (sh->sh_addr) obj_section_set_addr(ob, id, sh->sh_addr);
    749 
    750     /* Preserve format-specific bits the canonical SecSem/SecFlag
    751      * mapping can't represent so emit_elf can write them back
    752      * verbatim.  ext_type only set when the sh_type fell through
    753      * to the "unknown" path. */
    754     u32 leftover = (u32)(sh->sh_flags & ~ELF_KNOWN_FLAGS_MASK);
    755     if (!type_known || leftover) {
    756       obj_section_set_ext(ob, id, OBJ_EXT_ELF, type_known ? 0 : sh->sh_type,
    757                           leftover);
    758     }
    759 
    760     /* Body bytes. */
    761     if (sh->sh_type == SHT_NOBITS) {
    762       obj_reserve_bss(ob, id, (u32)sh->sh_size, align);
    763     } else if (sh->sh_size) {
    764       if (sh->sh_offset + sh->sh_size > len)
    765         compiler_panic(c, SRCLOC_NONE,
    766                        "read_elf: section '%.*s' bytes out of range",
    767                        SLICE_ARG(((Slice){.s = nm, .len = nlen})));
    768       /* For SYMTAB/STRTAB/RELA we still copy the raw bytes — the
    769        * post-finalize shape contract says these sections are
    770        * present; emit_elf will regenerate them on re-emit, so the
    771        * preserved bytes are informational rather than load-bearing.
    772        */
    773       obj_write(ob, id, data + sh->sh_offset, (size_t)sh->sh_size);
    774     }
    775   }
    776 
    777   /* Pass 2: parse the .symtab into ObjSyms, building an
    778    * elf_sym_idx -> ObjSymId table. There may be zero or one SYMTAB in
    779    * an ET_REL; pick the first. */
    780   u32 symtab_shndx = 0;
    781   for (u32 i = 1; i < e_shnum; ++i) {
    782     if (shdrs[i].sh_type == SHT_SYMTAB) {
    783       symtab_shndx = i;
    784       break;
    785     }
    786   }
    787 
    788   u32 nsyms = 0;
    789   u32* sym_elf_to_obj = NULL;
    790 
    791   if (symtab_shndx) {
    792     const ShdrRec* sh = &shdrs[symtab_shndx];
    793     if (sh->sh_entsize != sym_size)
    794       compiler_panic(c, SRCLOC_NONE, "read_elf: .symtab entsize %llu != %u",
    795                      (unsigned long long)sh->sh_entsize, sym_size);
    796     if (sh->sh_size % sym_size)
    797       compiler_panic(c, SRCLOC_NONE,
    798                      "read_elf: .symtab size %llu not a multiple of %u",
    799                      (unsigned long long)sh->sh_size, sym_size);
    800     if (sh->sh_link >= e_shnum)
    801       compiler_panic(c, SRCLOC_NONE,
    802                      "read_elf: .symtab sh_link %u out of range", sh->sh_link);
    803     const ShdrRec* str_sh = &shdrs[sh->sh_link];
    804     if (str_sh->sh_offset + str_sh->sh_size > len)
    805       compiler_panic(c, SRCLOC_NONE, "read_elf: .strtab out of range");
    806     const u8* strtab = data + str_sh->sh_offset;
    807     u64 strtab_sz = str_sh->sh_size;
    808 
    809     nsyms = (u32)(sh->sh_size / sym_size);
    810     obj_reserve_symbols(ob, nsyms); /* skip the 256->nsyms resize cascade */
    811     sym_elf_to_obj = arena_zarray(c->scratch, u32, nsyms ? nsyms : 1);
    812 
    813     const u8* base = data + sh->sh_offset;
    814     for (u32 i = 1; i < nsyms; ++i) { /* skip index 0 */
    815       const u8* p = base + (u64)i * sym_size;
    816       /* Elf32_Sym REORDERS: st_name@0, st_value@4, st_size@8, st_info@12,
    817        * st_other@13, st_shndx@14. Elf64_Sym: st_name@0, st_info@4,
    818        * st_other@5, st_shndx@6, st_value@8, st_size@16. */
    819       u32 st_name = rd_u32_le(p + 0);
    820       u8 st_info = is32 ? p[12] : p[4];
    821       u8 st_other = is32 ? p[13] : p[5];
    822       u16 st_shndx = is32 ? rd_u16_le(p + 14) : rd_u16_le(p + 6);
    823       u64 st_value = is32 ? (u64)rd_u32_le(p + 4) : rd_u64_le(p + 8);
    824       u64 st_size = is32 ? (u64)rd_u32_le(p + 8) : rd_u64_le(p + 16);
    825 
    826       u32 nlen;
    827       const char* nm = strtab_lookup(strtab, strtab_sz, st_name, &nlen);
    828       nlen = elf_default_version_namelen(nm, nlen);
    829       Sym sn = nlen
    830                    ? pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen})
    831                    : 0;
    832 
    833       u32 e_bind = ELF64_ST_BIND(st_info);
    834       u32 e_type = ELF64_ST_TYPE(st_info);
    835       u16 bind = elf_bind_to_obj(e_bind);
    836       u16 kind = elf_type_to_kind(e_type, st_shndx);
    837       u8 vis = elf_other_to_vis(st_other);
    838 
    839       ObjSecId sec_id;
    840       u64 value;
    841       u64 cmnalign = 0;
    842       if (st_shndx == SHN_UNDEF) {
    843         sec_id = OBJ_SEC_NONE;
    844         value = st_value;
    845       } else if (st_shndx == SHN_ABS || st_shndx == SHN_COMMON) {
    846         sec_id = OBJ_SEC_NONE;
    847         value = st_value;
    848         if (st_shndx == SHN_COMMON) cmnalign = st_value;
    849       } else if (st_shndx < e_shnum && shdrs[st_shndx].sh_type == SHT_GROUP) {
    850         /* A COMDAT group's signature symbol is defined in its SHT_GROUP
    851          * section, which we consume into an ObjGroup and never keep as an
    852          * obj section (so elf_to_obj is OBJ_SEC_NONE for it). The symbol just
    853          * names the group; it is not a data location and is never a reloc
    854          * target. Record it as an absolute defined symbol so it doesn't look
    855          * like a phantom undefined reference -- FreeBSD's crt1.o brands the
    856          * binary with such a symbol (.freebsd.note*). */
    857         sec_id = OBJ_SEC_NONE;
    858         value = st_value;
    859         kind = SK_ABS;
    860       } else if (st_shndx < e_shnum) {
    861         sec_id = elf_to_obj[st_shndx];
    862         value = st_value;
    863       } else {
    864         compiler_panic(c, SRCLOC_NONE, "read_elf: symbol shndx %u out of range",
    865                        (u32)st_shndx);
    866         sec_id = OBJ_SEC_NONE;
    867         value = 0; /* unreachable */
    868       }
    869 
    870       ObjSymId id =
    871           obj_symbol_ex(ob, sn, (SymBind)bind, (SymVis)vis, (SymKind)kind,
    872                         sec_id, value, st_size, cmnalign);
    873       obj_sym_mark_referenced(ob, id);
    874       sym_elf_to_obj[i] = id;
    875     }
    876   }
    877 
    878   /* Pass 3: parse each SHT_RELA / SHT_REL into ObjBuilder relocations
    879    * targeting the section the rela header's sh_info points at. */
    880   for (u32 i = 1; i < e_shnum; ++i) {
    881     const ShdrRec* sh = &shdrs[i];
    882     int is_rela = (sh->sh_type == SHT_RELA);
    883     int is_rel = (sh->sh_type == SHT_REL);
    884     if (!is_rela && !is_rel) continue;
    885     /* Allocatable rela/rel in ET_EXEC/ET_DYN are loader (dynamic)
    886      * relocations — sh_info is 0 or a .got index, not a target section.
    887      * They belong to the image's dynamic-reloc view (read_elf_image), not
    888      * the section-relocation table. ET_REL link-time relocs are never
    889      * SHF_ALLOC, so this is a no-op for relocatable objects. */
    890     if (sh->sh_flags & SHF_ALLOC) continue;
    891 
    892     u32 entsize = is_rela ? rela_size : rel_size;
    893     if (sh->sh_entsize != entsize)
    894       compiler_panic(c, SRCLOC_NONE, "read_elf: rela entsize %llu != %u",
    895                      (unsigned long long)sh->sh_entsize, entsize);
    896     if (sh->sh_info == 0 || sh->sh_info >= e_shnum)
    897       compiler_panic(c, SRCLOC_NONE, "read_elf: rela sh_info %u out of range",
    898                      sh->sh_info);
    899     ObjSecId target = elf_to_obj[sh->sh_info];
    900     if (target == OBJ_SEC_NONE) continue;
    901 
    902     /* For REL the addend lives in the relocated field, not an r_addend slot.
    903      * Locate the target section's bytes so the per-arch decoder can recover
    904      * it (matching what a field-reading linker like ld.lld does). A NOBITS
    905      * target has no file bytes — its relocs keep addend 0. */
    906     const ShdrRec* tgt_sh = &shdrs[sh->sh_info];
    907     const u8* tgt_bytes = NULL;
    908     u64 tgt_size = 0;
    909     if (is_rel && arch && arch->reloc_field_addend &&
    910         tgt_sh->sh_type != SHT_NOBITS &&
    911         tgt_sh->sh_offset + tgt_sh->sh_size <= len) {
    912       tgt_bytes = data + tgt_sh->sh_offset;
    913       tgt_size = tgt_sh->sh_size;
    914     }
    915 
    916     u32 nrec = (u32)(sh->sh_size / entsize);
    917     const u8* base = data + sh->sh_offset;
    918     for (u32 j = 0; j < nrec; ++j) {
    919       /* Elf32_Rel (8B): r_offset@0, r_info@4 — addend in-field.
    920        * Elf32_Rela (12B): + r_addend@8. Elf64_Rela (24B): r_offset@0,
    921        * r_info@8, r_addend@16. */
    922       const u8* p = base + (u64)j * entsize;
    923       u64 r_offset = elf_rd_addr(p + 0, is32);
    924       u64 r_info = is32 ? (u64)rd_u32_le(p + 4) : rd_u64_le(p + 8);
    925       i64 r_addend =
    926           is_rela ? (is32 ? (i64)(i32)rd_u32_le(p + 8) : (i64)rd_u64_le(p + 16))
    927                   : 0;
    928       u32 esym = is32 ? ELF32_R_SYM(r_info) : ELF64_R_SYM(r_info);
    929       u32 etype = is32 ? ELF32_R_TYPE(r_info) : ELF64_R_TYPE(r_info);
    930 
    931       u32 kind = reloc_from(etype);
    932       if (kind == (u32)-1)
    933         compiler_panic(c, SRCLOC_NONE,
    934                        "read_elf: unsupported reloc type %u for e_machine 0x%x",
    935                        etype, (u32)e_machine);
    936 
    937       /* REL: reconstruct the implicit in-field addend (width capped to the
    938        * field's remaining bytes; Thumb-2 instructions and data words are 4). */
    939       if (tgt_bytes && r_offset < tgt_size) {
    940         u32 width = (u32)(tgt_size - r_offset);
    941         if (width > 4) width = 4;
    942         r_addend = arch->reloc_field_addend((u32)kind, tgt_bytes + r_offset,
    943                                             width);
    944       }
    945 
    946       ObjSymId target_sym = OBJ_SYM_NONE;
    947       if (esym && sym_elf_to_obj && esym < nsyms)
    948         target_sym = sym_elf_to_obj[esym];
    949 
    950       obj_reloc_ex(ob, target, (u32)r_offset, (RelocKind)kind, target_sym,
    951                    r_addend, is_rela ? 1 : 0, 0);
    952     }
    953   }
    954 
    955   /* Pass 4: SHT_GROUP. Each GROUP section's body is a sequence of
    956    * 4-byte LE indices: [flags, shndx, shndx, ...]. The signature is
    957    * the symbol named by sh_link/sh_info convention (sh_link=symtab,
    958    * sh_info=symbol index in that symtab). */
    959   for (u32 i = 1; i < e_shnum; ++i) {
    960     const ShdrRec* sh = &shdrs[i];
    961     if (sh->sh_type != SHT_GROUP) continue;
    962 
    963     if (sh->sh_size < 4 || (sh->sh_size % 4)) continue;
    964     const u8* p = data + sh->sh_offset;
    965     u32 flags = rd_u32_le(p);
    966     u32 nm_len;
    967     const char* gnm =
    968         strtab_lookup(shstrtab, shstrtab_sz, sh->sh_name, &nm_len);
    969     Sym gname = pool_intern_slice(c->global, (Slice){.s = gnm, .len = nm_len});
    970 
    971     ObjSymId signature = OBJ_SYM_NONE;
    972     if (sym_elf_to_obj && sh->sh_info < nsyms)
    973       signature = sym_elf_to_obj[sh->sh_info];
    974 
    975     ObjGroupId gid = obj_group(ob, gname, signature, flags);
    976     u32 n = (u32)(sh->sh_size / 4) - 1;
    977     for (u32 j = 0; j < n; ++j) {
    978       u32 shndx = rd_u32_le(p + 4 + j * 4);
    979       if (shndx < e_shnum && elf_to_obj[shndx] != OBJ_SEC_NONE)
    980         obj_group_add_section(ob, gid, elf_to_obj[shndx]);
    981     }
    982   }
    983 
    984   /* ET_EXEC / ET_DYN: attach the linked-image view (segments + dynamic). */
    985   if (e_type != ET_REL)
    986     read_elf_image(c, ob, data, len, e_type, is32, shdrs, e_shnum, elf_to_obj,
    987                    reloc_from);
    988 
    989   obj_finalize(ob);
    990   return ob;
    991 }
    992 
    993 /* ---- ET_DYN (shared object) reader ----
    994  *
    995  * Produces an ObjBuilder containing only the DSO's exported symbols
    996  * (parsed from .dynsym, not .symtab). The DSO's sections, relocations,
    997  * and groups are skipped — DSOs contribute no bytes to the output
    998  * image. The DT_SONAME (if any) is interned and returned via
    999  * `*soname_out` so the caller can record DT_NEEDED at link time.
   1000  *
   1001  * Symbol shape: each defined dynsym entry produces an ObjSym whose
   1002  * (bind, kind, vis) match the source. `section_id` is OBJ_SEC_NONE —
   1003  * the symbol's value is its DSO-internal vaddr, not meaningful to the
   1004  * consuming linker, so we record `value=0`. The linker layer
   1005  * (resolve_undefs) only consults the name and the defined-ness flag.
   1006  *
   1007  * Undefined dynsym entries (st_shndx==SHN_UNDEF) are imports the DSO
   1008  * itself has against other libraries; they're not relevant to a
   1009  * consumer that's linking against this DSO and are dropped. */
   1010 
   1011 static int parse_phdr(const u8* data, size_t len, u64 e_phoff, u16 e_phentsize,
   1012                       u16 e_phnum, u32 want_type, u64* out_offset,
   1013                       u64* out_filesz) {
   1014   u32 i;
   1015   if (e_phentsize != ELF64_PHDR_SIZE) return 0;
   1016   if (e_phoff + (u64)e_phnum * ELF64_PHDR_SIZE > len) return 0;
   1017   for (i = 0; i < e_phnum; ++i) {
   1018     const u8* p = data + e_phoff + (u64)i * ELF64_PHDR_SIZE;
   1019     u32 p_type = rd_u32_le(p + 0);
   1020     if (p_type != want_type) continue;
   1021     *out_offset = rd_u64_le(p + 8);
   1022     *out_filesz = rd_u64_le(p + 32);
   1023     return 1;
   1024   }
   1025   return 0;
   1026 }
   1027 
   1028 ObjBuilder* read_elf_dso(Compiler* c, const char* name, const u8* data,
   1029                          size_t len, Sym* soname_out) {
   1030   (void)name;
   1031   if (soname_out) *soname_out = 0;
   1032 
   1033   if (len < ELF64_EHDR_SIZE)
   1034     compiler_panic(c, SRCLOC_NONE,
   1035                    "read_elf_dso: input shorter than ELF header");
   1036   if (data[EI_MAG0] != ELFMAG0 || data[EI_MAG1] != ELFMAG1 ||
   1037       data[EI_MAG2] != ELFMAG2 || data[EI_MAG3] != ELFMAG3)
   1038     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: bad ELF magic");
   1039   if (data[EI_CLASS] != ELFCLASS64)
   1040     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: not ELFCLASS64");
   1041   if (data[EI_DATA] != ELFDATA2LSB)
   1042     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: not ELFDATA2LSB");
   1043 
   1044   u16 e_type = rd_u16_le(data + 16);
   1045   if (e_type != ET_DYN)
   1046     compiler_panic(c, SRCLOC_NONE,
   1047                    "read_elf_dso: expected ET_DYN, got e_type=%u", (u32)e_type);
   1048 
   1049   u16 e_machine = rd_u16_le(data + 18);
   1050   {
   1051     const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_ELF);
   1052     const ObjElfArchOps* arch =
   1053         fmt && fmt->elf_machine ? fmt->elf_machine(e_machine) : NULL;
   1054     if (!arch)
   1055       compiler_panic(c, SRCLOC_NONE, "read_elf_dso: unsupported e_machine 0x%x",
   1056                      (u32)e_machine);
   1057   }
   1058 
   1059   u64 e_phoff = rd_u64_le(data + 32);
   1060   u64 e_shoff = rd_u64_le(data + 40);
   1061   u16 e_phentsize = rd_u16_le(data + 54);
   1062   u16 e_phnum = rd_u16_le(data + 56);
   1063   u16 e_shentsize = rd_u16_le(data + 58);
   1064   u16 e_shnum = rd_u16_le(data + 60);
   1065   u16 e_shstrndx = rd_u16_le(data + 62);
   1066 
   1067   if (e_shentsize != ELF64_SHDR_SIZE)
   1068     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: unexpected e_shentsize %u",
   1069                    (u32)e_shentsize);
   1070   if (e_shoff + (u64)e_shnum * ELF64_SHDR_SIZE > len)
   1071     compiler_panic(c, SRCLOC_NONE,
   1072                    "read_elf_dso: section header table out of range");
   1073   if (e_shstrndx >= e_shnum)
   1074     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: e_shstrndx out of range");
   1075 
   1076   /* read_elf_dso is ELFCLASS64-only (panics above on other classes), so
   1077    * parse with the ELF64 layout (is32 = 0). */
   1078   ShdrRec* shdrs = arena_array(c->scratch, ShdrRec, e_shnum);
   1079   for (u32 i = 0; i < e_shnum; ++i)
   1080     parse_shdr(data + e_shoff + (u64)i * ELF64_SHDR_SIZE, 0, &shdrs[i]);
   1081 
   1082   /* Locate .dynsym (preferred over .symtab — a stripped DSO carries
   1083    * only .dynsym) and its associated strtab via sh_link. */
   1084   u32 dynsym_idx = 0, dynamic_idx = 0;
   1085   for (u32 i = 1; i < e_shnum; ++i) {
   1086     if (shdrs[i].sh_type == SHT_DYNSYM && !dynsym_idx) dynsym_idx = i;
   1087     if (shdrs[i].sh_type == SHT_DYNAMIC && !dynamic_idx) dynamic_idx = i;
   1088   }
   1089 
   1090   if (!dynsym_idx)
   1091     compiler_panic(c, SRCLOC_NONE,
   1092                    "read_elf_dso: no SHT_DYNSYM in shared object");
   1093 
   1094   /* Parse PT_DYNAMIC for DT_SONAME. The .dynamic section gives us the
   1095    * dynstr to resolve the SONAME's offset; if there's no .dynamic
   1096    * section we fall back to scanning the PT_DYNAMIC segment. */
   1097   Sym soname = 0;
   1098   if (dynamic_idx) {
   1099     const ShdrRec* dsh = &shdrs[dynamic_idx];
   1100     if (dsh->sh_link >= e_shnum)
   1101       compiler_panic(c, SRCLOC_NONE,
   1102                      "read_elf_dso: .dynamic sh_link %u out of range",
   1103                      dsh->sh_link);
   1104     const ShdrRec* str_sh = &shdrs[dsh->sh_link];
   1105     if (str_sh->sh_offset + str_sh->sh_size > len)
   1106       compiler_panic(c, SRCLOC_NONE,
   1107                      "read_elf_dso: .dynamic strtab out of range");
   1108     const u8* dynstr = data + str_sh->sh_offset;
   1109     u64 dynstr_sz = str_sh->sh_size;
   1110 
   1111     if (dsh->sh_offset + dsh->sh_size > len)
   1112       compiler_panic(c, SRCLOC_NONE,
   1113                      "read_elf_dso: .dynamic body out of range");
   1114     const u8* dynp = data + dsh->sh_offset;
   1115     u64 dynsz = dsh->sh_size;
   1116     /* DT entries are 16 bytes: (d_tag: u64, d_un: u64). */
   1117     for (u64 off = 0; off + 16 <= dynsz; off += 16) {
   1118       u64 tag = rd_u64_le(dynp + off);
   1119       u64 val = rd_u64_le(dynp + off + 8);
   1120       if (tag == DT_NULL) break;
   1121       if (tag == DT_SONAME) {
   1122         u32 nlen;
   1123         const char* nm = strtab_lookup(dynstr, dynstr_sz, (u32)val, &nlen);
   1124         if (nlen)
   1125           soname = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen});
   1126         break;
   1127       }
   1128     }
   1129   } else if (e_phnum) {
   1130     /* Fallback: walk PT_DYNAMIC straight from program headers. We
   1131      * only need DT_SONAME, so skip if we can't find a strtab pointer
   1132      * inline (DT_STRTAB carries a vaddr, not a file offset — stripped
   1133      * DSOs without SHT_DYNAMIC are exceedingly rare in practice). */
   1134     u64 dyn_off, dyn_sz;
   1135     (void)parse_phdr(data, len, e_phoff, e_phentsize, e_phnum, PT_DYNAMIC,
   1136                      &dyn_off, &dyn_sz);
   1137   }
   1138   if (soname_out) *soname_out = soname;
   1139 
   1140   /* Now parse .dynsym. */
   1141   const ShdrRec* sh = &shdrs[dynsym_idx];
   1142   if (sh->sh_entsize != ELF64_SYM_SIZE)
   1143     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: .dynsym entsize %llu != %u",
   1144                    (unsigned long long)sh->sh_entsize, (u32)ELF64_SYM_SIZE);
   1145   if (sh->sh_size % ELF64_SYM_SIZE)
   1146     compiler_panic(c, SRCLOC_NONE,
   1147                    "read_elf_dso: .dynsym size not multiple of entry size");
   1148   if (sh->sh_link >= e_shnum)
   1149     compiler_panic(c, SRCLOC_NONE,
   1150                    "read_elf_dso: .dynsym sh_link out of range");
   1151   const ShdrRec* str_sh = &shdrs[sh->sh_link];
   1152   if (str_sh->sh_offset + str_sh->sh_size > len)
   1153     compiler_panic(c, SRCLOC_NONE, "read_elf_dso: .dynstr out of range");
   1154   const u8* strtab = data + str_sh->sh_offset;
   1155   u64 strtab_sz = str_sh->sh_size;
   1156 
   1157   ObjBuilder* ob = obj_new(c);
   1158   if (!ob) compiler_panic(c, SRCLOC_NONE, "read_elf_dso: obj_new failed");
   1159 
   1160   /* The DSO always gets an ObjImage: its dynsyms record each export's default
   1161    * version (so the linker can emit a matching .gnu.version_r — see
   1162    * build_versions in link_dyn.c, harmless/empty for unversioned DSOs like
   1163    * musl), and its undef list records the symbols this DSO references so
   1164    * --gc-sections keeps the executable's definitions of them alive. */
   1165   u32 verdef_max = 0;
   1166   Sym* verdef_tbl = read_elf_verdefs(c, data, len, shdrs, e_shnum, &verdef_max);
   1167   const u8* versym = NULL;
   1168   u32 nversym = 0;
   1169   for (u32 i = 1; i < e_shnum; ++i) {
   1170     if (shdrs[i].sh_type != SHT_GNU_VERSYM) continue;
   1171     if (shdrs[i].sh_offset + shdrs[i].sh_size <= len &&
   1172         shdrs[i].sh_entsize == 2)
   1173       versym = data + shdrs[i].sh_offset,
   1174       nversym = (u32)(shdrs[i].sh_size / 2u);
   1175     break;
   1176   }
   1177   ObjImage* im = obj_image_ensure(ob, OBJ_KIND_DYN);
   1178   if (im && soname) obj_image_set_soname(im, soname);
   1179 
   1180   u32 nsyms = (u32)(sh->sh_size / ELF64_SYM_SIZE);
   1181   const u8* base = data + sh->sh_offset;
   1182   for (u32 i = 1; i < nsyms; ++i) { /* skip index 0 */
   1183     const u8* p = base + (u64)i * ELF64_SYM_SIZE;
   1184     u32 st_name = rd_u32_le(p + 0);
   1185     u8 st_info = p[4];
   1186     u8 st_other = p[5];
   1187     u16 st_shndx = rd_u16_le(p + 6);
   1188     u32 e_bind = ELF64_ST_BIND(st_info);
   1189     u32 nlen;
   1190     const char* nm;
   1191     Sym sn;
   1192 
   1193     /* Locals are neither exports nor reference dependencies we track. */
   1194     if (e_bind == STB_LOCAL) continue;
   1195     nm = strtab_lookup(strtab, strtab_sz, st_name, &nlen);
   1196     if (!nlen) continue;
   1197     sn = pool_intern_slice(c->global, (Slice){.s = nm, .len = nlen});
   1198 
   1199     /* The DSO's own undefined references: not exports, but if the executable
   1200      * defines one (e.g. libc.so.7's `environ` / `__progname`, defined by the
   1201      * crt) the static linker must keep that definition under --gc-sections. */
   1202     if (st_shndx == SHN_UNDEF) {
   1203       obj_image_add_undef(im, sn);
   1204       continue;
   1205     }
   1206 
   1207     u32 e_type_field = ELF64_ST_TYPE(st_info);
   1208     u16 bind = elf_bind_to_obj(e_bind);
   1209     u16 kind = elf_type_to_kind(e_type_field, st_shndx);
   1210     u8 vis = elf_other_to_vis(st_other);
   1211 
   1212     /* DSO exports land as defined symbols in OBJ_SEC_NONE with
   1213      * value=0. The consumer treats them as imports — see
   1214      * resolve_undefs in src/link/link_layout.c. */
   1215     {
   1216       ObjSymId did = obj_symbol_ex(ob, sn, (SymBind)bind, (SymVis)vis,
   1217                                    (SymKind)kind, OBJ_SEC_NONE, 0, 0, 0);
   1218       obj_sym_mark_referenced(ob, did);
   1219     }
   1220     if (im) {
   1221       ObjImageSym ds;
   1222       ds.name = sn;
   1223       ds.bind = (SymBind)bind;
   1224       ds.kind = (SymKind)kind;
   1225       ds.section = OBJ_SEC_NONE;
   1226       ds.value = 0;
   1227       ds.size = 0;
   1228       ds.version = 0;
   1229       ds.version_hidden = 0;
   1230       if (i < nversym) {
   1231         u16 v = rd_u16_le(versym + (u64)i * 2u);
   1232         u32 ndx = (u32)(v & VERSYM_VERSION);
   1233         if (ndx >= 2u && ndx <= verdef_max) {
   1234           ds.version = verdef_tbl[ndx];
   1235           ds.version_hidden = (u8)((v & VERSYM_HIDDEN) != 0);
   1236         }
   1237       }
   1238       obj_image_add_dynsym(im, &ds);
   1239     }
   1240   }
   1241 
   1242   obj_finalize(ob);
   1243   return ob;
   1244 }