// 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 #include #include #include #include #include #include #include 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; }