65816-llvm-mos/demos/gnoComposite.c
2026-07-07 18:33:48 -05:00

199 lines
6 KiB
C

// Composite "real program" regression for GNO/ME: a SINGLE process that chains
// every verified POSIX subsystem in one run, in an order chosen to stress the
// interactions the isolated probes never exercise -- fork AFTER file I/O (does a
// thread-spawn disturb the parent's FST/heap state?), signals AFTER fork (does
// SIGALRM delivery survive a fork/wait cycle?), and exec AFTER all of it (does
// image-replacement still work once the process has opened files, installed a
// handler, and touched the heap?).
//
// A stage counter at 0x025002 is bumped at the START of each stage, so a hang or
// crash pinpoints the offending subsystem in a single MAME run (instrument
// before theorizing). Run with GNO_PROG2=demos/gnoExeced.omf so the final
// execvp actually transfers control.
//
// Bank-2 absolute uint16 markers (DBR-independent), gnoHeapProbe convention:
// 0x025000 C0DE reached end (set by gnoExeced after exec, else by fallback)
// 0x025002 0007 stage counter: last stage reached (1..7); 7 == pre-exec
// 0x025010 E0EC exec transferred control to gnoExeced (set by the 2nd stage)
// 0x025020 0001 env: setenv/getenv("COMPOSITE") round-trip
// 0x025022 0001 file+stat: write/read/verify + stat st_size==16 + lseek
// 0x025024 0001 id/stat: fstat(1) char dev + isatty(1) + stat(".") dir
// 0x025026 0001 fork+waitpid: leaf child _exit(42), WEXITSTATUS==42, pid match
// 0x025028 0001 signals: SIGALRM via __sigTrampoline fired, pause() resumed
// 0x02502A 0001 times(): ret==0 && tms_stime==0 && tms_cstime==0
// 0x02502E C41D fork child was scheduled + ran (set by the child)
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <signal.h>
extern __attribute__((noreturn)) void _exit(int status);
#define M(a) (*(volatile uint16_t *)(a))
#define OK 0x0001
#define BAD 0xDEAD
static const char kPath[] = "composite.tmp";
static const uint8_t kData[16] = {
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50
};
static volatile uint16_t rang;
// GNO fork child: a LEAF using only absolute-long stores + _exit (fresh DP).
static void child(void) {
M(0x02502EUL) = 0xC41D;
_exit(42);
}
static void onAlarm(int sig) {
(void)sig;
rang = 1;
M(0x025028UL) = OK; // handler executed via the trampoline
}
int main(void) {
struct stat sb;
struct tms tb;
uint8_t rbuf[16];
char *ev;
char *cargv[2];
long n;
long pos;
char one;
int fd;
int pid;
int st;
int w;
uint8_t i;
uint16_t ok;
M(0x025000UL) = 0x0000;
M(0x025010UL) = BAD;
M(0x025020UL) = BAD;
M(0x025022UL) = BAD;
M(0x025024UL) = BAD;
M(0x025026UL) = BAD;
M(0x025028UL) = BAD;
M(0x02502AUL) = BAD;
M(0x02502EUL) = BAD;
// Stage 1: environment round-trip (GS/OS shell-variable table).
M(0x025002UL) = 1;
if (setenv("COMPOSITE", "yes", 1) == 0) {
ev = getenv("COMPOSITE");
if (ev != 0 && ev[0] == 'y' && ev[1] == 'e' && ev[2] == 's' && ev[3] == 0) {
M(0x025020UL) = OK;
}
}
// Stage 2: file I/O + stat on the SAME file (compose fd layer + stat).
M(0x025002UL) = 2;
ok = OK;
fd = creat(kPath, 0);
if (fd >= 4) {
n = write(fd, kData, 16);
if (n != 16) {
ok = BAD;
}
close(fd);
} else {
ok = BAD;
}
if (stat(kPath, &sb) != 0 || sb.st_size != 16) {
ok = BAD; // stat on a real file we just wrote
}
fd = open(kPath, O_RDONLY);
if (fd >= 4) {
n = read(fd, rbuf, 16);
if (n != 16) {
ok = BAD;
}
for (i = 0; i < 16; i++) {
if (rbuf[i] != kData[i]) {
ok = BAD;
}
}
pos = lseek(fd, 4, SEEK_SET);
one = 0;
read(fd, &one, 1);
if (pos != 4 || (uint8_t)one != kData[4]) {
ok = BAD;
}
close(fd);
} else {
ok = BAD;
}
if (unlink(kPath) != 0) {
ok = BAD;
}
M(0x025022UL) = ok;
// Stage 3: fstat/isatty on the console + stat(".") + credential calls.
M(0x025002UL) = 3;
ok = OK;
if (fstat(1, &sb) != 0 || !S_ISCHR(sb.st_mode)) {
ok = BAD;
}
if (isatty(1) != 1) {
ok = BAD;
}
if (stat(".", &sb) != 0 || !S_ISDIR(sb.st_mode)) {
ok = BAD;
}
(void)getuid(); // must not crash mid-composition
(void)getgid();
M(0x025024UL) = ok;
// Stage 4: times() sanity (deterministic zero-fill fields).
M(0x025002UL) = 4;
tb.tms_stime = 0xFFFFFFFFUL;
tb.tms_cstime = 0xFFFFFFFFUL;
if (times(&tb) == 0 && tb.tms_stime == 0 && tb.tms_cstime == 0) {
M(0x02502AUL) = OK;
}
// Stage 5: fork a leaf child + reap with waitpid -- AFTER file I/O, to catch
// any FST/heap corruption a thread-spawn leaves in the parent.
M(0x025002UL) = 5;
pid = fork((void *)child);
if (pid > 0) {
st = 0;
w = waitpid(pid, &st, 0);
if (WIFEXITED(st) && WEXITSTATUS(st) == 42 && w == pid) {
M(0x025026UL) = OK;
}
}
// Stage 6: signals -- AFTER fork, to prove SIGALRM delivery through the
// trampoline survives a fork/wait cycle and pause() resumes cleanly.
M(0x025002UL) = 6;
rang = 0;
signal(SIGALRM, onAlarm);
alarm(1);
pause();
if (rang != 1) {
M(0x025028UL) = BAD; // handler did NOT run (overrides onAlarm)
}
// Stage 7: exec into the 2nd program -- AFTER all the above. On success this
// never returns; gnoExeced sets 0x025010=E0EC and 0x025000=C0DE.
M(0x025002UL) = 7;
cargv[0] = "gnoexeced";
cargv[1] = 0;
execvp("gnoexeced", cargv);
// Reached only if the exec-into failed.
M(0x025000UL) = 0xC0DE;
for (volatile unsigned long j = 0; j < 300000UL; j++) {
}
return 0;
}