Files
ww/cmd/w6l/dyn.c
Hojun-Cho 2c33228b7e ww: rename toolchain to w-prefix + hare-style build/run/test driver
Plan 9-style w-prefix on the per-arch tools, disambiguating from the
real Plan 9 6c/6a/6l in ref/plan9front/:

    cmd/wwc/      → cmd/wcc/        libwwc.a → libwcc.a
    cmd/6{c,a,l}  → cmd/w6{c,a,l}   binary names too
    test/wwc/     → test/wcc/       6 test files w/ w6 prefix
    selfhost/cmd  mirror in lockstep
    bootstrap/amd64/{w6c,w6a,w6l}   snapshot binaries (gitignored)
    WW_6{C,A,L}   → WW_W6{C,A,L}    env-var overrides

Plan 9 source-tree refs ("Plan 9 6c shape", ref/plan9front/, etc.)
preserved. Hare-style driver, both C and ww sides:

    ww test [path]   discover *_test.ww in a directory module, run
                     each; single-file mode for `ww test foo.ww`
    Module-by-name   `ww build foo` resolves to foo.ww or foo/foo.ww
                     via search path (cwd : -I dirs : $WW_LIB)
    Default-to-cwd   `ww build` / `ww test` build the cwd module
    Run pass-through `ww run path arg1 arg2` reaches the program

lib/os: getcwd (79) and getdents64 (217) syscalls power `.` resolution
and directory enumeration on the ww side.

Makefile: wwstage tool deps now include lib/os/os.ww (+ lib/strconv
for wwdump_ww) so lib/* edits force their rebuild instead of leaving
stale binaries — surfaced when test 995 first failed against a stale
w6c_ww built before the lib/os additions.

Test 993 byte-identical parity gate (C-side ww vs ww-side ww_ww on a
build corpus) stays green; all 19 tests pass.
2026-05-11 13:49:27 +09:00

263 lines
7.1 KiB
C

/*
* 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 <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;
}