Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/ fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT- tolerant checked unlinks, exact-path deletion (rm -rf only for an owned pid-keyed dir or a .sepwork beneath one), and cleanup failure fails a passing carrier without overwriting its diagnostic. In the same pass the carriers adapt to the driver contract this branch lands: --sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch contract are asserted, and rows whose runtime or reject coverage moved to test/wcc/data fixtures or test/lang @test owners are trimmed to the byte/artifact/diagnostic observations only they can make. Repair and adaptation ride together because most files interleave both in the same hunks; splitting would manufacture intermediate carrier states that never existed and cannot run against either driver.
248 lines
7.6 KiB
C
248 lines
7.6 KiB
C
/*
|
|
* 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; reports the harness
|
|
* skip status when libc / nm are unavailable or too few symbols are available
|
|
* to overflow the first page.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.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(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;
|
|
}
|