Files
ww/test/wcc/989_dynentry_run.c

244 lines
7.4 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;
}
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;
}
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 -L %s -l c %s "
"2>/dev/null", tmpdir, driver, outbin, libdir, src);
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: the s%03d calls are gated dead — they exist only to force
* enough PLT/dynsym entries to push the headers past one page. */
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;
}
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;
}