/* * The shape we produce is the simplest valid one: PT_INTERP + * PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at * startup (no lazy binding, no PLT0 trampoline). SysV .hash, not * .gnu.hash. Non-PIE, fixed base. * * File layout: * [0] Ehdr * [64] Phdrs (PT_LOAD R+X, PT_LOAD R+W, PT_INTERP, PT_DYNAMIC) * [interp_off] "/lib64/ld-linux-x86-64.so.2\0" * [dynstr_off] .dynstr * [dynsym_off] .dynsym * [hash_off] .hash * [relaplt_off] .rela.plt * [pad to 0x1000] * [text_off] .text * [plt_off] .plt * [pad to next page] * [gotplt_off] .got.plt (writable; mapped by PT_LOAD #2) * [dynamic_off] .dynamic (writable; covered by PT_DYNAMIC) * * Each PLT entry is 8 bytes: `jmpq *(rip+disp)` (6 bytes) + 2 bytes * pad so the next entry stays naturally aligned. */ #include "l.h" #include #include #include #define ET_EXEC 2 #define EM_X86_64 62 #define EV_CURRENT 1 #define ELFCLASS64 2 #define ELFDATA2LSB 1 #define PT_LOAD 1 #define PT_DYNAMIC 2 #define PT_INTERP 3 #define PF_X 1 #define PF_W 2 #define PF_R 4 #define DT_NULL 0 #define DT_NEEDED 1 #define DT_PLTRELSZ 2 #define DT_PLTGOT 3 #define DT_HASH 4 #define DT_STRTAB 5 #define DT_SYMTAB 6 #define DT_STRSZ 10 #define DT_SYMENT 11 #define DT_PLTREL 20 #define DT_RELA 7 #define DT_JMPREL 23 #define DT_BIND_NOW 24 /* GNU extensions for symbol versioning. */ #define DT_VERSYM 0x6ffffff0 #define DT_VERNEED 0x6ffffffe #define DT_VERNEEDNUM 0x6fffffff #define VER_NDX_LOCAL 0 #define VER_NDX_GLOBAL 1 #define R_X86_64_PC32 2 #define R_X86_64_PLT32 4 #define R_X86_64_JUMP_SLOT 7 #define STB_GLOBAL 1 #define STT_FUNC 2 #define ST_INFO(b,t) (((b) << 4) | ((t) & 0xf)) #define INTERP "/lib64/ld-linux-x86-64.so.2" #pragma pack(push, 1) typedef struct { u8 e_ident[16]; u16 e_type, e_machine; u32 e_version; u64 e_entry, e_phoff, e_shoff; u32 e_flags; u16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx; } Ehdr; typedef struct { u32 p_type, p_flags; u64 p_offset, p_vaddr, p_paddr; u64 p_filesz, p_memsz, p_align; } Phdr; typedef struct { u32 st_name; u8 st_info, st_other; u16 st_shndx; u64 st_value, st_size; } Sym64; typedef struct { u64 r_offset; u64 r_info; i64 r_addend; } Rela64; typedef struct { i64 d_tag; u64 d_val; } Dyn64; #pragma pack(pop) #define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u32)(t))) /* SysV ELF hash (the older format; .gnu.hash is faster but more code). */ static u32 elf_hash(const char *name) { u32 h = 0, g; for (const u8 *s = (const u8 *)name; *s; s++) { h = (h << 4) + *s; g = h & 0xf0000000u; if (g) h ^= g >> 24; h &= ~g; } return h; } static void poke32(u8 *buf, u64 off, u32 v) { buf[off + 0] = (u8)(v); buf[off + 1] = (u8)(v >> 8); buf[off + 2] = (u8)(v >> 16); buf[off + 3] = (u8)(v >> 24); } #define PLT_STUB_BYTES 8 /* jmpq *disp(%rip) + 2 nop pad */ int l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) { /* Writable globals share the dyn-path R+W segment with .got.plt * and .dynamic. .data is placed after .dynamic; the segment's * filesz/memsz are extended to cover all three. Relocation * targeting .data uses data_va computed inside this function * (l_relocate now runs from here, not main, so the dyn layout's * data VA is the one that lands in patched offsets). */ const int N = l->dyn_n; /* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then * one symbol name per dynamic Lsym. We index dyn syms by * plt_idx (assigned in l_resolve). Build an array sorted by * plt_idx so we can walk in slot order. */ Lsym **dynsyms = calloc((size_t)N, sizeof *dynsyms); for (Lsym *s = l->syms; s; s = s->next) { if (s->is_dyn && s->plt_idx >= 0 && s->plt_idx < N) dynsyms[s->plt_idx] = s; } for (int i = 0; i < N; i++) { if (dynsyms[i] == NULL) { fprintf(stderr, "w6l: dynout: no sym for plt_idx %d\n", i); free(dynsyms); return 1; } } /* Count Lso's that any dyn sym references; only those need DT_NEEDED. */ int nsos = 0; for (Lso *so = l->sos; so; so = so->next) { int used = 0; for (int i = 0; i < N; i++) if (dynsyms[i]->dyn_lib == so) { used = 1; break; } if (used) nsos++; } Lso **sos_used = calloc((size_t)nsos, sizeof *sos_used); { int idx = 0; for (Lso *so = l->sos; so; so = so->next) { int used = 0; for (int i = 0; i < N; i++) if (dynsyms[i]->dyn_lib == so) { used = 1; break; } if (used) sos_used[idx++] = so; } } u8 *dynstr = NULL; u64 dynstr_cap = 0, dynstr_len = 0; #define DSTR_PUT(s) do { \ size_t _n = strlen(s) + 1; \ if (dynstr_len + _n > dynstr_cap) { \ dynstr_cap = dynstr_cap ? dynstr_cap * 2 : 256; \ while (dynstr_cap < dynstr_len + _n) dynstr_cap *= 2; \ dynstr = realloc(dynstr, dynstr_cap); \ } \ memcpy(dynstr + dynstr_len, s, _n); \ dynstr_len += _n; \ } while (0) DSTR_PUT(""); /* leading null entry */ u32 *soname_str = calloc((size_t)nsos, sizeof *soname_str); for (int i = 0; i < nsos; i++) { soname_str[i] = (u32)dynstr_len; DSTR_PUT(sos_used[i]->soname); } u32 *symname_str = calloc((size_t)N, sizeof *symname_str); for (int i = 0; i < N; i++) { symname_str[i] = (u32)dynstr_len; DSTR_PUT(dynsyms[i]->name); } /* For every sym whose dyn_version is non-NULL, there's a * Vernaux record under that lib's Verneed. The vna_other * value (assigned starting at 2; 1 is reserved for "global, * unversioned") becomes that sym's .gnu.version entry. * Unversioned syms get .gnu.version = 1. * * vlibs is parallel-indexed with sos_used so we can look * up the SONAME's dynstr offset directly. */ struct vlib_ver { const char *name; u32 dynstr_off; u16 vna_other; }; struct vlib { int sos_idx; int n_versions; struct vlib_ver *versions; }; struct vlib *vlibs = calloc((size_t)nsos, sizeof *vlibs); int n_vlibs = 0; for (int i = 0; i < nsos; i++) { int has = 0; for (int j = 0; j < N; j++) { if (dynsyms[j]->dyn_lib == sos_used[i] && dynsyms[j]->dyn_version != NULL) { has = 1; break; } } if (!has) continue; struct vlib *vl = &vlibs[n_vlibs]; vl->sos_idx = i; vl->versions = calloc((size_t)N, sizeof *vl->versions); vl->n_versions = 0; for (int j = 0; j < N; j++) { if (dynsyms[j]->dyn_lib != sos_used[i]) continue; const char *vname = dynsyms[j]->dyn_version; if (vname == NULL) continue; int seen = 0; for (int k = 0; k < vl->n_versions; k++) { if (strcmp(vl->versions[k].name, vname) == 0) { seen = 1; break; } } if (!seen) { vl->versions[vl->n_versions].name = vname; vl->n_versions++; } } n_vlibs++; } u16 next_vna = 2; for (int i = 0; i < n_vlibs; i++) for (int k = 0; k < vlibs[i].n_versions; k++) vlibs[i].versions[k].vna_other = next_vna++; for (int i = 0; i < n_vlibs; i++) { for (int k = 0; k < vlibs[i].n_versions; k++) { vlibs[i].versions[k].dynstr_off = (u32)dynstr_len; DSTR_PUT(vlibs[i].versions[k].name); } } /* Per-dyn-sym versym index: 1 (global) for unversioned, else * the matched Vernaux's vna_other. */ u16 *versym_for = calloc((size_t)N, sizeof *versym_for); for (int j = 0; j < N; j++) { const char *vname = dynsyms[j]->dyn_version; if (vname == NULL) { versym_for[j] = VER_NDX_GLOBAL; continue; } int matched = 0; for (int i = 0; i < n_vlibs && !matched; i++) { if (sos_used[vlibs[i].sos_idx] != dynsyms[j]->dyn_lib) continue; for (int k = 0; k < vlibs[i].n_versions; k++) { if (strcmp(vlibs[i].versions[k].name, vname) == 0) { versym_for[j] = vlibs[i].versions[k].vna_other; matched = 1; break; } } } if (!matched) { fprintf(stderr, "w6l: dynout: unmatched version %s for %s\n", vname, dynsyms[j]->name); versym_for[j] = VER_NDX_GLOBAL; } } const u64 ehdr_sz = sizeof(Ehdr); const int n_phdrs = 4; const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr); const u64 interp_sz = strlen(INTERP) + 1; /* nsyms = 1 (undef at index 0) + N */ const int nsyms_total = 1 + N; const u64 dynsym_sz = (u64)nsyms_total * sizeof(Sym64); const u64 dynstr_sz = dynstr_len; /* SysV hash: nbuckets + nchain + buckets[] + chain[]. We use one * bucket; loader scans the whole chain. Cheap to compute, easy to * validate. */ const u32 nbuckets = 1; const u32 nchain = (u32)nsyms_total; const u64 hash_sz = (2 + nbuckets + nchain) * 4; const u64 relaplt_sz = (u64)N * sizeof(Rela64); const u64 plt_sz = (u64)N * PLT_STUB_BYTES; const u64 gotplt_sz = (3 + (u64)N) * 8; /* .gnu.version: one Elf64_Half per .dynsym entry. */ const u64 versym_sz = (u64)nsyms_total * 2; /* .gnu.version_r: per lib, 16-byte Verneed plus 16-byte Vernaux * for each version under it. */ u64 verneed_sz = 0; for (int i = 0; i < n_vlibs; i++) verneed_sz += 16 + 16 * (u64)vlibs[i].n_versions; /* dynamic entries: NEEDED*nsos, HASH, STRTAB, SYMTAB, STRSZ, SYMENT, * PLTGOT, PLTRELSZ, PLTREL, JMPREL, BIND_NOW, [VERSYM, VERNEED, * VERNEEDNUM], NULL. The version trio is conditional on having any * versioned references. */ const int with_ver = (n_vlibs > 0); const u64 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0); const u64 dynamic_sz = ndyn * sizeof(Dyn64); /* Everything from the Ehdr through .text+.plt is in the R+X * load segment at base+0..text_end. .got.plt and .dynamic land * in the R+W segment at the next page boundary. */ u64 off = ehdr_sz + phdr_sz; const u64 interp_off = off; off += interp_sz; off = (off + 7) & ~(u64)7; const u64 dynstr_off = off; off += dynstr_sz; off = (off + 7) & ~(u64)7; const u64 dynsym_off = off; off += dynsym_sz; const u64 hash_off = off; off += hash_sz; off = (off + 1) & ~(u64)1; const u64 versym_off = off; off += versym_sz; off = (off + 3) & ~(u64)3; const u64 verneed_off = off; off += verneed_sz; off = (off + 7) & ~(u64)7; const u64 relaplt_off = off; off += relaplt_sz; /* Pad to 0x1000 so .text is page-aligned (matters for the loader * mapping our R+X PT_LOAD). */ const u64 page = 0x1000; const u64 text_off = (off + page - 1) & ~(page - 1); const u64 plt_off = text_off + l->textlen; const u64 rx_end = plt_off + plt_sz; /* Page-align the writable segment. We skip a page of file bytes; * the data lands at file offset gotplt_off, vaddr at base+gotplt_va. * .data sits after .dynamic so the whole R+W run is one segment. */ const u64 gotplt_off = (rx_end + page - 1) & ~(page - 1); const u64 dynamic_off = gotplt_off + gotplt_sz; const u64 data_off = dynamic_off + dynamic_sz; const u64 file_end = data_off + l->datalen; /* Virtual addresses mirror file offsets within their segment. * The R+W segment in particular needs vaddr = base + gotplt_off * so file offset and vaddr modulo page agree (loader requirement). */ const u64 interp_va = base + interp_off; const u64 dynstr_va = base + dynstr_off; const u64 dynsym_va = base + dynsym_off; const u64 hash_va = base + hash_off; const u64 versym_va = base + versym_off; const u64 verneed_va = base + verneed_off; const u64 relaplt_va = base + relaplt_off; const u64 text_va = base + text_off; const u64 plt_va = base + plt_off; const u64 gotplt_va = base + gotplt_off; const u64 dynamic_va = base + dynamic_off; const u64 data_va = base + data_off; /* Now that the dyn layout pins text_va/data_va, apply * relocations. main.c defers this; the static path runs it from * out.c with its own VAs. */ if (l_relocate(l, text_va, data_va) != 0) return 1; /* BSS optimisation — same trailing-zero scan as out.c. */ u64 bsslen = 0; if (l->datalen > 0) { while (bsslen < l->datalen && l->data[l->datalen - 1 - bsslen] == 0) bsslen++; } const u64 data_file_len = l->datalen - bsslen; const u64 file_data_end = data_off + data_file_len; Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym); for (int i = 0; i < N; i++) { Sym64 *e = &dynsym[1 + i]; e->st_name = symname_str[i]; e->st_info = ST_INFO(STB_GLOBAL, STT_FUNC); e->st_other = 0; e->st_shndx = 0; /* SHN_UNDEF */ e->st_value = 0; e->st_size = 0; dynsyms[i]->dynsym_idx = 1 + i; } /* .hash (SysV format, 1 bucket). */ u32 *hash = calloc(2 + nbuckets + nchain, 4); hash[0] = nbuckets; hash[1] = nchain; /* buckets[0] = first entry that lives in this bucket; we put * everything in bucket 0, so the bucket head is symbol 1. */ hash[2] = nsyms_total > 1 ? 1 : 0; /* chain[i] = next sym in the bucket. Last one terminates with 0. */ for (int i = 1; i < nsyms_total; i++) { u32 next = (i + 1 < nsyms_total) ? (u32)(i + 1) : 0; hash[2 + nbuckets + i] = next; } /* elf_hash is also used by .gnu.version_r for vna_hash below. */ Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt); for (int i = 0; i < N; i++) { relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8; relaplt[i].r_info = ELF64_R_INFO(1 + i, R_X86_64_JUMP_SLOT); relaplt[i].r_addend = 0; } /* .gnu.version: u16 per .dynsym entry. [0] = LOCAL, [1+i] = the * versym index we computed for dyn sym i. */ u16 *versym = calloc((size_t)nsyms_total, 2); versym[0] = VER_NDX_LOCAL; for (int i = 0; i < N; i++) versym[1 + i] = versym_for[i]; /* .gnu.version_r: chain of Verneed records, one per versioned lib, * each with a chain of Vernaux records, one per version under it. * We write directly into a u8 buffer with little-endian poke * helpers to avoid alignment concerns. */ u8 *verneed = NULL; if (verneed_sz > 0) { verneed = calloc((size_t)verneed_sz, 1); u64 vnoff = 0; for (int i = 0; i < n_vlibs; i++) { struct vlib *vl = &vlibs[i]; u64 vn_start = vnoff; /* Verneed header (16 bytes). */ u8 *vn = verneed + vnoff; /* vn_version = 1, vn_cnt = nversions */ vn[0] = 1; vn[1] = 0; vn[2] = (u8)(vl->n_versions); vn[3] = (u8)(vl->n_versions >> 8); poke32(vn, 4, soname_str[vl->sos_idx]); /* vn_file */ poke32(vn, 8, 16); /* vn_aux */ /* vn_next set after we know the vernaux count */ vnoff += 16; for (int k = 0; k < vl->n_versions; k++) { u8 *va = verneed + vnoff; poke32(va, 0, elf_hash(vl->versions[k].name)); /* vna_flags = 0 */ va[4] = 0; va[5] = 0; /* vna_other (versym index) */ va[6] = (u8)(vl->versions[k].vna_other); va[7] = (u8)(vl->versions[k].vna_other >> 8); poke32(va, 8, vl->versions[k].dynstr_off); poke32(va, 12, (k + 1 < vl->n_versions) ? 16u : 0u); vnoff += 16; } /* Now patch vn_next at vn_start+12. */ poke32(verneed, vn_start + 12, (i + 1 < n_vlibs) ? (u32)(vnoff - vn_start) : 0u); } } /* .plt — `jmpq *got.plt[3+i](%rip)` per stub. * Encoding: FF 25 . The disp is computed from the address * of the *next* instruction (RIP after the 6-byte jmp) to the * GOT slot. */ u8 *plt = calloc((size_t)plt_sz, 1); for (int i = 0; i < N; i++) { u64 stub_va = plt_va + (u64)i * PLT_STUB_BYTES; u64 next_ip = stub_va + 6; u64 slot_va = gotplt_va + (3 + (u64)i) * 8; i64 disp = (i64)slot_va - (i64)next_ip; u8 *p = plt + (u64)i * PLT_STUB_BYTES; p[0] = 0xff; p[1] = 0x25; poke32(p, 2, (u32)(i32)disp); /* p[6], p[7] left as zero — pad. */ } /* .got.plt — first three slots are reserved. * [0] = address of .dynamic (loader reads this). * [1] = link_map * (loader writes at startup). * [2] = dl_runtime_resolve (loader writes; unused with BIND_NOW). */ u8 *gotplt = calloc((size_t)gotplt_sz, 1); { u64 v = dynamic_va; for (int b = 0; b < 8; b++) gotplt[b] = (u8)(v >> (b * 8)); } /* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */ Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic); { int k = 0; for (int i = 0; i < nsos; i++) { dynamic[k].d_tag = DT_NEEDED; dynamic[k].d_val = soname_str[i]; k++; } dynamic[k].d_tag = DT_HASH; dynamic[k].d_val = hash_va; k++; dynamic[k].d_tag = DT_STRTAB; dynamic[k].d_val = dynstr_va; k++; dynamic[k].d_tag = DT_SYMTAB; dynamic[k].d_val = dynsym_va; k++; dynamic[k].d_tag = DT_STRSZ; dynamic[k].d_val = dynstr_sz; k++; dynamic[k].d_tag = DT_SYMENT; dynamic[k].d_val = sizeof(Sym64); k++; dynamic[k].d_tag = DT_PLTGOT; dynamic[k].d_val = gotplt_va; k++; dynamic[k].d_tag = DT_PLTRELSZ; dynamic[k].d_val = relaplt_sz; k++; dynamic[k].d_tag = DT_PLTREL; dynamic[k].d_val = DT_RELA; k++; dynamic[k].d_tag = DT_JMPREL; dynamic[k].d_val = relaplt_va; k++; dynamic[k].d_tag = DT_BIND_NOW; dynamic[k].d_val = 0; k++; if (with_ver) { dynamic[k].d_tag = DT_VERSYM; dynamic[k].d_val = versym_va; k++; dynamic[k].d_tag = DT_VERNEED; dynamic[k].d_val = verneed_va; k++; dynamic[k].d_tag = DT_VERNEEDNUM; dynamic[k].d_val = (u64)n_vlibs; k++; } dynamic[k].d_tag = DT_NULL; dynamic[k].d_val = 0; k++; if ((u64)k != ndyn) { fprintf(stderr, "w6l: dynamic entry count mismatch\n"); return 1; } } /* Patch .text relocations targeting dynamic syms: the site is * the existing PC32/PLT32 displacement field, the target the * address of the symbol's PLT stub. */ for (Lrel *r = l->rels; r; r = r->next) { if (r->sym == NULL || !r->sym->is_dyn) continue; if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) { fprintf(stderr, "w6l: dynamic reloc kind %d unsupported\n", r->kind); free(dynsyms); free(sos_used); free(soname_str); free(symname_str); free(dynstr); free(dynsym); free(hash); free(relaplt); free(plt); free(gotplt); free(dynamic); return 1; } u64 site = text_va + r->off; u64 stub = plt_va + (u64)r->sym->plt_idx * PLT_STUB_BYTES; i64 disp = (i64)stub - (i64)site + r->addend; poke32(l->text, r->off, (u32)(i32)disp); } Ehdr eh = {0}; memcpy(eh.e_ident, "\x7f""ELF", 4); eh.e_ident[4] = ELFCLASS64; eh.e_ident[5] = ELFDATA2LSB; eh.e_ident[6] = EV_CURRENT; eh.e_type = ET_EXEC; eh.e_machine = EM_X86_64; eh.e_version = EV_CURRENT; /* #63: main computed `entry` as base + 0x1000 + val assuming .text sits * at file offset 0x1000, but text_off above is recomputed and overflows * 0x1000 once the dynamic headers exceed the first page (e.g. ~100 dyn * syms). Rebase e_entry onto the actual text_off so it points at the * real _start instead of into the headers. e_entry is a virtual address; * PT_LOAD #1 maps file offset 0 at `base`, so the entry VA tracks * text_off. Both stages fixed together (#263). */ eh.e_entry = entry - 0x1000 + text_off; eh.e_phoff = ehdr_sz; eh.e_ehsize = sizeof(Ehdr); eh.e_phentsize = sizeof(Phdr); eh.e_phnum = (u16)n_phdrs; Phdr ph[4] = {0}; /* PT_LOAD #1 — R+X covering everything from Ehdr through .plt. */ ph[0].p_type = PT_LOAD; ph[0].p_flags = PF_R | PF_X; ph[0].p_offset = 0; ph[0].p_vaddr = base; ph[0].p_paddr = base; ph[0].p_filesz = rx_end; /* The loader page-rounds mappings; gotplt_off independently starts * the next page, so padding is not part of the R+X extent. */ ph[0].p_memsz = rx_end; ph[0].p_align = page; /* PT_LOAD #2 — R+W covering .got.plt, .dynamic, and .data. * filesz drops the trailing-zero suffix (BSS); memsz keeps the * full extent so the loader zero-fills the gap. */ ph[1].p_type = PT_LOAD; ph[1].p_flags = PF_R | PF_W; ph[1].p_offset = gotplt_off; ph[1].p_vaddr = gotplt_va; ph[1].p_paddr = gotplt_va; ph[1].p_filesz = file_data_end - gotplt_off; ph[1].p_memsz = file_end - gotplt_off; ph[1].p_align = page; ph[2].p_type = PT_INTERP; ph[2].p_flags = PF_R; ph[2].p_offset = interp_off; ph[2].p_vaddr = interp_va; ph[2].p_paddr = interp_va; ph[2].p_filesz = interp_sz; ph[2].p_memsz = interp_sz; ph[2].p_align = 1; ph[3].p_type = PT_DYNAMIC; ph[3].p_flags = PF_R | PF_W; ph[3].p_offset = dynamic_off; ph[3].p_vaddr = dynamic_va; ph[3].p_paddr = dynamic_va; ph[3].p_filesz = dynamic_sz; ph[3].p_memsz = dynamic_sz; ph[3].p_align = 8; fwrite(&eh, 1, sizeof eh, f); fwrite(ph, 1, sizeof ph, f); #define PAD_TO(to) do { \ long _here = ftell(f); \ for (long _i = _here; _i < (long)(to); _i++) fputc(0, f); \ } while (0) PAD_TO(interp_off); fwrite(INTERP, 1, interp_sz, f); PAD_TO(dynstr_off); fwrite(dynstr, 1, dynstr_sz, f); PAD_TO(dynsym_off); fwrite(dynsym, 1, dynsym_sz, f); PAD_TO(hash_off); fwrite(hash, 4, 2 + nbuckets + nchain, f); PAD_TO(versym_off); fwrite(versym, 2, (size_t)nsyms_total, f); if (verneed_sz > 0) { PAD_TO(verneed_off); fwrite(verneed, 1, verneed_sz, f); } PAD_TO(relaplt_off); fwrite(relaplt, 1, relaplt_sz, f); PAD_TO(text_off); fwrite(l->text, 1, l->textlen, f); PAD_TO(plt_off); fwrite(plt, 1, plt_sz, f); PAD_TO(gotplt_off); fwrite(gotplt, 1, gotplt_sz, f); PAD_TO(dynamic_off); fwrite(dynamic, 1, dynamic_sz, f); if (data_file_len > 0) { PAD_TO(data_off); fwrite(l->data, 1, data_file_len, f); } for (int i = 0; i < n_vlibs; i++) free(vlibs[i].versions); free(vlibs); free(versym_for); free(versym); if (verneed) free(verneed); free(dynsyms); free(sos_used); free(soname_str); free(symname_str); free(dynstr); free(dynsym); free(hash); free(relaplt); free(plt); free(gotplt); free(dynamic); return 0; }