Files
ww/test/object/link_test.ww

260 lines
8.3 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). 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);
};