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.
This commit is contained in:
2026-08-09 01:07:19 +09:00
parent 4fe2e25da4
commit 5006bf4fb9
5 changed files with 58 additions and 14 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
};

View File

@@ -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;
};
};

View File

@@ -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;
};
};
};

View File

@@ -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;
};
};