test: port the w6l link-layout observers to ww
link_test.ww absorbs 600_w6l and 620_data_link: ET_EXEC on the trivial pipeline, writable-global read/write runs, the exact two-PT_LOAD R+X/R+W split, the single-PT_LOAD no-DATAW invariant 992 depends on, and the BSS filesz trim (filesz 1 < memsz 32, loader zero-fill). Strengthened: the ldd staticness proxy is replaced by a direct no-PT_INTERP phdr scan.
This commit is contained in:
2
Makefile
2
Makefile
@@ -395,7 +395,7 @@ ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%)
|
|||||||
# layout, program-header split and BSS trim, archive selectivity,
|
# layout, program-header split and BSS trim, archive selectivity,
|
||||||
# linker flag capacity, assembler input gates). Compiler/driver gates
|
# linker flag capacity, assembler input gates). Compiler/driver gates
|
||||||
# like test/sep: they run under test-compiler.
|
# like test/sep: they run under test-compiler.
|
||||||
OBJECT_WW_TESTS = test/object/elfobj_test.ww
|
OBJECT_WW_TESTS = test/object/elfobj_test.ww test/object/link_test.ww
|
||||||
OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%)
|
OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%)
|
||||||
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||||
test/wcc/991_w6a_ww.c \
|
test/wcc/991_w6a_ww.c \
|
||||||
|
|||||||
256
test/object/link_test.ww
Normal file
256
test/object/link_test.ww
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
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) — 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// assemble+link a hand-written .s; returns the exe path.
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- staticexec (600) --------------------------------------------------
|
||||||
|
|
||||||
|
@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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- readglobal (620 test_read) ----------------------------------------
|
||||||
|
|
||||||
|
@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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- writeglobal (620 test_write) --------------------------------------
|
||||||
|
|
||||||
|
@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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- twoloads (620 test_two_loads) -------------------------------------
|
||||||
|
|
||||||
|
@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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- nodatasingleload (620 test_no_data_single_load) -------------------
|
||||||
|
|
||||||
|
@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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- bssfilesz (620 test_bss_filesz) -----------------------------------
|
||||||
|
|
||||||
|
@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);
|
||||||
|
};
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* 600_6l — linker smoke. Drive w6c → w6a → w6l on a tiny program,
|
|
||||||
* confirm the result is a static ELF executable with no PT_INTERP.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "wwtestpkg.h"
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
|
|
||||||
char src[64], asmf[64], obj[64], exe[64];
|
|
||||||
snprintf(src, sizeof src, "/tmp/wwt_%d.ww", getpid());
|
|
||||||
snprintf(asmf, sizeof asmf, "/tmp/wwt_%d.s", getpid());
|
|
||||||
snprintf(obj, sizeof obj, "/tmp/wwt_%d.o", getpid());
|
|
||||||
snprintf(exe, sizeof exe, "/tmp/wwt_%d.x", getpid());
|
|
||||||
|
|
||||||
FILE *f = fopen(src, "wb");
|
|
||||||
wwtest_fputs("fn main() i32 = { return 42; };", f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
char cmd[1024];
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, asmf, src);
|
|
||||||
if (system(cmd) != 0) { fprintf(stderr, "w6c failed\n"); return 1; }
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, asmf);
|
|
||||||
if (system(cmd) != 0) { fprintf(stderr, "w6a failed\n"); return 1; }
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s", bin, exe, obj);
|
|
||||||
if (system(cmd) != 0) { fprintf(stderr, "w6l failed\n"); return 1; }
|
|
||||||
|
|
||||||
/* validate ELF magic + e_type=EXEC */
|
|
||||||
FILE *of = fopen(exe, "rb");
|
|
||||||
if (of == NULL) { fprintf(stderr, "exe missing\n"); return 1; }
|
|
||||||
unsigned char hdr[20];
|
|
||||||
if (fread(hdr, 1, sizeof hdr, of) != sizeof hdr) { fprintf(stderr, "short exe\n"); fclose(of); return 1; }
|
|
||||||
fclose(of);
|
|
||||||
|
|
||||||
if (memcmp(hdr, "\x7f""ELF", 4) != 0 || hdr[4] != 2) {
|
|
||||||
fprintf(stderr, "not an ELF64\n"); return 1;
|
|
||||||
}
|
|
||||||
/* e_type at offset 16, little-endian u16; ET_EXEC = 2 */
|
|
||||||
unsigned short etype = (unsigned short)hdr[16] | ((unsigned short)hdr[17] << 8);
|
|
||||||
if (etype != 2) {
|
|
||||||
fprintf(stderr, "not ET_EXEC, got %u\n", etype); return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ldd "not a dynamic executable" — proxy: check that file is not
|
|
||||||
* dynamic by looking for PT_INTERP. We have none, so ldd reports
|
|
||||||
* "not a dynamic executable" or similar. */
|
|
||||||
snprintf(cmd, sizeof cmd, "ldd %s 2>&1", exe);
|
|
||||||
FILE *p = popen(cmd, "r");
|
|
||||||
char buf[256] = {0};
|
|
||||||
fread(buf, 1, sizeof buf - 1, p);
|
|
||||||
pclose(p);
|
|
||||||
if (strstr(buf, "not a dynamic executable") == NULL
|
|
||||||
&& strstr(buf, "statically linked") == NULL) {
|
|
||||||
fprintf(stderr, "ldd says not static: %s\n", buf);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
unlink(src); unlink(asmf); unlink(obj); unlink(exe);
|
|
||||||
printf("w6l: ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
/*
|
|
||||||
* 620_data_link — end-to-end w6l support for writable globals.
|
|
||||||
* Assemble a small .s using DATAW, link it with w6l, run the binary,
|
|
||||||
* and verify both that the read returns the global's value and that
|
|
||||||
* a store updates it. Also confirms the binary has two PT_LOADs with
|
|
||||||
* the right R+X / R+W flags.
|
|
||||||
*
|
|
||||||
* Inputs without DATAW must still produce the single-PT_LOAD layout
|
|
||||||
* — 992 (selfhost w6l .o diff) depends on that invariant.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
#define PT_LOAD 1
|
|
||||||
#define PF_X 1
|
|
||||||
#define PF_W 2
|
|
||||||
#define PF_R 4
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
typedef struct {
|
|
||||||
uint8_t e_ident[16];
|
|
||||||
uint16_t e_type, e_machine;
|
|
||||||
uint32_t e_version;
|
|
||||||
uint64_t e_entry, e_phoff, e_shoff;
|
|
||||||
uint32_t e_flags;
|
|
||||||
uint16_t e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx;
|
|
||||||
} Ehdr;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint32_t p_type, p_flags;
|
|
||||||
uint64_t p_offset, p_vaddr, p_paddr;
|
|
||||||
uint64_t p_filesz, p_memsz, p_align;
|
|
||||||
} Phdr;
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
static int
|
|
||||||
write_file(const char *path, const char *body)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "wb");
|
|
||||||
if (!f) return -1;
|
|
||||||
fputs(body, f);
|
|
||||||
fclose(f);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
slurp(const char *path, uint8_t **out, size_t *outn)
|
|
||||||
{
|
|
||||||
FILE *f = fopen(path, "rb");
|
|
||||||
if (!f) return -1;
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
long n = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
uint8_t *b = malloc((size_t)n);
|
|
||||||
if (fread(b, 1, (size_t)n, f) != (size_t)n) {
|
|
||||||
free(b);
|
|
||||||
fclose(f);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
*out = b;
|
|
||||||
*outn = (size_t)n;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
build(const char *bin, const char *body, const char *exe)
|
|
||||||
{
|
|
||||||
char src[64], obj[64], cmd[1024];
|
|
||||||
snprintf(src, sizeof src, "/tmp/wwt_dl_%d.s", getpid());
|
|
||||||
snprintf(obj, sizeof obj, "/tmp/wwt_dl_%d.o", getpid());
|
|
||||||
if (write_file(src, body) < 0) return -1;
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, src);
|
|
||||||
if (system(cmd) != 0) { unlink(src); return -1; }
|
|
||||||
snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s", bin, exe, obj);
|
|
||||||
int rc = system(cmd);
|
|
||||||
unlink(src); unlink(obj);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_exit(const char *exe)
|
|
||||||
{
|
|
||||||
int rc = system(exe);
|
|
||||||
if (rc == -1) return -1;
|
|
||||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test 1: read a writable global. _start exits with the value stored
|
|
||||||
* in `counter` (0x2a == 42). */
|
|
||||||
static int
|
|
||||||
test_read(const char *bin)
|
|
||||||
{
|
|
||||||
char exe[64];
|
|
||||||
snprintf(exe, sizeof exe, "/tmp/wwt_dl_%d.x1", getpid());
|
|
||||||
const char *body =
|
|
||||||
"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 (build(bin, body, exe) != 0) {
|
|
||||||
fprintf(stderr, "test_read: build failed\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int rc = run_exit(exe);
|
|
||||||
unlink(exe);
|
|
||||||
if (rc != 42) {
|
|
||||||
fprintf(stderr, "test_read: exit=%d, want 42\n", rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test 2: store to a writable global, read it back, exit with the new
|
|
||||||
* value. Verifies the R+W mapping really is writable. */
|
|
||||||
static int
|
|
||||||
test_write(const char *bin)
|
|
||||||
{
|
|
||||||
char exe[64];
|
|
||||||
snprintf(exe, sizeof exe, "/tmp/wwt_dl_%d.x2", getpid());
|
|
||||||
const char *body =
|
|
||||||
"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 (build(bin, body, exe) != 0) {
|
|
||||||
fprintf(stderr, "test_write: build failed\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int rc = run_exit(exe);
|
|
||||||
unlink(exe);
|
|
||||||
if (rc != 99) {
|
|
||||||
fprintf(stderr, "test_write: exit=%d, want 99\n", rc);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test 3: structurally inspect the linked ELF — two PT_LOADs, the
|
|
||||||
* first R+X and the second R+W. Catches regressions where the layout
|
|
||||||
* is right but the flags or count are off. */
|
|
||||||
static int
|
|
||||||
test_two_loads(const char *bin)
|
|
||||||
{
|
|
||||||
char exe[64];
|
|
||||||
snprintf(exe, sizeof exe, "/tmp/wwt_dl_%d.x3", getpid());
|
|
||||||
const char *body =
|
|
||||||
"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";
|
|
||||||
if (build(bin, body, exe) != 0) {
|
|
||||||
fprintf(stderr, "test_two_loads: build failed\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
uint8_t *buf = NULL;
|
|
||||||
size_t n = 0;
|
|
||||||
if (slurp(exe, &buf, &n) < 0) {
|
|
||||||
fprintf(stderr, "test_two_loads: cannot read exe\n");
|
|
||||||
unlink(exe);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
unlink(exe);
|
|
||||||
|
|
||||||
const Ehdr *eh = (const Ehdr *)buf;
|
|
||||||
int rc = 0;
|
|
||||||
if (eh->e_phnum != 2) {
|
|
||||||
fprintf(stderr, "test_two_loads: e_phnum=%u, want 2\n", eh->e_phnum);
|
|
||||||
rc = -1;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
const Phdr *ph = (const Phdr *)(buf + eh->e_phoff);
|
|
||||||
int saw_rx = 0, saw_rw = 0;
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
if (ph[i].p_type != PT_LOAD) {
|
|
||||||
fprintf(stderr, "test_two_loads: ph[%d] not PT_LOAD\n", i);
|
|
||||||
rc = -1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ph[i].p_flags == (PF_R | PF_X)) saw_rx = 1;
|
|
||||||
else if (ph[i].p_flags == (PF_R | PF_W)) saw_rw = 1;
|
|
||||||
else {
|
|
||||||
fprintf(stderr, "test_two_loads: ph[%d] flags=0x%x\n",
|
|
||||||
i, ph[i].p_flags);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!saw_rx) { fprintf(stderr, "test_two_loads: missing R+X\n"); rc = -1; }
|
|
||||||
if (!saw_rw) { fprintf(stderr, "test_two_loads: missing R+W\n"); rc = -1; }
|
|
||||||
out:
|
|
||||||
free(buf);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test 4: an input without DATAW still produces a single PT_LOAD.
|
|
||||||
* 992 (selfhost w6l .o diff) depends on this byte-level invariant. */
|
|
||||||
static int
|
|
||||||
test_no_data_single_load(const char *bin)
|
|
||||||
{
|
|
||||||
char exe[64];
|
|
||||||
snprintf(exe, sizeof exe, "/tmp/wwt_dl_%d.x4", getpid());
|
|
||||||
const char *body =
|
|
||||||
"TEXT _start,$0\n"
|
|
||||||
"\tMOVQ\t$60, AX\n"
|
|
||||||
"\tMOVQ\t$0, DI\n"
|
|
||||||
"\tSYSCALL\n";
|
|
||||||
if (build(bin, body, exe) != 0) {
|
|
||||||
fprintf(stderr, "test_no_data_single_load: build failed\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
uint8_t *buf = NULL;
|
|
||||||
size_t n = 0;
|
|
||||||
if (slurp(exe, &buf, &n) < 0) {
|
|
||||||
fprintf(stderr, "test_no_data_single_load: cannot read exe\n");
|
|
||||||
unlink(exe);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
unlink(exe);
|
|
||||||
int rc = 0;
|
|
||||||
const Ehdr *eh = (const Ehdr *)buf;
|
|
||||||
if (eh->e_phnum != 1) {
|
|
||||||
fprintf(stderr, "test_no_data_single_load: e_phnum=%u, want 1\n",
|
|
||||||
eh->e_phnum);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Test 5: BSS optimisation — trailing zero bytes in .data are dropped
|
|
||||||
* from the file (p_filesz < p_memsz) and the loader zero-fills the gap.
|
|
||||||
* Asserts that a trailing zero-init slot doesn't bloat the binary. */
|
|
||||||
static int
|
|
||||||
test_bss_filesz(const char *bin)
|
|
||||||
{
|
|
||||||
char exe[64];
|
|
||||||
snprintf(exe, sizeof exe, "/tmp/wwt_dl_%d.x5", getpid());
|
|
||||||
/* Two DATAW slots: a 1-byte non-zero followed by a 31-byte zero
|
|
||||||
* run. Memsz must cover all 32B; filesz should stop after the 1B
|
|
||||||
* non-zero (so file_data == 1, memsz_data == 32). */
|
|
||||||
const char *body =
|
|
||||||
"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";
|
|
||||||
if (build(bin, body, exe) != 0) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: build failed\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
uint8_t *buf = NULL;
|
|
||||||
size_t n = 0;
|
|
||||||
if (slurp(exe, &buf, &n) < 0) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: cannot read exe\n");
|
|
||||||
unlink(exe);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int rc = 0;
|
|
||||||
const Ehdr *eh = (const Ehdr *)buf;
|
|
||||||
const Phdr *ph = (const Phdr *)(buf + eh->e_phoff);
|
|
||||||
int saw_rw = 0;
|
|
||||||
for (int i = 0; i < eh->e_phnum; i++) {
|
|
||||||
if (ph[i].p_type != PT_LOAD) continue;
|
|
||||||
if (ph[i].p_flags != (PF_R | PF_W)) continue;
|
|
||||||
saw_rw = 1;
|
|
||||||
if (ph[i].p_filesz >= ph[i].p_memsz) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: filesz=%lu, "
|
|
||||||
"memsz=%lu (want filesz<memsz)\n",
|
|
||||||
(unsigned long)ph[i].p_filesz,
|
|
||||||
(unsigned long)ph[i].p_memsz);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
/* The scan trims every trailing zero — it doesn't know
|
|
||||||
* symbol boundaries. nz is \x07 then 7 zero bytes, so
|
|
||||||
* the file ends at byte 1 (the 0x07). Memsz still covers
|
|
||||||
* all 32B and the loader zero-fills. */
|
|
||||||
if (ph[i].p_filesz != 1) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: filesz=%lu, "
|
|
||||||
"want 1\n", (unsigned long)ph[i].p_filesz);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
if (ph[i].p_memsz != 32) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: memsz=%lu, "
|
|
||||||
"want 32\n", (unsigned long)ph[i].p_memsz);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!saw_rw) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: no R+W PT_LOAD found\n");
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
if (rc != 0) { unlink(exe); return rc; }
|
|
||||||
/* Behaviour check: the zero-init slot must still read as zero
|
|
||||||
* at runtime — the loader has to zero-fill the BSS gap. */
|
|
||||||
int er = run_exit(exe);
|
|
||||||
unlink(exe);
|
|
||||||
if (er != 7) {
|
|
||||||
fprintf(stderr, "test_bss_filesz: exit=%d, want 7\n", er);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
int fail = 0;
|
|
||||||
if (test_read(bin) != 0) fail++;
|
|
||||||
if (test_write(bin) != 0) fail++;
|
|
||||||
if (test_two_loads(bin) != 0) fail++;
|
|
||||||
if (test_no_data_single_load(bin) != 0) fail++;
|
|
||||||
if (test_bss_filesz(bin) != 0) fail++;
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "data_link: %d/5 subtests failed\n", fail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("data_link: 5/5 ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user