/* * dyn.c — load a shared object (ET_DYN) so the linker knows which * symbols it exports and which DT_NEEDED entry to record. We do not * pull bytes from the .so; the dynamic loader maps it at runtime. * * Each call appends one Lso to lnk->sos. `l_so_provides` answers * "does this .so export the named symbol?" — l_resolve uses that to * promote unresolved references to dynamic. */ #include "l.h" #include #include #include #define ET_DYN 3 #define SHT_DYNAMIC 6 #define SHT_DYNSYM 11 /* GNU extensions, sh_type values. */ #define SHT_GNU_VERDEF 0x6ffffffd #define SHT_GNU_VERNEED 0x6ffffffe #define SHT_GNU_VERSYM 0x6fffffff #define DT_NULL 0 #define DT_SONAME 14 #define DT_STRTAB 5 /* Versym special values: 0 = local, 1 = base/global. */ #define VER_NDX_LOCAL 0 #define VER_NDX_GLOBAL 1 #define VER_FLG_BASE 1 #define VERSYM_HIDDEN 0x8000 #define VERSYM_VERSION 0x7fff #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 sh_name, sh_type; u64 sh_flags, sh_addr, sh_offset, sh_size; u32 sh_link, sh_info; u64 sh_addralign, sh_entsize; } Shdr; typedef struct { u32 st_name; u8 st_info, st_other; u16 st_shndx; u64 st_value, st_size; } Sym64; typedef struct { i64 d_tag; u64 d_val; } Dyn64; typedef struct { u16 vd_version; u16 vd_flags; u16 vd_ndx; u16 vd_cnt; u32 vd_hash; u32 vd_aux; u32 vd_next; } Verdef; typedef struct { u32 vda_name; u32 vda_next; } Verdaux; #pragma pack(pop) /* ELF binding values. STB_GLOBAL = 1, STB_WEAK = 2. */ #define ST_BIND(i) ((i) >> 4) int l_load_so(Lnk *l, const char *path) { u8 *buf; u64 len; if (l_read_all(path, &buf, &len) < 0) { fprintf(stderr, "w6l: %s: cannot read\n", path); return -1; } if (len < sizeof(Ehdr)) { fprintf(stderr, "w6l: %s: short ELF\n", path); free(buf); return -1; } Ehdr *eh = (Ehdr *)buf; if (memcmp(eh->e_ident, "\x7f""ELF", 4) != 0 || eh->e_ident[4] != 2 || eh->e_machine != 62 /* EM_X86_64 */ || eh->e_type != ET_DYN) { fprintf(stderr, "w6l: %s: not an amd64 ET_DYN\n", path); free(buf); return -1; } if (eh->e_shoff == 0 || eh->e_shnum == 0) { fprintf(stderr, "w6l: %s: stripped .so unsupported\n", path); free(buf); return -1; } Shdr *sh = (Shdr *)(buf + eh->e_shoff); int idx_dynsym = -1, idx_dynamic = -1; int idx_versym = -1, idx_verdef = -1; for (u16 i = 0; i < eh->e_shnum; i++) { if (sh[i].sh_type == SHT_DYNSYM) idx_dynsym = i; if (sh[i].sh_type == SHT_DYNAMIC) idx_dynamic = i; if (sh[i].sh_type == SHT_GNU_VERSYM) idx_versym = i; if (sh[i].sh_type == SHT_GNU_VERDEF) idx_verdef = i; } if (idx_dynsym < 0) { fprintf(stderr, "w6l: %s: no .dynsym\n", path); free(buf); return -1; } int idx_dynstr = sh[idx_dynsym].sh_link; const char *str = (const char *)(buf + sh[idx_dynstr].sh_offset); Sym64 *syms = (Sym64 *)(buf + sh[idx_dynsym].sh_offset); u64 nsyms = sh[idx_dynsym].sh_size / sizeof(Sym64); /* SONAME: the .dynamic section's strings live in the section pointed * at by its sh_link (not necessarily .dynstr — though usually). */ const char *soname = NULL; if (idx_dynamic >= 0) { int idx_dstr = sh[idx_dynamic].sh_link; const char *dstr = (const char *)(buf + sh[idx_dstr].sh_offset); Dyn64 *d = (Dyn64 *)(buf + sh[idx_dynamic].sh_offset); u64 nd = sh[idx_dynamic].sh_size / sizeof(Dyn64); for (u64 i = 0; i < nd; i++) { if (d[i].d_tag == DT_NULL) break; if (d[i].d_tag == DT_SONAME) { soname = dstr + d[i].d_val; break; } } } if (soname == NULL) { const char *bn = strrchr(path, '/'); soname = bn ? bn + 1 : path; } /* Build verdef-index → version-name table. The version name is in * the first Verdaux (the rest are predecessor names — version * inheritance for stable ABI within a release line). For our * purposes only the leading name matters. */ const char **verdef_names = NULL; int verdef_max = 0; if (idx_verdef >= 0) { const u8 *vbase = buf + sh[idx_verdef].sh_offset; const char *vstr = (const char *) (buf + sh[sh[idx_verdef].sh_link].sh_offset); /* First pass: discover max ndx so we can size the table. */ u64 vd_off = 0; while (vd_off < sh[idx_verdef].sh_size) { Verdef *vd = (Verdef *)(vbase + vd_off); if ((int)vd->vd_ndx > verdef_max) verdef_max = vd->vd_ndx; if (vd->vd_next == 0) break; vd_off += vd->vd_next; } verdef_names = calloc((size_t)verdef_max + 1, sizeof *verdef_names); vd_off = 0; while (vd_off < sh[idx_verdef].sh_size) { Verdef *vd = (Verdef *)(vbase + vd_off); Verdaux *va = (Verdaux *)((u8 *)vd + vd->vd_aux); verdef_names[vd->vd_ndx] = vstr + va->vda_name; if (vd->vd_next == 0) break; vd_off += vd->vd_next; } } /* Versym is one u16 per .dynsym entry. */ const u16 *versym = NULL; if (idx_versym >= 0) versym = (const u16 *)(buf + sh[idx_versym].sh_offset); Lso *so = calloc(1, sizeof *so); so->path = strdup(path); so->soname = strdup(soname); so->exports = calloc((size_t)nsyms + 1, sizeof *so->exports); so->versions = calloc((size_t)nsyms + 1, sizeof *so->versions); int n = 0; for (u64 i = 1; i < nsyms; i++) { if (syms[i].st_shndx == 0) continue; /* SHN_UNDEF */ u8 b = ST_BIND(syms[i].st_info); if (b != 1 && b != 2) continue; /* GLOBAL or WEAK */ const char *nm = str + syms[i].st_name; if (nm[0] == '\0') continue; /* Skip non-default versions: when a name has multiple version * definitions, the loader binds an unversioned reference to * the one whose Versym entry has the hidden bit clear. */ const char *vername = NULL; if (versym != NULL) { u16 v = versym[i]; if (v & VERSYM_HIDDEN) continue; /* non-default */ u16 vidx = v & VERSYM_VERSION; if (vidx == VER_NDX_LOCAL) continue; /* not exported */ if (vidx == VER_NDX_GLOBAL) { vername = NULL; /* unversioned */ } else if (verdef_names != NULL && (int)vidx <= verdef_max && verdef_names[vidx] != NULL) { /* index 1 in glibc's Verdef is the SONAME with * VER_FLG_BASE — we skip its export entries * as a side effect of vidx==1 mapping to the * BASE name (e.g. "libc.so.6"), which never * appears as a reference target. Treat any * lookup that lands on the BASE entry as * unversioned. */ if (vidx == 1) vername = NULL; else vername = verdef_names[vidx]; } } so->exports[n] = strdup(nm); so->versions[n] = vername ? strdup(vername) : NULL; n++; } so->exports[n] = NULL; so->versions[n] = NULL; if (verdef_names != NULL) free(verdef_names); so->next = l->sos; l->sos = so; free(buf); return 0; } int l_so_provides(Lso *so, const char *name) { const char *v; return l_so_provides_v(so, name, &v); } int l_so_provides_v(Lso *so, const char *name, const char **out_version) { if (so == NULL || so->exports == NULL) return 0; for (int i = 0; so->exports[i]; i++) { if (strcmp(so->exports[i], name) != 0) continue; *out_version = so->versions ? so->versions[i] : NULL; return 1; } return 0; }