kit

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

link_dyn.c (53975B)


      1 /* Phase 4 of dynamic linking: synthesize the dyn-link tables and
      2  * sections an ET_DYN ELF exe needs to be loadable by a real runtime
      3  * loader (musl ld-musl-aarch64.so.1).
      4  *
      5  * Inputs (computed by earlier passes):
      6  *   - LinkSymbol entries with `imported = 1` (set by resolve_undefs's
      7  *     DSO-search path; their dso_input_id names the providing DSO).
      8  *   - LinkInputs of kind LINK_INPUT_DSO_BYTES carrying SONAMEs.
      9  *
     10  * Outputs (deposited on LinkImage.dyn):
     11  *   - .interp                    PT_INTERP target string
     12  *   - .dynsym + .dynstr          symbol table + name pool
     13  *   - .gnu.hash                  GNU-style hash for the loader
     14  *   - .rela.dyn                  GLOB_DAT (data imports) + space for
     15  *                                R_AARCH64_RELATIVE records that
     16  *                                Phase 6 emit fills in
     17  *   - .rela.plt                  JUMP_SLOT records (one per imported func)
     18  *   - .plt                       allocated, body NOT emitted (Phase 5)
     19  *   - .got.plt                   3 reserved slots + 1 per PLT slot,
     20  *                                allocated, body NOT emitted
     21  *   - .dynamic                   PT_DYNAMIC body, populated
     22  *
     23  * The .plt body / GOT-slot fill / CALL26 reloc rewriting are Phase 5;
     24  * they're called out at the relevant allocation site so the missing
     25  * pieces are obvious to anyone reading the output. The static-exe path
     26  * is unaffected — layout_dyn early-outs when emit_pie is 0.
     27  *
     28  * Allocator pattern follows layout_iplt (link_layout.c): grow segments
     29  * + sections via realloc, then page-align each new segment after the
     30  * existing image span. Synthetic sections carry input_id == LINK_INPUT_NONE
     31  * so downstream passes (emit_reloc_records, GC) leave them alone.
     32  */
     33 
     34 #include "obj/elf/link_dyn.h"
     35 
     36 #include <string.h>
     37 
     38 #include "core/bytes.h"
     39 #include "core/heap.h"
     40 #include "core/pool.h"
     41 #include "core/slice.h"
     42 #include "core/util.h"
     43 #include "core/vec.h"
     44 #include "link/link.h"
     45 #include "link/link_arch.h"
     46 #include "link/link_internal.h"
     47 #include "link/link_reloc_desc.h"
     48 #include "obj/bytebuf.h"
     49 #include "obj/elf/elf.h"
     50 #include "obj/format.h"
     51 
     52 /* ---- small allocators (mirror layout_iplt's helpers) ---- */
     53 
     54 static u32 dyn_alloc_segments(LinkImage* img, u32 nseg) {
     55   Heap* h = img->heap;
     56   u32 base = img->nsegments;
     57   u32 new_nseg = base + nseg;
     58   LinkSegment* nsegs = (LinkSegment*)h->realloc(
     59       h, img->segments, sizeof(*img->segments) * img->nsegments,
     60       sizeof(*img->segments) * new_nseg, _Alignof(LinkSegment));
     61   u8** nsbufs = (u8**)h->realloc(
     62       h, img->segment_bytes, sizeof(*img->segment_bytes) * img->nsegments,
     63       sizeof(*img->segment_bytes) * new_nseg, _Alignof(u8*));
     64   size_t* nscaps = (size_t*)h->realloc(
     65       h, img->segment_bytes_cap,
     66       sizeof(*img->segment_bytes_cap) * img->nsegments,
     67       sizeof(*img->segment_bytes_cap) * new_nseg, _Alignof(size_t));
     68   if (!nsegs || !nsbufs || !nscaps)
     69     compiler_panic(img->c, SRCLOC_NONE, "link: oom on dyn segments");
     70   img->segments = nsegs;
     71   img->segment_bytes = nsbufs;
     72   img->segment_bytes_cap = nscaps;
     73   return base;
     74 }
     75 
     76 static u32 dyn_alloc_sections(LinkImage* img, u32 nsec) {
     77   Heap* h = img->heap;
     78   u32 base = img->nsections;
     79   u32 new_nsec = base + nsec;
     80   LinkSection* nsections = (LinkSection*)h->realloc(
     81       h, img->sections, sizeof(*img->sections) * img->nsections,
     82       sizeof(*img->sections) * new_nsec, _Alignof(LinkSection));
     83   if (!nsections)
     84     compiler_panic(img->c, SRCLOC_NONE, "link: oom on dyn sections");
     85   img->sections = nsections;
     86   return base;
     87 }
     88 
     89 /* The .dynstr / .gnu.hash byte-builder is the shared ObjByteBuf (objbb_*). */
     90 
     91 /* ---- GNU-hash computation (psABI v1 hash) ----
     92  * Body layout:
     93  *   u32 nbuckets
     94  *   u32 symoffset              (first hashed dynsym index)
     95  *   u32 bloom_size              (in 64-bit words)
     96  *   u32 bloom_shift
     97  *   u64 bloom[bloom_size]
     98  *   u32 buckets[nbuckets]
     99  *   u32 chains[ndynsym - symoffset]
    100  *
    101  * For Phase 4 we keep this very small: nbuckets = max(1, n/2),
    102  * bloom_size = 1, bloom_shift = 6 (64-bit ELFCLASS64). All hashed
    103  * symbols (sym_offset..ndynsym-1) participate in bloom + buckets +
    104  * chains. Slot 0..symoffset-1 are STN_UNDEF + locals, which the
    105  * loader doesn't hash. */
    106 
    107 static u32 gnu_hash_name(const char* s, u32 n) {
    108   /* h = 5381; for c in s: h = h * 33 + c */
    109   u32 h = 5381u;
    110   u32 i;
    111   for (i = 0; i < n; ++i) h = (h * 33u) + (u8)s[i];
    112   return h;
    113 }
    114 
    115 /* ---- partition: enumerate imports ----
    116  *
    117  * Walks LinkSyms and collects each `imported` symbol that's the
    118  * canonical entry in img->globals (resolve_undefs may stamp `imported`
    119  * onto multiple shadow slots of the same name; only the canonical one
    120  * lands in dynsym). The two output arrays are LinkSymIds: funcs first
    121  * (PLT-bound), then data (GOT-bound via GLOB_DAT). */
    122 
    123 typedef struct ImportLists {
    124   LinkSymId* exports;
    125   LinkSymId* funcs;
    126   u32 nfuncs;
    127   LinkSymId* datas;
    128   u32 ndatas;
    129   u32 nexports;
    130 } ImportLists;
    131 
    132 static int sym_is_func_import(const LinkSymbol* s) {
    133   /* Most undef shadows have kind = SK_UNDEF (the obj reader keys kind
    134    * off shndx, not STT_*).  Only useful when the canonical entry
    135    * carried a real type — fall through to the DSO lookup otherwise. */
    136   return s->kind == SK_FUNC || s->kind == SK_IFUNC;
    137 }
    138 
    139 /* Resolve an import's classifier kind by consulting its providing
    140  * DSO's dynsym.  read_elf_dso preserves STT_FUNC / STT_OBJECT / etc.
    141  * on each defined export; the consumer's undef may have arrived as
    142  * SK_UNDEF (clang emits external refs as SHN_UNDEF, which the reader
    143  * collapses to SK_UNDEF regardless of STT_*).  Returns 1 for func /
    144  * ifunc, 0 for everything else (or if the DSO export is missing). */
    145 static int dso_export_is_func(Linker* l, const LinkSymbol* s) {
    146   if (s->dso_input_id == LINK_INPUT_NONE) return 0;
    147   if (s->dso_input_id - 1u >= LinkInputs_count(&l->inputs)) return 0;
    148   LinkInput* in = LinkInputs_at(&l->inputs, s->dso_input_id - 1u);
    149   if (!in->obj) return 0;
    150   ObjSymIter* it = obj_symiter_new(in->obj);
    151   ObjSymEntry e;
    152   int is_func = 0;
    153   while (obj_symiter_next(it, &e)) {
    154     const ObjSym* es = e.sym;
    155     if (!es || es->name != s->name) continue;
    156     if (es->kind == SK_UNDEF) continue;
    157     is_func = (es->kind == SK_FUNC || es->kind == SK_IFUNC);
    158     break;
    159   }
    160   obj_symiter_free(it);
    161   return is_func;
    162 }
    163 
    164 static int import_has_branch_reloc(Linker* l, LinkImage* img,
    165                                    const LinkSymbol* s) {
    166   u32 i;
    167   if (!l || !img || !s || s->name == 0) return 0;
    168   for (i = 0; i < LinkRelocs_count(&img->relocs); ++i) {
    169     const LinkRelocApply* r = LinkRelocs_at(&img->relocs, i);
    170     const LinkSymbol* tgt;
    171     if (!reloc_kind_is_branch(l->c, r->kind)) continue;
    172     if (r->target == LINK_SYM_NONE) continue;
    173     tgt = LinkSyms_at(&img->syms, r->target - 1);
    174     if (!tgt || !tgt->imported || tgt->name != s->name) continue;
    175     return 1;
    176   }
    177   return 0;
    178 }
    179 
    180 static void collect_imports(Linker* l, LinkImage* img, Heap* h,
    181                             ImportLists* il) {
    182   u32 i;
    183   u32 cap_e = 0, cap_f = 0, cap_d = 0;
    184   il->exports = NULL;
    185   il->funcs = NULL;
    186   il->datas = NULL;
    187   il->nexports = il->nfuncs = il->ndatas = 0;
    188   for (i = 0; i < LinkSyms_count(&img->syms); ++i) {
    189     LinkSymbol* s = LinkSyms_at(&img->syms, i);
    190     if (s->name == 0) continue;
    191     /* Only the canonical (img->globals) entry per name. */
    192     LinkSymId canonical = symhash_get(&img->globals, s->name);
    193     if (canonical != LINK_SYM_NONE && canonical != s->id) continue;
    194     if (s->defined && !s->imported &&
    195         (s->bind == SB_GLOBAL || s->bind == SB_WEAK) && s->kind != SK_FILE &&
    196         s->kind != SK_SECTION && s->vis != SV_HIDDEN &&
    197         s->vis != SV_INTERNAL) {
    198       if (VEC_GROW(h, il->exports, cap_e, il->nexports + 1u))
    199         compiler_panic(img->c, SRCLOC_NONE, "link: oom on exports");
    200       il->exports[il->nexports++] = s->id;
    201       continue;
    202     }
    203     if (!s->imported) continue;
    204     int is_func = sym_is_func_import(s) || dso_export_is_func(l, s) ||
    205                   import_has_branch_reloc(l, img, s);
    206     if (is_func) {
    207       if (VEC_GROW(h, il->funcs, cap_f, il->nfuncs + 1u))
    208         compiler_panic(img->c, SRCLOC_NONE, "link: oom on import-funcs");
    209       il->funcs[il->nfuncs++] = s->id;
    210     } else {
    211       if (VEC_GROW(h, il->datas, cap_d, il->ndatas + 1u))
    212         compiler_panic(img->c, SRCLOC_NONE, "link: oom on import-datas");
    213       il->datas[il->ndatas++] = s->id;
    214     }
    215   }
    216 }
    217 
    218 static void free_imports(Heap* h, ImportLists* il) {
    219   if (il->exports) h->free(h, il->exports, sizeof(*il->exports) * il->nexports);
    220   if (il->funcs) h->free(h, il->funcs, sizeof(*il->funcs) * il->nfuncs);
    221   if (il->datas) h->free(h, il->datas, sizeof(*il->datas) * il->ndatas);
    222 }
    223 
    224 /* ---- DT_NEEDED set: each DSO input that contributed at least one
    225  * import. Order is input order so the loader sees deps in declaration
    226  * order. */
    227 static void collect_needed(Linker* l, LinkImage* img, LinkDynState* dyn) {
    228   Heap* h = img->heap;
    229   u8* used;
    230   u32 ninputs = LinkInputs_count(&l->inputs);
    231   u32 i, nused = 0;
    232 
    233   used = (u8*)h->alloc(h, ninputs ? ninputs : 1u, 1);
    234   if (!used) compiler_panic(img->c, SRCLOC_NONE, "link: oom on needed map");
    235   memset(used, 0, ninputs ? ninputs : 1u);
    236 
    237   /* Mark every DSO that ended up satisfying at least one import. */
    238   for (i = 0; i < LinkSyms_count(&img->syms); ++i) {
    239     LinkSymbol* s = LinkSyms_at(&img->syms, i);
    240     if (!s->imported) continue;
    241     if (s->dso_input_id == LINK_INPUT_NONE) continue;
    242     if (s->dso_input_id - 1u >= ninputs) continue;
    243     used[s->dso_input_id - 1u] = 1;
    244   }
    245   /* Always pull every explicitly-supplied DSO into DT_NEEDED, even if
    246    * no import landed on it — matches GNU ld without --as-needed.
    247    * Phase 4 doesn't plumb --as-needed through to the resolver, so the
    248    * default "needed" behavior is the right baseline. */
    249   for (i = 0; i < ninputs; ++i) {
    250     LinkInput* in = LinkInputs_at(&l->inputs, i);
    251     if (in->kind == LINK_INPUT_DSO_BYTES && in->soname != 0) used[i] = 1;
    252   }
    253   for (i = 0; i < ninputs; ++i)
    254     if (used[i]) ++nused;
    255 
    256   dyn->needed =
    257       nused ? (Sym*)h->alloc(h, sizeof(Sym) * nused, _Alignof(Sym)) : NULL;
    258   if (nused && !dyn->needed)
    259     compiler_panic(img->c, SRCLOC_NONE, "link: oom on needed list");
    260   dyn->nneeded = 0;
    261   for (i = 0; i < ninputs; ++i) {
    262     LinkInput* in = LinkInputs_at(&l->inputs, i);
    263     if (!used[i]) continue;
    264     if (in->soname == 0) continue;
    265     dyn->needed[dyn->nneeded++] = in->soname;
    266   }
    267   h->free(h, used, ninputs ? ninputs : 1u);
    268 }
    269 
    270 /* ---- dynsym + dynstr build ----
    271  *
    272  * Slot 0: STN_UNDEF (zero entry). The loader ignores names with index
    273  * 0; we still emit a dynstr entry at offset 0 (the leading NUL).
    274  *
    275  * Slots 1..nexports: executable-defined globals exported for DSO lookup.
    276  * Slots after exports: imported symbols (functions first, then data).
    277  * st_shndx = SHN_UNDEF; the loader fills in the value at bind time.
    278  * st_value/size are zero — the static linker has no value for an
    279  * imported symbol.
    280  *
    281  * Defined executable globals must be present too: ELF DSOs can resolve
    282  * references back to the main executable, and FreeBSD libc depends on that
    283  * for Scrt1.o's `environ` and `__progname` definitions. */
    284 
    285 static void build_dynsym(LinkImage* img, LinkDynState* dyn,
    286                          const ImportLists* il, ObjByteBuf* dynstr) {
    287   Heap* h = img->heap;
    288   u32 nimports = il->nfuncs + il->ndatas;
    289   u32 ndynsym = 1u + il->nexports + nimports; /* +1 for null slot */
    290   u32 i;
    291 
    292   dyn->ndynsym = ndynsym;
    293   dyn->dynsym = (DynSymRec*)h->alloc(h, sizeof(*dyn->dynsym) * ndynsym,
    294                                      _Alignof(DynSymRec));
    295   if (!dyn->dynsym) compiler_panic(img->c, SRCLOC_NONE, "link: oom on dynsym");
    296   memset(dyn->dynsym, 0, sizeof(*dyn->dynsym) * ndynsym);
    297 
    298   /* Slot 0: STN_UNDEF. dynstr leads with a NUL so st_name=0 reads as
    299    * the empty string. */
    300   {
    301     u8 z = 0;
    302     objbb_append(dynstr, &z, 1);
    303   }
    304 
    305   /* Per-symbol: dedupe `sym_dynidx` lookup table. Sized to LinkSymId
    306    * upper bound. Clean (zero-filled) by alloc convention; we set
    307    * indices for imports below. */
    308   dyn->sym_dynidx_size = LinkSyms_count(&img->syms) + 1u;
    309   dyn->sym_dynidx = (u32*)h->alloc(
    310       h, sizeof(*dyn->sym_dynidx) * dyn->sym_dynidx_size, _Alignof(u32));
    311   if (!dyn->sym_dynidx)
    312     compiler_panic(img->c, SRCLOC_NONE, "link: oom on sym_dynidx");
    313   memset(dyn->sym_dynidx, 0, sizeof(*dyn->sym_dynidx) * dyn->sym_dynidx_size);
    314   /* sym_plt_vaddr is populated alongside the PLT body emit below; here
    315    * we only allocate the parallel array. */
    316   dyn->sym_plt_vaddr = (u64*)h->alloc(
    317       h, sizeof(*dyn->sym_plt_vaddr) * dyn->sym_dynidx_size, _Alignof(u64));
    318   if (!dyn->sym_plt_vaddr)
    319     compiler_panic(img->c, SRCLOC_NONE, "link: oom on sym_plt_vaddr");
    320   memset(dyn->sym_plt_vaddr, 0,
    321          sizeof(*dyn->sym_plt_vaddr) * dyn->sym_dynidx_size);
    322 
    323   /* All dynamic entries we emit today are non-local, so first_global is
    324    * right after the single STN_UNDEF slot. */
    325   dyn->first_global = 1u;
    326 
    327   u32 idx = 1u;
    328   for (i = 0; i < il->nexports; ++i) {
    329     LinkSymId lsid = il->exports[i];
    330     LinkSymbol* s = LinkSyms_at(&img->syms, lsid - 1);
    331     DynSymRec* r = &dyn->dynsym[idx];
    332     Slice nm_s = pool_slice(img->c->global, s->name);
    333     const char* nm = nm_s.s;
    334     size_t namelen = nm_s.len;
    335     u8 elf_type = elf_st_type(s->kind);
    336     u8 elf_bind = elf_st_bind(s->bind);
    337     r->st_name = objbb_append_str(dynstr, nm, (u32)namelen);
    338     r->st_info = ELF64_ST_INFO(elf_bind, elf_type);
    339     r->st_other = elf_st_other(s->vis);
    340     /* The emitter refreshes defined-symbol values after the final header
    341      * shift. Any nonzero, non-special section index is enough for rtld to
    342      * treat the symbol as defined; section headers are not part of runtime
    343      * loading. */
    344     r->st_shndx = 1;
    345     r->st_value = s->vaddr;
    346     r->st_size = s->size;
    347     dyn->sym_dynidx[lsid] = idx;
    348     ++idx;
    349   }
    350   for (i = 0; i < il->nfuncs; ++i) {
    351     LinkSymId lsid = il->funcs[i];
    352     LinkSymbol* s = LinkSyms_at(&img->syms, lsid - 1);
    353     DynSymRec* r = &dyn->dynsym[idx];
    354     Slice nm_s = pool_slice(img->c->global, s->name);
    355     const char* nm = nm_s.s;
    356     size_t namelen = nm_s.len;
    357     r->st_name = objbb_append_str(dynstr, nm, (u32)namelen);
    358     r->st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
    359     r->st_other = elf_st_other(s->vis);
    360     r->st_shndx = SHN_UNDEF;
    361     r->st_value = 0;
    362     r->st_size = 0;
    363     dyn->sym_dynidx[lsid] = idx;
    364     ++idx;
    365   }
    366   for (i = 0; i < il->ndatas; ++i) {
    367     LinkSymId lsid = il->datas[i];
    368     LinkSymbol* s = LinkSyms_at(&img->syms, lsid - 1);
    369     DynSymRec* r = &dyn->dynsym[idx];
    370     Slice nm_s = pool_slice(img->c->global, s->name);
    371     const char* nm = nm_s.s;
    372     size_t namelen = nm_s.len;
    373     u8 elf_type = STT_OBJECT;
    374     if (s->kind == SK_TLS)
    375       elf_type = STT_TLS;
    376     else if (s->kind == SK_NOTYPE)
    377       elf_type = STT_NOTYPE;
    378     r->st_name = objbb_append_str(dynstr, nm, (u32)namelen);
    379     r->st_info = ELF64_ST_INFO(STB_GLOBAL, elf_type);
    380     r->st_other = elf_st_other(s->vis);
    381     r->st_shndx = SHN_UNDEF;
    382     r->st_value = 0;
    383     r->st_size = 0;
    384     dyn->sym_dynidx[lsid] = idx;
    385     ++idx;
    386   }
    387 }
    388 
    389 /* ---- GNU symbol versioning (.gnu.version + .gnu.version_r) ----
    390  *
    391  * For each imported symbol that binds to a versioned DSO export, require that
    392  * export's *default* version (read into ObjImageSym.version at input time) so
    393  * the runtime binds the right one. On FreeBSD this is mandatory: the INO64
    394  * transition left `stat`/`fstat`/... as two incompatible struct-stat ABIs, the
    395  * compat behind a hidden FBSD_1.0 and the modern one as the default FBSD_1.5;
    396  * an unversioned reference binds the compat and reads st_size at the wrong
    397  * offset. We emit:
    398  *   .gnu.version   — one u16 per .dynsym entry: 0 (null/unversioned import),
    399  *                    1 (defined export), or >=2 (a version requirement index).
    400  *   .gnu.version_r — Verneed per DT_NEEDED soname + Vernaux per required
    401  *                    version, numbered 2.. in first-seen order.
    402  * Both reference only .dynstr offsets and indices (no vaddrs), so the bytes are
    403  * final at layout time. Nothing is emitted when no import is versioned, leaving
    404  * musl/glibc-without-version and static links byte-for-byte unchanged. */
    405 
    406 static u32 elf_sysv_hash(const char* s, u32 n) {
    407   u32 h = 0, g, i;
    408   for (i = 0; i < n; ++i) {
    409     h = (h << 4) + (u8)s[i];
    410     g = h & 0xf0000000u;
    411     if (g) h ^= g >> 24;
    412     h &= ~g;
    413   }
    414   return h;
    415 }
    416 
    417 /* Default version name the DSO `in` exports for `name`, or 0 if `in` carries no
    418  * versioning / doesn't export `name` with a default version. */
    419 static Sym dso_default_version(LinkInput* in, Sym name) {
    420   const ObjImage* im = in->obj ? obj_image(in->obj) : NULL;
    421   u32 i, n;
    422   if (!im) return 0;
    423   n = obj_image_ndynsyms(im);
    424   for (i = 0; i < n; ++i) {
    425     const ObjImageSym* s = obj_image_dynsym(im, i);
    426     if (s->name == name && s->version != 0 && !s->version_hidden)
    427       return s->version;
    428   }
    429   return 0;
    430 }
    431 
    432 typedef struct VerReq {
    433   Sym soname;
    434   Sym version;
    435   u16 index;
    436 } VerReq;
    437 
    438 typedef struct VerBuild {
    439   Heap* h;
    440   Linker* l;
    441   LinkImage* img;
    442   LinkDynState* dyn;
    443   u8* vs; /* versym bytes being filled */
    444   VerReq* reqs;
    445   u32 nreq;
    446   u32 capreq;
    447 } VerBuild;
    448 
    449 /* Resolve one imported symbol's version requirement: explicit name@VERSION
    450  * imports keep that requested version on LinkSymbol; plain imports use the
    451  * providing DSO's default version for the name. The chosen (soname, version)
    452  * pair is interned as a requirement and stamped into the symbol's versym slot. */
    453 static void ver_process_import(VerBuild* vb, LinkSymId lsid) {
    454   LinkSymbol* s = LinkSyms_at(&vb->img->syms, lsid - 1);
    455   u32 di = vb->dyn->sym_dynidx[lsid];
    456   LinkInput* in;
    457   Sym ver;
    458   u16 vidx = 0;
    459   u32 r;
    460   if (!di || s->dso_input_id == LINK_INPUT_NONE) return;
    461   if (s->dso_input_id - 1u >= LinkInputs_count(&vb->l->inputs)) return;
    462   in = LinkInputs_at(&vb->l->inputs, s->dso_input_id - 1u);
    463   if (in->soname == 0) return;
    464   ver = s->elf_version ? s->elf_version : dso_default_version(in, s->name);
    465   if (ver == 0) return;
    466   for (r = 0; r < vb->nreq; ++r)
    467     if (vb->reqs[r].soname == in->soname && vb->reqs[r].version == ver) {
    468       vidx = vb->reqs[r].index;
    469       break;
    470     }
    471   if (!vidx) {
    472     if (VEC_GROW(vb->h, vb->reqs, vb->capreq, vb->nreq + 1u))
    473       compiler_panic(vb->img->c, SRCLOC_NONE, "link: oom on version reqs");
    474     vidx = (u16)(2u + vb->nreq);
    475     vb->reqs[vb->nreq].soname = in->soname;
    476     vb->reqs[vb->nreq].version = ver;
    477     vb->reqs[vb->nreq].index = vidx;
    478     vb->nreq++;
    479   }
    480   wr_u16_le(vb->vs + (u64)di * 2u, vidx);
    481 }
    482 
    483 static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn,
    484                            const ImportLists* il, ObjByteBuf* dynstr) {
    485   Heap* h = img->heap;
    486   VerBuild vb;
    487   u32 i;
    488 
    489   dyn->versym = NULL;
    490   dyn->versym_len = 0;
    491   dyn->verneed = NULL;
    492   dyn->verneed_len = 0;
    493   dyn->nverneed = 0;
    494   if (dyn->ndynsym == 0) return;
    495 
    496   /* versym: default 0 (local/unversioned); defined exports -> GLOBAL. */
    497   vb.h = h;
    498   vb.l = l;
    499   vb.img = img;
    500   vb.dyn = dyn;
    501   vb.reqs = NULL;
    502   vb.nreq = 0;
    503   vb.capreq = 0;
    504   vb.vs = (u8*)h->alloc(h, (size_t)dyn->ndynsym * 2u, 2);
    505   if (!vb.vs) compiler_panic(img->c, SRCLOC_NONE, "link: oom on versym");
    506   memset(vb.vs, 0, (size_t)dyn->ndynsym * 2u);
    507   for (i = 0; i < il->nexports; ++i) {
    508     u32 di = dyn->sym_dynidx[il->exports[i]];
    509     if (di) wr_u16_le(vb.vs + (u64)di * 2u, (u16)VER_NDX_GLOBAL);
    510   }
    511   for (i = 0; i < il->nfuncs; ++i) ver_process_import(&vb, il->funcs[i]);
    512   for (i = 0; i < il->ndatas; ++i) ver_process_import(&vb, il->datas[i]);
    513 
    514   if (vb.nreq == 0) {
    515     /* No versioned imports: emit nothing, keep the link unchanged. */
    516     h->free(h, vb.vs, (size_t)dyn->ndynsym * 2u);
    517     if (vb.reqs) h->free(h, vb.reqs, sizeof(*vb.reqs) * vb.capreq);
    518     return;
    519   }
    520   dyn->versym = vb.vs;
    521   dyn->versym_len = dyn->ndynsym * 2u;
    522 
    523   /* Group requirements by soname (first-seen order) into Verneed/Vernaux. */
    524   {
    525     Sym* sonames = NULL;
    526     u32 nson = 0, capson = 0;
    527     u32 r;
    528     for (r = 0; r < vb.nreq; ++r) {
    529       u32 k;
    530       int seen = 0;
    531       for (k = 0; k < nson; ++k)
    532         if (sonames[k] == vb.reqs[r].soname) {
    533           seen = 1;
    534           break;
    535         }
    536       if (!seen) {
    537         if (VEC_GROW(h, sonames, capson, nson + 1u))
    538           compiler_panic(img->c, SRCLOC_NONE, "link: oom on verneed sonames");
    539         sonames[nson++] = vb.reqs[r].soname;
    540       }
    541     }
    542     {
    543       u32 total =
    544           nson * (u32)ELF_VERNEED_SIZE + vb.nreq * (u32)ELF_VERNAUX_SIZE;
    545       u8* vn = (u8*)h->alloc(h, total, 4);
    546       u8* p;
    547       u32 si;
    548       if (!vn) compiler_panic(img->c, SRCLOC_NONE, "link: oom on verneed");
    549       memset(vn, 0, total);
    550       p = vn;
    551       for (si = 0; si < nson; ++si) {
    552         Slice so_s = pool_slice(l->c->global, sonames[si]);
    553         u32 file_off = objbb_append_str(dynstr, so_s.s, (u32)so_s.len);
    554         u8* vn_rec = p;
    555         u32 cnt = 0;
    556         u8* aux;
    557         p += ELF_VERNEED_SIZE;
    558         aux = p;
    559         for (r = 0; r < vb.nreq; ++r) {
    560           Slice ver_s;
    561           u32 name_off;
    562           if (vb.reqs[r].soname != sonames[si]) continue;
    563           ver_s = pool_slice(l->c->global, vb.reqs[r].version);
    564           name_off = objbb_append_str(dynstr, ver_s.s, (u32)ver_s.len);
    565           wr_u32_le(p + 0,
    566                     elf_sysv_hash(ver_s.s, (u32)ver_s.len)); /* vna_hash */
    567           wr_u16_le(p + 4, 0);                               /* vna_flags */
    568           wr_u16_le(p + 6, vb.reqs[r].index);                /* vna_other */
    569           wr_u32_le(p + 8, name_off);                        /* vna_name */
    570           /* vna_next: filled after we know if another aux follows. */
    571           p += ELF_VERNAUX_SIZE;
    572           ++cnt;
    573         }
    574         /* Verneed header. vn_aux is the byte offset to the first Vernaux. */
    575         wr_u16_le(vn_rec + 0, 1);                   /* vn_version */
    576         wr_u16_le(vn_rec + 2, (u16)cnt);            /* vn_cnt */
    577         wr_u32_le(vn_rec + 4, file_off);            /* vn_file */
    578         wr_u32_le(vn_rec + 8, (u32)(aux - vn_rec)); /* vn_aux */
    579         wr_u32_le(vn_rec + 12,
    580                   si + 1u < nson ? (u32)(p - vn_rec) : 0u); /* vn_next */
    581         /* Link the Vernaux chain (each entry -> next, last -> 0). */
    582         {
    583           u8* a = aux;
    584           u32 j;
    585           for (j = 0; j < cnt; ++j) {
    586             wr_u32_le(a + 12, j + 1u < cnt ? (u32)ELF_VERNAUX_SIZE : 0u);
    587             a += ELF_VERNAUX_SIZE;
    588           }
    589         }
    590       }
    591       dyn->verneed = vn;
    592       dyn->verneed_len = total;
    593       dyn->nverneed = nson;
    594     }
    595     if (sonames) h->free(h, sonames, sizeof(*sonames) * capson);
    596   }
    597   if (vb.reqs) h->free(h, vb.reqs, sizeof(*vb.reqs) * vb.capreq);
    598 }
    599 
    600 /* ---- .gnu.hash builder ----
    601  *
    602  * Hashed range is [first_global, ndynsym) — slot 0 (STN_UNDEF) is
    603  * unhashed. Layout matches loader expectations (musl, glibc, FreeBSD).
    604  *
    605  * Bucket count: one. That keeps the required chain ordering trivial even as
    606  * we mix executable exports and imports without sorting the dynsym table by
    607  * hash bucket. Bloom is 1 word for Phase 4 — a real implementation would
    608  * scale with hashed_count, but 1 word with shift=6 still satisfies the
    609  * loader's correctness check (false positives only cost a chain scan). */
    610 
    611 static void build_gnu_hash(Heap* h, LinkImage* img, LinkDynState* dyn,
    612                            const ObjByteBuf* dynstr) {
    613   u32 hashed = (dyn->ndynsym > dyn->first_global)
    614                    ? (dyn->ndynsym - dyn->first_global)
    615                    : 0u;
    616   u32 nbuckets = 1u;
    617   u32 bloom_size = 1u; /* 64-bit word */
    618   u32 bloom_shift = 6u;
    619   u32 sym_offset = dyn->first_global;
    620   u32 hdr_bytes = 16u; /* nbuckets/symoff/bloomsz/bloomshift */
    621   u32 bloom_bytes = bloom_size * 8u;
    622   u32 buckets_bytes = nbuckets * 4u;
    623   u32 chains_bytes = hashed * 4u;
    624   u32 total = hdr_bytes + bloom_bytes + buckets_bytes + chains_bytes;
    625 
    626   u8* buf = (u8*)h->alloc(h, total ? total : 1u, 4);
    627   if (!buf) compiler_panic(img->c, SRCLOC_NONE, "link: oom on .gnu.hash");
    628   memset(buf, 0, total);
    629 
    630   wr_u32_le(buf + 0, nbuckets);
    631   wr_u32_le(buf + 4, sym_offset);
    632   wr_u32_le(buf + 8, bloom_size);
    633   wr_u32_le(buf + 12, bloom_shift);
    634 
    635   /* Bloom + buckets + chains. We need each hashed symbol's hash. */
    636   if (hashed) {
    637     u32 i;
    638     u32* hashes = (u32*)h->alloc(h, sizeof(u32) * hashed, _Alignof(u32));
    639     if (!hashes)
    640       compiler_panic(img->c, SRCLOC_NONE, "link: oom on .gnu.hash hashes");
    641     for (i = 0; i < hashed; ++i) {
    642       const DynSymRec* r = &dyn->dynsym[sym_offset + i];
    643       const char* name = (const char*)dynstr->data + r->st_name;
    644       size_t n = name ? slice_from_cstr(name).len : 0;
    645       hashes[i] = gnu_hash_name(name, (u32)n);
    646     }
    647 
    648     /* Bloom filter: H[i] / H[i] >> shift */
    649     u64 bloom = 0;
    650     for (i = 0; i < hashed; ++i) {
    651       u32 h1 = hashes[i] % 64u;
    652       u32 h2 = (hashes[i] >> bloom_shift) % 64u;
    653       bloom |= ((u64)1 << h1) | ((u64)1 << h2);
    654     }
    655     wr_u64_le(buf + hdr_bytes, bloom);
    656 
    657     /* Buckets/chains: for each hashed sym, append to its bucket's
    658      * chain. The chain encodes (hash & ~1) per entry; the LSB is set
    659      * on the LAST entry in a bucket to terminate. Buckets are filled
    660      * with the first chain index that hashes there (1-based into the
    661      * dynsym, i.e. `sym_offset + i`). */
    662     u32* buckets = (u32*)(buf + hdr_bytes + bloom_bytes);
    663     u32* chains = (u32*)(buf + hdr_bytes + bloom_bytes + buckets_bytes);
    664     /* First pass: bucket = first sym index that hashes there. */
    665     for (i = 0; i < hashed; ++i) {
    666       u32 b = hashes[i] % nbuckets;
    667       if (buckets[b] == 0) buckets[b] = sym_offset + i;
    668     }
    669     /* Second pass: chain[i] = hash with LSB cleared; LSB set if next
    670      * sym is in a different bucket. Walk symbols in order; LSB on
    671      * chain[i] when sym i+1 is in a different bucket OR is the end. */
    672     for (i = 0; i < hashed; ++i) {
    673       u32 v = hashes[i] & ~1u;
    674       int last = (i + 1 == hashed) ||
    675                  ((hashes[i + 1] % nbuckets) != (hashes[i] % nbuckets));
    676       if (last) v |= 1u;
    677       chains[i] = v;
    678     }
    679     h->free(h, hashes, sizeof(u32) * hashed);
    680   }
    681 
    682   dyn->gnu_hash = buf;
    683   dyn->gnu_hash_len = total;
    684 }
    685 
    686 /* ---- .dynamic body builder ----
    687  *
    688  * Computed at layout time so the size is known before segments are
    689  * placed. Each entry is two u64s (d_tag, d_un.d_val|d_un.d_ptr).
    690  * Final entry is DT_NULL. The d_ptr fields that point at other
    691  * synthetic sections are filled with image-relative vaddrs; the emit
    692  * pass adds load-base / IMAGE_BASE only when ET_EXEC. */
    693 
    694 typedef struct DynEntry {
    695   u64 tag;
    696   u64 val; /* either d_val or d_ptr; emit just writes 8 bytes */
    697 } DynEntry;
    698 
    699 static u32 count_dynamic_entries(const LinkDynState* dyn) {
    700   /* Required: DT_STRTAB DT_STRSZ DT_SYMTAB DT_SYMENT DT_GNU_HASH
    701    *           DT_FLAGS_1 (DF_1_NOW for eager binding)
    702    *           DT_NULL terminator
    703    * Optional (only when there are .rela.dyn records):
    704    *           DT_RELA DT_RELASZ DT_RELAENT
    705    * Optional (only when there are imported functions / a PLT):
    706    *           DT_PLTGOT DT_PLTRELSZ DT_PLTREL DT_JMPREL
    707    * Plus DT_NEEDED per dependency. */
    708   u32 n = dyn->nneeded;
    709   n += 7;                        /* 5 fixed + DT_FLAGS_1 + DT_NULL */
    710   if (dyn->soname) n += 1;
    711   n += dyn->nrpaths + dyn->nrunpaths;
    712   if (dyn->cap_rela_dyn) n += 3; /* DT_RELA + DT_RELASZ + DT_RELAENT */
    713   if (dyn->nrela_plt) n += 4;    /* PLT-only entries */
    714   if (dyn->nverneed) n += 3;     /* DT_VERSYM + DT_VERNEED + DT_VERNEEDNUM */
    715   return n;
    716 }
    717 
    718 /* ---- main entry ---- */
    719 
    720 void layout_dyn(Linker* l, LinkImage* img) {
    721   Heap* h = img->heap;
    722   LinkDynState* dyn;
    723   LinkDynState dyn_probe;
    724   ImportLists imports;
    725   ObjByteBuf dynstr;
    726   u64 page;
    727   const LinkArchDesc* arch;
    728   const ObjElfArchOps* elf_arch;
    729 
    730   if (!l->emit_pie && !l->emit_shared) return;
    731 
    732   /* The dynamic-section layout below is ELF64-only (Elf64_Sym/Dyn/Rela wire
    733    * sizes, 8-byte GOT slots). rv32 is a static-only v1 target, so a dynamic /
    734    * PIE rv32 link is unsupported — fail with a clear diagnostic instead of
    735    * crashing on the ELF64 assumptions. Link rv32 images statically (kit ld
    736    * -no-pie, or a -T script for bare-metal section placement). */
    737   if (img->c->target.ptr_size == 4u) {
    738     compiler_panic(img->c, SRCLOC_NONE,
    739                    "link: dynamic/PIE linking is not supported for 32-bit "
    740                    "RISC-V (ELFCLASS32); link statically (kit ld -no-pie)");
    741   }
    742 
    743   arch = link_arch_desc_for(l->c);
    744   if (!arch)
    745     compiler_panic(img->c, SRCLOC_NONE, "link: layout_dyn: no arch descriptor");
    746   {
    747     const ObjFormatImpl* fmt = obj_format_lookup(KIT_OBJ_ELF);
    748     elf_arch = fmt && fmt->elf_arch ? fmt->elf_arch(l->c->target.arch) : NULL;
    749     if (!elf_arch)
    750       compiler_panic(img->c, SRCLOC_NONE,
    751                      "link: layout_dyn: no ELF arch descriptor");
    752   }
    753 
    754   /* Step 1: enumerate imports + DT_NEEDED. A PIE with no imports and no
    755    * DSO inputs is effectively static; keep ET_DYN output but do not stamp
    756    * PT_INTERP/PT_DYNAMIC or an empty .dynamic section. */
    757   memset(&dyn_probe, 0, sizeof dyn_probe);
    758   collect_imports(l, img, h, &imports);
    759   collect_needed(l, img, &dyn_probe);
    760   if (!l->emit_shared && l->emit_static_exe && imports.nfuncs == 0 &&
    761       imports.ndatas == 0 &&
    762       dyn_probe.nneeded == 0) {
    763     img->pie = 1;
    764     free_imports(h, &imports);
    765     return;
    766   }
    767 
    768   dyn = (LinkDynState*)h->alloc(h, sizeof(*dyn), _Alignof(LinkDynState));
    769   if (!dyn) compiler_panic(img->c, SRCLOC_NONE, "link: oom on dyn state");
    770   *dyn = dyn_probe;
    771   img->dyn = dyn;
    772   img->pie = l->emit_pie ? 1 : 0;
    773   img->shared = l->emit_shared ? 1 : 0;
    774 
    775   /* PT_INTERP path. Default to the canonical musl loader matching the
    776    * target arch (per-arch table in src/arch/<arch>/link.c) when the caller
    777    * didn't set one. Drivers like kit-cc always override via
    778    * link_set_interp_path; this default is correctness for direct
    779    * libkit consumers.  glibc users have to set their interp
    780    * explicitly — we don't pick a default for them. */
    781   if (!l->emit_shared) {
    782     dyn->interp_path =
    783         l->interp_path
    784             ? l->interp_path
    785             : pool_intern_slice(l->c->global,
    786                                 slice_from_cstr(elf_arch->default_musl_interp));
    787   }
    788   if (l->soname.s && l->soname.len)
    789     dyn->soname = pool_intern_slice(l->c->global, l->soname);
    790   if (l->nrpaths) {
    791     u32 ri;
    792     dyn->rpaths =
    793         (Sym*)h->alloc(h, sizeof(*dyn->rpaths) * l->nrpaths, _Alignof(Sym));
    794     if (!dyn->rpaths) compiler_panic(img->c, SRCLOC_NONE, "link: oom on rpaths");
    795     dyn->nrpaths = l->nrpaths;
    796     for (ri = 0; ri < l->nrpaths; ++ri)
    797       dyn->rpaths[ri] = pool_intern_slice(l->c->global, l->rpaths[ri]);
    798   }
    799   if (l->nrunpaths) {
    800     u32 ri;
    801     dyn->runpaths = (Sym*)h->alloc(h, sizeof(*dyn->runpaths) * l->nrunpaths,
    802                                    _Alignof(Sym));
    803     if (!dyn->runpaths)
    804       compiler_panic(img->c, SRCLOC_NONE, "link: oom on runpaths");
    805     dyn->nrunpaths = l->nrunpaths;
    806     for (ri = 0; ri < l->nrunpaths; ++ri)
    807       dyn->runpaths[ri] = pool_intern_slice(l->c->global, l->runpaths[ri]);
    808   }
    809 
    810   /* Step 2: build .dynstr + .dynsym. .dynstr must also carry the
    811    * DT_NEEDED soname strings the .dynamic body references; intern
    812    * them after the import names so build_dynsym's de-dup also covers
    813    * any name that happens to collide with a soname. */
    814   objbb_init(&dynstr, h);
    815   build_dynsym(img, dyn, &imports, &dynstr);
    816   {
    817     u32 ni;
    818     for (ni = 0; ni < dyn->nneeded; ++ni) {
    819       Slice s_s = pool_slice(l->c->global, dyn->needed[ni]);
    820       const char* s = s_s.s;
    821       size_t slen = s_s.len;
    822       if (s && slen) (void)objbb_append_str(&dynstr, s, (u32)slen);
    823     }
    824     if (dyn->soname) {
    825       Slice s_s = pool_slice(l->c->global, dyn->soname);
    826       if (s_s.s && s_s.len)
    827         (void)objbb_append_str(&dynstr, s_s.s, (u32)s_s.len);
    828     }
    829     for (ni = 0; ni < dyn->nrpaths; ++ni) {
    830       Slice s_s = pool_slice(l->c->global, dyn->rpaths[ni]);
    831       if (s_s.s && s_s.len)
    832         (void)objbb_append_str(&dynstr, s_s.s, (u32)s_s.len);
    833     }
    834     for (ni = 0; ni < dyn->nrunpaths; ++ni) {
    835       Slice s_s = pool_slice(l->c->global, dyn->runpaths[ni]);
    836       if (s_s.s && s_s.len)
    837         (void)objbb_append_str(&dynstr, s_s.s, (u32)s_s.len);
    838     }
    839   }
    840   /* Symbol versioning: assign per-import version requirements and append the
    841    * version strings ("FBSD_1.5", ...) to .dynstr. Must run before .dynstr is
    842    * finalized below; emits nothing when no import is versioned. */
    843   build_versions(l, img, dyn, &imports, &dynstr);
    844   dyn->dynstr = dynstr.data;
    845   dyn->dynstr_len = dynstr.len;
    846 
    847   /* Step 3: .gnu.hash. */
    848   build_gnu_hash(h, img, dyn, &dynstr);
    849 
    850   /* Step 4: pre-size all the synthetic sections.
    851    * .interp:      strlen + 1
    852    * .dynsym:      24 * ndynsym
    853    * .dynstr:      dynstr_len
    854    * .gnu.hash:    gnu_hash_len
    855    * .rela.dyn:    24 * (runtime GLOB_DAT + RELATIVE records)
    856    * .rela.plt:    24 * nfuncs
    857    * .plt:         32 + 16 * nfuncs   (PLT0 + per-slot)
    858    * .got.plt:     8 * (3 + nfuncs)
    859    * .dynamic:     16 * count_dynamic_entries
    860    */
    861   dyn->nplt = imports.nfuncs;
    862   dyn->nrela_plt = imports.nfuncs;
    863   dyn->rela_plt = imports.nfuncs
    864                       ? (DynRela*)h->alloc(h, sizeof(DynRela) * imports.nfuncs,
    865                                            _Alignof(DynRela))
    866                       : NULL;
    867   if (imports.nfuncs && !dyn->rela_plt)
    868     compiler_panic(img->c, SRCLOC_NONE, "link: oom on rela_plt");
    869 
    870   /* RELA dyn: GLOB_DAT (one per imported abs-relocated symbol) +
    871    * RELATIVE (one per PIE internal abs reloc against a defined sym).
    872    * Phase 5 emits these dynamically during reloc-apply; pre-count the
    873    * exact total here (img->relocs and the resolve-time `imported` flags
    874    * are already settled by the time layout_dyn runs) so the section
    875    * isn't padded with hundreds of trailing R_*_NONE records. */
    876   u32 cap_rel = 0;
    877   {
    878     u32 ri;
    879     for (ri = 0; ri < LinkRelocs_count(&img->relocs); ++ri) {
    880       const LinkRelocApply* r = LinkRelocs_at(&img->relocs, ri);
    881       const LinkSymbol* tgt = LinkSyms_at(&img->syms, r->target - 1);
    882       const LinkSection* sec;
    883       if (r->kind != R_ABS32 && r->kind != R_ABS64) continue;
    884       if (r->link_section_id == LINK_SEC_NONE ||
    885           r->link_section_id > img->nsections)
    886         continue;
    887       sec = &img->sections[r->link_section_id - 1];
    888       if (sec->segment_id == LINK_SEG_NONE || sec->file_only) continue;
    889       if (tgt->imported) {
    890         cap_rel++; /* GLOB_DAT */
    891       } else if (tgt->defined && tgt->kind != SK_ABS) {
    892         cap_rel++; /* RELATIVE */
    893       }
    894     }
    895   }
    896   dyn->cap_rela_dyn = cap_rel;
    897   dyn->rela_dyn =
    898       dyn->cap_rela_dyn
    899           ? (DynRela*)h->alloc(h, sizeof(DynRela) * dyn->cap_rela_dyn,
    900                                _Alignof(DynRela))
    901           : NULL;
    902   if (dyn->cap_rela_dyn && !dyn->rela_dyn)
    903     compiler_panic(img->c, SRCLOC_NONE, "link: oom on rela_dyn");
    904   dyn->nrela_dyn = 0;
    905 
    906   Slice interp_s = l->emit_shared ? SLICE_NULL
    907                                   : pool_slice(l->c->global, dyn->interp_path);
    908   const char* interp_str = interp_s.s;
    909   size_t namelen = interp_s.len;
    910   u64 interp_bytes = l->emit_shared ? 0u : (u64)namelen + 1u;
    911   u64 dynsym_bytes = (u64)dyn->ndynsym * ELF64_SYM_SIZE;
    912   u64 dynstr_bytes = (u64)dyn->dynstr_len;
    913   u64 gnuhash_bytes = (u64)dyn->gnu_hash_len;
    914   int has_ver = dyn->nverneed > 0;
    915   u64 versym_bytes = (u64)dyn->versym_len;
    916   u64 verneed_bytes = (u64)dyn->verneed_len;
    917   /* rela.dyn is pre-counted exactly; rela.plt is one record per PLT slot. */
    918   u64 rela_dyn_bytes = (u64)dyn->cap_rela_dyn * ELF64_RELA_SIZE;
    919   u64 rela_plt_bytes = (u64)dyn->nrela_plt * ELF64_RELA_SIZE;
    920   u64 plt_bytes =
    921       (u64)(imports.nfuncs
    922                 ? arch->plt0_size + arch->plt_entry_size * imports.nfuncs
    923                 : 0u);
    924   u64 gotplt_bytes = (u64)(imports.nfuncs ? 8u * (3u + imports.nfuncs) : 0u);
    925   dyn->ndyn_entries = count_dynamic_entries(dyn);
    926   u64 dynamic_bytes = (u64)dyn->ndyn_entries * ELF64_DYN_SIZE;
    927 
    928   /* Step 5: place segments, page-aligned after the existing image
    929    * span. New segments:
    930    *   ro_seg   (PF_R)  — .interp + .dynsym + .dynstr + .gnu.hash +
    931    *                       .rela.dyn + .rela.plt
    932    *   rx_seg   (PF_R+X)— .plt              (only when imports.nfuncs > 0)
    933    *   rw_seg   (PF_R+W)— .got.plt + .dynamic
    934    *
    935    * .dynamic lives in rw_seg because glibc's loader patches DT_*
    936    * d_un.d_ptr fields in-place at startup (elf_get_dynamic_info
    937    * adjusts STRTAB/SYMTAB/etc. by l_addr); a PF_R-only segment
    938    * causes SEGV_ACCERR. musl's loader doesn't do this rewrite, but
    939    * the RW placement is conventional and works for both.
    940    */
    941   page = 0x4000u; /* keep aligned with layout_page_size default */
    942   {
    943     /* Read the page size from layout_page_size by re-using the
    944      * configured execmem if present — duplicates the helper rather
    945      * than expose it; the value is only used for alignment. */
    946     const KitExecMem* m = (l && l->jit_host) ? l->jit_host->execmem : NULL;
    947     if (m && m->page_size) page = (u64)m->page_size;
    948   }
    949 
    950   u64 base_vaddr = 0;
    951   u32 i;
    952   for (i = 0; i < img->nsegments; ++i) {
    953     u64 end = img->segments[i].vaddr + img->segments[i].mem_size;
    954     if (end > base_vaddr) base_vaddr = end;
    955   }
    956   base_vaddr = ALIGN_UP(base_vaddr, page);
    957 
    958   /* Pack ro section offsets (relative to ro_seg.vaddr). 8-byte
    959    * alignment for tables; 4-byte for .interp string. */
    960   u64 off = 0;
    961   u64 interp_off = off;
    962   off = ALIGN_UP(off + interp_bytes, 8u);
    963   u64 dynsym_off = off;
    964   off = ALIGN_UP(off + dynsym_bytes, 8u);
    965   u64 dynstr_off = off;
    966   off = ALIGN_UP(off + dynstr_bytes, 8u);
    967   u64 gnuhash_off = off;
    968   off = ALIGN_UP(off + gnuhash_bytes, 8u);
    969   u64 rela_dyn_off = off;
    970   off = ALIGN_UP(off + rela_dyn_bytes, 8u);
    971   u64 rela_plt_off = off;
    972   off = ALIGN_UP(off + rela_plt_bytes, 8u);
    973   /* .gnu.version + .gnu.version_r (zero-sized and skipped when no import is
    974    * versioned, so the ro segment is unchanged for unversioned links). */
    975   u64 versym_off = off;
    976   off = ALIGN_UP(off + versym_bytes, 8u);
    977   u64 verneed_off = off;
    978   off = ALIGN_UP(off + verneed_bytes, 8u);
    979   u64 ro_seg_size = off;
    980 
    981   /* When no PLT is needed, suppress the RX/.plt segment entirely. */
    982   int has_plt = imports.nfuncs > 0;
    983 
    984   /* Pack rw_seg offsets: .got.plt (when has_plt) followed by .dynamic. */
    985   u64 rw_off = 0;
    986   u64 gotplt_off = rw_off;
    987   if (has_plt) rw_off = ALIGN_UP(rw_off + gotplt_bytes, 8u);
    988   u64 dynamic_off = rw_off;
    989   rw_off = ALIGN_UP(rw_off + dynamic_bytes, 8u);
    990   u64 rw_seg_size = rw_off;
    991 
    992   u64 ro_vaddr = base_vaddr;
    993   u64 rx_vaddr = ALIGN_UP(ro_vaddr + ro_seg_size, page);
    994   u64 rw_vaddr = ALIGN_UP(rx_vaddr + (has_plt ? plt_bytes : 0u), page);
    995 
    996   /* rw_seg always exists (it carries .dynamic). */
    997   u32 nseg = 2u + (has_plt ? 1u : 0u);
    998   u32 seg_base = dyn_alloc_segments(img, nseg);
    999   u32 ro_seg_idx = seg_base + 0u;
   1000   u32 rx_seg_idx = has_plt ? seg_base + 1u : 0u;
   1001   u32 rw_seg_idx = seg_base + (has_plt ? 2u : 1u);
   1002 
   1003   LinkSegment* ro_seg = &img->segments[ro_seg_idx];
   1004   memset(ro_seg, 0, sizeof(*ro_seg));
   1005   ro_seg->id = (LinkSegmentId)(ro_seg_idx + 1u);
   1006   ro_seg->flags = SF_ALLOC; /* PF_R */
   1007   ro_seg->file_offset = ro_vaddr;
   1008   ro_seg->vaddr = ro_vaddr;
   1009   ro_seg->paddr = ro_vaddr;
   1010   ro_seg->file_size = ro_seg_size;
   1011   ro_seg->mem_size = ro_seg_size;
   1012   ro_seg->align = (u32)page;
   1013   ro_seg->nsections = (l->emit_shared ? 5u : 6u) + (has_ver ? 2u : 0u);
   1014   img->segment_bytes[ro_seg_idx] =
   1015       ro_seg_size ? (u8*)h->alloc(h, (size_t)ro_seg_size, 16) : NULL;
   1016   img->segment_bytes_cap[ro_seg_idx] = (size_t)ro_seg_size;
   1017   if (ro_seg_size && !img->segment_bytes[ro_seg_idx])
   1018     compiler_panic(img->c, SRCLOC_NONE, "link: oom on ro dyn segment");
   1019   if (ro_seg_size)
   1020     memset(img->segment_bytes[ro_seg_idx], 0, (size_t)ro_seg_size);
   1021 
   1022   if (has_plt) {
   1023     LinkSegment* rx_seg = &img->segments[rx_seg_idx];
   1024     memset(rx_seg, 0, sizeof(*rx_seg));
   1025     rx_seg->id = (LinkSegmentId)(rx_seg_idx + 1u);
   1026     rx_seg->flags = SF_ALLOC | SF_EXEC;
   1027     rx_seg->file_offset = rx_vaddr;
   1028     rx_seg->vaddr = rx_vaddr;
   1029     rx_seg->paddr = rx_vaddr;
   1030     rx_seg->file_size = plt_bytes;
   1031     rx_seg->mem_size = plt_bytes;
   1032     rx_seg->align = (u32)page;
   1033     rx_seg->nsections = 1;
   1034     img->segment_bytes[rx_seg_idx] = (u8*)h->alloc(h, (size_t)plt_bytes, 16);
   1035     img->segment_bytes_cap[rx_seg_idx] = (size_t)plt_bytes;
   1036     if (!img->segment_bytes[rx_seg_idx])
   1037       compiler_panic(img->c, SRCLOC_NONE, "link: oom on .plt segment");
   1038     memset(img->segment_bytes[rx_seg_idx], 0, (size_t)plt_bytes);
   1039     /* Stash plt / got.plt vaddrs now — the PLT body emit just below
   1040      * reads them, and the post-shift fixup in shift_image_addresses
   1041      * (link_elf.c) keys on these fields too. */
   1042     dyn->plt_vaddr = rx_vaddr;
   1043     dyn->plt_size = plt_bytes;
   1044     dyn->got_plt_vaddr = rw_vaddr;
   1045     dyn->got_plt_size = gotplt_bytes;
   1046     /* PLT body emit: the descriptor owns the psABI-specific bytes. */
   1047     if (!arch->emit_plt0 || !arch->emit_plt_entry)
   1048       compiler_panic(l->c, SRCLOC_NONE, "link: PLT emit not configured");
   1049     {
   1050       u8* plt_b = img->segment_bytes[rx_seg_idx];
   1051       u32 ki;
   1052       arch->emit_plt0(plt_b, dyn->plt_vaddr, dyn->got_plt_vaddr);
   1053       for (ki = 0; ki < imports.nfuncs; ++ki) {
   1054         u64 entry_vaddr = dyn->plt_vaddr + arch->plt0_size +
   1055                           (u64)arch->plt_entry_size * (u64)ki;
   1056         u64 slot_vaddr = dyn->got_plt_vaddr + 8u * (3u + ki);
   1057         u8* p =
   1058             plt_b + arch->plt0_size + (size_t)arch->plt_entry_size * (size_t)ki;
   1059         arch->emit_plt_entry(p, entry_vaddr, slot_vaddr);
   1060       }
   1061     }
   1062   }
   1063   /* rw_seg always exists — it carries .dynamic, plus .got.plt when
   1064    * imports are present. */
   1065   {
   1066     LinkSegment* rw_seg = &img->segments[rw_seg_idx];
   1067     memset(rw_seg, 0, sizeof(*rw_seg));
   1068     rw_seg->id = (LinkSegmentId)(rw_seg_idx + 1u);
   1069     rw_seg->flags = SF_ALLOC | SF_WRITE;
   1070     rw_seg->file_offset = rw_vaddr;
   1071     rw_seg->vaddr = rw_vaddr;
   1072     rw_seg->paddr = rw_vaddr;
   1073     rw_seg->file_size = rw_seg_size;
   1074     rw_seg->mem_size = rw_seg_size;
   1075     rw_seg->align = (u32)page;
   1076     rw_seg->nsections = has_plt ? 2u : 1u;
   1077     img->segment_bytes[rw_seg_idx] = (u8*)h->alloc(h, (size_t)rw_seg_size, 16);
   1078     img->segment_bytes_cap[rw_seg_idx] = (size_t)rw_seg_size;
   1079     if (!img->segment_bytes[rw_seg_idx])
   1080       compiler_panic(img->c, SRCLOC_NONE, "link: oom on rw dyn segment");
   1081     /* Zero-initialize. .got.plt[0] (&.dynamic) is filled later, after
   1082      * shift_image_addresses has bumped dyn->dynamic_vaddr. .dynamic
   1083      * body is built post-shift in link_emit_elf. Loader
   1084      * patches all .got.plt slots from .rela.plt before user code
   1085      * under DF_1_NOW. */
   1086     memset(img->segment_bytes[rw_seg_idx], 0, (size_t)rw_seg_size);
   1087   }
   1088   img->nsegments += nseg;
   1089 
   1090   /* Step 6: synthetic LinkSection entries. Order in img->sections
   1091    * matches the loader-friendly file order and feeds emit's
   1092    * outshdr-merge pass. */
   1093   u32 nsec = (l->emit_shared ? 6u : 7u) + (has_plt ? 2u : 0u) +
   1094              (has_ver ? 2u : 0u);
   1095   u32 sec_base = dyn_alloc_sections(img, nsec);
   1096 
   1097   /* helper: populate a fresh LinkSection for a segment-internal range */
   1098   /* Inline because the args differ enough (sem, name) per slot. */
   1099   Sym name_interp = pool_intern_slice(l->c->global, SLICE_LIT(".interp"));
   1100   Sym name_dynsym = pool_intern_slice(l->c->global, SLICE_LIT(".dynsym"));
   1101   Sym name_dynstr = pool_intern_slice(l->c->global, SLICE_LIT(".dynstr"));
   1102   Sym name_gnu_hash = pool_intern_slice(l->c->global, SLICE_LIT(".gnu.hash"));
   1103   Sym name_rela_dyn = pool_intern_slice(l->c->global, SLICE_LIT(".rela.dyn"));
   1104   Sym name_rela_plt = pool_intern_slice(l->c->global, SLICE_LIT(".rela.plt"));
   1105   Sym name_dynamic = pool_intern_slice(l->c->global, SLICE_LIT(".dynamic"));
   1106   Sym name_plt = pool_intern_slice(l->c->global, SLICE_LIT(".plt"));
   1107   Sym name_got_plt = pool_intern_slice(l->c->global, SLICE_LIT(".got.plt"));
   1108   Sym name_gnu_version =
   1109       pool_intern_slice(l->c->global, SLICE_LIT(".gnu.version"));
   1110   Sym name_gnu_version_r =
   1111       pool_intern_slice(l->c->global, SLICE_LIT(".gnu.version_r"));
   1112 
   1113 #define INIT_SEC(IDX, NAME, SEG_IDX, OFF_IN_SEG, SIZE, ALIGN, FLAGS, SEM)  \
   1114   do {                                                                     \
   1115     LinkSection* ls = &img->sections[sec_base + (IDX)];                    \
   1116     memset(ls, 0, sizeof(*ls));                                            \
   1117     ls->id = (LinkSectionId)(sec_base + (IDX) + 1u);                       \
   1118     ls->input_id = LINK_INPUT_NONE;                                        \
   1119     ls->obj_section_id = OBJ_SEC_NONE;                                     \
   1120     ls->segment_id = img->segments[(SEG_IDX)].id;                          \
   1121     ls->input_offset = (OFF_IN_SEG);                                       \
   1122     ls->file_offset = img->segments[(SEG_IDX)].file_offset + (OFF_IN_SEG); \
   1123     ls->vaddr = img->segments[(SEG_IDX)].vaddr + (OFF_IN_SEG);             \
   1124     ls->size = (SIZE);                                                     \
   1125     ls->flags = (FLAGS);                                                   \
   1126     ls->align = (ALIGN);                                                   \
   1127     ls->name = (NAME);                                                     \
   1128     ls->sem = (SEM);                                                       \
   1129   } while (0)
   1130 
   1131   u32 si_base = 0;
   1132   if (!l->emit_shared) {
   1133     INIT_SEC(0, name_interp, ro_seg_idx, interp_off, interp_bytes, 1, SF_ALLOC,
   1134              SSEM_PROGBITS);
   1135     dyn->sec_interp = (LinkSectionId)(sec_base + 0 + 1u);
   1136     si_base = 1u;
   1137   }
   1138   INIT_SEC(si_base + 0u, name_dynsym, ro_seg_idx, dynsym_off, dynsym_bytes, 8, SF_ALLOC,
   1139            SSEM_PROGBITS);
   1140   INIT_SEC(si_base + 1u, name_dynstr, ro_seg_idx, dynstr_off, dynstr_bytes, 1, SF_ALLOC,
   1141            SSEM_PROGBITS);
   1142   INIT_SEC(si_base + 2u, name_gnu_hash, ro_seg_idx, gnuhash_off, gnuhash_bytes, 8,
   1143            SF_ALLOC, SSEM_PROGBITS);
   1144   INIT_SEC(si_base + 3u, name_rela_dyn, ro_seg_idx, rela_dyn_off, rela_dyn_bytes, 8,
   1145            SF_ALLOC, SSEM_PROGBITS);
   1146   INIT_SEC(si_base + 4u, name_rela_plt, ro_seg_idx, rela_plt_off, rela_plt_bytes, 8,
   1147            SF_ALLOC, SSEM_PROGBITS);
   1148   INIT_SEC(si_base + 5u, name_dynamic, rw_seg_idx, dynamic_off, dynamic_bytes, 8,
   1149            SF_ALLOC | SF_WRITE, SSEM_PROGBITS);
   1150 
   1151   dyn->sec_dynsym = (LinkSectionId)(sec_base + si_base + 0u + 1u);
   1152   dyn->sec_dynstr = (LinkSectionId)(sec_base + si_base + 1u + 1u);
   1153   dyn->sec_gnu_hash = (LinkSectionId)(sec_base + si_base + 2u + 1u);
   1154   dyn->sec_rela_dyn = (LinkSectionId)(sec_base + si_base + 3u + 1u);
   1155   dyn->sec_rela_plt = (LinkSectionId)(sec_base + si_base + 4u + 1u);
   1156   dyn->sec_dynamic = (LinkSectionId)(sec_base + si_base + 5u + 1u);
   1157   dyn->dynamic_vaddr = img->segments[rw_seg_idx].vaddr + dynamic_off;
   1158   dyn->dynamic_size = dynamic_bytes;
   1159 
   1160   if (has_plt) {
   1161     u32 plt0 = si_base + 6u;
   1162     INIT_SEC(plt0, name_plt, rx_seg_idx, 0, plt_bytes, 16, SF_ALLOC | SF_EXEC,
   1163              SSEM_PROGBITS);
   1164     INIT_SEC(plt0 + 1u, name_got_plt, rw_seg_idx, gotplt_off, gotplt_bytes, 8,
   1165              SF_ALLOC | SF_WRITE, SSEM_PROGBITS);
   1166     dyn->sec_plt = (LinkSectionId)(sec_base + plt0 + 1u);
   1167     dyn->sec_got_plt = (LinkSectionId)(sec_base + plt0 + 1u + 1u);
   1168   }
   1169   if (has_ver) {
   1170     /* Appended after the optional PLT slots; emit sorts the section-header
   1171      * table by (segment, vaddr), so array order here is not load-bearing. The
   1172      * SSEM_PROGBITS sem just parks the bytes in the ro segment — the runtime
   1173      * reads them via DT_VERSYM/DT_VERNEED, not the section headers. */
   1174     u32 vb0 = si_base + 6u + (has_plt ? 2u : 0u);
   1175     INIT_SEC(vb0, name_gnu_version, ro_seg_idx, versym_off, versym_bytes, 2,
   1176              SF_ALLOC, SSEM_PROGBITS);
   1177     INIT_SEC(vb0 + 1u, name_gnu_version_r, ro_seg_idx, verneed_off,
   1178              verneed_bytes, 4, SF_ALLOC, SSEM_PROGBITS);
   1179     dyn->sec_gnu_version = (LinkSectionId)(sec_base + vb0 + 1u);
   1180     dyn->sec_gnu_version_r = (LinkSectionId)(sec_base + vb0 + 1u + 1u);
   1181   }
   1182 #undef INIT_SEC
   1183 
   1184   img->nsections += nsec;
   1185 
   1186   /* Step 7: copy .interp / .dynsym / .dynstr / .gnu.hash bytes into
   1187    * the ro segment. .dynamic body is built during emit (it embeds
   1188    * runtime vaddrs that PIE keeps image-relative; emit just reads
   1189    * the section ids' final vaddrs). */
   1190   u8* ro_bytes = img->segment_bytes[ro_seg_idx];
   1191 
   1192   /* .interp */
   1193   if (interp_bytes && ro_bytes)
   1194     memcpy(ro_bytes + interp_off, interp_str, (size_t)interp_bytes);
   1195 
   1196   /* .dynsym: serialize DynSymRec to ELF64 wire layout. */
   1197   {
   1198     u32 si;
   1199     for (si = 0; si < dyn->ndynsym; ++si) {
   1200       u8* p = ro_bytes + dynsym_off + (u64)si * ELF64_SYM_SIZE;
   1201       const DynSymRec* r = &dyn->dynsym[si];
   1202       wr_u32_le(p + 0, r->st_name);
   1203       p[4] = r->st_info;
   1204       p[5] = r->st_other;
   1205       wr_u16_le(p + 6, r->st_shndx);
   1206       wr_u64_le(p + 8, r->st_value);
   1207       wr_u64_le(p + 16, r->st_size);
   1208     }
   1209   }
   1210 
   1211   /* .dynstr */
   1212   if (dynstr_bytes && ro_bytes && dyn->dynstr)
   1213     memcpy(ro_bytes + dynstr_off, dyn->dynstr, dyn->dynstr_len);
   1214 
   1215   /* .gnu.hash */
   1216   if (gnuhash_bytes && ro_bytes && dyn->gnu_hash)
   1217     memcpy(ro_bytes + gnuhash_off, dyn->gnu_hash, dyn->gnu_hash_len);
   1218 
   1219   /* .gnu.version + .gnu.version_r (no vaddrs inside; copied verbatim). */
   1220   if (has_ver && ro_bytes) {
   1221     if (versym_bytes && dyn->versym)
   1222       memcpy(ro_bytes + versym_off, dyn->versym, dyn->versym_len);
   1223     if (verneed_bytes && dyn->verneed)
   1224       memcpy(ro_bytes + verneed_off, dyn->verneed, dyn->verneed_len);
   1225   }
   1226 
   1227   /* .rela.plt: emit JUMP_SLOT records, one per imported function, and
   1228    * stash each import's PLT-entry vaddr in `sym_plt_vaddr` so the
   1229    * apply pass can redirect CALL26/JUMP26 against the import.  The
   1230    * record's r_offset addresses the .got.plt slot the PLT stub reads
   1231    * through; the loader patches that slot to the resolved runtime
   1232    * address before user code runs (DF_1_NOW, BIND_NOW).  Bytes are
   1233    * written here at pre-shift vaddrs; link_emit re-serializes them
   1234    * after shift_image_addresses bumps the dyn vaddrs by headers_load. */
   1235   {
   1236     u32 ki;
   1237     for (ki = 0; ki < imports.nfuncs; ++ki) {
   1238       LinkSymId lsid = imports.funcs[ki];
   1239       u32 dynidx = dyn->sym_dynidx[lsid];
   1240       u64 slot_vaddr = dyn->got_plt_vaddr + 8u * (3u + ki);
   1241       u64 plt_entry_vaddr = dyn->plt_vaddr + arch->plt0_size +
   1242                             (u64)arch->plt_entry_size * (u64)ki;
   1243       DynRela* r = &dyn->rela_plt[ki];
   1244       r->r_offset = slot_vaddr;
   1245       r->r_info = ELF64_R_INFO((u64)dynidx, elf_arch->r_jump_slot);
   1246       r->r_addend = 0;
   1247       /* Serialize into segment bytes (will be re-serialized post-shift). */
   1248       u8* p = ro_bytes + rela_plt_off + (u64)ki * ELF64_RELA_SIZE;
   1249       wr_u64_le(p + 0, r->r_offset);
   1250       wr_u64_le(p + 8, r->r_info);
   1251       wr_u64_le(p + 16, (u64)r->r_addend);
   1252       /* sym_plt_vaddr is consulted by apply_all_relocs. */
   1253       dyn->sym_plt_vaddr[lsid] = plt_entry_vaddr;
   1254     }
   1255   }
   1256 
   1257   /* .rela.dyn entries (GLOB_DAT for imports referenced via .got, and
   1258    * RELATIVE for PIE internal abs fixups) are emitted by
   1259    * apply_all_relocs as it walks every relocation.  layout_dyn
   1260    * leaves .rela.dyn empty here; the bytes are written post-shift in
   1261    * link_emit_elf. */
   1262 
   1263   /* .got.plt prelude: for BIND_NOW we leave the body zero — the
   1264    * loader patches every slot from .rela.plt before user code. Some
   1265    * loaders still inspect slot 0 (&.dynamic) at startup; provide it
   1266    * so glibc-style loaders don't fault. The loader writes the link_map
   1267    * cookie into slot 1 at load time. */
   1268   if (has_plt) {
   1269     u8* gp_bytes = img->segment_bytes[rw_seg_idx];
   1270     if (gp_bytes && gotplt_bytes >= 8u) {
   1271       wr_u64_le(gp_bytes, dyn->dynamic_vaddr);
   1272       /* Slots 1, 2, and per-PLT slots stay zero until the loader
   1273        * fills them. Phase 5 would prefill the per-PLT slots with
   1274        * the address of PLT0 to support lazy binding. */
   1275     }
   1276   }
   1277 
   1278   /* The .dynamic body is built later, after segment shifts are
   1279    * applied during emit. link_emit_elf (src/obj/elf/link.c) takes the
   1280    * post-shift vaddrs of every other dyn section and writes one
   1281    * DT_* entry per index. */
   1282 
   1283   /* Synthesize linker-defined symbols that reference the .dynamic
   1284    * vaddr.  Scrt1.o on Linux loads `_DYNAMIC` via ADRP+ADD, and
   1285    * libc_nonshared.a's atexit shim takes `__dso_handle` as the
   1286    * per-image identity (we use the .dynamic vaddr — any stable
   1287    * per-image address satisfies the contract since the shim only
   1288    * passes it through to __cxa_atexit, which the program-side glibc
   1289    * just stashes). */
   1290   link_define_boundary(l, img, "_DYNAMIC", dyn->dynamic_vaddr);
   1291   link_define_boundary(l, img, "__dso_handle", dyn->dynamic_vaddr);
   1292 
   1293   free_imports(h, &imports);
   1294 }
   1295 
   1296 /* ---- cleanup ---- */
   1297 
   1298 void link_dyn_state_free(LinkImage* img) {
   1299   Heap* h = img->heap;
   1300   LinkDynState* dyn = img->dyn;
   1301   if (!dyn) return;
   1302   if (dyn->dynsym) h->free(h, dyn->dynsym, sizeof(*dyn->dynsym) * dyn->ndynsym);
   1303   if (dyn->dynstr) h->free(h, dyn->dynstr, dyn->dynstr_len);
   1304   if (dyn->gnu_hash) h->free(h, dyn->gnu_hash, dyn->gnu_hash_len);
   1305   if (dyn->versym) h->free(h, dyn->versym, dyn->versym_len);
   1306   if (dyn->verneed) h->free(h, dyn->verneed, dyn->verneed_len);
   1307   if (dyn->rela_dyn)
   1308     h->free(h, dyn->rela_dyn, sizeof(*dyn->rela_dyn) * dyn->cap_rela_dyn);
   1309   if (dyn->rela_plt)
   1310     h->free(h, dyn->rela_plt, sizeof(*dyn->rela_plt) * dyn->nrela_plt);
   1311   if (dyn->needed) h->free(h, dyn->needed, sizeof(*dyn->needed) * dyn->nneeded);
   1312   if (dyn->rpaths) h->free(h, dyn->rpaths, sizeof(*dyn->rpaths) * dyn->nrpaths);
   1313   if (dyn->runpaths)
   1314     h->free(h, dyn->runpaths, sizeof(*dyn->runpaths) * dyn->nrunpaths);
   1315   if (dyn->sym_dynidx)
   1316     h->free(h, dyn->sym_dynidx,
   1317             sizeof(*dyn->sym_dynidx) * dyn->sym_dynidx_size);
   1318   if (dyn->sym_plt_vaddr)
   1319     h->free(h, dyn->sym_plt_vaddr,
   1320             sizeof(*dyn->sym_plt_vaddr) * dyn->sym_dynidx_size);
   1321   h->free(h, dyn, sizeof(*dyn));
   1322   img->dyn = NULL;
   1323 }