w6l: dynamic e_entry rebases with the actual text offset, both stages
When the dynamic section pushed the header past the first page, the entry point kept its first-page address — every sufficiently large dynamic binary SIGSEGV'd into the headers. Recompute e_entry as entry - 0x1000 + text_off in both stages (ELF: the entry must point into .text wherever it lands). Both stages move in one commit: one ELF contract; the 989_dynentry gate pins the field and the run.
This commit is contained in:
14
Makefile
14
Makefile
@@ -265,6 +265,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_enumcap_run \
|
||||
$(BIN)/test_wwdumpgate_run \
|
||||
$(BIN)/test_datargate_run \
|
||||
$(BIN)/test_dynentry_run \
|
||||
$(BIN)/test_ampfncollide_run \
|
||||
$(BIN)/test_trycallcollide_run \
|
||||
$(BIN)/test_gunsigned_run \
|
||||
@@ -846,6 +847,19 @@ $(BIN)/test_datargate_run: test/wcc/989_datargate_run.c \
|
||||
$(BIN)/w6a $(BIN)/w6a_ww | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_dynentry_run (#63, F15 c6): w6l's dynamic e_entry is rebased onto the
|
||||
# actual .text file offset once the dynamic headers overflow the first page,
|
||||
# instead of the stale base+0x1000+val (which mapped into the headers →
|
||||
# SIGSEGV). BOTH stages (#263). Builds + runs ~160-dyn-sym binaries on both
|
||||
# driver twins and asserts exit 42 + byte-identical output (rule-10). Skips
|
||||
# without libc. See the test header.
|
||||
$(BIN)/test_dynentry_run: test/wcc/989_dynentry_run.c \
|
||||
$(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_ampfncollide_run (#4, c2): `&fn` synthesis (unoptype TK_AMP +
|
||||
# assignableaddrfn) prefers the current module's fn when a same-leaf fn is
|
||||
# declared in a later module. Builds/rejects on BOTH driver twins (rule-10).
|
||||
|
||||
@@ -589,7 +589,14 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
eh.e_type = ET_EXEC;
|
||||
eh.e_machine = EM_X86_64;
|
||||
eh.e_version = EV_CURRENT;
|
||||
eh.e_entry = entry;
|
||||
/* #63: main computed `entry` as base + 0x1000 + val assuming .text sits
|
||||
* at file offset 0x1000, but text_off above is recomputed and overflows
|
||||
* 0x1000 once the dynamic headers exceed the first page (e.g. ~100 dyn
|
||||
* syms). Rebase e_entry onto the actual text_off so it points at the
|
||||
* real _start instead of into the headers. e_entry is a virtual address;
|
||||
* PT_LOAD #1 maps file offset 0 at `base`, so the entry VA tracks
|
||||
* text_off. Both stages fixed together (#263). */
|
||||
eh.e_entry = entry - 0x1000 + text_off;
|
||||
eh.e_phoff = ehdr_sz;
|
||||
eh.e_ehsize = sizeof(Ehdr);
|
||||
eh.e_phentsize = sizeof(Phdr);
|
||||
|
||||
@@ -661,7 +661,13 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
dwr16(filebuf.ptr, 16u64, ET_EXEC_D);
|
||||
dwr16(filebuf.ptr, 18u64, EM_X86_64_D);
|
||||
dwr32(filebuf.ptr, 20u64, EV_CURRENT_D);
|
||||
dwr64(filebuf.ptr, 24u64, entry);
|
||||
// #63: main computed `entry` as BASE + CODE_VA_OFF (== PAGE) + val
|
||||
// assuming .text sits at file offset 0x1000, but textoff above is
|
||||
// recomputed and overflows 0x1000 once the dynamic headers exceed the
|
||||
// first page (e.g. ~100 dyn syms). Rebase e_entry onto the actual textoff
|
||||
// so it points at the real _start instead of into the headers. Both
|
||||
// stages (#263). PAGE is dynout-local; equals main.ww's CODE_VA_OFF.
|
||||
dwr64(filebuf.ptr, 24u64, entry - PAGE + textoff);
|
||||
dwr64(filebuf.ptr, 32u64, ehdrsz);
|
||||
dwr64(filebuf.ptr, 40u64, 0u64);
|
||||
dwr32(filebuf.ptr, 48u64, 0u32);
|
||||
|
||||
@@ -4754,7 +4754,13 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
dwr16(filebuf.ptr, 16u64, ET_EXEC_D);
|
||||
dwr16(filebuf.ptr, 18u64, EM_X86_64_D);
|
||||
dwr32(filebuf.ptr, 20u64, EV_CURRENT_D);
|
||||
dwr64(filebuf.ptr, 24u64, entry);
|
||||
// #63: main computed `entry` as BASE + CODE_VA_OFF (== PAGE) + val
|
||||
// assuming .text sits at file offset 0x1000, but textoff above is
|
||||
// recomputed and overflows 0x1000 once the dynamic headers exceed the
|
||||
// first page (e.g. ~100 dyn syms). Rebase e_entry onto the actual textoff
|
||||
// so it points at the real _start instead of into the headers. Both
|
||||
// stages (#263). PAGE is dynout-local; equals main.ww's CODE_VA_OFF.
|
||||
dwr64(filebuf.ptr, 24u64, entry - PAGE + textoff);
|
||||
dwr64(filebuf.ptr, 32u64, ehdrsz);
|
||||
dwr64(filebuf.ptr, 40u64, 0u64);
|
||||
dwr32(filebuf.ptr, 48u64, 0u32);
|
||||
|
||||
186
test/wcc/989_dynentry_run.c
Normal file
186
test/wcc/989_dynentry_run.c
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 989_dynentry_run (#63, F15 c6) — w6l's dynamic e_entry must track the actual
|
||||
* .text file offset once the dynamic headers overflow the first page.
|
||||
*
|
||||
* THE BUG (BOTH stages, cat-A #263, real ELF ABI): main computed the entry VA
|
||||
* as base + 0x1000 + entry->val assuming .text sits at file offset 0x1000, but
|
||||
* the dynamic emitter recomputes text_off = alignup(headers_end, page) and
|
||||
* wrote the stale entry into e_entry unchanged. With ~100+ dynamic symbols the
|
||||
* dynsym/dynstr/hash/versym/verneed/rela.plt headers push past 0x1000, so
|
||||
* .text lands at 0x2000+ while e_entry still pointed at base+0x1000+val — a
|
||||
* mapped R+X page of header bytes. The linker exits 0; the binary SIGSEGVs.
|
||||
* Byte-id gates can't see it (both stages identically wrong); test 992 covers
|
||||
* only static links and 810_dyn uses a handful of syms. THE FIX (both stages):
|
||||
* rebase e_entry = entry - 0x1000 + text_off (cmd/w6l/dynout.c +
|
||||
* selfhost/cmd/w6l/dynout.ww).
|
||||
*
|
||||
* row | dyn syms | result (cs==ww)
|
||||
* ------+----------+-------------------------------------------
|
||||
* big | ~160 | exit 42 (was SIGSEGV 139 both stages), byte-id
|
||||
* small | 1 | exit 42 (control, headers fit page), byte-id
|
||||
*
|
||||
* big was RED pre-c6 on BOTH stages (e_entry into the headers → SIGSEGV).
|
||||
* small pins the headers-fit-page path stays correct and byte-identical.
|
||||
*
|
||||
* Generates the dyn-sym set from `nm -D <libc>` (default-versioned text
|
||||
* exports) so it adapts to whatever glibc is present; skips (passing) when
|
||||
* libc / nm are unavailable or too few symbols to overflow the first page.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Harvest up to `max` default-versioned libc text exports into syms[][].
|
||||
* Returns the count, or -1 if nm/libc unavailable. */
|
||||
static int
|
||||
harvest_syms(const char *libc, char syms[][32], int max)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"nm -D %s 2>/dev/null | grep ' T ' | grep '@@GLIBC_2' "
|
||||
"| grep -v PRIVATE | sed 's/.* //; s/@@.*//' "
|
||||
"| grep -E '^[a-z_][a-zA-Z0-9_]{2,18}$' | sort -u", libc);
|
||||
FILE *p = popen(cmd, "r");
|
||||
if (!p) return -1;
|
||||
int n = 0;
|
||||
char line[128];
|
||||
while (n < max && fgets(line, sizeof line, p)) {
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
if (line[0] == '\0') continue;
|
||||
snprintf(syms[n], 32, "%s", line);
|
||||
n++;
|
||||
}
|
||||
pclose(p);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Build with `driver` into <tmpdir>/main, run, return exit code (or -1 on
|
||||
* build failure). Sets *outbin to the produced binary path. */
|
||||
static int
|
||||
build_run(const char *driver, const char *src, const char *libdir,
|
||||
const char *tmpdir, char *outbin, size_t obsz)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build -o %s/main %s -L %s -l c "
|
||||
"2>/dev/null", tmpdir, driver, tmpdir, src, libdir);
|
||||
if (runwait(cmd) != 0) return -1;
|
||||
snprintf(outbin, obsz, "%s/main", tmpdir);
|
||||
return runwait(outbin);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *libc = NULL, *libdir = NULL;
|
||||
if (access("/usr/lib/libc.so.6", 0) == 0) { libc = "/usr/lib/libc.so.6"; libdir = "/usr/lib"; }
|
||||
else if (access("/lib/x86_64-linux-gnu/libc.so.6", 0) == 0) { libc = "/lib/x86_64-linux-gnu/libc.so.6"; libdir = "/lib/x86_64-linux-gnu"; }
|
||||
else if (access("/lib64/libc.so.6", 0) == 0) { libc = "/lib64/libc.so.6"; libdir = "/lib64"; }
|
||||
if (!libc) { puts("dynentry: no libc.so.6 — skipping"); return 0; }
|
||||
|
||||
static char syms[200][32];
|
||||
int ns = harvest_syms(libc, syms, 200);
|
||||
if (ns < 130) {
|
||||
printf("dynentry: only %d libc syms harvested (<130, can't overflow "
|
||||
"first page) — skipping\n", ns);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cdrv[1100], wdrv[1100];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
|
||||
/* ---- big: ~160 gated dead PLT calls, return 42 ---- */
|
||||
char bigsrc[80];
|
||||
snprintf(bigsrc, sizeof bigsrc, "/tmp/dynent_big_%d.ww", getpid());
|
||||
FILE *f = fopen(bigsrc, "wb");
|
||||
if (!f) return 1;
|
||||
fputs("package main;\n", f);
|
||||
for (int i = 0; i < ns; i++)
|
||||
fprintf(f, "@symbol(\"%s\") fn s%03d() void;\n", syms[i], i);
|
||||
fputs("let g: i32 = 0;\nexport fn main() i32 = {\n", f);
|
||||
for (int i = 0; i < ns; i++)
|
||||
fprintf(f, "\tif (g != 0) { s%03d(); };\n", i);
|
||||
fputs("\treturn 42;\n};\n", f);
|
||||
fclose(f);
|
||||
|
||||
/* ---- small: 1 dyn sym, headers fit the page ---- */
|
||||
char smallsrc[80];
|
||||
snprintf(smallsrc, sizeof smallsrc, "/tmp/dynent_small_%d.ww", getpid());
|
||||
f = fopen(smallsrc, "wb");
|
||||
if (!f) return 1;
|
||||
fputs("package main;\n"
|
||||
"@symbol(\"getpid\") fn cgetpid() i32;\n"
|
||||
"let g: i32 = 0;\n"
|
||||
"export fn main() i32 = { if (g != 0) { cgetpid(); }; return 42; };\n",
|
||||
f);
|
||||
fclose(f);
|
||||
|
||||
struct { const char *label; const char *src; } cases[] = {
|
||||
{ "big", bigsrc }, { "small", smallsrc },
|
||||
};
|
||||
|
||||
int fail = 0, total = 0;
|
||||
for (int ci = 0; ci < 2; ci++) {
|
||||
total++;
|
||||
char ctd[80], wtd[80], cob[160], wob[160];
|
||||
snprintf(ctd, sizeof ctd, "/tmp/dynent_c_%d_%d", getpid(), ci);
|
||||
snprintf(wtd, sizeof wtd, "/tmp/dynent_w_%d_%d", getpid(), ci);
|
||||
mkdir(ctd, 0755); mkdir(wtd, 0755);
|
||||
|
||||
int crc = build_run(cdrv, cases[ci].src, libdir, ctd, cob, sizeof cob);
|
||||
if (crc != 42) {
|
||||
fprintf(stderr, "dynentry[cstage][%s]: exit %d want 42 "
|
||||
"(e_entry into headers? #63)\n", cases[ci].label, crc);
|
||||
fail++;
|
||||
}
|
||||
if (have_ww) {
|
||||
int wrc = build_run(wdrv, cases[ci].src, libdir, wtd, wob, sizeof wob);
|
||||
if (wrc != 42) {
|
||||
fprintf(stderr, "dynentry[wwstage][%s]: exit %d want 42 "
|
||||
"(e_entry into headers? #63)\n", cases[ci].label, wrc);
|
||||
fail++;
|
||||
}
|
||||
char cmp[400];
|
||||
snprintf(cmp, sizeof cmp, "cmp -s %s %s", cob, wob);
|
||||
if (runwait(cmp) != 0) {
|
||||
fprintf(stderr, "dynentry[%s]: cs/ww binary differ (#63)\n",
|
||||
cases[ci].label);
|
||||
fail++;
|
||||
}
|
||||
unlink(wob);
|
||||
}
|
||||
unlink(cob);
|
||||
rmdir(ctd); rmdir(wtd);
|
||||
}
|
||||
|
||||
unlink(bigsrc); unlink(smallsrc);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "dynentry_run: %d/%d check(s) failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dynentry_run: %d/%d ok (%d dyn syms)\n", total, total, ns);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user