From 5006bf4fb93ebf8a5388ba421fe0f326dd699a70 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 9 Aug 2026 01:07:19 +0900 Subject: [PATCH] w6l: link GNU long-name archive members; bad members fail the link The archive walk skipped every member whose header name starts with '/', which also dropped GNU long-name members (/N) -- any member with a >15-char filename silently never linked. Only '/ ' (symbol index) and '//' (long-name table) are non-objects; /N members index by content (names are never consulted). A member that fails loadimage in the selective pull also stayed silently unlinked with rc=0; it now reports and fails the link (rule 7). wwstage w6l diagnostics ride the same .len idiom as the w6a sweep. --- cmd/w6l/obj.c | 19 ++++++++++++++++++- selfhost/cmd/w6l/dyn.ww | 12 ++++++++---- selfhost/cmd/w6l/dynout.ww | 9 ++++++--- selfhost/cmd/w6l/obj.ww | 23 ++++++++++++++++++++--- selfhost/cmd/w6l/pass.ww | 9 ++++++--- 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/cmd/w6l/obj.c b/cmd/w6l/obj.c index f56484d1..1a8a53ce 100644 --- a/cmd/w6l/obj.c +++ b/cmd/w6l/obj.c @@ -185,7 +185,16 @@ load_archive(Lnk *l, const char *path, u8 *buf, u64 len) u64 size = ar_field(hdr + 48, 10); u64 hdr_end = pos + 60; if (hdr_end + size > len) break; - if (hdr[0] != '/' && hdr[0] != 0 && hdr[0] != ' ') { + /* '/'-led names: '/ ' is the symbol index and '//' the GNU + * long-name table — neither is an object. '/N' (digits) IS + * a real member whose name lives in that table; names are + * never consulted here, so index it by content. Pre-fix + * every long-named (>15 char) member was silently skipped + * and its definitions never linked. */ + int special = (hdr[0] == '/' + && (hdr[1] == ' ' || hdr[1] == '/')) + || hdr[0] == 0 || hdr[0] == ' '; + if (!special) { ArMember *m = calloc(1, sizeof *m); m->size = size; m->data = malloc((size_t)size); @@ -213,6 +222,14 @@ load_archive(Lnk *l, const char *path, u8 *buf, u64 len) if (load_image(l, path, copy, m->size) == 0) { m->loaded = 1; changed = 1; + } else { + /* a member that fails to parse must fail + * the link, not stay silently unlinked + * (rule 7). Consumed either way. */ + fprintf(stderr, + "w6l: %s: bad archive member\n", path); + l->errs++; + m->loaded = 1; } } } diff --git a/selfhost/cmd/w6l/dyn.ww b/selfhost/cmd/w6l/dyn.ww index 50aa4b90..e4caa0ee 100644 --- a/selfhost/cmd/w6l/dyn.ww +++ b/selfhost/cmd/w6l/dyn.ww @@ -168,11 +168,13 @@ export fn loadso(l: *lnk, path: *u8) i32 = { let blen: u64; buf, blen = slurpso(path); if (buf == nil) { - os.write(2, "w6l: cannot read .so\n".ptr, 20u64); + let wm1: str = "w6l: cannot read .so\n"; + os.write(2, wm1.ptr, wm1.len: u64); return -1; }; if (blen < 64u64) { - os.write(2, "w6l: short ELF\n".ptr, 14u64); + let wm2: str = "w6l: short ELF\n"; + os.write(2, wm2.ptr, wm2.len: u64); return -1; }; if (buf[0u64] != 127u8) { return soerr("not ELF"); }; @@ -349,9 +351,11 @@ export fn loadso(l: *lnk, path: *u8) i32 = { }; fn soerr(msg: str) i32 = { - os.write(2, "w6l: ".ptr, 4u64); + let wm3: str = "w6l: "; + os.write(2, wm3.ptr, wm3.len: u64); os.write(2, msg.ptr, msg.len: u64); - os.write(2, "\n".ptr, 1u64); + let wm4: str = "\n"; + os.write(2, wm4.ptr, wm4.len: u64); return -1; }; diff --git a/selfhost/cmd/w6l/dynout.ww b/selfhost/cmd/w6l/dynout.ww index 6b867c35..c56c0b8c 100644 --- a/selfhost/cmd/w6l/dynout.ww +++ b/selfhost/cmd/w6l/dynout.ww @@ -171,7 +171,8 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let i: i32 = 0; for (i < n) { if (dynsyms[i] == nil) { - os.write(2, "w6l: dynout: no sym for plt_idx\n".ptr, 31u64); + let wm1: str = "w6l: dynout: no sym for plt_idx\n"; + os.write(2, wm1.ptr, wm1.len: u64); return 1; }; i += 1; @@ -602,7 +603,8 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; dwri64(dynamicbuf.ptr, dk * 16u64, DT_NULL); dwr64(dynamicbuf.ptr, dk * 16u64 + 8u64, 0u64); dk += 1u64; if (dk != ndyn) { - os.write(2, "w6l: dynamic entry count mismatch\n".ptr, 33u64); + let wm2: str = "w6l: dynamic entry count mismatch\n"; + os.write(2, wm2.ptr, wm2.len: u64); return 1; }; @@ -613,7 +615,8 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { if (rsym.isdyn != 0) { if (r.kind != R_X86_64_PC32_D) { if (r.kind != R_X86_64_PLT32_D) { - os.write(2, "w6l: dynamic reloc kind unsupported\n".ptr, 35u64); + let wm3: str = "w6l: dynamic reloc kind unsupported\n"; + os.write(2, wm3.ptr, wm3.len: u64); return 1; }; }; diff --git a/selfhost/cmd/w6l/obj.ww b/selfhost/cmd/w6l/obj.ww index 0f4642c9..622827a3 100644 --- a/selfhost/cmd/w6l/obj.ww +++ b/selfhost/cmd/w6l/obj.ww @@ -316,9 +316,17 @@ fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let hdrend: u64 = pos + 60u64; if (hdrend + hdrsize > len) { break; }; let first: u8 = buf[pos]; - // Skip the symbol table ('/'), long-name table ('//'), and - // any padding entries (NUL or space leading byte). - if (first != '/' && first != 0u8 && first != ' ') { + // '/'-led names: '/ ' is the symbol index and '//' the GNU + // long-name table — neither is an object. '/N' (digits) IS + // a real member whose name lives in that table; names are + // never consulted here, so index it by content. Pre-fix + // every long-named (>15 char) member was silently skipped + // and its definitions never linked. + let second: u8 = buf[pos + 1u64]; + let special: bool = (first == '/' + && (second == ' ' || second == '/')) + || first == 0u8 || first == ' '; + if (!special) { let m: *armember = alloc(armember { size = hdrsize })!; let mbs: []u8 = alloc([], hdrsize)!; let mb: *u8 = mbs.ptr; @@ -347,6 +355,15 @@ fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { if (loadimage(l, path, m.data, m.size) == 0) { m.loaded = 1; changed = 1; + } else { + // a member that fails to parse must + // fail the link, not stay silently + // unlinked (rule 7). Consumed either + // way. + let mbad: str = "w6l: bad archive member\n"; + os.write(2, mbad.ptr, mbad.len: u64); + l.errs += 1; + m.loaded = 1; }; }; }; diff --git a/selfhost/cmd/w6l/pass.ww b/selfhost/cmd/w6l/pass.ww index e67b4872..493248a8 100644 --- a/selfhost/cmd/w6l/pass.ww +++ b/selfhost/cmd/w6l/pass.ww @@ -56,10 +56,12 @@ export fn resolve(l: *lnk) i32 = { if (r2.sym != nil) { if (r2.sym.defined == 0) { if (r2.sym.isdyn == 0) { - os.write(2, "w6l: undefined reference to '".ptr, 28u64); + let wm1: str = "w6l: undefined reference to '"; + os.write(2, wm1.ptr, wm1.len: u64); let nm: str = r2.sym.name; os.write(2, nm.ptr, nm.len: u64); - os.write(2, "'\n".ptr, 2u64); + let wm2: str = "'\n"; + os.write(2, wm2.ptr, wm2.len: u64); l.errs += 1; }; }; @@ -113,7 +115,8 @@ export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = { patchu64(l.text + r.off, v); }; case: - os.write(2, "w6l: unsupported reloc kind\n".ptr, 27u64); + let wm3: str = "w6l: unsupported reloc kind\n"; + os.write(2, wm3.ptr, wm3.len: u64); l.errs += 1; }; };