199 lines
6 KiB
C
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;
|
|
}
|