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; }; @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); }; @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); }; @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); }; @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); };