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.
407 lines
11 KiB
Plaintext
407 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 d_u16(p: *u8, off: u64) u16 = {
|
|
let b0: u16 = p[off]: u16;
|
|
let b1: u16 = p[off + 1u64]: u16;
|
|
return b0 | (b1 << 8u16);
|
|
};
|
|
|
|
fn d_u32(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 d_u64(p: *u8, off: u64) u64 = {
|
|
let lo: u64 = d_u32(p, off): u64;
|
|
let hi: u64 = d_u32(p, off + 4u64): u64;
|
|
return lo | (hi << 32u64);
|
|
};
|
|
|
|
fn d_i64(p: *u8, off: u64) i64 = {
|
|
return d_u64(p, off): i64;
|
|
};
|
|
|
|
// ---- C-string helpers --------------------------------------------------
|
|
|
|
fn d_cstrlen(p: *u8) u64 = {
|
|
let n: u64 = 0u64;
|
|
for (p[n] != 0u8) { n += 1u64; };
|
|
return n;
|
|
};
|
|
|
|
fn d_cstr_to_str(a: *arena, p: *u8) str = {
|
|
let n: u64 = d_cstrlen(p);
|
|
return astrndup(a, p, n);
|
|
};
|
|
|
|
// basename: scan for last '/' and return pointer past it.
|
|
fn d_basename(p: *u8) *u8 = {
|
|
let n: u64 = d_cstrlen(p);
|
|
let i: u64 = n;
|
|
for (i > 0u64) {
|
|
i -= 1u64;
|
|
if (p[i] == 47u8) { // '/'
|
|
return p + i + 1u64;
|
|
};
|
|
};
|
|
return p;
|
|
};
|
|
|
|
// ---- file slurp --------------------------------------------------------
|
|
|
|
fn read_all_so(path_cs: *u8) (*u8, u64) = {
|
|
let fd: i32 = os.open(path_cs, 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.readfull(fd, buf, n: u64);
|
|
os.close(fd);
|
|
if (got != n) { return nil, 0u64; };
|
|
return buf, n: u64;
|
|
};
|
|
|
|
// ---- verdef helpers ----------------------------------------------------
|
|
|
|
// vd_name_at — 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 vd_name_at(buf: *u8, verdef_off: u64, verdef_size: u64,
|
|
verstr: *u8, ndx: u16) *u8 = {
|
|
let off: u64 = 0u64;
|
|
for (off < verdef_size) {
|
|
let vd_p: u64 = verdef_off + off;
|
|
let vd_ndx: u16 = d_u16(buf, vd_p + VD_NDX);
|
|
let vd_aux: u32 = d_u32(buf, vd_p + VD_AUX);
|
|
let vd_next: u32 = d_u32(buf, vd_p + VD_NEXT);
|
|
if (vd_ndx == ndx) {
|
|
let aux_p: u64 = vd_p + (vd_aux: u64);
|
|
let vda_name: u32 = d_u32(buf, aux_p + VA_NAME);
|
|
return verstr + (vda_name: u64);
|
|
};
|
|
if (vd_next == 0u32) { return nil; };
|
|
off += vd_next: u64;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// ---- entry points ------------------------------------------------------
|
|
|
|
export fn l_load_so(l: *lnk, path_cs: *u8) i32 = {
|
|
let buf: *u8;
|
|
let blen: u64;
|
|
buf, blen = read_all_so(path_cs);
|
|
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 so_err("not ELF"); };
|
|
if (buf[1u64] != 69u8) { return so_err("not ELF"); };
|
|
if (buf[2u64] != 76u8) { return so_err("not ELF"); };
|
|
if (buf[3u64] != 70u8) { return so_err("not ELF"); };
|
|
if (buf[4u64] != 2u8) { return so_err("not ELFCLASS64"); };
|
|
if (d_u16(buf, EH_EMACHINE) != EM_X86_64_SO) {
|
|
return so_err("not amd64");
|
|
};
|
|
if (d_u16(buf, EH_ETYPE) != ET_DYN_SO) {
|
|
return so_err("not ET_DYN");
|
|
};
|
|
|
|
let shoff: u64 = d_u64(buf, EH_SHOFF);
|
|
let shnum: u32 = d_u16(buf, EH_SHNUM): u32;
|
|
if (shoff == 0u64) { return so_err("stripped .so unsupported"); };
|
|
if (shnum == 0u32) { return so_err("stripped .so unsupported"); };
|
|
|
|
// Locate the four sections we care about.
|
|
let idx_dynsym: i32 = -1;
|
|
let idx_dynamic: i32 = -1;
|
|
let idx_versym: i32 = -1;
|
|
let idx_verdef: i32 = -1;
|
|
let i: u32 = 0u32;
|
|
for (i < shnum) {
|
|
let sh_p: u64 = shoff + (i: u64) * SH_SIZE;
|
|
let sh_type: u32 = d_u32(buf, sh_p + SH_TYPE);
|
|
if (sh_type == SHT_DYNSYM) { idx_dynsym = i: i32; };
|
|
if (sh_type == SHT_DYNAMIC) { idx_dynamic = i: i32; };
|
|
if (sh_type == SHT_GNU_VERSYM) { idx_versym = i: i32; };
|
|
if (sh_type == SHT_GNU_VERDEF) { idx_verdef = i: i32; };
|
|
i += 1u32;
|
|
};
|
|
if (idx_dynsym < 0) {
|
|
return so_err("no .dynsym");
|
|
};
|
|
|
|
let dynsym_sh: u64 = shoff + (idx_dynsym: u64) * SH_SIZE;
|
|
let dynsym_off: u64 = d_u64(buf, dynsym_sh + SH_OFFSET);
|
|
let dynsym_size: u64 = d_u64(buf, dynsym_sh + SH_SIZE_F);
|
|
let dynsym_link: u32 = d_u32(buf, dynsym_sh + SH_LINK);
|
|
let nsyms: u64 = dynsym_size / SY_SIZE;
|
|
|
|
let dynstr_sh: u64 = shoff + (dynsym_link: u64) * SH_SIZE;
|
|
let dynstr_off: u64 = d_u64(buf, dynstr_sh + SH_OFFSET);
|
|
let dynstr: *u8 = buf + dynstr_off;
|
|
|
|
// SONAME: .dynamic strings live in the section pointed at by its
|
|
// sh_link (almost always .dynstr).
|
|
let soname_cs: *u8 = nil;
|
|
if (idx_dynamic >= 0) {
|
|
let dyn_sh: u64 = shoff + (idx_dynamic: u64) * SH_SIZE;
|
|
let dyn_off: u64 = d_u64(buf, dyn_sh + SH_OFFSET);
|
|
let dyn_size: u64 = d_u64(buf, dyn_sh + SH_SIZE_F);
|
|
let dyn_link: u32 = d_u32(buf, dyn_sh + SH_LINK);
|
|
let dstr_sh: u64 = shoff + (dyn_link: u64) * SH_SIZE;
|
|
let dstr_off: u64 = d_u64(buf, dstr_sh + SH_OFFSET);
|
|
let dstr: *u8 = buf + dstr_off;
|
|
let nd: u64 = dyn_size / DY_SIZE;
|
|
let di: u64 = 0u64;
|
|
for (di < nd) {
|
|
let d_p: u64 = dyn_off + di * DY_SIZE;
|
|
let d_tag: i64 = d_i64(buf, d_p + DY_TAG);
|
|
if (d_tag == DT_NULL_TAG) {
|
|
di = nd; // break
|
|
} else {
|
|
if (d_tag == DT_SONAME_TAG) {
|
|
let d_val: u64 = d_u64(buf, d_p + DY_VAL);
|
|
soname_cs = dstr + d_val;
|
|
di = nd; // break
|
|
} else {
|
|
di += 1u64;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (soname_cs == nil) {
|
|
soname_cs = d_basename(path_cs);
|
|
};
|
|
|
|
// Versym is one u16 per dynsym entry.
|
|
let versym_off: u64 = 0u64;
|
|
let has_versym: i32 = 0;
|
|
if (idx_versym >= 0) {
|
|
let vs_sh: u64 = shoff + (idx_versym: u64) * SH_SIZE;
|
|
versym_off = d_u64(buf, vs_sh + SH_OFFSET);
|
|
has_versym = 1;
|
|
};
|
|
|
|
// Verdef section bounds + the .dynstr-like string section it uses.
|
|
let verdef_off: u64 = 0u64;
|
|
let verdef_size: u64 = 0u64;
|
|
let verstr: *u8 = nil;
|
|
if (idx_verdef >= 0) {
|
|
let vd_sh: u64 = shoff + (idx_verdef: u64) * SH_SIZE;
|
|
verdef_off = d_u64(buf, vd_sh + SH_OFFSET);
|
|
verdef_size = d_u64(buf, vd_sh + SH_SIZE_F);
|
|
let vd_link: u32 = d_u32(buf, vd_sh + SH_LINK);
|
|
let vstr_sh: u64 = shoff + (vd_link: u64) * SH_SIZE;
|
|
let vstr_off: u64 = d_u64(buf, vstr_sh + SH_OFFSET);
|
|
verstr = buf + vstr_off;
|
|
};
|
|
|
|
// Build the lso. Exports are appended in dynsym order so
|
|
// l_so_provides_v's first-match semantics match the C version.
|
|
let so: *lso = amalloc(l.a, 64u64): *lso;
|
|
so.path = d_cstr_to_str(l.a, path_cs);
|
|
so.soname = d_cstr_to_str(l.a, soname_cs);
|
|
so.exports = nil;
|
|
let tail: *lexport = nil;
|
|
|
|
let si: u64 = 1u64;
|
|
for (si < nsyms) {
|
|
let s_p: u64 = dynsym_off + si * SY_SIZE;
|
|
let st_shndx: u16 = d_u16(buf, s_p + SY_SHNDX);
|
|
if (st_shndx == 0u16) { si += 1u64; } else {
|
|
let st_info: u8 = buf[s_p + SY_INFO];
|
|
let bind: u32 = (st_info: u32) >> 4u32;
|
|
if (bind != 1u32) { if (bind != 2u32) {
|
|
// not GLOBAL/WEAK
|
|
si += 1u64;
|
|
continue;
|
|
}; };
|
|
let st_name: u32 = d_u32(buf, s_p + SY_NAME);
|
|
let nm_p: *u8 = dynstr + (st_name: u64);
|
|
if (nm_p[0u64] == 0u8) {
|
|
si += 1u64;
|
|
continue;
|
|
};
|
|
|
|
// Determine version. Skip non-default (hidden) and
|
|
// local entries.
|
|
let vername_cs: *u8 = nil;
|
|
let keep: i32 = 1;
|
|
if (has_versym != 0) {
|
|
let v: u16 = d_u16(buf, versym_off + 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) {
|
|
vername_cs = 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.)
|
|
vername_cs = nil;
|
|
} else {
|
|
if (verstr != nil) {
|
|
let nm: *u8 = vd_name_at(buf, verdef_off, verdef_size, verstr, vidx);
|
|
vername_cs = nm;
|
|
};
|
|
}; }; };
|
|
};
|
|
};
|
|
|
|
if (keep != 0) {
|
|
let e: *lexport = amalloc(l.a, 48u64): *lexport;
|
|
e.name = d_cstr_to_str(l.a, nm_p);
|
|
if (vername_cs == nil) {
|
|
e.version.ptr = nil;
|
|
e.version.len = 0i32;
|
|
} else {
|
|
e.version = d_cstr_to_str(l.a, vername_cs);
|
|
};
|
|
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 so_err(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;
|
|
};
|
|
|
|
// l_so_provides — 1 if so exports name, 0 otherwise.
|
|
export fn l_so_provides(so: *lso, name: str) i32 = {
|
|
if (so == nil) { return 0; };
|
|
let e: *lexport = so.exports;
|
|
for (e != nil) {
|
|
if (s_eq(e.name, name)) { return 1; };
|
|
e = e.enext;
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
// l_so_version — 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 l_so_version(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 (s_eq(e.name, name)) {
|
|
result.ptr = e.version.ptr;
|
|
result.len = e.version.len;
|
|
return result;
|
|
};
|
|
e = e.enext;
|
|
};
|
|
return result;
|
|
};
|
|
|
|
fn s_eq(a: str, b: str) bool = {
|
|
if (a.len != b.len) { return false; };
|
|
let i: i32 = 0;
|
|
for (i < a.len) {
|
|
if (a[i] != b[i]) { return false; };
|
|
i += 1;
|
|
};
|
|
return true;
|
|
};
|