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). R+X filesz/memsz stop at content rather // than absorbing the page padding before R+W. // // 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 rxfilesz: u64 = 0u64; let rxmemsz: u64 = 0u64; let rwoff: u64 = 0u64; 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; rxfilesz = testenv.leu64(o, ph + 32); rxmemsz = testenv.leu64(o, ph + 40); } else if (flags == 6u64) { sawrw = true; rwoff = testenv.leu64(o, ph + 8); } 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"); }; if (rxfilesz != rxmemsz) { fail("twoloads", "R+X filesz != memsz"); }; if (rxmemsz >= rwoff) { fail("twoloads", "R+X memsz includes page padding"); }; 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); };