test: port the w6a ELF-object observers to ww
elfobj_test.ww (test/object, on testenv) absorbs 500_w6a, 510_dataw, and 520_datar: ELF64 header magic/class over the 4 inline programs, DATAW section/symbol layout plus the 6-section no-DATAW legacy invariant 991 depends on, and the DATAR .rela.data reloc through link and run. Strengthened: the reloc symbol is resolved to 'greeting' (declared but never implemented in the C carrier).
This commit is contained in:
24
Makefile
24
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 \
|
||||
|
||||
341
test/object/elfobj_test.ww
Normal file
341
test/object/elfobj_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user