Every section banner dies (103 -> 0) across test/lang, the observer suites, the C carriers, and the five comment-heavy corpus fixtures; banner provenance (#N cites, carrier numbers, repair-cluster labels) folded into headers or adjacent WHY comments. Narration deleted; row provenance, ref cites, divergence pins, and layout contracts kept (fwd-ref decl-order guards and bootstrap-gate corpus rationale restored where the sweep over-cut). Comment-only proven: all 3742 wwbuild workdir .s byte-identical before/after; test-commit and test-byteid (161 lang + 1399 data, 0 pinned-divergent) green.
244 lines
7.9 KiB
Plaintext
244 lines
7.9 KiB
Plaintext
package link_test;
|
|
|
|
// w6l LINK-level observers. Ports of the retired native carriers
|
|
// test/wcc/600_w6l.c and 620_data_link.c; every assertion preserved.
|
|
//
|
|
// staticexec (600) — w6c/w6a/w6l on a trivial program produce an
|
|
// ELF64 ET_EXEC. Strengthened over the carrier: the ldd host-tool
|
|
// proxy for staticness is replaced by a direct scan of every program
|
|
// header asserting no PT_INTERP.
|
|
//
|
|
// readglobal / writeglobal (620 test_read / test_write) — a DATAW
|
|
// global is readable (exit 42) and the R+W mapping truly writable
|
|
// (store 99, read back, exit 99).
|
|
//
|
|
// twoloads (620) — a DATAW input links to exactly 2 program headers,
|
|
// both PT_LOAD, exactly one R+X and one R+W (exact p_flags equality;
|
|
// any other combination fails).
|
|
//
|
|
// nodatasingleload (620) — an input without DATAW keeps the single
|
|
// PT_LOAD layout; test 992 (selfhost w6l diff, the terminal bootstrap
|
|
// gate) depends on that invariant.
|
|
//
|
|
// bssfilesz (620) — trailing .data zeros are trimmed from the file:
|
|
// the R+W PT_LOAD has p_filesz 1 < p_memsz 32 (the trim ignores
|
|
// symbol boundaries — nz's own zero tail goes too), and the loader
|
|
// zero-fills the gap (run exit 7).
|
|
//
|
|
// Dropped C machinery, not assertions: wwtestpkg.h package-clause
|
|
// injection and per-file unlink accounting (testenv.clean).
|
|
|
|
import os;
|
|
import os.exec;
|
|
import strings;
|
|
import testenv;
|
|
import time;
|
|
|
|
fn fail(label: str, why: str) void = {
|
|
let m: str = strings.concat("link FAIL: ", label, " -- ", why,
|
|
"\n");
|
|
os.write(2, m.ptr, m.len: u64);
|
|
assert(false);
|
|
};
|
|
|
|
fn tmo() time.duration = {
|
|
return (240i64 * (time.second: i64)): time.duration;
|
|
};
|
|
|
|
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
|
|
fn runcode(dir: str, name: str, argv: []str) i32 = {
|
|
let co: testenv.commandout;
|
|
testenv.runcommand(dir, dir, name, argv, tmo(), &co);
|
|
if (co.termination != exec.termination.EXIT) { return -1; };
|
|
return co.code;
|
|
};
|
|
|
|
// External-ABI constants (System V gABI, ELF64 executables) — the
|
|
// layout contract w6l's exe writer encodes: e_ident magic + class at
|
|
// 0..4, Ehdr e_type at 16 (u16), e_phoff at 32 (u64), e_phnum at 56
|
|
// (u16); Phdr entries of 56 bytes with p_type +0 (u32), p_flags +4
|
|
// (u32), p_filesz +32 (u64), p_memsz +40 (u64). PT_LOAD == 1,
|
|
// PT_INTERP == 3; PF_X == 1, PF_W == 2, PF_R == 4.
|
|
def PHENT: i32 = 56;
|
|
|
|
fn phnum(o: str) i32 = { return testenv.leu16(o, 56): i32; };
|
|
|
|
fn phoff(o: str, i: i32) i32 = {
|
|
return (testenv.leu64(o, 32): i32) + PHENT * i;
|
|
};
|
|
|
|
fn iself64(o: str) bool = {
|
|
if (o.len < 64) { return false; };
|
|
return o[0] == 0x7fu8 && o[1] == 0x45u8 && o[2] == 0x4cu8
|
|
&& o[3] == 0x46u8 && o[4] == 2u8;
|
|
};
|
|
|
|
fn build(td: str, tag: str, body: str) str = {
|
|
let src: str = strings.concat(td, "/", tag, ".s");
|
|
let obj: str = strings.concat(td, "/", tag, ".o");
|
|
let exe: str = strings.concat(td, "/", tag, ".x");
|
|
testenv.writefile(src, body);
|
|
let aav: []str = [testenv.driver("w6a"), "-o", obj, src];
|
|
if (runcode(td, strings.concat("a_", tag), aav) != 0) {
|
|
fail(tag, "w6a failed");
|
|
};
|
|
let lav: []str = [testenv.driver("w6l"), "-o", exe, obj];
|
|
if (runcode(td, strings.concat("l_", tag), lav) != 0) {
|
|
fail(tag, "w6l failed");
|
|
};
|
|
return exe;
|
|
};
|
|
|
|
fn runexit(td: str, tag: str, exe: str) i32 = {
|
|
let rav: []str = [exe];
|
|
return runcode(td, strings.concat("run_", tag), rav);
|
|
};
|
|
|
|
@test fn staticexec() void = {
|
|
let td: str = testenv.fresh();
|
|
let src: str = strings.concat(td, "/m.ww");
|
|
let asmf: str = strings.concat(td, "/m.s");
|
|
let obj: str = strings.concat(td, "/m.o");
|
|
let exe: str = strings.concat(td, "/m.x");
|
|
testenv.writefile(src,
|
|
"package main;\nfn main() i32 = { return 42; };\n");
|
|
let cav: []str = [testenv.driver("w6c"), "-o", asmf, src];
|
|
if (runcode(td, "c_m", cav) != 0) { fail("staticexec", "w6c failed"); };
|
|
let aav: []str = [testenv.driver("w6a"), "-o", obj, asmf];
|
|
if (runcode(td, "a_m", aav) != 0) { fail("staticexec", "w6a failed"); };
|
|
let lav: []str = [testenv.driver("w6l"), "-o", exe, obj];
|
|
if (runcode(td, "l_m", lav) != 0) { fail("staticexec", "w6l failed"); };
|
|
|
|
let o: str = testenv.readfile(exe);
|
|
if (!iself64(o)) { fail("staticexec", "not an ELF64"); };
|
|
// ET_EXEC == 2
|
|
if (testenv.leu16(o, 16) != 2u64) {
|
|
fail("staticexec", "e_type != ET_EXEC");
|
|
};
|
|
// static executable: no PT_INTERP anywhere in the phdr table
|
|
let i: i32 = 0;
|
|
for (i < phnum(o)) {
|
|
if (testenv.leu32(o, phoff(o, i)) == 3u64) {
|
|
fail("staticexec", "PT_INTERP present, want static");
|
|
};
|
|
i += 1;
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
@test fn readglobal() void = {
|
|
let td: str = testenv.fresh();
|
|
let exe: str = build(td, "rd", strings.concat(
|
|
"TEXT _start,$0\n",
|
|
"\tMOVQ\tcounter(SB), DI\n",
|
|
"\tMOVQ\t$60, AX\n",
|
|
"\tSYSCALL\n",
|
|
"DATAW counter(SB),\"\\x2a\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"));
|
|
if (runexit(td, "rd", exe) != 42) {
|
|
fail("readglobal", "exit != 42");
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
@test fn writeglobal() void = {
|
|
let td: str = testenv.fresh();
|
|
let exe: str = build(td, "wr", strings.concat(
|
|
"TEXT _start,$0\n",
|
|
"\tMOVQ\t$99, AX\n",
|
|
"\tMOVQ\tAX, counter(SB)\n",
|
|
"\tMOVQ\tcounter(SB), DI\n",
|
|
"\tMOVQ\t$60, AX\n",
|
|
"\tSYSCALL\n",
|
|
"DATAW counter(SB),\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"));
|
|
if (runexit(td, "wr", exe) != 99) {
|
|
fail("writeglobal", "exit != 99 (R+W mapping not writable?)");
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
@test fn twoloads() void = {
|
|
let td: str = testenv.fresh();
|
|
let exe: str = build(td, "tl", strings.concat(
|
|
"TEXT _start,$0\n",
|
|
"\tMOVQ\tcounter(SB), AX\n",
|
|
"\tMOVQ\t$60, AX\n",
|
|
"\tMOVQ\t$0, DI\n",
|
|
"\tSYSCALL\n",
|
|
"DATAW counter(SB),\"\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"));
|
|
let o: str = testenv.readfile(exe);
|
|
if (phnum(o) != 2) { fail("twoloads", "e_phnum != 2"); };
|
|
let sawrx: bool = false;
|
|
let sawrw: bool = false;
|
|
let i: i32 = 0;
|
|
for (i < 2) {
|
|
let ph: i32 = phoff(o, i);
|
|
if (testenv.leu32(o, ph) != 1u64) {
|
|
fail("twoloads", "phdr not PT_LOAD");
|
|
};
|
|
let flags: u64 = testenv.leu32(o, ph + 4);
|
|
if (flags == 5u64) { sawrx = true; }
|
|
else if (flags == 6u64) { sawrw = true; }
|
|
else {
|
|
fail("twoloads", "phdr flags not exactly R+X or R+W");
|
|
};
|
|
i += 1;
|
|
};
|
|
if (!sawrx) { fail("twoloads", "missing R+X PT_LOAD"); };
|
|
if (!sawrw) { fail("twoloads", "missing R+W PT_LOAD"); };
|
|
testenv.clean(td);
|
|
};
|
|
|
|
@test fn nodatasingleload() void = {
|
|
let td: str = testenv.fresh();
|
|
let exe: str = build(td, "nd", strings.concat(
|
|
"TEXT _start,$0\n",
|
|
"\tMOVQ\t$60, AX\n",
|
|
"\tMOVQ\t$0, DI\n",
|
|
"\tSYSCALL\n"));
|
|
let o: str = testenv.readfile(exe);
|
|
// 992 (selfhost w6l diff) depends on the single-PT_LOAD layout.
|
|
if (phnum(o) != 1) {
|
|
fail("nodatasingleload", "e_phnum != 1 (legacy layout lost)");
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
@test fn bssfilesz() void = {
|
|
let td: str = testenv.fresh();
|
|
// nz = \x07 + 7 zeros, zlong = 24 zeros: memsz covers all 32B,
|
|
// the file stops after the single non-zero byte.
|
|
let exe: str = build(td, "bss", strings.concat(
|
|
"TEXT _start,$0\n",
|
|
"\tMOVQ\tnz(SB), DI\n",
|
|
"\tMOVQ\t$60, AX\n",
|
|
"\tSYSCALL\n",
|
|
"DATAW nz(SB),\"\\x07\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n",
|
|
"DATAW zlong(SB),\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00",
|
|
"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00",
|
|
"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"));
|
|
let o: str = testenv.readfile(exe);
|
|
let sawrw: bool = false;
|
|
let i: i32 = 0;
|
|
for (i < phnum(o)) {
|
|
let ph: i32 = phoff(o, i);
|
|
if (testenv.leu32(o, ph) != 1u64) { i += 1; continue; };
|
|
if (testenv.leu32(o, ph + 4) != 6u64) { i += 1; continue; };
|
|
sawrw = true;
|
|
let filesz: u64 = testenv.leu64(o, ph + 32);
|
|
let memsz: u64 = testenv.leu64(o, ph + 40);
|
|
if (filesz >= memsz) {
|
|
fail("bssfilesz", "want filesz < memsz");
|
|
};
|
|
// the trim ignores symbol boundaries: nz's zero tail goes too
|
|
if (filesz != 1u64) { fail("bssfilesz", "filesz != 1"); };
|
|
if (memsz != 32u64) { fail("bssfilesz", "memsz != 32"); };
|
|
i += 1;
|
|
};
|
|
if (!sawrw) { fail("bssfilesz", "no R+W PT_LOAD found"); };
|
|
// the loader must zero-fill the BSS gap
|
|
if (runexit(td, "bss", exe) != 7) {
|
|
fail("bssfilesz", "exit != 7");
|
|
};
|
|
testenv.clean(td);
|
|
};
|