Files
ww/selfhost/cmd/w6l/dyn.ww
Hojun-Cho 1ac1d985f6 lib: rename stdlib surface to Hare names; add endian/math
Sweeping rename so the lib/ surface mirrors Hare's stdlib spellings.
- ascii: rune-taking predicates; ishex -> isxdigit
- bufio: rinit -> init; take1/takeline -> readbyte/readline
- bytes: indexsub -> index
- encoding/utf8: runelen -> runesz
- errors: eEOF/eShortRead/... -> eof/underread/...
- fmt: errln -> errorln; println/fprintln return i64
- os: readfull/writefull -> readall/writeall; unlink -> remove
- path: isabs -> abs; drop lastindex (now strings.rbyteindex)
- strconv: u64toa/i64toa -> u64tos/i64tos; parse64/parseu64 -> stoi64/stou64
- strings: drop len/isempty; equal -> compare; indexbyte -> byteindex; +rbyteindex
- types: drop numeric helpers (moved to math)
- new lib/endian (htonu16/ntohu16), lib/math (absi32/absi64)
- net: drop htons (use endian.htonu16)

Callers in selfhost/, lib/ww/, cmd/w6c/cgen.c, and test/wcc/700_e2e.c
updated to match.
2026-05-12 00:45:18 +09:00

399 lines
11 KiB
Plaintext

// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/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_v answers
// "does this .so export the named symbol, and at which version?" —
// l_resolve uses that to promote unresolved references to dynamic.
use os;
use mem;
use sym;
def ET_DYN_SO: u16 = 3u16;
def EM_X86_64_SO: u16 = 62u16;
def SHT_DYNAMIC: u32 = 6u32;
def SHT_DYNSYM: u32 = 11u32;
// GNU extensions, sh_type values.
def SHT_GNU_VERDEF: u32 = 1879048189u32; // 0x6ffffffd
def SHT_GNU_VERNEED: u32 = 1879048190u32; // 0x6ffffffe
def SHT_GNU_VERSYM: u32 = 1879048191u32; // 0x6fffffff
def DT_NULL_TAG: i64 = 0i64;
def DT_SONAME_TAG: i64 = 14i64;
// Versym special values.
def VER_NDX_LOCAL_C: u16 = 0u16;
def VER_NDX_GLOBAL_C: u16 = 1u16;
def VERSYM_HIDDEN_C: u16 = 32768u16; // 0x8000
def VERSYM_VERSION_C: u16 = 32767u16; // 0x7fff
// ELF64 ehdr field offsets (subset)
def EH_SHOFF: u64 = 40u64;
def EH_ETYPE: u64 = 16u64;
def EH_EMACHINE: u64 = 18u64;
def EH_SHENTSIZE: u64 = 58u64;
def EH_SHNUM: u64 = 60u64;
def EH_SHSTRNDX: u64 = 62u64;
// ELF64 Shdr (64 bytes)
def SH_SIZE: u64 = 64u64;
def SH_TYPE: u64 = 4u64;
def SH_OFFSET: u64 = 24u64;
def SH_SIZE_F: u64 = 32u64;
def SH_LINK: u64 = 40u64;
def SH_ENTSIZE: u64 = 56u64;
// ELF64 Sym (24 bytes)
def SY_SIZE: u64 = 24u64;
def SY_NAME: u64 = 0u64;
def SY_INFO: u64 = 4u64;
def SY_SHNDX: u64 = 6u64;
// ELF64 Dyn (16 bytes)
def DY_SIZE: u64 = 16u64;
def DY_TAG: u64 = 0u64;
def DY_VAL: u64 = 8u64;
// Verdef (20 bytes)
def VD_SIZE: u64 = 20u64;
def VD_NDX: u64 = 4u64;
def VD_CNT: u64 = 6u64;
def VD_AUX: u64 = 12u64;
def VD_NEXT: u64 = 16u64;
// Verdaux (8 bytes)
def VA_NAME: u64 = 0u64;
def VA_NEXT: u64 = 4u64;
// ---- little-endian byte readers ---------------------------------------
fn du16(p: *u8, off: u64) u16 = {
let b0: u16 = p[off]: u16;
let b1: u16 = p[off + 1u64]: u16;
return b0 | (b1 << 8u16);
};
fn du32(p: *u8, off: u64) u32 = {
let b0: u32 = p[off]: u32;
let b1: u32 = p[off + 1u64]: u32;
let b2: u32 = p[off + 2u64]: u32;
let b3: u32 = p[off + 3u64]: u32;
return b0 | (b1 << 8u32) | (b2 << 16u32) | (b3 << 24u32);
};
fn du64(p: *u8, off: u64) u64 = {
let lo: u64 = du32(p, off): u64;
let hi: u64 = du32(p, off + 4u64): u64;
return lo | (hi << 32u64);
};
fn di64(p: *u8, off: u64) i64 = {
return du64(p, off): i64;
};
// ---- C-string helpers --------------------------------------------------
fn dcstrlen(p: *u8) u64 = {
let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; };
return n;
};
fn dcstrtostr(a: *arena, p: *u8) str = {
let n: u64 = dcstrlen(p);
return astrndup(a, p, n);
};
// basename: scan for last '/' and return pointer past it.
fn dbasename(p: *u8) *u8 = {
let n: u64 = dcstrlen(p);
let i: u64 = n;
for (i > 0u64) {
i -= 1u64;
if (p[i] == 47u8) { // '/'
return p + i + 1u64;
};
};
return p;
};
// ---- file slurp --------------------------------------------------------
fn slurpso(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(path, os.O_RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; };
let n: i64 = os.filesize(fd);
if (n < 0i64) { os.close(fd); return nil, 0u64; };
let buf: *u8 = os.alloc(n: u64): *u8;
let got: i64 = os.readall(fd, buf, n: u64);
os.close(fd);
if (got != n) { return nil, 0u64; };
return buf, n: u64;
};
// ---- verdef helpers ----------------------------------------------------
// vdnameat — walk verdef records and return the name (as *u8 into
// the .so's verstr buffer) for the entry whose vd_ndx == ndx. The name
// is the first Verdaux's vda_name (subsequent auxes are predecessor
// names). Returns nil if no entry matches.
fn vdnameat(buf: *u8, verdefoff: u64, verdefsize: u64,
verstr: *u8, ndx: u16) *u8 = {
let off: u64 = 0u64;
for (off < verdefsize) {
let vdp: u64 = verdefoff + off;
let vdndx: u16 = du16(buf, vdp + VD_NDX);
let vdaux: u32 = du32(buf, vdp + VD_AUX);
let vdnext: u32 = du32(buf, vdp + VD_NEXT);
if (vdndx == ndx) {
let auxp: u64 = vdp + (vdaux: u64);
let vdaname: u32 = du32(buf, auxp + VA_NAME);
return verstr + (vdaname: u64);
};
if (vdnext == 0u32) { return nil; };
off += vdnext: u64;
};
return nil;
};
// ---- entry points ------------------------------------------------------
export fn loadso(l: *lnk, path: *u8) i32 = {
let buf: *u8;
let blen: u64;
buf, blen = slurpso(path);
if (buf == nil) {
os.write(2, "w6l: cannot read .so\n".ptr, 20u64);
return -1;
};
if (blen < 64u64) {
os.write(2, "w6l: short ELF\n".ptr, 14u64);
return -1;
};
if (buf[0u64] != 127u8) { return soerr("not ELF"); };
if (buf[1u64] != 69u8) { return soerr("not ELF"); };
if (buf[2u64] != 76u8) { return soerr("not ELF"); };
if (buf[3u64] != 70u8) { return soerr("not ELF"); };
if (buf[4u64] != 2u8) { return soerr("not ELFCLASS64"); };
if (du16(buf, EH_EMACHINE) != EM_X86_64_SO) {
return soerr("not amd64");
};
if (du16(buf, EH_ETYPE) != ET_DYN_SO) {
return soerr("not ET_DYN");
};
let shoff: u64 = du64(buf, EH_SHOFF);
let shnum: u32 = du16(buf, EH_SHNUM): u32;
if (shoff == 0u64) { return soerr("stripped .so unsupported"); };
if (shnum == 0u32) { return soerr("stripped .so unsupported"); };
// Locate the four sections we care about.
let idxdynsym: i32 = -1;
let idxdynamic: i32 = -1;
let idxversym: i32 = -1;
let idxverdef: i32 = -1;
let i: u32 = 0u32;
for (i < shnum) {
let shp: u64 = shoff + (i: u64) * SH_SIZE;
let shtype: u32 = du32(buf, shp + SH_TYPE);
if (shtype == SHT_DYNSYM) { idxdynsym = i: i32; };
if (shtype == SHT_DYNAMIC) { idxdynamic = i: i32; };
if (shtype == SHT_GNU_VERSYM) { idxversym = i: i32; };
if (shtype == SHT_GNU_VERDEF) { idxverdef = i: i32; };
i += 1u32;
};
if (idxdynsym < 0) {
return soerr("no .dynsym");
};
let dynsymsh: u64 = shoff + (idxdynsym: u64) * SH_SIZE;
let dynsymoff: u64 = du64(buf, dynsymsh + SH_OFFSET);
let dynsymsize: u64 = du64(buf, dynsymsh + SH_SIZE_F);
let dynsymlink: u32 = du32(buf, dynsymsh + SH_LINK);
let nsyms: u64 = dynsymsize / SY_SIZE;
let dynstrsh: u64 = shoff + (dynsymlink: u64) * SH_SIZE;
let dynstroff: u64 = du64(buf, dynstrsh + SH_OFFSET);
let dynstr: *u8 = buf + dynstroff;
// SONAME: .dynamic strings live in the section pointed at by its
// sh_link (almost always .dynstr).
let sonamecs: *u8 = nil;
if (idxdynamic >= 0) {
let dynsh: u64 = shoff + (idxdynamic: u64) * SH_SIZE;
let dynoff: u64 = du64(buf, dynsh + SH_OFFSET);
let dynsize: u64 = du64(buf, dynsh + SH_SIZE_F);
let dynlink: u32 = du32(buf, dynsh + SH_LINK);
let dstrsh: u64 = shoff + (dynlink: u64) * SH_SIZE;
let dstroff: u64 = du64(buf, dstrsh + SH_OFFSET);
let dstr: *u8 = buf + dstroff;
let nd: u64 = dynsize / DY_SIZE;
let di: u64 = 0u64;
for (di < nd) {
let dp: u64 = dynoff + di * DY_SIZE;
let dtag: i64 = di64(buf, dp + DY_TAG);
if (dtag == DT_NULL_TAG) {
di = nd; // break
} else {
if (dtag == DT_SONAME_TAG) {
let dval: u64 = du64(buf, dp + DY_VAL);
sonamecs = dstr + dval;
di = nd; // break
} else {
di += 1u64;
};
};
};
};
if (sonamecs == nil) {
sonamecs = dbasename(path);
};
// Versym is one u16 per dynsym entry.
let versymoff: u64 = 0u64;
let hasversym: i32 = 0;
if (idxversym >= 0) {
let vssh: u64 = shoff + (idxversym: u64) * SH_SIZE;
versymoff = du64(buf, vssh + SH_OFFSET);
hasversym = 1;
};
// Verdef section bounds + the .dynstr-like string section it uses.
let verdefoff: u64 = 0u64;
let verdefsize: u64 = 0u64;
let verstr: *u8 = nil;
if (idxverdef >= 0) {
let vdsh: u64 = shoff + (idxverdef: u64) * SH_SIZE;
verdefoff = du64(buf, vdsh + SH_OFFSET);
verdefsize = du64(buf, vdsh + SH_SIZE_F);
let vdlink: u32 = du32(buf, vdsh + SH_LINK);
let vstrsh: u64 = shoff + (vdlink: u64) * SH_SIZE;
let vstroff: u64 = du64(buf, vstrsh + SH_OFFSET);
verstr = buf + vstroff;
};
// Build the lso. Exports are appended in dynsym order so
// soprovides_v's first-match semantics match the C version.
let so: *lso = amalloc(l.a, 64u64): *lso;
so.path = dcstrtostr(l.a, path);
so.soname = dcstrtostr(l.a, sonamecs);
so.exports = nil;
let tail: *lexport = nil;
let si: u64 = 1u64;
for (si < nsyms) {
let sp: u64 = dynsymoff + si * SY_SIZE;
let stshndx: u16 = du16(buf, sp + SY_SHNDX);
if (stshndx == 0u16) { si += 1u64; } else {
let stinfo: u8 = buf[sp + SY_INFO];
let bind: u32 = (stinfo: u32) >> 4u32;
if (bind != 1u32) { if (bind != 2u32) {
// not GLOBAL/WEAK
si += 1u64;
continue;
}; };
let stname: u32 = du32(buf, sp + SY_NAME);
let nmp: *u8 = dynstr + (stname: u64);
if (nmp[0u64] == 0u8) {
si += 1u64;
continue;
};
// Determine version. Skip non-default (hidden) and
// local entries.
let vernamecs: *u8 = nil;
let keep: i32 = 1;
if (hasversym != 0) {
let v: u16 = du16(buf, versymoff + si * 2u64);
if ((v & VERSYM_HIDDEN_C) != 0u16) {
keep = 0; // non-default
} else {
let vidx: u16 = v & VERSYM_VERSION_C;
if (vidx == VER_NDX_LOCAL_C) {
keep = 0; // not exported
} else { if (vidx == VER_NDX_GLOBAL_C) {
vernamecs = nil;
} else { if (vidx == 1u16) {
// glibc's BASE entry: treat as
// unversioned. (The C version
// notes that vidx==1 in Verdef
// maps to the SONAME BASE.)
vernamecs = nil;
} else {
if (verstr != nil) {
let nm: *u8 = vdnameat(buf, verdefoff, verdefsize, verstr, vidx);
vernamecs = nm;
};
}; }; };
};
};
if (keep != 0) {
let e: *lexport = amalloc(l.a, 48u64): *lexport;
e.name = dcstrtostr(l.a, nmp);
if (vernamecs == nil) {
e.version.ptr = nil;
e.version.len = 0i32;
} else {
e.version = dcstrtostr(l.a, vernamecs);
};
e.enext = nil;
if (tail == nil) {
so.exports = e;
} else {
tail.enext = e;
};
tail = e;
};
si += 1u64;
};
};
so.sonext = l.sos;
l.sos = so;
return 0;
};
fn soerr(msg: str) i32 = {
os.write(2, "w6l: ".ptr, 4u64);
os.write(2, msg.ptr, msg.len: u64);
os.write(2, "\n".ptr, 1u64);
return -1;
};
// soprovides — 1 if so exports name, 0 otherwise.
export fn soprovides(so: *lso, name: str) i32 = {
if (so == nil) { return 0; };
let e: *lexport = so.exports;
for (e != nil) {
if (streq(e.name, name)) { return 1; };
e = e.enext;
};
return 0;
};
// soversion — the version of so's export named `name`, or an empty
// str (ptr=nil, len=0) if the export is unversioned or not present.
export fn soversion(so: *lso, name: str) str = {
let result: str;
result.ptr = nil;
result.len = 0i32;
if (so == nil) { return result; };
let e: *lexport = so.exports;
for (e != nil) {
if (streq(e.name, name)) {
result.ptr = e.version.ptr;
result.len = e.version.len;
return result;
};
e = e.enext;
};
return result;
};
// `streq` lives in sym.ww — same bundle, single definition.