/* * 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 ` (default-versioned text * exports) so it adapts to whatever glibc is present; reports the harness * skip status when libc / nm are unavailable or too few symbols are available * to overflow the first page. */ #include #include #include #include #include #include #include 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 /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(outbin, obsz, "%s/main", tmpdir); snprintf(cmd, sizeof cmd, "cd %s && %s build -o %s %s -L %s -l c " "2>/dev/null", tmpdir, driver, outbin, src, libdir); if (runwait(cmd) != 0) return -1; 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 77; } 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 77; } 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); char td[] = "/tmp/dynent_XXXXXX"; if (mkdtemp(td) == NULL) { perror("dynentry mkdtemp"); return 1; } int fail = 0, total = 0; /* ---- big: ~160 gated dead PLT calls, return 42 ---- */ char bigsrc[1024], smallsrc[1024]; snprintf(bigsrc, sizeof bigsrc, "%s/big.ww", td); snprintf(smallsrc, sizeof smallsrc, "%s/small.ww", td); FILE *f = fopen(bigsrc, "wb"); if (!f) { perror(bigsrc); fail++; goto out; } 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); int writebad = ferror(f); if (fclose(f) != 0) writebad = 1; if (writebad) { perror(bigsrc); fail++; goto out; } /* ---- small: 1 dyn sym, headers fit the page ---- */ f = fopen(smallsrc, "wb"); if (!f) { perror(smallsrc); fail++; goto out; } 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); writebad = ferror(f); if (fclose(f) != 0) writebad = 1; if (writebad) { perror(smallsrc); fail++; goto out; } struct { const char *label; const char *src; } cases[] = { { "big", bigsrc }, { "small", smallsrc }, }; for (int ci = 0; ci < 2; ci++) { total++; char ctd[1024], wtd[1024], cob[1100], wob[1100]; snprintf(ctd, sizeof ctd, "%s/c_%d", td, ci); snprintf(wtd, sizeof wtd, "%s/w_%d", td, ci); if (mkdir(ctd, 0755) != 0) { perror(ctd); fail++; continue; } if (mkdir(wtd, 0755) != 0) { perror(wtd); if (rmdir(ctd) != 0) fprintf(stderr, "dynentry[%s]: setup cleanup failed\n", cases[ci].label); fail++; continue; } snprintf(cob, sizeof cob, "%s/main", ctd); snprintf(wob, sizeof wob, "%s/main", wtd); 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++; } } char clean[2400]; int cleanfail = 0; if (unlink(cob) != 0 && errno != ENOENT) cleanfail = 1; if (unlink(wob) != 0 && errno != ENOENT) cleanfail = 1; snprintf(clean, sizeof clean, "rm -rf %s/main.sepwork", ctd); if (runwait(clean) != 0) cleanfail = 1; snprintf(clean, sizeof clean, "rm -rf %s/main.sepwork", wtd); if (runwait(clean) != 0) cleanfail = 1; if (rmdir(ctd) != 0) cleanfail = 1; if (rmdir(wtd) != 0) cleanfail = 1; if (cleanfail) { fprintf(stderr, "dynentry[%s]: temporary cleanup failed\n", cases[ci].label); fail++; } } out: if (unlink(bigsrc) != 0 && errno != ENOENT) { perror(bigsrc); fail++; } if (unlink(smallsrc) != 0 && errno != ENOENT) { perror(smallsrc); fail++; } if (rmdir(td) != 0) { perror(td); fail++; } 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; }