selfhost/, cmd/, internal/ join the tree-wide sweep: every section banner dies (91 selfhost + the cmd C-style dividers -> 0); narration and stale contracts deleted (pre-#22 bundler notes, retired single-PT_LOAD and no-archive claims, superseded ABI tables); every ref/harec/qbe cite, task cite, encoding/ELF contract, and rule-10 twin pointer kept; lost lifetime/rationale lines restored where the sweep over-cut (elf_globals ownership, kwtab linear-scan). Comment- only proven: all five wwstage tool binaries byte-identical across the sweep; test-commit, test-byteid (161+1399, 0 pinned-divergent), and test-bootstrap (fixed point + 991-995 byte-id) all exit 0. The read-through banked 66 latent-bug leads (checkpoint).
258 lines
6.8 KiB
C
258 lines
6.8 KiB
C
/*
|
|
* We do not pull bytes from the .so; the dynamic loader maps it at
|
|
* runtime.
|
|
*/
|
|
#include "l.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
}
|