// 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 readallso(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.readfull(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, 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 loadso(l: *lnk, path: *u8) i32 = { let buf: *u8; let blen: u64; buf, blen = readallso(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 (d_u16(buf, EH_EMACHINE) != EM_X86_64_SO) { return soerr("not amd64"); }; if (d_u16(buf, EH_ETYPE) != ET_DYN_SO) { return soerr("not ET_DYN"); }; let shoff: u64 = d_u64(buf, EH_SHOFF); let shnum: u32 = d_u16(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 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 soerr("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); }; // 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 // soprovides_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); 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 = vdnameat(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 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.