diff --git a/Makefile b/Makefile index e65aefec..088ffcf2 100644 --- a/Makefile +++ b/Makefile @@ -388,6 +388,15 @@ ASM_WW_TESTS = test/asm/modshadow_test.ww test/asm/sret_test.ww \ test/asm/callarg_test.ww test/asm/chain_test.ww \ test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) + +# Ww-native assembler/linker OBJECT-level observers: single-file ww +# tests under test/object/ on the test/testenv helper package, porting +# the residual w6a/w6l artifact C carriers (ELF section/symbol/reloc +# layout, program-header split and BSS trim, archive selectivity, +# linker flag capacity, assembler input gates). Compiler/driver gates +# like test/sep: they run under test-compiler. +OBJECT_WW_TESTS = test/object/elfobj_test.ww +OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \ @@ -473,7 +482,8 @@ test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww $(BIN)/w6a_ww \ # gates that cannot be represented by a single compiler fixture, plus the # ww-native sep observers that replaced the 989_sep* carriers. test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \ - $(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) $(ASM_WW_TARGETS) + $(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) $(ASM_WW_TARGETS) \ + $(OBJECT_WW_TARGETS) @$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS) @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ @@ -543,6 +553,18 @@ $(ASM_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ -I $(CURDIR)/test $* +# The object observers feed hand-written .s (and w6c output) to the +# assemblers and linkers of both stages and parse the resulting ELF +# artifacts, so w6c plus both stages' w6a/w6l are prerequisites; +# libwwrt.a carries the $(OBJ)/rt/start.o the archive rows link. +$(OBJECT_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \ + $(BIN)/w6l $(LIB)/libwwrt.a $(BIN)/w6a_ww $(BIN)/w6l_ww + @echo "ww test $*" + @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) + @WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \ + -w $(WWBUILD)/wwtest/$(subst /,_,$*) \ + -I $(CURDIR)/test $* + test-lang: $(LANG_TEST_TARGETS) test-library: $(LIBRARY_TEST_TARGETS) $(BIN)/w6c $(BIN)/w6c_ww \ diff --git a/test/object/elfobj_test.ww b/test/object/elfobj_test.ww new file mode 100644 index 00000000..838e33c6 --- /dev/null +++ b/test/object/elfobj_test.ww @@ -0,0 +1,341 @@ +package elfobj_test; + +// w6a OBJECT-level observers. Ports of the retired native carriers +// test/wcc/500_w6a.c, 510_dataw.c, and 520_datar.c; every assertion +// preserved. +// +// elf64header (500) — all 4 inline programs: w6c exit 0, w6a exit 0, +// and the .o opens with the ELF magic + ELFCLASS64 ident byte. +// +// dataw (510) — hand-written DATAW .s: e_shnum == 7 (NULL, .text, +// .rela.text, .data, .symtab, .strtab, .shstrtab); .data is +// SHT_PROGBITS, ALLOC|WRITE, non-EXECINSTR, 8 bytes equal to the LE +// i64 42; symbol `counter` sits in .data at st_value 0. +// +// nodataw (510) — a .s without DATAW keeps the legacy 6-section +// layout with no .data; test 991 (selfhost w6a .o byte-diff, the +// terminal bootstrap gate) depends on that invariant. +// +// datar (520) — DATAR emits exactly one R_X86_64_64 entry at offset 0 +// in .rela.data; w6l links the .o and the binary prints the 5 bytes +// `hello` through the relocated pointer and exits 0. Strengthened +// over the carrier: the reloc's symbol index is resolved through +// .symtab and must name `greeting` (the C code declared but never +// implemented that check). +// +// Dropped C machinery, not assertions: wwtestpkg.h package-clause +// injection (the sources here spell `package main;` out) and per-file +// unlink accounting (testenv.clean asserts the removal). + +import os; +import os.exec; +import strconv; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("elfobj 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; +}; + +// itos returns a view into a static buffer; dup materializes it. +fn dec(n: i32) str = { + return strings.dup(strconv.itos(n: int, strconv.base.DEC)); +}; + +// External-ABI constants (System V gABI, ELF64 relocatable objects) — +// the layout contract w6a's obj writer encodes: Ehdr e_shoff at 40 +// (u64), e_shnum at 60 (u16), e_shstrndx at 62 (u16); Shdr entries of +// 64 bytes with sh_name +0 (u32), sh_type +4 (u32), sh_flags +8 +// (u64), sh_offset +24 (u64), sh_size +32 (u64), sh_link +40 (u32); +// Sym64 entries of 24 bytes with st_name +0 (u32), st_shndx +6 (u16), +// st_value +8 (u64); Rela64 entries of 24 bytes with r_offset +0 +// (u64), r_info +8 (u64; low u32 = type, next u32 = symbol index). +def SHENT: i32 = 64; +def SYMENT: i32 = 24; +def RELAENT: i32 = 24; + +fn shnum(o: str) i32 = { return testenv.leu16(o, 60): i32; }; + +fn shoff(o: str, i: i32) i32 = { + return (testenv.leu64(o, 40): i32) + SHENT * i; +}; + +fn cstr(o: str, off: i32) str = { + let nb: []u8 = alloc([], 64u64)!; + let k: i32 = 0; + for (o[off + k] != 0u8) { + append(nb, o[off + k]); + k += 1; + }; + return strings.frombytes(nb); +}; + +fn shname(o: str, i: i32) str = { + let strsh: i32 = shoff(o, testenv.leu16(o, 62): i32); + let stroff: i32 = testenv.leu64(o, strsh + 24): i32; + return cstr(o, stroff + (testenv.leu32(o, shoff(o, i)): i32)); +}; + +fn findsection(o: str, name: str) i32 = { + let i: i32 = 0; + for (i < shnum(o)) { + if (testenv.same(shname(o, i), name)) { return i; }; + i += 1; + }; + return -1; +}; + +fn findsectiontype(o: str, ty: u64) i32 = { + let i: i32 = 0; + for (i < shnum(o)) { + if (testenv.leu32(o, shoff(o, i) + 4) == ty) { return i; }; + i += 1; + }; + return -1; +}; + +// Name of .symtab entry `symidx` through the sh_link strtab; "" when +// there is no symtab (SHT_SYMTAB == 2). +fn symname(o: str, symidx: i32) str = { + let ist: i32 = findsectiontype(o, 2u64); + if (ist < 0) { return ""; }; + let sh: i32 = shoff(o, ist); + let symoff: i32 = testenv.leu64(o, sh + 24): i32; + let strsh: i32 = shoff(o, testenv.leu32(o, sh + 40): i32); + let stroff: i32 = testenv.leu64(o, strsh + 24): i32; + let e: i32 = symoff + SYMENT * symidx; + return cstr(o, stroff + (testenv.leu32(o, e): i32)); +}; + +// Byte offset of the named Sym64 entry, or -1. Entry 0 is the +// reserved null symbol. +fn findsym(o: str, name: str) i32 = { + let ist: i32 = findsectiontype(o, 2u64); + if (ist < 0) { return -1; }; + let sh: i32 = shoff(o, ist); + let symoff: i32 = testenv.leu64(o, sh + 24): i32; + let n: i32 = (testenv.leu64(o, sh + 32): i32) / SYMENT; + let i: i32 = 1; + for (i < n) { + if (testenv.same(symname(o, i), name)) { + return symoff + SYMENT * i; + }; + i += 1; + }; + return -1; +}; + +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; +}; + +// ---- elf64header (500) ------------------------------------------------- + +@test fn elf64header() void = { + let td: str = testenv.fresh(); + let programs: []str = [ + "fn main() i32 = { return 42; };", + "fn add(a: i32, b: i32) i32 = { return a + b; };", + "fn loop() i32 = { let i: i32 = 0; for (i < 10) { i += 1; }; return i; };", + "fn cmp(a: i32, b: i32) bool = { return a < b; };", + ]; + let i: i32 = 0; + for (i < 4) { + let stem: str = strings.concat(td, "/p", dec(i)); + let src: str = strings.concat(stem, ".ww"); + let asmf: str = strings.concat(stem, ".s"); + let obj: str = strings.concat(stem, ".o"); + testenv.writefile(src, strings.concat("package main;\n", + programs[i], "\n")); + let cav: []str = [testenv.driver("w6c"), "-o", asmf, src]; + if (runcode(td, strings.concat("c", dec(i)), cav) != 0) { + fail("elf64header", strings.concat("w6c failed on ", + programs[i])); + }; + let aav: []str = [testenv.driver("w6a"), "-o", obj, asmf]; + if (runcode(td, strings.concat("a", dec(i)), aav) != 0) { + fail("elf64header", strings.concat("w6a failed on ", + programs[i])); + }; + if (!iself64(testenv.readfile(obj))) { + fail("elf64header", strings.concat("not an ELF64: ", + programs[i])); + }; + i += 1; + }; + testenv.clean(td); +}; + +// ---- dataw (510) ------------------------------------------------------- + +@test fn dataw() void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/dataw.s"); + let obj: str = strings.concat(td, "/dataw.o"); + testenv.writefile(src, strings.concat( + "TEXT _start(SB),$0\n", + "\tMOVQ\t$0, AX\n", + "\tRET\n", + "DATAW counter(SB),\"\\x2a\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n")); + let av: []str = [testenv.driver("w6a"), "-o", obj, src]; + if (runcode(td, "dataw_a", av) != 0) { + fail("dataw", "w6a failed"); + }; + let o: str = testenv.readfile(obj); + + // NULL, .text, .rela.text, .data, .symtab, .strtab, .shstrtab + if (shnum(o) != 7) { + fail("dataw", strings.concat("e_shnum=", dec(shnum(o)), + ", want 7")); + }; + let idx: i32 = findsection(o, ".data"); + if (idx < 0) { fail("dataw", ".data section not found"); }; + let sh: i32 = shoff(o, idx); + // SHT_PROGBITS == 1 + if (testenv.leu32(o, sh + 4) != 1u64) { + fail("dataw", ".data sh_type != PROGBITS"); + }; + // SHF_WRITE|SHF_ALLOC == 0x3, SHF_EXECINSTR == 0x4 + let fl: u64 = testenv.leu64(o, sh + 8); + if ((fl & 3u64) != 3u64) { + fail("dataw", ".data flags miss ALLOC|WRITE"); + }; + if ((fl & 4u64) != 0u64) { + fail("dataw", ".data is executable, want non-X"); + }; + if (testenv.leu64(o, sh + 32) != 8u64) { + fail("dataw", ".data size != 8"); + }; + let doff: i32 = testenv.leu64(o, sh + 24): i32; + if (o[doff] != 0x2au8) { + fail("dataw", ".data contents mismatch (LE i64 42)"); + }; + let k: i32 = 1; + for (k < 8) { + if (o[doff + k] != 0u8) { + fail("dataw", ".data contents mismatch (LE i64 42)"); + }; + k += 1; + }; + let sym: i32 = findsym(o, "counter"); + if (sym < 0) { fail("dataw", "symbol 'counter' not in .symtab"); }; + if ((testenv.leu16(o, sym + 6): i32) != idx) { + fail("dataw", "'counter'.st_shndx is not the .data index"); + }; + if (testenv.leu64(o, sym + 8) != 0u64) { + fail("dataw", "'counter'.st_value != 0"); + }; + testenv.clean(td); +}; + +// ---- nodataw (510) ----------------------------------------------------- + +@test fn nodataw() void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/nodataw.s"); + let obj: str = strings.concat(td, "/nodataw.o"); + testenv.writefile(src, strings.concat( + "TEXT _start(SB),$0\n", + "\tMOVQ\t$0, AX\n", + "\tRET\n")); + let av: []str = [testenv.driver("w6a"), "-o", obj, src]; + if (runcode(td, "nodataw_a", av) != 0) { + fail("nodataw", "w6a failed"); + }; + let o: str = testenv.readfile(obj); + // 991 (selfhost w6a .o byte-diff) relies on the legacy layout. + if (shnum(o) != 6) { + fail("nodataw", strings.concat("e_shnum=", dec(shnum(o)), + ", want 6 (legacy)")); + }; + if (findsection(o, ".data") >= 0) { + fail("nodataw", ".data section present, want absent"); + }; + testenv.clean(td); +}; + +// ---- datar (520) ------------------------------------------------------- + +@test fn datar() void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/datar.s"); + let obj: str = strings.concat(td, "/datar.o"); + let exe: str = strings.concat(td, "/datar.x"); + testenv.writefile(src, strings.concat( + "TEXT _start,$0\n", + "\tMOVQ\t$1, AX\n", + "\tMOVQ\t$1, DI\n", + "\tMOVQ\tpair(SB), SI\n", + "\tMOVQ\t$5, DX\n", + "\tSYSCALL\n", + "\tMOVQ\t$60, AX\n", + "\tMOVQ\t$0, DI\n", + "\tSYSCALL\n", + "\n", + "DATAW pair(SB),\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00", + "\\x05\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n", + "DATAR pair+0(SB),greeting(SB)\n", + "DATA greeting(SB),\"hello\"\n")); + let aav: []str = [testenv.driver("w6a"), "-o", obj, src]; + if (runcode(td, "datar_a", aav) != 0) { + fail("datar", "w6a failed"); + }; + let o: str = testenv.readfile(obj); + let idx: i32 = findsection(o, ".rela.data"); + if (idx < 0) { fail("datar", ".rela.data section missing"); }; + let sh: i32 = shoff(o, idx); + // SHT_RELA == 4 + if (testenv.leu32(o, sh + 4) != 4u64) { + fail("datar", ".rela.data sh_type != SHT_RELA"); + }; + // exactly one Rela64 entry + if (testenv.leu64(o, sh + 32) != RELAENT: u64) { + fail("datar", ".rela.data entry count != 1"); + }; + let ro: i32 = testenv.leu64(o, sh + 24): i32; + if (testenv.leu64(o, ro) != 0u64) { + fail("datar", "reloc offset != 0"); + }; + // R_X86_64_64 == 1 + if (testenv.leu32(o, ro + 8) != 1u64) { + fail("datar", "reloc kind != R_X86_64_64"); + }; + if (!testenv.same(symname(o, testenv.leu32(o, ro + 12): i32), + "greeting")) { + fail("datar", "reloc symbol != greeting"); + }; + + let lav: []str = [testenv.driver("w6l"), "-o", exe, obj]; + if (runcode(td, "datar_l", lav) != 0) { + fail("datar", "w6l failed"); + }; + let rav: []str = [exe]; + let co: testenv.commandout; + testenv.runcommand(td, td, "datar_run", rav, tmo(), &co); + if (co.termination != exec.termination.EXIT || co.code != 0) { + fail("datar", "exe exit != 0"); + }; + // the linker patched the .data slot with greeting's runtime VA + if (!testenv.same(co.stdout, "hello")) { + fail("datar", "stdout != 'hello'"); + }; + testenv.clean(td); +}; diff --git a/test/wcc/500_w6a.c b/test/wcc/500_w6a.c deleted file mode 100644 index abb28b55..00000000 --- a/test/wcc/500_w6a.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 500_asm — assembler smoke. Drive w6c on a tiny program, feed the - * output to w6a, then read back the .o magic to confirm we produced - * a valid ELF64 relocatable object. - */ -#include -#include -#include -#include -#include "wwtestpkg.h" - -static int -run(const char *cmd) -{ - return system(cmd); -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - - char src[64], asmf[64], obj[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()); - - const char *programs[] = { - "fn main() i32 = { return 42; };", - "fn add(a: i32, b: i32) i32 = { return a + b; };", - "fn loop() i32 = { let i: i32 = 0; for (i < 10) { i += 1; }; return i; };", - "fn cmp(a: i32, b: i32) bool = { return a < b; };", - NULL - }; - int n = 0, fail = 0; - for (int i = 0; programs[i]; i++, n++) { - FILE *f = fopen(src, "wb"); - wwtest_fputs(programs[i], f); - fclose(f); - - char cmd[512]; - snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s", bin, asmf, src); - if (run(cmd) != 0) { fprintf(stderr, "w6c fail: %s\n", programs[i]); fail++; continue; } - snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, asmf); - if (run(cmd) != 0) { fprintf(stderr, "w6a fail: %s\n", programs[i]); fail++; continue; } - - FILE *of = fopen(obj, "rb"); - if (of == NULL) { fail++; continue; } - unsigned char hdr[16]; - size_t r = fread(hdr, 1, sizeof hdr, of); - fclose(of); - if (r != 16 || memcmp(hdr, "\x7f""ELF", 4) != 0 - || hdr[4] != 2 /* ELFCLASS64 */) { - fprintf(stderr, "not an ELF64: %s\n", programs[i]); - fail++; - } - } - unlink(src); unlink(asmf); unlink(obj); - if (fail) { fprintf(stderr, "%d/%d w6a tests failed\n", fail, n); return 1; } - printf("w6a: %d/%d ok\n", n, n); - return 0; -} diff --git a/test/wcc/510_dataw.c b/test/wcc/510_dataw.c deleted file mode 100644 index 52bc50f9..00000000 --- a/test/wcc/510_dataw.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 510_dataw — DATAW directive smoke. Assemble a tiny .s that uses - * DATAW and verify the resulting .o has a writable .data section - * containing the right bytes, with a symbol that points to it. - * - * Also confirms the no-DATAW path stays byte-identical to the - * pre-DATAW format (6 sections, no .data); the selfhost .o diff in - * 991 depends on that invariant. - */ -#include -#include -#include -#include -#include - -#define SHT_PROGBITS 1 -#define SHT_SYMTAB 2 -#define SHF_WRITE 0x1 -#define SHF_ALLOC 0x2 -#define SHF_EXECINSTR 0x4 - -#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 sh_name, sh_type; - uint64_t sh_flags, sh_addr, sh_offset, sh_size; - uint32_t sh_link, sh_info; - uint64_t sh_addralign, sh_entsize; -} Shdr; - -typedef struct { - uint32_t st_name; - uint8_t st_info, st_other; - uint16_t st_shndx; - uint64_t st_value, st_size; -} Sym64; -#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; -} - -/* Find the section with the given name. Returns index, or -1. */ -static int -find_section(const uint8_t *buf, const char *name) -{ - const Ehdr *eh = (const Ehdr *)buf; - const Shdr *sh = (const Shdr *)(buf + eh->e_shoff); - const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset); - for (uint16_t i = 0; i < eh->e_shnum; i++) { - if (strcmp(shstr + sh[i].sh_name, name) == 0) - return (int)i; - } - return -1; -} - -/* Find a symbol by name in the .symtab. Returns pointer, or NULL. */ -static const Sym64 * -find_symbol(const uint8_t *buf, const char *name) -{ - const Ehdr *eh = (const Ehdr *)buf; - const Shdr *sh = (const Shdr *)(buf + eh->e_shoff); - int idx_symtab = -1; - for (uint16_t i = 0; i < eh->e_shnum; i++) - if (sh[i].sh_type == SHT_SYMTAB) { idx_symtab = i; break; } - if (idx_symtab < 0) return NULL; - int idx_strtab = (int)sh[idx_symtab].sh_link; - const Sym64 *syms = (const Sym64 *)(buf + sh[idx_symtab].sh_offset); - uint64_t nsyms = sh[idx_symtab].sh_size / sizeof(Sym64); - const char *str = (const char *)(buf + sh[idx_strtab].sh_offset); - for (uint64_t i = 1; i < nsyms; i++) { - if (strcmp(str + syms[i].st_name, name) == 0) - return &syms[i]; - } - return NULL; -} - -static int -test_dataw_emits_writable_section(const char *bin) -{ - char src[64], obj[64]; - snprintf(src, sizeof src, "/tmp/wwt_dataw_%d.s", getpid()); - snprintf(obj, sizeof obj, "/tmp/wwt_dataw_%d.o", getpid()); - - /* Hand-crafted .s: one writable data symbol holding 8 bytes (the - * little-endian encoding of the i64 42) plus a trivial TEXT so - * the assembler has something in .text to anchor. */ - const char *body = - "TEXT _start(SB),$0\n" - "\tMOVQ\t$0, AX\n" - "\tRET\n" - "DATAW counter(SB),\"\\x2a\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n"; - if (write_file(src, body) < 0) { - fprintf(stderr, "dataw: cannot write %s\n", src); - return -1; - } - - char cmd[512]; - snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, src); - if (system(cmd) != 0) { - fprintf(stderr, "dataw: w6a failed\n"); - unlink(src); - return -1; - } - - uint8_t *buf = NULL; - size_t n = 0; - if (slurp(obj, &buf, &n) < 0) { - fprintf(stderr, "dataw: cannot read .o\n"); - unlink(src); unlink(obj); - return -1; - } - - const Ehdr *eh = (const Ehdr *)buf; - int rc = 0; - - /* Expect 7 sections: NULL, .text, .rela.text, .data, .symtab, .strtab, .shstrtab. */ - if (eh->e_shnum != 7) { - fprintf(stderr, "dataw: e_shnum=%u, want 7\n", eh->e_shnum); - rc = -1; - } - - int idx_data = find_section(buf, ".data"); - if (idx_data < 0) { - fprintf(stderr, "dataw: .data section not found\n"); - rc = -1; - goto out; - } - const Shdr *sh = (const Shdr *)(buf + eh->e_shoff); - const Shdr *sd = &sh[idx_data]; - if (sd->sh_type != SHT_PROGBITS) { - fprintf(stderr, "dataw: .data sh_type=%u, want PROGBITS\n", sd->sh_type); - rc = -1; - } - if ((sd->sh_flags & (SHF_ALLOC | SHF_WRITE)) != (SHF_ALLOC | SHF_WRITE)) { - fprintf(stderr, "dataw: .data flags=0x%lx, want ALLOC|WRITE\n", - (unsigned long)sd->sh_flags); - rc = -1; - } - if ((sd->sh_flags & SHF_EXECINSTR) != 0) { - fprintf(stderr, "dataw: .data is executable, want non-X\n"); - rc = -1; - } - if (sd->sh_size != 8) { - fprintf(stderr, "dataw: .data size=%lu, want 8\n", - (unsigned long)sd->sh_size); - rc = -1; - } - - /* Verify the bytes themselves match the LE i64 42 we wrote. */ - const uint8_t *db = buf + sd->sh_offset; - uint8_t want[8] = { 0x2a, 0, 0, 0, 0, 0, 0, 0 }; - if (memcmp(db, want, 8) != 0) { - fprintf(stderr, "dataw: .data contents mismatch\n"); - rc = -1; - } - - const Sym64 *s = find_symbol(buf, "counter"); - if (s == NULL) { - fprintf(stderr, "dataw: symbol 'counter' not in .symtab\n"); - rc = -1; - goto out; - } - if ((int)s->st_shndx != idx_data) { - fprintf(stderr, "dataw: 'counter'.st_shndx=%u, want %d (.data)\n", - s->st_shndx, idx_data); - rc = -1; - } - if (s->st_value != 0) { - fprintf(stderr, "dataw: 'counter'.st_value=%lu, want 0\n", - (unsigned long)s->st_value); - rc = -1; - } - -out: - free(buf); - unlink(src); - unlink(obj); - return rc; -} - -static int -test_no_dataw_keeps_legacy_layout(const char *bin) -{ - char src[64], obj[64]; - snprintf(src, sizeof src, "/tmp/wwt_nodataw_%d.s", getpid()); - snprintf(obj, sizeof obj, "/tmp/wwt_nodataw_%d.o", getpid()); - - /* A .s without any DATAW must still produce the legacy 6-section - * layout. Test 991 (selfhost .o byte-diff) relies on this. */ - const char *body = - "TEXT _start(SB),$0\n" - "\tMOVQ\t$0, AX\n" - "\tRET\n"; - if (write_file(src, body) < 0) { - fprintf(stderr, "nodataw: cannot write %s\n", src); - return -1; - } - - char cmd[512]; - snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, src); - if (system(cmd) != 0) { - fprintf(stderr, "nodataw: w6a failed\n"); - unlink(src); - return -1; - } - - uint8_t *buf = NULL; - size_t n = 0; - if (slurp(obj, &buf, &n) < 0) { - fprintf(stderr, "nodataw: cannot read .o\n"); - unlink(src); unlink(obj); - return -1; - } - - const Ehdr *eh = (const Ehdr *)buf; - int rc = 0; - if (eh->e_shnum != 6) { - fprintf(stderr, "nodataw: e_shnum=%u, want 6 (legacy)\n", - eh->e_shnum); - rc = -1; - } - if (find_section(buf, ".data") >= 0) { - fprintf(stderr, "nodataw: .data section present, want absent\n"); - rc = -1; - } - - free(buf); - unlink(src); - unlink(obj); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - - int fail = 0; - if (test_dataw_emits_writable_section(bin) != 0) fail++; - if (test_no_dataw_keeps_legacy_layout(bin) != 0) fail++; - - if (fail) { - fprintf(stderr, "dataw: %d/2 subtests failed\n", fail); - return 1; - } - printf("dataw: 2/2 ok\n"); - return 0; -} diff --git a/test/wcc/520_datar.c b/test/wcc/520_datar.c deleted file mode 100644 index fe7e0f48..00000000 --- a/test/wcc/520_datar.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * 520_datar — DATAR reloc smoke. Assemble + link a tiny .s that - * uses DATAR to patch a .data slot with a symbol's runtime VA, - * then run the binary and confirm the linker filled the slot - * correctly: the program prints "hello" via the relocated pointer - * and exits 0. - * - * Also confirms the .o has a `.rela.data` section with one - * R_X86_64_64 entry against the expected symbol — catches - * regressions where the asm emits DATAR but obj.c doesn't reach - * the writable-data reloc path. - */ -#include -#include -#include -#include -#include -#include - -#define SHT_RELA 4 -#define R_X86_64_64 1 - -#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 sh_name, sh_type; - uint64_t sh_flags, sh_addr, sh_offset, sh_size; - uint32_t sh_link, sh_info; - uint64_t sh_addralign, sh_entsize; -} Shdr; - -typedef struct { - uint64_t r_offset; - uint64_t r_info; - int64_t r_addend; -} Rela64; -#pragma pack(pop) - -static int -run_exit(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -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 const char *prog = - "TEXT _start,$0\n" - "\tMOVQ\t$1, AX\n" /* write syscall */ - "\tMOVQ\t$1, DI\n" /* fd = 1 (stdout) */ - "\tMOVQ\tpair(SB), SI\n" /* ptr (relocated) */ - "\tMOVQ\t$5, DX\n" /* count = 5 */ - "\tSYSCALL\n" - "\tMOVQ\t$60, AX\n" /* exit */ - "\tMOVQ\t$0, DI\n" - "\tSYSCALL\n" - "\n" - "DATAW pair(SB),\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" - "\\x05\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n" - "DATAR pair+0(SB),greeting(SB)\n" - "DATA greeting(SB),\"hello\"\n"; - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - - char src[64], obj[64], exe[64], cmd[1024]; - snprintf(src, sizeof src, "/tmp/wwt_datar_%d.s", getpid()); - snprintf(obj, sizeof obj, "/tmp/wwt_datar_%d.o", getpid()); - snprintf(exe, sizeof exe, "/tmp/wwt_datar_%d.x", getpid()); - - FILE *f = fopen(src, "wb"); - if (!f) return 1; - fputs(prog, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s", bin, obj, src); - if (system(cmd) != 0) { - fprintf(stderr, "datar: w6a failed\n"); - return 1; - } - - int fail = 0; - - /* Inspect the .o: there must be a .rela.data section with one - * R_X86_64_64 entry against `greeting`. */ - uint8_t *buf = NULL; size_t n = 0; - if (slurp(obj, &buf, &n) < 0) { - fprintf(stderr, "datar: cannot read .o\n"); - fail++; - goto run_step; - } - const Ehdr *eh = (const Ehdr *)buf; - const Shdr *sh = (const Shdr *)(buf + eh->e_shoff); - const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset); - int idx_relad = -1, idx_strtab = -1, idx_symtab = -1; - for (uint16_t i = 0; i < eh->e_shnum; i++) { - const char *nm = shstr + sh[i].sh_name; - if (sh[i].sh_type == SHT_RELA && strcmp(nm, ".rela.data") == 0) - idx_relad = i; - if (sh[i].sh_type == 2 /* SHT_SYMTAB */) - idx_symtab = i; - } - if (idx_symtab >= 0) - idx_strtab = (int)sh[idx_symtab].sh_link; - if (idx_relad < 0) { - fprintf(stderr, "datar: .rela.data section missing\n"); - fail++; - } else { - const Rela64 *rd = (const Rela64 *)(buf + sh[idx_relad].sh_offset); - uint64_t nrel = sh[idx_relad].sh_size / sizeof(Rela64); - if (nrel != 1) { - fprintf(stderr, "datar: .rela.data has %lu entries, want 1\n", - (unsigned long)nrel); - fail++; - } else { - uint32_t kind = (uint32_t)(rd[0].r_info & 0xffffffff); - if (kind != R_X86_64_64) { - fprintf(stderr, "datar: reloc kind %u, want %d\n", - kind, R_X86_64_64); - fail++; - } - if (rd[0].r_offset != 0) { - fprintf(stderr, "datar: reloc offset %lu, want 0\n", - (unsigned long)rd[0].r_offset); - fail++; - } - } - } - free(buf); - -run_step: - snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s", bin, exe, obj); - if (system(cmd) != 0) { - fprintf(stderr, "datar: w6l failed\n"); - unlink(src); unlink(obj); - return 1; - } - - /* Run: should print "hello" and exit 0. We pipe stdout to a - * tmp file and check both the output and the exit code. */ - char outf[64]; - snprintf(outf, sizeof outf, "/tmp/wwt_datar_%d.out", getpid()); - snprintf(cmd, sizeof cmd, "%s > %s", exe, outf); - int rc = run_exit(cmd); - if (rc != 0) { - fprintf(stderr, "datar: exit=%d, want 0\n", rc); - fail++; - } - uint8_t *out = NULL; size_t on = 0; - if (slurp(outf, &out, &on) < 0 || on != 5 - || memcmp(out, "hello", 5) != 0) { - fprintf(stderr, "datar: stdout != 'hello'\n"); - fail++; - } - free(out); - - unlink(src); unlink(obj); unlink(exe); unlink(outf); - if (fail) { - fprintf(stderr, "datar: %d check(s) failed\n", fail); - return 1; - } - printf("datar: ok\n"); - (void)idx_strtab; - return 0; -}