Another pass at GNO.
This commit is contained in:
parent
8d0bac168e
commit
8d30c06605
36 changed files with 3198 additions and 159 deletions
|
|
@ -103,7 +103,7 @@ fi
|
||||||
"$RT/libcxxabi.o" "$RT/libcxxabiSjlj.o" "$RT/libunwindStub.o"
|
"$RT/libcxxabi.o" "$RT/libcxxabiSjlj.o" "$RT/libunwindStub.o"
|
||||||
|
|
||||||
echo "OMF: -> $OUTBASE.omf"
|
echo "OMF: -> $OUTBASE.omf"
|
||||||
# Declare a dedicated DP/Stack (OMF KIND=0x1012) segment. Without it GNO
|
# Declare a dedicated DP/Stack (OMF KIND=0x4012) segment. Without it GNO
|
||||||
# falls back to a 4 KB default stack shared with DP and placed low in bank 0,
|
# falls back to a 4 KB default stack shared with DP and placed low in bank 0,
|
||||||
# which is too small / mis-placed for GS/OS file I/O: GNO's GS/OS interceptor
|
# which is too small / mis-placed for GS/OS file I/O: GNO's GS/OS interceptor
|
||||||
# (StackGSOS) carves its direct-page work area off the caller's S and the FST
|
# (StackGSOS) carves its direct-page work area off the caller's S and the FST
|
||||||
|
|
@ -111,10 +111,20 @@ echo "OMF: -> $OUTBASE.omf"
|
||||||
# A sized $12 segment makes the Loader allocate a larger bank-0 block (S set
|
# A sized $12 segment makes the Loader allocate a larger bank-0 block (S set
|
||||||
# high in it), giving headroom and moving stack buffers off the collision.
|
# high in it), giving headroom and moving stack buffers off the collision.
|
||||||
# This is the idiomatic ORCA/GNO mechanism (== #pragma stacksize).
|
# This is the idiomatic ORCA/GNO mechanism (== #pragma stacksize).
|
||||||
|
#
|
||||||
|
# Size ceiling for exec-into: KERNexecve loads the NEW program via GS/OS
|
||||||
|
# InitialLoad2 BEFORE freeing the outgoing image (kern/gno/sys.c:762 vs 912),
|
||||||
|
# so during an execve() BOTH the caller's and the callee's DP/Stack segments
|
||||||
|
# occupy bank 0 at once. Bank 0 has ~48 KB usable for stacks, so a 16 KB
|
||||||
|
# default (== 32 KB transient across an exec) overflows and InitialLoad2 fails
|
||||||
|
# with a Memory Manager error that GNO maps to errno EIO -- exec-into never
|
||||||
|
# transfers control. 8 KB (16 KB transient) fits with margin and still gives
|
||||||
|
# ample GS/OS file-I/O headroom. Verified: 8 KB and 4 KB exec cleanly, 16 KB
|
||||||
|
# does not (demos/gnoExecProbe -> gnoExeced under real GNO/MAME).
|
||||||
"$OMF" --input "$BIN" --map "$MAP" \
|
"$OMF" --input "$BIN" --map "$MAP" \
|
||||||
--base 0x1000 --entry __start --output "$OUT" \
|
--base 0x1000 --entry __start --output "$OUT" \
|
||||||
--name "$(echo "$OUTBASE" | tr '[:lower:]' '[:upper:]' | cut -c1-8)" \
|
--name "$(echo "$OUTBASE" | tr '[:lower:]' '[:upper:]' | cut -c1-8)" \
|
||||||
--expressload --relocs "$RELOC" --stack-size "${GNO_STACK_SIZE:-0x4000}"
|
--expressload --relocs "$RELOC" --stack-size "${GNO_STACK_SIZE:-0x2000}"
|
||||||
|
|
||||||
ls -la "$OUT"
|
ls -la "$OUT"
|
||||||
if [ "$DEBUG" = 1 ]; then
|
if [ "$DEBUG" = 1 ]; then
|
||||||
|
|
|
||||||
29
demos/gnoBigBssProbe.c
Normal file
29
demos/gnoBigBssProbe.c
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// D1 regression for the crt0Gno BSS-clear heartbeat bug: a large zero-init
|
||||||
|
// BSS array pushes __bss_end past the old ~0xB100 corruption threshold.
|
||||||
|
// crt0Gno no longer clears BSS (the loader fills the embedded zeros), so
|
||||||
|
// the program must run to the end AND read the array back as all-zero.
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// ~5.75 KB. With the current GNO libc the runtime BSS base already sits at
|
||||||
|
// ~0xD000 (well above the old ~0xB100 heartbeat threshold), so this array's job
|
||||||
|
// is to keep __bss_end large (near the bank-0 top) while still fitting bank 0.
|
||||||
|
static volatile uint8_t bigBss[0x1700];
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
*(volatile uint16_t *)0x025010UL = 0xBEEF;
|
||||||
|
uint16_t zeroed = 1;
|
||||||
|
unsigned i;
|
||||||
|
for (i = 0; i < 0x1700; i++) {
|
||||||
|
if (bigBss[i] != 0) {
|
||||||
|
zeroed = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*(volatile uint16_t *)0x025012UL = zeroed ? 0x0001 : 0xDEAD;
|
||||||
|
bigBss[0] = 1; // retain
|
||||||
|
bigBss[0x16FF] = 2;
|
||||||
|
*(volatile uint16_t *)0x025000UL = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 600000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
199
demos/gnoComposite.c
Normal file
199
demos/gnoComposite.c
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
42
demos/gnoEnvProbe.c
Normal file
42
demos/gnoEnvProbe.c
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Environment (GNO shell-variable table) round-trip regression.
|
||||||
|
// GNO has no Unix environ[]; the "environment" is the kernel SHELL-
|
||||||
|
// VARIABLE table reached via GS/OS Shell calls (SetGS $0146 /
|
||||||
|
// ReadVariableGS $014B) dispatched through the same inline $E100A8
|
||||||
|
// mechanism as the file calls -- zero new asm. This probe sets a
|
||||||
|
// variable, reads it straight back, and asserts the value survives.
|
||||||
|
//
|
||||||
|
// Deterministic: the variable name/value are literals, no pids or
|
||||||
|
// addresses, so the round-trip is identical on every boot.
|
||||||
|
//
|
||||||
|
// Bank-2 absolute markers (DBR-independent), per gnoHeapProbe convention:
|
||||||
|
// 0x025000 C0DE reached end (no hang in the shell-call path)
|
||||||
|
// 0x025002 0001 getenv("LLVMTEST") == "42" (round-trip OK), else DEAD
|
||||||
|
// 0x025012 0001 setenv("LLVMTEST","42",1) == 0 (call succeeded), else DEAD
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
int sr;
|
||||||
|
char *v;
|
||||||
|
uint16_t match;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
|
||||||
|
sr = setenv("LLVMTEST", "42", 1);
|
||||||
|
M(0x025012UL) = (sr == 0) ? 0x0001 : 0xDEAD;
|
||||||
|
|
||||||
|
v = getenv("LLVMTEST");
|
||||||
|
match = 0xDEAD;
|
||||||
|
if (v != 0 && v[0] == '4' && v[1] == '2' && v[2] == 0) {
|
||||||
|
match = 0x0001;
|
||||||
|
}
|
||||||
|
M(0x025002UL) = match;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
93
demos/gnoErrnoProbe.c
Normal file
93
demos/gnoErrnoProbe.c
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
// errno-space consistency probe for GNO/ME. The composite happy-path test
|
||||||
|
// proves the subsystems compose; this one hunts the KNOWN latent hazard: the
|
||||||
|
// fd layer maps GS/OS errors to POSIX errno via _mapErr, but the stat/exec/
|
||||||
|
// access wrappers stash the RAW kernel errno. A real program that branches on
|
||||||
|
// `errno == ENOENT` breaks wherever the raw value leaks through. We trigger the
|
||||||
|
// SAME logical error (a missing file) through four wrappers and record the raw
|
||||||
|
// errno each sets, so the divergence (if any) is visible against ENOENT.
|
||||||
|
//
|
||||||
|
// Bank-2 absolute uint16 markers (DBR-independent):
|
||||||
|
// 0x025000 C0DE reached end
|
||||||
|
// 0x025030 ENOENT (from errno.h) -- the value a POSIX app expects
|
||||||
|
// 0x025032 errno after open("nosuch", O_RDONLY) [fd layer, _mapErr]
|
||||||
|
// 0x025034 errno after stat("nosuch") [stat wrapper, raw kernel]
|
||||||
|
// 0x025036 errno after unlink("nosuch") [fd layer, _mapErr]
|
||||||
|
// 0x025038 errno after access("nosuch", F_OK) [access over stat]
|
||||||
|
// 0x02503A 0001 if all four calls actually FAILED (rc==-1), else which passed
|
||||||
|
// 0x02503C 0001 if every failing errno == ENOENT (POSIX-consistent), else DEAD
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
#define OK 0x0001
|
||||||
|
#define BAD 0xDEAD
|
||||||
|
|
||||||
|
static const char kMissing[] = "nosuchfile.xyz";
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct stat sb;
|
||||||
|
uint16_t failed;
|
||||||
|
uint16_t consistent;
|
||||||
|
int eOpen;
|
||||||
|
int eStat;
|
||||||
|
int eUnlink;
|
||||||
|
int eAccess;
|
||||||
|
int rc;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
|
||||||
|
M(0x025030UL) = (uint16_t)ENOENT; // what a POSIX app checks against
|
||||||
|
|
||||||
|
failed = OK;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
fd = open(kMissing, O_RDONLY);
|
||||||
|
eOpen = errno;
|
||||||
|
if (fd != -1) {
|
||||||
|
failed = BAD; // unexpectedly succeeded
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
M(0x025032UL) = (uint16_t)eOpen;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
rc = stat(kMissing, &sb);
|
||||||
|
eStat = errno;
|
||||||
|
if (rc != -1) {
|
||||||
|
failed = BAD;
|
||||||
|
}
|
||||||
|
M(0x025034UL) = (uint16_t)eStat;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
rc = unlink(kMissing);
|
||||||
|
eUnlink = errno;
|
||||||
|
if (rc != -1) {
|
||||||
|
failed = BAD;
|
||||||
|
}
|
||||||
|
M(0x025036UL) = (uint16_t)eUnlink;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
rc = access(kMissing, F_OK);
|
||||||
|
eAccess = errno;
|
||||||
|
if (rc != -1) {
|
||||||
|
failed = BAD;
|
||||||
|
}
|
||||||
|
M(0x025038UL) = (uint16_t)eAccess;
|
||||||
|
|
||||||
|
M(0x02503AUL) = failed;
|
||||||
|
|
||||||
|
consistent = OK;
|
||||||
|
if (eOpen != ENOENT || eStat != ENOENT || eUnlink != ENOENT || eAccess != ENOENT) {
|
||||||
|
consistent = BAD;
|
||||||
|
}
|
||||||
|
M(0x02503CUL) = consistent;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
65
demos/gnoExecDiag.c
Normal file
65
demos/gnoExecDiag.c
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Diagnostic for the gnoExecProbe exec-into failure: distinguishes a PATH/
|
||||||
|
// access() resolution miss from a K_execve transfer-of-control failure.
|
||||||
|
// Runs in the SAME harness as gnoExecProbe (GNO_PROG2=gnoExeced deployed to
|
||||||
|
// /bin). Each marker isolates one step so we can see exactly where exec dies.
|
||||||
|
//
|
||||||
|
// Bank-2 absolute uint16 markers (DBR-independent):
|
||||||
|
// 0x025000 C0DE reached end (fallback; set only if every exec returned)
|
||||||
|
// 0x025010 E0EC gnoExeced ran -> some exec transferred control (success)
|
||||||
|
// 0x025020 F00D access("/bin/gnoexeced", F_OK) == 0 (our libc finds it)
|
||||||
|
// else EE00|errno (access failed -> which errno)
|
||||||
|
// 0x025022 EE00|errno after execv("/bin/gnoexeced") RETURNED (explicit path,
|
||||||
|
// bypasses PATH; only set if the exec failed to transfer)
|
||||||
|
// 0x025024 EE00|errno after execvp("gnoexeced") RETURNED (PATH-resolved;
|
||||||
|
// only set if that exec failed too)
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern int access(const char *path, int mode);
|
||||||
|
extern int execv(const char *path, char *const *argv);
|
||||||
|
extern int execvp(const char *file, char *const *argv);
|
||||||
|
extern int errno;
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
#define BAD 0xDEAD
|
||||||
|
#define F_OK 0
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
char *argv[2];
|
||||||
|
int r;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
M(0x025010UL) = BAD;
|
||||||
|
M(0x025020UL) = BAD;
|
||||||
|
M(0x025022UL) = BAD;
|
||||||
|
M(0x025024UL) = BAD;
|
||||||
|
|
||||||
|
argv[0] = "gnoexeced";
|
||||||
|
argv[1] = 0;
|
||||||
|
|
||||||
|
// Step 1: does our own access() see the file gsh clearly found at /bin?
|
||||||
|
errno = 0;
|
||||||
|
r = access("/bin/gnoexeced", F_OK);
|
||||||
|
if (r == 0) {
|
||||||
|
M(0x025020UL) = 0xF00D;
|
||||||
|
} else {
|
||||||
|
M(0x025020UL) = 0xEE00 | (uint16_t)(errno & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: explicit-path execv (bypasses $PATH entirely). On success this
|
||||||
|
// never returns -- gnoExeced sets 0x025010 + 0x025000 in the same image.
|
||||||
|
errno = 0;
|
||||||
|
execv("/bin/gnoexeced", argv);
|
||||||
|
M(0x025022UL) = 0xEE00 | (uint16_t)(errno & 0xFF); // only if it returned
|
||||||
|
|
||||||
|
// Step 3: PATH-resolved execvp, as gnoExecProbe does.
|
||||||
|
errno = 0;
|
||||||
|
execvp("gnoexeced", argv);
|
||||||
|
M(0x025024UL) = 0xEE00 | (uint16_t)(errno & 0xFF); // only if it returned
|
||||||
|
|
||||||
|
// Reached only if BOTH execs failed to transfer control.
|
||||||
|
M(0x025000UL) = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
81
demos/gnoExecProbe.c
Normal file
81
demos/gnoExecProbe.c
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
// exec-family regression for the GNO libc (punch-list #5 + #7).
|
||||||
|
// Exercises access() over Kstat and the exec* wrappers over K_execve.
|
||||||
|
//
|
||||||
|
// Two halves, run in order so one boot can cover both:
|
||||||
|
// 1. Negative / no-2nd-program tests (always run, fully deterministic):
|
||||||
|
// - access(".", F_OK) succeeds on the login cwd (a directory).
|
||||||
|
// - access("/nonesuch/no", F_OK) fails (-1) on a missing path.
|
||||||
|
// - execvp("nosuchprog", ...) fails (-1, errno set): buildPath's
|
||||||
|
// $PATH search finds no candidate and returns before ever reaching
|
||||||
|
// the kernel, so this validates buildCmd/buildPath/access without
|
||||||
|
// needing a second executable.
|
||||||
|
// 2. exec-into (needs GNO_PROG2=demos/gnoExeced.omf placed on /bin): the
|
||||||
|
// LAST action execvp's the tiny 2nd program "gnoexeced", which sets
|
||||||
|
// its own bank-2 marker and the reached-end word. If GNO_PROG2 is
|
||||||
|
// not supplied the exec-into fails, prog1 sets the fallback reached-
|
||||||
|
// end word, and only the half-1 checks are meaningful.
|
||||||
|
//
|
||||||
|
// Bank-2 absolute uint16 markers (DBR-independent), gnoHeapProbe convention:
|
||||||
|
// 0x025000 C0DE reached end (set by gnoExeced on success, else by prog1)
|
||||||
|
// 0x025002 0001 execvp("nosuchprog") returned -1, else DEAD
|
||||||
|
// 0x025004 0001 errno was set nonzero by the failed execvp, else DEAD
|
||||||
|
// 0x025008 0001 access(".", F_OK) == 0 (existing dir), else DEAD
|
||||||
|
// 0x02500A 0001 access("/nonesuch/no", F_OK) == -1 (missing), else DEAD
|
||||||
|
// 0x025010 E0EC gnoExeced ran (exec-into transferred control), else DEAD
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
extern int access(const char *path, int mode);
|
||||||
|
extern int execvp(const char *file, char *const *argv);
|
||||||
|
extern int errno;
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
#define OK 0x0001
|
||||||
|
#define BAD 0xDEAD
|
||||||
|
#define F_OK 0
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
char *nope[2];
|
||||||
|
char *prog2[2];
|
||||||
|
int r;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
M(0x025002UL) = BAD;
|
||||||
|
M(0x025004UL) = BAD;
|
||||||
|
M(0x025008UL) = BAD;
|
||||||
|
M(0x02500AUL) = BAD;
|
||||||
|
M(0x025010UL) = BAD;
|
||||||
|
|
||||||
|
// access() over Kstat: existing directory vs. missing path.
|
||||||
|
if (access(".", F_OK) == 0) {
|
||||||
|
M(0x025008UL) = OK;
|
||||||
|
}
|
||||||
|
if (access("/nonesuch/no", F_OK) == -1) {
|
||||||
|
M(0x02500AUL) = OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed exec: no such program on $PATH -> -1 with errno set. (This
|
||||||
|
// fails in buildPath, before K_execve, so it is safe and deterministic.)
|
||||||
|
nope[0] = "nosuchprog";
|
||||||
|
nope[1] = 0;
|
||||||
|
errno = 0;
|
||||||
|
r = execvp("nosuchprog", nope);
|
||||||
|
if (r == -1) {
|
||||||
|
M(0x025002UL) = OK;
|
||||||
|
}
|
||||||
|
if (errno != 0) {
|
||||||
|
M(0x025004UL) = OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exec-into the 2nd program (GNO_PROG2). On success this never returns;
|
||||||
|
// gnoExeced sets 0x025010 and the reached-end word in the same image.
|
||||||
|
prog2[0] = "gnoexeced";
|
||||||
|
prog2[1] = 0;
|
||||||
|
execvp("gnoexeced", prog2);
|
||||||
|
|
||||||
|
// Only reached if the exec-into failed (no GNO_PROG2 on /bin).
|
||||||
|
M(0x025000UL) = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
21
demos/gnoExeced.c
Normal file
21
demos/gnoExeced.c
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// 2nd program for gnoExecProbe (punch-list #5 + #7 exec-into test).
|
||||||
|
// Proves exec* transferred control: GNO exec replaces the process image in
|
||||||
|
// place, so this runs in the SAME process after gnoExecProbe's execvp. It
|
||||||
|
// sets its exec-into marker plus the shared reached-end word, then spins so
|
||||||
|
// the harness can sample bank-2 memory.
|
||||||
|
//
|
||||||
|
// Deployed to /bin as "gnoexeced" via GNO_PROG2 (runInGno.sh copies it to
|
||||||
|
// /GNO.BOOT/bin), which is how gnoExecProbe's execvp("gnoexeced") resolves
|
||||||
|
// it through the default $PATH ("/bin /usr/bin").
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
M(0x025010UL) = 0xE0EC; // exec-into transferred control here
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end (via exec'd program)
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
103
demos/gnoFdProbe.c
Normal file
103
demos/gnoFdProbe.c
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// POSIX fd-layer regression for GNO (punch-list #9): exercise the GS/OS-toolbox
|
||||||
|
// emulation of creat/write/close/open/read/lseek/unlink over the gsos* wrappers.
|
||||||
|
// Round-trips a fixed 16-byte payload through a real GS/OS file in the login cwd
|
||||||
|
// (/home/root) and asserts every stage via bank-2 absolute markers (DBR-
|
||||||
|
// independent), per the gnoHeapProbe/gnoForkWaitProbe convention.
|
||||||
|
//
|
||||||
|
// 0x025000 C0DE reached end (no hang in the fd layer)
|
||||||
|
// 0x025002 0001 read-back payload matched what was written
|
||||||
|
// 0x025004 0001 creat() returned a real GS/OS refNum (>= 4)
|
||||||
|
// 0x025006 0001 write() wrote all 16 bytes
|
||||||
|
// 0x025008 0001 read() read all 16 bytes back
|
||||||
|
// 0x02500A 0001 open(O_RDONLY) returned a real refNum (>= 4)
|
||||||
|
// 0x02500C 0001 lseek(SEEK_SET,4) + 1-byte read landed on payload[4]
|
||||||
|
// 0x02500E 0001 unlink() removed the file
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
static const char kPath[] = "fdprobe.tmp";
|
||||||
|
static const uint8_t kData[16] = {
|
||||||
|
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||||
|
0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint8_t rbuf[16];
|
||||||
|
uint16_t ok;
|
||||||
|
int fd;
|
||||||
|
long n;
|
||||||
|
long pos;
|
||||||
|
char one;
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
M(0x025002UL) = 0xDEAD;
|
||||||
|
M(0x025004UL) = 0xDEAD;
|
||||||
|
M(0x025006UL) = 0xDEAD;
|
||||||
|
M(0x025008UL) = 0xDEAD;
|
||||||
|
M(0x02500AUL) = 0xDEAD;
|
||||||
|
M(0x02500CUL) = 0xDEAD;
|
||||||
|
M(0x02500EUL) = 0xDEAD;
|
||||||
|
ok = 1;
|
||||||
|
|
||||||
|
// Create + write the payload.
|
||||||
|
fd = creat(kPath, 0);
|
||||||
|
if (fd >= 4) {
|
||||||
|
M(0x025004UL) = 0x0001;
|
||||||
|
n = write(fd, kData, 16);
|
||||||
|
if (n == 16) {
|
||||||
|
M(0x025006UL) = 0x0001;
|
||||||
|
} else {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
} else {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reopen read-only and read the payload back.
|
||||||
|
fd = open(kPath, O_RDONLY);
|
||||||
|
if (fd >= 4) {
|
||||||
|
M(0x02500AUL) = 0x0001;
|
||||||
|
n = read(fd, rbuf, 16);
|
||||||
|
if (n == 16) {
|
||||||
|
M(0x025008UL) = 0x0001;
|
||||||
|
} else {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
if (rbuf[i] != kData[i]) {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Seek back into the file and confirm the byte at offset 4.
|
||||||
|
pos = lseek(fd, 4, SEEK_SET);
|
||||||
|
one = 0;
|
||||||
|
read(fd, &one, 1);
|
||||||
|
if (pos == 4 && (uint8_t)one == kData[4]) {
|
||||||
|
M(0x02500CUL) = 0x0001;
|
||||||
|
} else {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
} else {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the file.
|
||||||
|
if (unlink(kPath) == 0) {
|
||||||
|
M(0x02500EUL) = 0x0001;
|
||||||
|
} else {
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
M(0x025002UL) = ok ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025000UL) = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 600000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
56
demos/gnoForkWaitProbe.c
Normal file
56
demos/gnoForkWaitProbe.c
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Process-lifecycle core regression for GNO: fork a leaf child that _exit(42)s,
|
||||||
|
// have the parent wait() and assert the real exit status propagates. Covers two
|
||||||
|
// fixed bugs at once:
|
||||||
|
// 1. Kernel dispatch vector: gnoKernel.s called $E10000 (the standard Tool
|
||||||
|
// Locator, toolset $03 = Misc Tools) instead of GNO's udispatch $E10008, so
|
||||||
|
// EVERY K* primitive hung. Latent until this probe -- fork is the first K*
|
||||||
|
// call ever exercised under GNO.
|
||||||
|
// 2. Exit status: _exit routed QUIT through __gnoGsosCall, which loaded
|
||||||
|
// A = callNum (0x2029) right before the trap, so GNO's StackGSOS recorded
|
||||||
|
// 0x2029 as the exit code and wait() decoded WEXITSTATUS = 0x29 (41).
|
||||||
|
// __gnoQuit now leaves A = status, so wait() sees 42 (0x2A).
|
||||||
|
//
|
||||||
|
// GNO fork is a thread-spawn: the child begins at child() on a fresh stack/DP, so
|
||||||
|
// child() is a LEAF using only absolute-long stores + _exit (no DP scratch).
|
||||||
|
// Bank-2 absolute markers (DBR-independent), per the gnoHeapProbe convention.
|
||||||
|
// 0x025000 C0DE reached end (no hang in fork/wait)
|
||||||
|
// 0x025010 C41D child was scheduled + ran
|
||||||
|
// 0x025014 002A WEXITSTATUS == 42 (the status propagated)
|
||||||
|
// 0x025016 0001 wait() reaped the same pid fork() returned
|
||||||
|
// 0x025018 600D exit status is NOT the old 41/0x29 miscompile
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
extern int fork(void *subr); // GNO thread-spawn: subr = (void *)&childFn
|
||||||
|
extern int wait(int *status);
|
||||||
|
extern __attribute__((noreturn)) void _exit(int status);
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
|
||||||
|
static void child(void) {
|
||||||
|
M(0x025010UL) = 0xC41D; // child was scheduled + entered child()
|
||||||
|
_exit(42); // exit code 42 -> status word 0x2A00
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
int pid = fork((void *)child);
|
||||||
|
if (pid <= 0) {
|
||||||
|
M(0x025016UL) = 0xDEAD; // fork failed / returned no child
|
||||||
|
M(0x025000UL) = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int st = 0;
|
||||||
|
int w = wait(&st); // blocks until the child dies (race-free)
|
||||||
|
M(0x025014UL) = WIFEXITED(st) ? (uint16_t)WEXITSTATUS(st) : 0xEEEE; // == 0x002A
|
||||||
|
M(0x025016UL) = (w == pid) ? 0x0001 : 0xDEAD; // pid round-trip
|
||||||
|
M(0x025018UL) = (WEXITSTATUS(st) == 41) ? 0xDEAD : 0x600D; // guard old bug
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
41
demos/gnoHeapProbe.c
Normal file
41
demos/gnoHeapProbe.c
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
// A1 regression: pre-fix, malloc bumped the bank-0 phantom heap window
|
||||||
|
// [__bss_end, 0xBF00) into GNO's kernel heartbeat block (alarmHB ~0xB7xx)
|
||||||
|
// -> SysFailMgr $0308 halt. crt0Gno now backs malloc with an MM-owned
|
||||||
|
// handle (__heapInitMM), so several KB of allocations survive intact.
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
*(volatile uint16_t *)0x025010UL = 0xBEEF; // main entered
|
||||||
|
uint16_t ok = 1;
|
||||||
|
char *b[8];
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
b[i] = (char *)malloc(0x400); // 8 KB total
|
||||||
|
if (!b[i]) {
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memset(b[i], (char)(0xA0 + i), 0x400);
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
if ((uint8_t)b[i][0] != (uint8_t)(0xA0 + i) ||
|
||||||
|
(uint8_t)b[i][0x3FF] != (uint8_t)(0xA0 + i)) {
|
||||||
|
ok = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The observable regression signal is survival: pre-fix, malloc bumped
|
||||||
|
// into GNO's kernel heartbeat block and 8 KB of allocations could not
|
||||||
|
// complete intact. (A bank read cannot prove MM-ownership -- NewHandle
|
||||||
|
// uses attr without attrBank, so the MM may legitimately place the block
|
||||||
|
// in bank 0 -- so this probe asserts survival, not the block's bank.)
|
||||||
|
*(volatile uint16_t *)0x025012UL = ok ? 0x0001 : 0xDEAD;
|
||||||
|
*(volatile uint16_t *)0x025000UL = 0xC0DE; // reached end
|
||||||
|
for (volatile unsigned long j = 0; j < 600000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
47
demos/gnoIdProbe.c
Normal file
47
demos/gnoIdProbe.c
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
// uid/gid + job-control regression for GNO (punch-list #11). Exercises the
|
||||||
|
// non-destructive getter surface -- getuid/geteuid/getgid/getegid, getpid, and
|
||||||
|
// getpgrp -- and asserts the boot-independent invariants: real == effective
|
||||||
|
// ids, a valid (non-error) process group, and a positive pid. The MUTATING
|
||||||
|
// calls (setuid/setgid/setpgid/tcsetpgrp) are deliberately NOT driven here:
|
||||||
|
// they would alter this process' credentials / process group under gsh and make
|
||||||
|
// the run non-deterministic. tcgetpgrp/setsid are ENOSYS stubs and need no run.
|
||||||
|
//
|
||||||
|
// Bank-2 absolute markers (DBR-independent), per the gnoHeapProbe convention.
|
||||||
|
// 0x025000 C0DE reached end
|
||||||
|
// 0x025010 0001 getuid() == geteuid() (real/effective uid consistent)
|
||||||
|
// 0x025012 0001 getgid() == getegid() (real/effective gid consistent)
|
||||||
|
// 0x025014 0001 getpgrp() >= 0 (K_getpgrp did not fail)
|
||||||
|
// 0x025016 0001 getpid() > 0 (valid pid)
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
int uid;
|
||||||
|
int euid;
|
||||||
|
int gid;
|
||||||
|
int egid;
|
||||||
|
int pid;
|
||||||
|
int pgrp;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
uid = (int)getuid();
|
||||||
|
euid = (int)geteuid();
|
||||||
|
gid = (int)getgid();
|
||||||
|
egid = (int)getegid();
|
||||||
|
pid = getpid();
|
||||||
|
pgrp = (int)getpgrp();
|
||||||
|
|
||||||
|
M(0x025010UL) = (uid == euid) ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025012UL) = (gid == egid) ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025014UL) = (pgrp >= 0) ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025016UL) = (pid > 0) ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025018UL) = (uint16_t)pgrp; // informational
|
||||||
|
M(0x02501AUL) = (uint16_t)uid; // informational
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
43
demos/gnoSignalProbe.c
Normal file
43
demos/gnoSignalProbe.c
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Signals end-to-end regression for GNO: install a SIGALRM handler via our
|
||||||
|
// signal() (which routes the kernel callback through __sigTrampoline), arm a
|
||||||
|
// 1-second alarm, and pause(). The kernel delivers SIGALRM through the
|
||||||
|
// trampoline, which adapts the ORCA/C callee-pops frame to our arg0-in-A
|
||||||
|
// handler; the handler flips a flag + drops a marker, pause() returns (EINTR),
|
||||||
|
// and main verifies the handler ran and control resumed cleanly (proving the
|
||||||
|
// trampoline's ORCA callee-pop return kept the kernel's context-restore stack
|
||||||
|
// intact).
|
||||||
|
//
|
||||||
|
// Bank-2 absolute uint16 markers (DBR-independent), gnoHeapProbe convention:
|
||||||
|
// 0x025000 C0DE reached end (pause returned; no hang/crash)
|
||||||
|
// 0x025010 C41D the SIGALRM handler actually executed
|
||||||
|
// 0x025012 0001 handler-ran flag observed after pause (0xDEAD if not)
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
static volatile uint16_t rang;
|
||||||
|
|
||||||
|
|
||||||
|
static void onAlarm(int sig) {
|
||||||
|
(void)sig;
|
||||||
|
rang = 1;
|
||||||
|
M(0x025010UL) = 0xC41D; // handler executed via the trampoline
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
M(0x025010UL) = 0x0000;
|
||||||
|
M(0x025012UL) = 0x0000;
|
||||||
|
rang = 0;
|
||||||
|
signal(SIGALRM, onAlarm); // install through __sigTrampoline
|
||||||
|
alarm(1); // fire SIGALRM in ~1 second
|
||||||
|
pause(); // block until the signal is delivered
|
||||||
|
M(0x025012UL) = rang ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end (returned cleanly)
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
72
demos/gnoStatProbe.c
Normal file
72
demos/gnoStatProbe.c
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// stat/fstat/isatty regression for the GNO libc file-status surface.
|
||||||
|
//
|
||||||
|
// Exercises the three K* status primitives (Kstat/Kfstat 0x2703/0x2803)
|
||||||
|
// through their POSIX wrappers in libcGno.c, plus isatty() built on
|
||||||
|
// fstat. The hard part these wrappers get right is struct stat's
|
||||||
|
// byte-layout: the GNO kernel (kern/gno/stat.c, ORCA/C) writes the buffer
|
||||||
|
// directly, so if runtime/include/sys/stat.h drifts by even one field the
|
||||||
|
// S_IF* type bits land in the wrong word and every test below flips.
|
||||||
|
//
|
||||||
|
// The program is launched at the gsh prompt with stdout on the console
|
||||||
|
// TTY, so:
|
||||||
|
// - fstat(1) succeeds and st_mode is a character special (the TTY).
|
||||||
|
// - isatty(1) is therefore true.
|
||||||
|
// - stat(".") on the login cwd is a directory.
|
||||||
|
// All deterministic (no pids / addresses / times inspected).
|
||||||
|
//
|
||||||
|
// Bank-2 absolute uint16 markers (DBR-independent), per the gnoHeapProbe
|
||||||
|
// convention:
|
||||||
|
// 0x025000 C0DE reached end (no hang in any stat path)
|
||||||
|
// 0x025010 0001 fstat(1) returned 0
|
||||||
|
// 0x025012 0001 S_ISCHR(fstat st_mode) -- stdout is a char device
|
||||||
|
// 0x025014 0001 isatty(1) == 1
|
||||||
|
// 0x025016 0001 stat(".") returned 0
|
||||||
|
// 0x025018 0001 S_ISDIR(stat st_mode) -- cwd is a directory
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
extern int isatty(int fd);
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
#define OK 0x0001
|
||||||
|
#define BAD 0xDEAD
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
struct stat sb;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
M(0x025010UL) = BAD;
|
||||||
|
M(0x025012UL) = BAD;
|
||||||
|
M(0x025014UL) = BAD;
|
||||||
|
M(0x025016UL) = BAD;
|
||||||
|
M(0x025018UL) = BAD;
|
||||||
|
|
||||||
|
// fstat(1): stdout, which the harness leaves on the console TTY.
|
||||||
|
rc = fstat(1, &sb);
|
||||||
|
if (rc == 0) {
|
||||||
|
M(0x025010UL) = OK;
|
||||||
|
}
|
||||||
|
if (S_ISCHR(sb.st_mode)) {
|
||||||
|
M(0x025012UL) = OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// isatty(1): true because stdout is a character device.
|
||||||
|
if (isatty(1) == 1) {
|
||||||
|
M(0x025014UL) = OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat("."): the login cwd, which is a directory.
|
||||||
|
rc = stat(".", &sb);
|
||||||
|
if (rc == 0) {
|
||||||
|
M(0x025016UL) = OK;
|
||||||
|
}
|
||||||
|
if (S_ISDIR(sb.st_mode)) {
|
||||||
|
M(0x025018UL) = OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end
|
||||||
|
for (volatile unsigned long j = 0; j < 600000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
59
demos/gnoWaitpidTimesProbe.c
Normal file
59
demos/gnoWaitpidTimesProbe.c
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// waitpid + times regression for GNO/ME. Forks a leaf child that _exit(37)s,
|
||||||
|
// then the parent reaps it with waitpid(pid, &st, 0) -- verifying the pure-C
|
||||||
|
// waitpid loop returns the exact child pid and the real exit status -- and
|
||||||
|
// finally calls times() and checks the deterministic fields the GNO kernel
|
||||||
|
// zero-fills (return value, tms_stime, tms_cstime), a crash-free sanity marker.
|
||||||
|
//
|
||||||
|
// GNO fork is a thread-spawn: the child begins at child() on a fresh stack/DP,
|
||||||
|
// so child() is a LEAF using only absolute-long stores + _exit (no DP scratch).
|
||||||
|
// Bank-2 absolute markers (DBR-independent), per the gnoForkWaitProbe convention.
|
||||||
|
// 0x025000 C0DE reached end (no hang in waitpid/times)
|
||||||
|
// 0x025010 C41D child was scheduled + ran
|
||||||
|
// 0x025014 0025 WEXITSTATUS == 37 (status propagated through waitpid)
|
||||||
|
// 0x025016 0001 waitpid reaped the same pid fork() returned
|
||||||
|
// 0x025018 0001 times() sanity: ret==0 && tms_stime==0 && tms_cstime==0
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/times.h>
|
||||||
|
|
||||||
|
extern int fork(void *subr); // GNO thread-spawn: subr = (void *)&childFn
|
||||||
|
extern __attribute__((noreturn)) void _exit(int status);
|
||||||
|
extern int waitpid(int pid, int *status, int options);
|
||||||
|
extern clock_t times(struct tms *buf);
|
||||||
|
|
||||||
|
#define M(a) (*(volatile uint16_t *)(a))
|
||||||
|
|
||||||
|
|
||||||
|
static void child(void) {
|
||||||
|
M(0x025010UL) = 0xC41D; // child was scheduled + entered child()
|
||||||
|
_exit(37); // exit code 37 -> status word 0x2500
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
M(0x025000UL) = 0x0000;
|
||||||
|
int pid = fork((void *)child);
|
||||||
|
if (pid <= 0) {
|
||||||
|
M(0x025016UL) = 0xDEAD; // fork failed / returned no child
|
||||||
|
M(0x025000UL) = 0xC0DE;
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int st = 0;
|
||||||
|
int w = waitpid(pid, &st, 0); // pure-C loop over wait() until pid reaped
|
||||||
|
M(0x025014UL) = WIFEXITED(st) ? (uint16_t)WEXITSTATUS(st) : 0xEEEE; // == 0x0025
|
||||||
|
M(0x025016UL) = (w == pid) ? 0x0001 : 0xDEAD; // pid round-trip
|
||||||
|
|
||||||
|
struct tms tb;
|
||||||
|
tb.tms_stime = 0xFFFFFFFFUL; // poison; kernel must overwrite with 0
|
||||||
|
tb.tms_cstime = 0xFFFFFFFFUL;
|
||||||
|
clock_t rc = times(&tb);
|
||||||
|
uint16_t timesOk = (rc == 0 && tb.tms_stime == 0 && tb.tms_cstime == 0) ? 0x0001 : 0xDEAD;
|
||||||
|
M(0x025018UL) = timesOk;
|
||||||
|
|
||||||
|
M(0x025000UL) = 0xC0DE; // reached end
|
||||||
|
for (volatile unsigned long j = 0; j < 300000UL; j++) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Binary file not shown.
49
runtime/include/fcntl.h
Normal file
49
runtime/include/fcntl.h
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// fcntl.h -- file-control definitions for the GNO/ME runtime.
|
||||||
|
//
|
||||||
|
// O_* values match GNO's <sys/fcntl.h> exactly so binaries agree with the
|
||||||
|
// kernel and shell. open()/creat() live in runtime/src/libcGno.c as GS/OS-
|
||||||
|
// toolbox emulation over the gsos* wrappers.
|
||||||
|
#ifndef _FCNTL_H
|
||||||
|
#define _FCNTL_H
|
||||||
|
|
||||||
|
#include <sys/types.h> // mode_t / off_t (single source of truth)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Access modes (open-only), matching GNO <sys/fcntl.h>.
|
||||||
|
#define O_RDONLY 0x0001
|
||||||
|
#define O_WRONLY 0x0002
|
||||||
|
#define O_RDWR 0x0004
|
||||||
|
#define O_ACCMODE 0x0007
|
||||||
|
|
||||||
|
// Status / creation flags, matching GNO <sys/fcntl.h>.
|
||||||
|
#define O_NONBLOCK 0x0008
|
||||||
|
#define O_APPEND 0x0010
|
||||||
|
#define O_CREAT 0x0020
|
||||||
|
#define O_TRUNC 0x0040
|
||||||
|
#define O_EXCL 0x0080
|
||||||
|
#define O_BINARY 0x0100 // non-POSIX GNO flag: create as BIN not TXT
|
||||||
|
#define O_TRANS 0x0200 // GNO-specific (text translation)
|
||||||
|
#define O_NOCTTY 0 // no controlling-tty assignment (no bit)
|
||||||
|
|
||||||
|
// whence values for lseek(); guarded so <stdio.h>'s copies do not clash.
|
||||||
|
#ifndef SEEK_SET
|
||||||
|
#define SEEK_SET 0
|
||||||
|
#endif
|
||||||
|
#ifndef SEEK_CUR
|
||||||
|
#define SEEK_CUR 1
|
||||||
|
#endif
|
||||||
|
#ifndef SEEK_END
|
||||||
|
#define SEEK_END 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int open(const char *path, int flags, ...);
|
||||||
|
int creat(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -24,10 +24,56 @@ typedef void (*__sighandler_t)(int);
|
||||||
#define SIGFPE 8
|
#define SIGFPE 8
|
||||||
#define SIGSEGV 11
|
#define SIGSEGV 11
|
||||||
#define SIGTERM 15
|
#define SIGTERM 15
|
||||||
|
#define SIGALRM 14 // alarm-clock signal (GNO value; used by sleep/usleep)
|
||||||
|
|
||||||
__sighandler_t signal(int sig, __sighandler_t handler);
|
__sighandler_t signal(int sig, __sighandler_t handler);
|
||||||
int raise(int sig);
|
int raise(int sig);
|
||||||
|
|
||||||
|
// ---- POSIX signal surface (GNO/ME kernel-backed; see libcGno.c) --------
|
||||||
|
#define NSIG 32 // GNO: signal numbers are 1..31 (mask is 1-32)
|
||||||
|
|
||||||
|
#define SIGHUP 1
|
||||||
|
#define SIGQUIT 3
|
||||||
|
#define SIGTRAP 5
|
||||||
|
#define SIGKILL 9
|
||||||
|
#define SIGBUS 10
|
||||||
|
#define SIGPIPE 13
|
||||||
|
#define SIGSTOP 17
|
||||||
|
#define SIGTSTP 18
|
||||||
|
#define SIGCONT 19
|
||||||
|
#define SIGCHLD 20
|
||||||
|
#define SIGTTIN 21
|
||||||
|
#define SIGTTOU 22
|
||||||
|
#define SIGUSR1 30
|
||||||
|
#define SIGUSR2 31
|
||||||
|
|
||||||
|
typedef unsigned long sigset_t;
|
||||||
|
typedef void (*sig_t)(int);
|
||||||
|
|
||||||
|
#define SIG_BLOCK 0
|
||||||
|
#define SIG_UNBLOCK 1
|
||||||
|
#define SIG_SETMASK 2
|
||||||
|
|
||||||
|
struct sigaction {
|
||||||
|
void (*sa_handler)(int);
|
||||||
|
sigset_t sa_mask;
|
||||||
|
int sa_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
int kill(int pid, int sig);
|
||||||
|
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
|
||||||
|
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
|
||||||
|
int sigsuspend(const sigset_t *mask);
|
||||||
|
unsigned long sigblock(unsigned long mask);
|
||||||
|
unsigned long sigsetmask(unsigned long mask);
|
||||||
|
|
||||||
|
// Pure-C bitset ops (GNO defines these as macros, not functions).
|
||||||
|
#define sigemptyset(set) (*(set) = 0, 0)
|
||||||
|
#define sigfillset(set) (*(set) = ~(sigset_t)0, 0)
|
||||||
|
#define sigaddset(set, signo) (*(set) |= (sigset_t)1 << ((signo) - 1), 0)
|
||||||
|
#define sigdelset(set, signo) (*(set) &= ~((sigset_t)1 << ((signo) - 1)), 0)
|
||||||
|
#define sigismember(set, signo) ((*(set) & ((sigset_t)1 << ((signo) - 1))) != 0)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,15 @@ typedef void (*__atexit_fn)(void);
|
||||||
int atexit(__atexit_fn fn);
|
int atexit(__atexit_fn fn);
|
||||||
int at_quick_exit(__atexit_fn fn);
|
int at_quick_exit(__atexit_fn fn);
|
||||||
|
|
||||||
// No environment under GS/OS — `getenv` always returns NULL,
|
// Environment: bare GS/OS has none, so libc.c's weak `getenv` returns NULL and
|
||||||
// `system` always returns 0 (no shell to invoke). These exist for
|
// `system` returns 0. Under GNO (libcGno.o linked) getenv/setenv/putenv/
|
||||||
// portable-code compile compatibility.
|
// unsetenv are backed by the kernel shell-variable table. `environ` stays NULL
|
||||||
|
// in that thin port -- use the accessors, not environ[] directly.
|
||||||
char *getenv(const char *name);
|
char *getenv(const char *name);
|
||||||
|
int setenv(const char *name, const char *value, int overwrite);
|
||||||
|
int unsetenv(const char *name);
|
||||||
|
int putenv(const char *str);
|
||||||
|
extern char **environ;
|
||||||
int system(const char *cmd);
|
int system(const char *cmd);
|
||||||
|
|
||||||
#define EXIT_SUCCESS 0
|
#define EXIT_SUCCESS 0
|
||||||
|
|
|
||||||
100
runtime/include/sys/stat.h
Normal file
100
runtime/include/sys/stat.h
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// sys/stat.h -- file status for the GNO/ME runtime.
|
||||||
|
//
|
||||||
|
// struct stat MUST byte-match the layout the GNO kernel's stat.c writes
|
||||||
|
// (kern/gno/stat.c, compiled by ORCA/C with 16-bit int / 32-bit long).
|
||||||
|
// Kstat/Kfstat/Klstat (gno/kernel.h) fill this buffer directly, so any
|
||||||
|
// field-order or size drift silently corrupts every returned field. The
|
||||||
|
// _Static_assert below pins sizeof to the ORCA/C layout (54 bytes); the
|
||||||
|
// field offsets all land on even boundaries, so our 2-byte i32 alignment
|
||||||
|
// (datalayout i32:16) inserts no padding and matches ORCA/C's packing.
|
||||||
|
// (Verified: sizeof==54, offsetof(st_size)==16, st_mtime==26, st_blocks==42,
|
||||||
|
// matching the __GNO__ branch of tools/gno/src-repo/include/sys/stat.h.)
|
||||||
|
//
|
||||||
|
// Field semantics follow GNO's kernel: st_nlink/st_uid/st_gid/st_rdev are
|
||||||
|
// always 0, st_ino is a per-call fake, times are Unix epoch seconds, and
|
||||||
|
// st_mode carries the S_IF* type bits plus ProDOS-derived permission bits.
|
||||||
|
#ifndef _SYS_STAT_H_
|
||||||
|
#define _SYS_STAT_H_
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h> // time_t (single source of truth)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct stat {
|
||||||
|
dev_t st_dev; // device number
|
||||||
|
ino_t st_ino; // (faked) inode number
|
||||||
|
unsigned short st_mode; // file type + permission bits
|
||||||
|
short st_nlink; // link count (always 0 under GNO)
|
||||||
|
uid_t st_uid; // owner uid (always 0)
|
||||||
|
gid_t st_gid; // owner gid (always 0)
|
||||||
|
dev_t st_rdev; // device type (always 0)
|
||||||
|
off_t st_size; // file size in bytes
|
||||||
|
time_t st_atime; // last access time (== mod time)
|
||||||
|
int st_spare1;
|
||||||
|
time_t st_mtime; // last modification time
|
||||||
|
int st_spare2;
|
||||||
|
time_t st_ctime; // creation / status-change time
|
||||||
|
int st_spare3;
|
||||||
|
long st_blksize; // preferred I/O block size (512)
|
||||||
|
long st_blocks; // 512-byte blocks allocated
|
||||||
|
long st_spare4[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
|
||||||
|
_Static_assert(sizeof(struct stat) == 54, "struct stat must byte-match the GNO kernel layout");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// File type mask + type values (ProDOS/GS-OS mapped by the kernel).
|
||||||
|
#define S_IFMT 0170000 // type-of-file mask
|
||||||
|
#define S_IFIFO 0010000 // named pipe (fifo)
|
||||||
|
#define S_IFCHR 0020000 // character special (TTY / device)
|
||||||
|
#define S_IFDIR 0040000 // directory
|
||||||
|
#define S_IFBLK 0060000 // block special
|
||||||
|
#define S_IFREG 0100000 // regular file
|
||||||
|
#define S_IFLNK 0120000 // symbolic link
|
||||||
|
#define S_IFSOCK 0140000 // socket (GNO reports pipes as this)
|
||||||
|
|
||||||
|
// Permission bits.
|
||||||
|
#define S_ISUID 0004000 // set-user-id on execution
|
||||||
|
#define S_ISGID 0002000 // set-group-id on execution
|
||||||
|
#define S_ISVTX 0001000 // sticky bit
|
||||||
|
|
||||||
|
#define S_IRWXU 0000700 // RWX mask for owner
|
||||||
|
#define S_IRUSR 0000400 // R for owner
|
||||||
|
#define S_IWUSR 0000200 // W for owner
|
||||||
|
#define S_IXUSR 0000100 // X for owner
|
||||||
|
#define S_IREAD S_IRUSR
|
||||||
|
#define S_IWRITE S_IWUSR
|
||||||
|
#define S_IEXEC S_IXUSR
|
||||||
|
|
||||||
|
#define S_IRWXG 0000070 // RWX mask for group
|
||||||
|
#define S_IRGRP 0000040 // R for group
|
||||||
|
#define S_IWGRP 0000020 // W for group
|
||||||
|
#define S_IXGRP 0000010 // X for group
|
||||||
|
|
||||||
|
#define S_IRWXO 0000007 // RWX mask for other
|
||||||
|
#define S_IROTH 0000004 // R for other
|
||||||
|
#define S_IWOTH 0000002 // W for other
|
||||||
|
#define S_IXOTH 0000001 // X for other
|
||||||
|
|
||||||
|
// File-type test macros.
|
||||||
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
||||||
|
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
|
||||||
|
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
|
||||||
|
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||||
|
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
|
||||||
|
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
|
||||||
|
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
|
||||||
|
|
||||||
|
int stat(const char *path, struct stat *sb);
|
||||||
|
int fstat(int fd, struct stat *sb);
|
||||||
|
int lstat(const char *path, struct stat *sb);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
25
runtime/include/sys/times.h
Normal file
25
runtime/include/sys/times.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// sys/times.h -- process CPU-time accounting for the GNO/ME runtime.
|
||||||
|
//
|
||||||
|
// Layout byte-matches GNO's own <sys/times.h> (tools/gno kernel fills it):
|
||||||
|
// four clock_t fields, clock_t == unsigned long (32-bit) on this target,
|
||||||
|
// so sizeof(struct tms) == 16 on both sides. times() maps to the GNO
|
||||||
|
// kernel Ktimes primitive (toolset $03, func $35), which fills utime/cutime
|
||||||
|
// with the process' 60 Hz tick counts and leaves stime/cstime zero.
|
||||||
|
#ifndef _SYS_TIMES_H
|
||||||
|
#define _SYS_TIMES_H
|
||||||
|
|
||||||
|
// Single source of truth for clock_t (avoids a duplicate typedef).
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
struct tms {
|
||||||
|
clock_t tms_utime; // user CPU time (60 Hz ticks) for this process
|
||||||
|
clock_t tms_stime; // system CPU time (always 0 under GNO)
|
||||||
|
clock_t tms_cutime; // user CPU time of reaped child processes
|
||||||
|
clock_t tms_cstime; // system CPU time of reaped children (always 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
_Static_assert(sizeof(struct tms) == 16, "struct tms must match GNO kernel layout");
|
||||||
|
|
||||||
|
clock_t times(struct tms *buf);
|
||||||
|
|
||||||
|
#endif
|
||||||
20
runtime/include/sys/types.h
Normal file
20
runtime/include/sys/types.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// sys/types.h -- primitive POSIX id/size typedefs for the GNO/ME runtime.
|
||||||
|
//
|
||||||
|
// Single source of truth for the scalar types struct stat and the file
|
||||||
|
// syscalls are built from. Sizes are pinned to the GNO ABI (int == 16
|
||||||
|
// bits, long == 32 bits), so a struct laid out from these types byte-
|
||||||
|
// matches what the GNO kernel writes. time_t lives in <time.h> (its own
|
||||||
|
// single source); it is not redefined here.
|
||||||
|
#ifndef _SYS_TYPES_H_
|
||||||
|
#define _SYS_TYPES_H_
|
||||||
|
|
||||||
|
typedef unsigned short dev_t; // device number (2 bytes)
|
||||||
|
typedef unsigned long ino_t; // inode number (4 bytes)
|
||||||
|
typedef unsigned short uid_t; // user id (2 bytes)
|
||||||
|
typedef unsigned short gid_t; // group id (2 bytes)
|
||||||
|
typedef unsigned short mode_t; // permission bits (2 bytes)
|
||||||
|
typedef unsigned short nlink_t; // hard-link count (2 bytes)
|
||||||
|
typedef long off_t; // file offset / size (4 bytes)
|
||||||
|
typedef int pid_t; // process id (2 bytes)
|
||||||
|
|
||||||
|
#endif
|
||||||
28
runtime/include/sys/wait.h
Normal file
28
runtime/include/sys/wait.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
// sys/wait.h -- GNO/ME process-wait status decoding.
|
||||||
|
//
|
||||||
|
// GNO's wait status is a single 16-bit int (sizeof(int)==2 on this target), the
|
||||||
|
// BSD "union wait" word (see tools/gno/src-repo/include/sys/wait.h):
|
||||||
|
// bits 0-6 termination signal (0 => normal exit)
|
||||||
|
// bit 7 core-dump flag (always 0 under GNO)
|
||||||
|
// bits 8-15 exit code (valid when the termination signal is 0)
|
||||||
|
// So a normal exit(N) yields status (N << 8); WEXITSTATUS shifts it back down.
|
||||||
|
#ifndef _SYS_WAIT_H
|
||||||
|
#define _SYS_WAIT_H
|
||||||
|
|
||||||
|
#define WIFEXITED(s) (((s) & 0x7F) == 0)
|
||||||
|
#define WEXITSTATUS(s) (((s) >> 8) & 0xFF)
|
||||||
|
#define WIFSIGNALED(s) (((s) & 0x7F) != 0 && ((s) & 0x7F) != 0x7F)
|
||||||
|
#define WTERMSIG(s) ((s) & 0x7F)
|
||||||
|
#define WIFSTOPPED(s) (((s) & 0x7F) == 0x7F)
|
||||||
|
#define WSTOPSIG(s) (((s) >> 8) & 0xFF)
|
||||||
|
#define WCOREDUMP(s) 0
|
||||||
|
|
||||||
|
// Option bits for waitpid's third argument. WNOHANG cannot be honored (the GNO
|
||||||
|
// kernel wait() blocks); waitpid rejects it.
|
||||||
|
#define WNOHANG 1
|
||||||
|
#define WUNTRACED 2
|
||||||
|
|
||||||
|
int wait(int *status);
|
||||||
|
int waitpid(int pid, int *status, int options);
|
||||||
|
|
||||||
|
#endif
|
||||||
87
runtime/include/unistd.h
Normal file
87
runtime/include/unistd.h
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
// unistd.h -- POSIX process, credential, job-control + timing glue for the
|
||||||
|
// GNO/ME runtime. These wrap the GNO kernel toolset $03 primitives (see
|
||||||
|
// gno/kernel.h) into POSIX names; the bodies live in runtime/src/libcGno.c.
|
||||||
|
// File/stat decls live in <fcntl.h> / <sys/stat.h>.
|
||||||
|
#ifndef _UNISTD_H
|
||||||
|
#define _UNISTD_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h> // single source of truth for pid_t/uid_t/gid_t/off_t
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// usleep granularity argument. 32-bit so callers can request more than the
|
||||||
|
// 65 ms an int would cap at, even though the kernel floors us at 0.1 s.
|
||||||
|
typedef unsigned long useconds_t;
|
||||||
|
|
||||||
|
// Standard stream fd numbers (POSIX Unix values; gnoFd remaps to GNO 1/2/3).
|
||||||
|
#define STDIN_FILENO 0
|
||||||
|
#define STDOUT_FILENO 1
|
||||||
|
#define STDERR_FILENO 2
|
||||||
|
|
||||||
|
// ---- process control (runtime/src/libcGno.c) ----
|
||||||
|
int fork(void *subr); // GNO thread-spawn (subr = (void *)&leafFn)
|
||||||
|
int getpid(void);
|
||||||
|
int getppid(void);
|
||||||
|
int dup(int oldfd);
|
||||||
|
int dup2(int oldfd, int newfd);
|
||||||
|
int pipe(int fds[2]);
|
||||||
|
int isatty(int fd); // terminal test via fstat + S_ISCHR
|
||||||
|
|
||||||
|
// ---- credentials (runtime/src/libcGno.c) ----
|
||||||
|
uid_t getuid(void);
|
||||||
|
uid_t geteuid(void);
|
||||||
|
gid_t getgid(void);
|
||||||
|
gid_t getegid(void);
|
||||||
|
int setuid(uid_t uid);
|
||||||
|
int setgid(gid_t gid);
|
||||||
|
|
||||||
|
// ---- job control (runtime/src/libcGno.c) ----
|
||||||
|
pid_t getpgrp(void);
|
||||||
|
int setpgid(pid_t pid, pid_t pgrp);
|
||||||
|
int setpgrp(pid_t pid, pid_t pgrp); // BSD alias of setpgid (same 0x3403 entry)
|
||||||
|
int tcsetpgrp(int fd, pid_t pgrp);
|
||||||
|
pid_t tcgetpgrp(int fd); // stub: ENOSYS (kernel routes TIOCGPGRP to `invalid`)
|
||||||
|
pid_t setsid(void); // stub: ENOSYS (no GNO session primitive)
|
||||||
|
|
||||||
|
// ---- raw I/O + file (bodies in libcGno.c) ----
|
||||||
|
long read(int fd, void *buf, unsigned long n);
|
||||||
|
long write(int fd, const void *buf, unsigned long n);
|
||||||
|
int close(int fd);
|
||||||
|
off_t lseek(int fd, off_t offset, int whence);
|
||||||
|
int unlink(const char *path);
|
||||||
|
|
||||||
|
// ---- file access test (runtime/src/libcGno.c) ----
|
||||||
|
// Only F_OK (existence) is honored under GNO; R/W/X_OK degrade to an existence
|
||||||
|
// test (GNO stat reports no meaningful owner/perm bits).
|
||||||
|
#define F_OK 0 // test for existence of the file
|
||||||
|
#define X_OK 0x01 // test for execute/search permission
|
||||||
|
#define W_OK 0x02 // test for write permission
|
||||||
|
#define R_OK 0x04 // test for read permission
|
||||||
|
int access(const char *path, int mode);
|
||||||
|
|
||||||
|
// ---- exec family (runtime/src/libcGno.c) ----
|
||||||
|
// GNO's kernel exec takes ONE flattened command line (not argv/envp): these
|
||||||
|
// space-join argv, stripping argv[0]'s GS/OS prefix, and IGNORE envp. On
|
||||||
|
// success they replace the process image and do not return; on failure they
|
||||||
|
// return -1 with errno set. execvp/execlp resolve a bare name through $PATH.
|
||||||
|
int execv (const char *path, char *const *argv);
|
||||||
|
int execve(const char *path, char *const *argv, char *const *envp);
|
||||||
|
int execvp(const char *file, char *const *argv);
|
||||||
|
int execl (const char *path, const char *arg, ...);
|
||||||
|
int execle(const char *path, const char *arg, ...);
|
||||||
|
int execlp(const char *file, const char *arg, ...);
|
||||||
|
|
||||||
|
// ---- timing ----
|
||||||
|
unsigned long alarm(unsigned long seconds);
|
||||||
|
unsigned int sleep(unsigned int seconds);
|
||||||
|
int usleep(useconds_t useconds);
|
||||||
|
int pause(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -3,12 +3,17 @@
|
||||||
; Use this INSTEAD OF crt0.s / crt0Gsos.s when building an OMF intended
|
; Use this INSTEAD OF crt0.s / crt0Gsos.s when building an OMF intended
|
||||||
; to run as a GNO shell command (filetype $B5 EXE, aux 0).
|
; to run as a GNO shell command (filetype $B5 EXE, aux 0).
|
||||||
;
|
;
|
||||||
; GNO/ME entry contract for a shell command (per ORCA's ~GNO_COMMAND):
|
; GNO/ME entry contract for a shell command (per ORCA's ~GNO_COMMAND,
|
||||||
|
; verified against tools/gno/src-repo/lib/libc/gno/gnocmd.asm: it does
|
||||||
|
; `sta ~USER_ID` / `sty ~COMMANDLINE` / `stx ~COMMANDLINE+2`):
|
||||||
; E=0 (native), M=0 (16-bit accumulator), X=0 (16-bit index)
|
; E=0 (native), M=0 (16-bit accumulator), X=0 (16-bit index)
|
||||||
; A:X = command line pointer (32-bit; A=low word, X=high word)
|
; A = user ID
|
||||||
; Y = user ID (currently unused by us)
|
; Y:X = command line pointer (32-bit; Y=low word, X=high word)
|
||||||
; DBR = bank of our entry segment (set by GS/OS Loader)
|
; DBR = bank of our entry segment (set by GS/OS Loader)
|
||||||
; DP = pointer to a Memory-Manager-allocated DP page
|
; DP = a Memory-Manager-allocated, PER-PROCESS direct page. We HONOR
|
||||||
|
; it (do NOT force DP=0) so codegen's DP slots are process-private
|
||||||
|
; under GNO's preemptive multitasking — see the __start body.
|
||||||
|
; (~GNO_COMMAND itself keeps this DP and stores argv/argc in it.)
|
||||||
; The GS/OS Loader's launch frame on the stack is discarded by Quit.
|
; The GS/OS Loader's launch frame on the stack is discarded by Quit.
|
||||||
;
|
;
|
||||||
; Differences from crt0Gsos.s:
|
; Differences from crt0Gsos.s:
|
||||||
|
|
@ -23,42 +28,51 @@
|
||||||
|
|
||||||
.globl __start
|
.globl __start
|
||||||
__start:
|
__start:
|
||||||
; GNO entry contract (from kern ~GNO_COMMAND, lib/libc/gno/gnocmd.asm):
|
; MULTITASKING: HONOR GNO's per-process direct page -- do NOT force
|
||||||
; A = user ID
|
; DP=0. GNO is preemptive. Every codegen DP slot (the __jsl_indir
|
||||||
; Y:X = command-line pointer (Y = low word, X = high word) — a
|
; vector at $B8, the PBR stash at $BE, the libcall scratch at
|
||||||
; NUL-terminated C string of the invocation command line.
|
; $E0..$FF) and our own cmdline/user-ID stash at $B0..$B7 below are
|
||||||
; Stash the cmdline pointer (Y:X) to bank-0 scratch $00:00B0..B3
|
; DP-relative, so keeping DP at GNO's per-process page makes them all
|
||||||
; and the user ID (A) to $00:00B6, BEFORE we switch to DP=0.
|
; PROCESS-PRIVATE. Forcing DP=0 (as this crt used to) put every one
|
||||||
; Bank-explicit `sta long` (force-long `>` prefix) so we don't depend
|
; of them on the shared bank-0 zero page -- so two of our binaries run
|
||||||
; on DP/DBR here.
|
; concurrently (e.g. a `a | b` pipeline that gsh forks) aliased each
|
||||||
|
; other's saved DP / pointer bank / scratch -> silent cross-process
|
||||||
|
; corruption. This mirrors crt0Gsos.s honoring the Loader DP; the
|
||||||
|
; backend has no DP=0 requirement (it addresses every scratch slot
|
||||||
|
; DP-relative). (GNO's GS/OS interceptor identifies the caller by
|
||||||
|
; curProcInd -- scheduler state, not DP -- and StackGSOS saves/restores
|
||||||
|
; the caller's DP itself, so honoring GNO's DP is purely about keeping our
|
||||||
|
; own scratch process-private, not about being identified -- see
|
||||||
|
; gnoGsos.s.)
|
||||||
|
;
|
||||||
|
; Stash cmdline (Y:X) at DP $B0..$B3 and user ID (A) at $B6, DP-form so
|
||||||
|
; they land in our private page. DP addressing uses D (valid on entry)
|
||||||
|
; and ignores DBR, so this is safe before the phk/plb below.
|
||||||
rep #0x30
|
rep #0x30
|
||||||
sta >$0000B6 ; sta long $00:00B6 (user ID)
|
sta 0xb6 ; DP $B6 = user ID (per-process)
|
||||||
tya
|
tya
|
||||||
sta >$0000B0 ; sta long $00:00B0 (cmdline low word = Y)
|
sta 0xb0 ; DP $B0 = cmdline low word (= Y)
|
||||||
txa
|
txa
|
||||||
sta >$0000B2 ; sta long $00:00B2 (cmdline high word = X)
|
sta 0xb2 ; DP $B2 = cmdline high word (= X)
|
||||||
|
|
||||||
; Set DBR := PBR (our entry-segment bank) — same Loader-contract
|
; Set DBR := PBR (our entry-segment bank) — same Loader-contract
|
||||||
; mitigation as crt0Gsos.s.
|
; mitigation as crt0Gsos.s.
|
||||||
phk
|
phk
|
||||||
plb
|
plb
|
||||||
|
|
||||||
; Save GNO's per-process direct page BEFORE zeroing it. GNO's
|
; Point the libgcc __jsl_indir trampoline's JMP-(abs) operand at
|
||||||
; GS/OS-call interceptor uses the caller's DP to identify the
|
; (DP + $B8) so indirect dispatch reads its target from our private DP
|
||||||
; calling process (and find its fd table). Our codegen needs
|
; page instead of bank-0 $00B8. Identical to crt0Gsos.s and REQUIRED
|
||||||
; DP=0, so we stash GNO's DP at $00:00B4 and restore it around
|
; now that DP is non-zero (the trampoline operand defaults to $00B8).
|
||||||
; GS/OS calls (see __gnoSaveDP / gsosWrite-under-GNO path).
|
|
||||||
rep #0x30
|
rep #0x30
|
||||||
tdc ; A = current (GNO) DP
|
tdc
|
||||||
sta >$0000B4 ; sta long $00:00B4 (stash GNO DP word)
|
clc
|
||||||
|
adc #0xb8
|
||||||
; Set DP=0 — backend assumes DP=0 for `sta dp` / `[dp],y` etc.
|
sta __jsl_indir_op
|
||||||
lda #0
|
|
||||||
tcd
|
|
||||||
|
|
||||||
; Persistent "current data bank" byte at DP $BE = PBR (our bank).
|
; Persistent "current data bank" byte at DP $BE = PBR (our bank).
|
||||||
; See crt0.s comment for rationale (codegen reads this for the
|
; See crt0.s comment (codegen reads DP $BE for the bank half of
|
||||||
; bank half of `&symbol` 32-bit pointers).
|
; `&symbol` 32-bit pointers). DP-form -> per-process.
|
||||||
sep #0x20
|
sep #0x20
|
||||||
phk
|
phk
|
||||||
pla
|
pla
|
||||||
|
|
@ -66,20 +80,42 @@ __start:
|
||||||
stz 0xbf ; pad so `lda 0xbe` in 16-bit M reads $00PBR
|
stz 0xbf ; pad so `lda 0xbe` in 16-bit M reads $00PBR
|
||||||
rep #0x20
|
rep #0x20
|
||||||
|
|
||||||
; BSS zero-init (DBR-relative byte stores). M is held at 8 across
|
; BSS zero-init: NOT NEEDED under GNO -- and actively harmful. omfEmit
|
||||||
; the whole loop while X stays 16-bit, so we don't flip SEP/REP per
|
; embeds the BSS region as LCONST zeros inside the code segment (seg1
|
||||||
; byte (llvm-mc still encodes cpx/ldx as 16-bit X-immediates in M=8).
|
; LENGTH covers [text .. __bss_end], RESSPC=0), so the GS/OS InitialLoad
|
||||||
rep #0x30 ; M=16, X=16
|
; that GNO's exec uses fills BSS with zeros at load time -- BSS is
|
||||||
sep #0x20 ; M=8 for the byte stores; X remains 16-bit
|
; already zero when __start runs (verified: cxxChronoProbe's zero-init
|
||||||
ldx #__bss_start
|
; clock statics read correctly with this loop gone).
|
||||||
.Lbss_loop:
|
;
|
||||||
cpx #__bss_end
|
; The old DBR-relative `stz $0000,X` loop over [__bss_start, __bss_end]
|
||||||
bcs .Lbss_done
|
; was redundant AND corrupted GNO's heartbeat task queue: once BSS grew
|
||||||
stz 0x0000, x ; 1-byte store (M=8)
|
; large (cxxChronoProbe, ~6 KB BSS, __bss_end ~0xB74C) the clear reached
|
||||||
inx
|
; the kernel's bank-0 heartbeat block (alarmHB), and the next VBL tick
|
||||||
bra .Lbss_loop
|
; hit SysFailMgr $0308 "Damaged heartbeat queue" -> system halt BEFORE
|
||||||
.Lbss_done:
|
; main. A non-chrono probe with an equally large BSS reproduced it, so
|
||||||
rep #0x20 ; restore M=16
|
; it is a pure BSS-ceiling collision, not a chrono bug. Small programs
|
||||||
|
; (BSS below ~0xB100) escaped, which is why it went unnoticed. This is
|
||||||
|
; the same fix crt0Gsos.s already applied for the analogous GS/OS
|
||||||
|
; Memory-Manager corruption -- see feedback_gsos_fopen_partial_diagnosis.
|
||||||
|
|
||||||
|
; Back the C malloc heap with an MM-owned handle -- the SAME fix
|
||||||
|
; crt0Gsos.s applies. crt0Gno historically kept malloc on link816's
|
||||||
|
; fixed link-time window [__bss_end, 0xBF00) in BANK 0, but that span is
|
||||||
|
; PHANTOM memory the Memory Manager never reserved for us: it runs
|
||||||
|
; straight through GNO's high-bank-0 kernel region (the heartbeat block
|
||||||
|
; alarmHB ~0xB7xx), so any GNO program that mallocs a few KB reproduces
|
||||||
|
; the same SysFailMgr $0308 halt the BSS clear did. __heapInitMM
|
||||||
|
; NewHandles a locked block in MM-owned (safe-bank) memory and repoints
|
||||||
|
; malloc at it. Our GNO process MM user ID was handed to __start in A and
|
||||||
|
; stashed at $00:00B6; GNO already started the Memory Manager (see NOTE
|
||||||
|
; below), so -- unlike crt0Gsos -- we must NOT call MMStartUp, we reuse
|
||||||
|
; the handed-in ID (read DP-relative from our private page). NewHandle
|
||||||
|
; keys ownership off the userID PARAMETER, not DP. On NewHandle failure
|
||||||
|
; __heapInitMM leaves malloc on the link-time window (no worse than
|
||||||
|
; before). crt0.s (bare-metal, no MM) still does not do this.
|
||||||
|
rep #0x30
|
||||||
|
lda 0xb6 ; DP $B6 = our GNO process MM user ID (per-process)
|
||||||
|
jsl __heapInitMM ; __heapInitMM(userID); arg0 in A
|
||||||
|
|
||||||
; Walk .init_array (C++ ctors). Inlined from crt0Gsos.s. The
|
; Walk .init_array (C++ ctors). Inlined from crt0Gsos.s. The
|
||||||
; `jsl __jsl_indir` and `jsl main` operands are relocated by the
|
; `jsl __jsl_indir` and `jsl main` operands are relocated by the
|
||||||
|
|
@ -119,12 +155,12 @@ __start:
|
||||||
rep #0x30
|
rep #0x30
|
||||||
jsl __srandInitFromTime
|
jsl __srandInitFromTime
|
||||||
|
|
||||||
; Reload cmdline ptr from $00:00B0..$00:00B3 into A:X.
|
; Reload cmdline ptr from our private DP page ($B0..$B3) into A:X.
|
||||||
; Use bank-explicit `lda long` so we don't depend on DBR.
|
; DP-form (not `lda long`) so we read the per-process copy.
|
||||||
rep #0x30
|
rep #0x30
|
||||||
.byte 0xaf, 0xb0, 0x00, 0x00 ; lda long $00:00B0 -> A (cmdline lo word)
|
lda 0xb0 ; DP $B0 -> A (cmdline lo word)
|
||||||
pha ; stash A on stack briefly
|
pha ; stash A on stack briefly
|
||||||
.byte 0xaf, 0xb2, 0x00, 0x00 ; lda long $00:00B2 -> A (cmdline hi word)
|
lda 0xb2 ; DP $B2 -> A (cmdline hi word)
|
||||||
tax ; X = cmdline hi
|
tax ; X = cmdline hi
|
||||||
pla ; A = cmdline lo
|
pla ; A = cmdline lo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,11 @@ __start:
|
||||||
; system's) can place a block on top of live malloc data (JoeyLib
|
; system's) can place a block on top of live malloc data (JoeyLib
|
||||||
; item 5, heap channel). MMStartUp ($0202) returns our Memory-Manager
|
; item 5, heap channel). MMStartUp ($0202) returns our Memory-Manager
|
||||||
; user ID in A; pass it as arg0 to __heapInitMM (strong impl in libc.o;
|
; user ID in A; pass it as arg0 to __heapInitMM (strong impl in libc.o;
|
||||||
; weak no-op in libgcc.s for programs that don't link libc). crt0.s /
|
; weak no-op in libgcc.s for programs that don't link libc). crt0Gno.s
|
||||||
; crt0Gno.s do NOT do this -- bare-metal has no Memory Manager and GNO
|
; does the SAME (it reuses the MM user ID GNO hands it in A -- stashed at
|
||||||
; manages its own heap; both keep malloc's link-time window.
|
; DP $B6 -- instead of calling MMStartUp, since GNO already started the
|
||||||
|
; Memory Manager). Only crt0.s (bare-metal, no Memory Manager) skips this
|
||||||
|
; and keeps malloc's link-time window.
|
||||||
rep #0x30
|
rep #0x30
|
||||||
pha ; MMStartUp result space (word)
|
pha ; MMStartUp result space (word)
|
||||||
ldx #0x0202 ; MMStartUp call number
|
ldx #0x0202 ; MMStartUp call number
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,134 @@ __gnoGsosCall:
|
||||||
sta __gnoPBlock+2 ; inline pBlock bank+pad (X = bank : pad)
|
sta __gnoPBlock+2 ; inline pBlock bank+pad (X = bank : pad)
|
||||||
lda 4, s ; callNum from the stack
|
lda 4, s ; callNum from the stack
|
||||||
sta __gnoCallNum ; inline callNum
|
sta __gnoCallNum ; inline callNum
|
||||||
lda 0xb4 ; restore GNO's per-process DP
|
; DP is ALREADY our per-process page: crt0Gno honors GNO's DP and never
|
||||||
tcd
|
; forces DP=0, so codegen's bank-0 DP scratch stays process-private and
|
||||||
|
; two of our binaries running concurrently no longer alias a shared $B4
|
||||||
|
; saved DP. GNO's interceptor keys the caller off curProcInd (scheduler
|
||||||
|
; state), NOT off DP: StackGSOS (kern/gno/gsos.asm) does `ldx curProcInd`,
|
||||||
|
; brackets the call with its own phd/pld, and installs its own DP work
|
||||||
|
; area, so the caller's DP is preserved by GNO regardless of its value.
|
||||||
|
; The phd/pld here is a harmless-redundant belt-and-suspenders; the GS/OS
|
||||||
|
; error is returned in A (0 = OK) and pld does not touch A.
|
||||||
|
phd
|
||||||
jsl 0xe100a8
|
jsl 0xe100a8
|
||||||
__gnoCallNum:
|
__gnoCallNum:
|
||||||
.word 0 ; patched: GS/OS call number
|
.word 0 ; patched: GS/OS call number
|
||||||
__gnoPBlock:
|
__gnoPBlock:
|
||||||
.long 0 ; patched: param-block pointer
|
.long 0 ; patched: param-block pointer
|
||||||
; --- OurGSOS returns here (return addr bumped +6) ---
|
; --- OurGSOS returns here (return addr bumped +6); error in A ---
|
||||||
tay ; Y = error (survives DP change)
|
pld ; restore our per-process DP (A = error, untouched)
|
||||||
lda #0
|
|
||||||
tcd ; DP := 0 for the C caller
|
|
||||||
tya
|
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
||||||
|
; __gnoQuit(void *pBlock, int status) -> noreturn
|
||||||
|
; GS/OS QUIT ($2029) that carries the REAL exit status. Same inline-dispatch
|
||||||
|
; trick as __gnoGsosCall, but with ONE critical difference: the call number is
|
||||||
|
; a hardcoded inline CONSTANT (.word 0x2029), leaving A free to hold `status`
|
||||||
|
; at the trap. GNO's StackGSOS does `ldx curProcInd; sta exitCode,x` on entry
|
||||||
|
; (kern/gno/gsos.asm), so whatever is in A at the JSL is recorded as this
|
||||||
|
; process's exit code and later decoded by the parent's wait() (WEXITSTATUS).
|
||||||
|
; __gnoGsosCall loads A = callNum right before its trap, so routing QUIT
|
||||||
|
; through it recorded EVERY process's exit status as 0x2029 -> wait() saw
|
||||||
|
; w_retcode = 0x2029 & 0xFF = 0x29 (41). This helper fixes that.
|
||||||
|
; C ABI: arg0 (pBlock ptr32) in A:X (A=offset, X=bank); arg1 (status i16) at
|
||||||
|
; (4,s). QUIT reaps the process and returns to the launcher -- never here.
|
||||||
|
.globl __gnoQuit
|
||||||
|
__gnoQuit:
|
||||||
|
rep #0x30
|
||||||
|
sta __gnoQuitPB ; inline pBlock offset (low 16) = A
|
||||||
|
txa
|
||||||
|
sta __gnoQuitPB+2 ; inline pBlock bank+pad (X = bank : pad)
|
||||||
|
lda 4, s ; A = status (arg1) -- StackGSOS stashes it as exitCode
|
||||||
|
jsl 0xe100a8
|
||||||
|
.word 0x2029 ; inline callNum = GS/OS QUIT (CONSTANT, never via A)
|
||||||
|
__gnoQuitPB:
|
||||||
|
.long 0 ; patched: param-block pointer
|
||||||
|
; QUIT never returns; if it somehow does, spin rather than run off the end.
|
||||||
|
.LgnoQuitSpin:
|
||||||
|
bra .LgnoQuitSpin
|
||||||
|
|
||||||
|
|
||||||
|
; __sigTrampoline -- the GNO kernel's signal-handler callback ABI shim.
|
||||||
|
;
|
||||||
|
; signal() installs THIS as the kernel handler (via Ksignal) for every caught
|
||||||
|
; signal, with the real C handler stashed in userHandlers[sig]. The kernel
|
||||||
|
; delivers a signal by either (case 1, raise/self-signal) calling the handler
|
||||||
|
; directly from KERNkill, or (case 2, async, e.g. an expired alarm) building a
|
||||||
|
; frame on the process stack that RTIs to the handler. BOTH paths present the
|
||||||
|
; SAME ORCA/C frame at handler entry (kern/gno/signal.c KERNkill + ctool.asm
|
||||||
|
; ctxtRestore):
|
||||||
|
; entry S=S0, native, m=x=0:
|
||||||
|
; [S0+1..S0+3] = 24-bit RTL return (case2: ctxtRestore-1; case1: KERNkill)
|
||||||
|
; [S0+4..S0+5] = param word #1 = the SIGNAL NUMBER
|
||||||
|
; [S0+6..S0+7] = param word #2 (0)
|
||||||
|
; ORCA/C handlers are CALLEE-POPS: on return the handler removes its 2 param
|
||||||
|
; words (4 bytes) and RTLs, leaving S=S0+7. Our handlers are caller-pops with
|
||||||
|
; arg0 in A, so this trampoline calls the C handler with the signal number in A,
|
||||||
|
; then does the ORCA callee-pop return the kernel expects. It also swaps to our
|
||||||
|
; process DP (case 1 enters in the KERNEL's DP) and restores it on the way out.
|
||||||
|
.globl __sigTrampoline
|
||||||
|
__sigTrampoline:
|
||||||
|
rep #0x30 ; force 16-bit A/X/Y (kernel entry is native m=x=0)
|
||||||
|
lda 4, s ; A = signal number (param word #1) -- before any push
|
||||||
|
tay ; Y = signal number (survives the pushes below)
|
||||||
|
phb ; save incoming DBR
|
||||||
|
phd ; save incoming DP
|
||||||
|
lda >__gnoProcDP ; A = this process's direct page (abs-long)
|
||||||
|
tcd ; D = our process DP
|
||||||
|
phk ; \ DBR = our program bank (PBR of this trampoline)
|
||||||
|
plb ; /
|
||||||
|
tya ; A = signal number
|
||||||
|
asl a ; \ X = sig * 4 (userHandlers[] entries are 4-byte)
|
||||||
|
asl a ; /
|
||||||
|
tax
|
||||||
|
lda >userHandlers, x ; low 16 of handler ptr (abs-long indexed)
|
||||||
|
ora >userHandlers+2, x ; | bank word
|
||||||
|
beq .LsigSkip ; no handler (raced to SIG_DFL/IGN) -> just return
|
||||||
|
; --- push a return frame so the C handler RTLs back to .LsigTrampBack ---
|
||||||
|
phk ; handler-return bank = our PBR
|
||||||
|
per .LsigTrampBack-1 ; handler-return offset (PC-relative -> reloc-safe)
|
||||||
|
; --- push the handler address-1 as a 24-bit RTL-jump target ---
|
||||||
|
sep #0x20
|
||||||
|
lda >userHandlers+2, x ; handler bank byte
|
||||||
|
pha
|
||||||
|
rep #0x20
|
||||||
|
lda >userHandlers, x ; handler offset
|
||||||
|
dec a ; -1 (RTL adds 1)
|
||||||
|
pha
|
||||||
|
tya ; A = signal number (handler arg0, our ABI)
|
||||||
|
rtl ; -> C handler; it RTLs back to .LsigTrampBack
|
||||||
|
.LsigTrampBack:
|
||||||
|
.LsigSkip:
|
||||||
|
pld ; restore incoming DP
|
||||||
|
plb ; restore incoming DBR (S now back to entry S0)
|
||||||
|
; --- ORCA callee-pop return: move the 3-byte RTL return up over the 4 ---
|
||||||
|
; --- param bytes, discard them, RTL to the kernel, leaving S=S0+7. ---
|
||||||
|
rep #0x30
|
||||||
|
lda 1, s ; ret low 16 ([S0+1,S0+2])
|
||||||
|
sta 5, s ; -> [S0+5,S0+6]
|
||||||
|
sep #0x20
|
||||||
|
lda 3, s ; ret bank ([S0+3])
|
||||||
|
sta 7, s ; -> [S0+7]
|
||||||
|
rep #0x20
|
||||||
|
tsc
|
||||||
|
clc
|
||||||
|
adc #4 ; drop the 4 param bytes
|
||||||
|
tcs
|
||||||
|
rtl ; -> ctxtRestore (case2) / KERNkill (case1)
|
||||||
|
|
||||||
|
|
||||||
|
; __gnoCaptureDP -- record this process's direct page for __sigTrampoline.
|
||||||
|
; Called from signal() (normal process context, D = our per-process page).
|
||||||
|
.globl __gnoCaptureDP
|
||||||
|
__gnoCaptureDP:
|
||||||
|
rep #0x30
|
||||||
|
tdc ; A = D (our process direct page)
|
||||||
|
sta >__gnoProcDP ; abs-long store (bank-safe)
|
||||||
|
rtl
|
||||||
|
|
||||||
|
|
||||||
|
.data
|
||||||
|
.globl __gnoProcDP
|
||||||
|
__gnoProcDP:
|
||||||
|
.word 0 ; our process DP; set by __gnoCaptureDP before delivery
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
; AUTOGENERATED by scripts/genGnoKernel.py — DO NOT EDIT by hand.
|
; AUTOGENERATED by scripts/genGnoKernel.py — DO NOT EDIT by hand.
|
||||||
;
|
;
|
||||||
; GNO/ME kernel toolset $03 wrappers.
|
; GNO/ME kernel toolset $03 wrappers.
|
||||||
; Dispatcher: JSL $E10000 with LDX #funcId.
|
; Dispatcher: JSL $E10008 (GNO's udispatch vector) with LDX #funcId.
|
||||||
;
|
;
|
||||||
; C ABI: arg0 (i16) in A, arg0 (i32) in A:X, arg1+ on stack
|
; C ABI: arg0 (i16) in A, arg0 (i32) in A:X, arg1+ on stack
|
||||||
; (caller-pushed, lo-word first for 32-bit values).
|
; (caller-pushed, lo-word first for 32-bit values).
|
||||||
|
|
@ -18,7 +18,7 @@ Kgetpid:
|
||||||
; --- result space (2 bytes) ---
|
; --- result space (2 bytes) ---
|
||||||
pea 0
|
pea 0
|
||||||
ldx #0x0903
|
ldx #0x0903
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ Kkill:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x0A03
|
ldx #0x0A03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ Kfork:
|
||||||
lda 12, s
|
lda 12, s
|
||||||
pha
|
pha
|
||||||
ldx #0x0B03
|
ldx #0x0B03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ Kgetpid_dup:
|
||||||
; --- result space (2 bytes) ---
|
; --- result space (2 bytes) ---
|
||||||
pea 0
|
pea 0
|
||||||
ldx #0x0903
|
ldx #0x0903
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ Kgetppid:
|
||||||
lda 0xE0
|
lda 0xE0
|
||||||
pha
|
pha
|
||||||
ldx #0x4003
|
ldx #0x4003
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -125,7 +125,7 @@ Kwait:
|
||||||
lda 12, s
|
lda 12, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1703
|
ldx #0x1703
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ K_execve:
|
||||||
lda 20, s
|
lda 20, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1D03
|
ldx #0x1D03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -183,7 +183,7 @@ Ksignal:
|
||||||
lda 20, s
|
lda 20, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1603
|
ldx #0x1603
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
plx ; result hi -> X
|
plx ; result hi -> X
|
||||||
rtl
|
rtl
|
||||||
|
|
@ -210,7 +210,7 @@ Kalarm:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1E03
|
ldx #0x1E03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
plx ; result hi -> X
|
plx ; result hi -> X
|
||||||
rtl
|
rtl
|
||||||
|
|
@ -237,7 +237,7 @@ Kalarm10:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x4203
|
ldx #0x4203
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
plx ; result hi -> X
|
plx ; result hi -> X
|
||||||
rtl
|
rtl
|
||||||
|
|
@ -263,7 +263,7 @@ Ksigpause:
|
||||||
lda 12, s
|
lda 12, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2103
|
ldx #0x2103
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -289,7 +289,7 @@ Ksigsetmask:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1B03
|
ldx #0x1B03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
plx ; result hi -> X
|
plx ; result hi -> X
|
||||||
rtl
|
rtl
|
||||||
|
|
@ -316,7 +316,7 @@ Ksigblock:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1C03
|
ldx #0x1C03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
plx ; result hi -> X
|
plx ; result hi -> X
|
||||||
rtl
|
rtl
|
||||||
|
|
@ -339,7 +339,7 @@ Kdup:
|
||||||
lda 10, s
|
lda 10, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2203
|
ldx #0x2203
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -364,7 +364,7 @@ Kdup2:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2303
|
ldx #0x2303
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -389,7 +389,7 @@ Kpipe:
|
||||||
lda 12, s
|
lda 12, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2403
|
ldx #0x2403
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -421,7 +421,7 @@ Kioctl:
|
||||||
lda 26, s
|
lda 26, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2603
|
ldx #0x2603
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -451,7 +451,7 @@ Kstat:
|
||||||
lda 20, s
|
lda 20, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2703
|
ldx #0x2703
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -478,7 +478,7 @@ Kfstat:
|
||||||
lda 18, s
|
lda 18, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2803
|
ldx #0x2803
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -508,7 +508,7 @@ Klstat:
|
||||||
lda 20, s
|
lda 20, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2903
|
ldx #0x2903
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -528,7 +528,7 @@ Kgetuid:
|
||||||
lda 0xE0
|
lda 0xE0
|
||||||
pha
|
pha
|
||||||
ldx #0x2A03
|
ldx #0x2A03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -548,7 +548,7 @@ Kgetgid:
|
||||||
lda 0xE0
|
lda 0xE0
|
||||||
pha
|
pha
|
||||||
ldx #0x2B03
|
ldx #0x2B03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -568,7 +568,7 @@ Kgeteuid:
|
||||||
lda 0xE0
|
lda 0xE0
|
||||||
pha
|
pha
|
||||||
ldx #0x2C03
|
ldx #0x2C03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -588,7 +588,7 @@ Kgetegid:
|
||||||
lda 0xE0
|
lda 0xE0
|
||||||
pha
|
pha
|
||||||
ldx #0x2D03
|
ldx #0x2D03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -610,7 +610,7 @@ Ksetuid:
|
||||||
lda 10, s
|
lda 10, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2E03
|
ldx #0x2E03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -632,7 +632,7 @@ Ksetgid:
|
||||||
lda 10, s
|
lda 10, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2F03
|
ldx #0x2F03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -654,7 +654,7 @@ Ktcnewpgrp:
|
||||||
lda 10, s
|
lda 10, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1803
|
ldx #0x1803
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -676,7 +676,7 @@ Ksettpgrp:
|
||||||
lda 10, s
|
lda 10, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1903
|
ldx #0x1903
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -701,7 +701,7 @@ Ktctpgrp:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1A03
|
ldx #0x1A03
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -723,7 +723,7 @@ K_getpgrp:
|
||||||
lda 10, s
|
lda 10, s
|
||||||
pha
|
pha
|
||||||
ldx #0x2503
|
ldx #0x2503
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -748,7 +748,7 @@ Ksetpgrp:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x3403
|
ldx #0x3403
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -768,7 +768,7 @@ Kkvm_open:
|
||||||
lda 0xE0
|
lda 0xE0
|
||||||
pha
|
pha
|
||||||
ldx #0x1103
|
ldx #0x1103
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -793,7 +793,7 @@ Kkvm_close:
|
||||||
lda 12, s
|
lda 12, s
|
||||||
pha
|
pha
|
||||||
ldx #0x1203
|
ldx #0x1203
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
|
|
@ -819,7 +819,7 @@ Ktimes:
|
||||||
lda 14, s
|
lda 14, s
|
||||||
pha
|
pha
|
||||||
ldx #0x3503
|
ldx #0x3503
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
pla ; result lo -> A
|
pla ; result lo -> A
|
||||||
plx ; result hi -> X
|
plx ; result hi -> X
|
||||||
rtl
|
rtl
|
||||||
|
|
@ -848,5 +848,5 @@ KSetGNOQuitRec:
|
||||||
lda 20, s
|
lda 20, s
|
||||||
pha
|
pha
|
||||||
ldx #0x4103
|
ldx #0x4103
|
||||||
jsl 0xe10000
|
jsl 0xe10008
|
||||||
rtl
|
rtl
|
||||||
|
|
|
||||||
|
|
@ -549,14 +549,16 @@ static void mallocInitOnce(void) {
|
||||||
__heap_end ? __heap_end : HEAP_DEFAULT_END);
|
__heap_end ? __heap_end : HEAP_DEFAULT_END);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GS/OS: back the malloc heap with a locked Memory-Manager handle so the
|
// GS/OS and GNO: back the malloc heap with a locked Memory-Manager handle
|
||||||
// heap span is owned by the MM and no later NewHandle (ours or the
|
// so the heap span is owned by the MM and no later NewHandle (ours or the
|
||||||
// system's) can land on top of live malloc data. crt0Gsos calls this with
|
// system's) can land on top of live malloc data. crt0Gsos and crt0Gno both
|
||||||
// our MM user ID in A, before .init_array. crt0.s (bare-metal, no Memory
|
// call this with our MM user ID in A, before .init_array -- crt0Gsos runs
|
||||||
// Manager) and crt0Gno.s do NOT call it, so -ffunction-sections GC drops
|
// MMStartUp first to fetch the ID; crt0Gno reuses the ID GNO hands __start
|
||||||
// this function -- and its NewHandle reference -- from those links. A weak
|
// (GNO already started the Memory Manager). Only crt0.s (bare-metal, no
|
||||||
// no-op __heapInitMM in libgcc.s resolves crt0Gsos's `jsl` for GS/OS
|
// Memory Manager) does NOT call it, so -ffunction-sections GC drops this
|
||||||
// programs that don't link libc.o.
|
// function -- and its NewHandle reference -- from bare-metal links alone. A
|
||||||
|
// weak no-op __heapInitMM in libgcc.s resolves the `jsl` for GS/OS programs
|
||||||
|
// that don't link libc.o.
|
||||||
//
|
//
|
||||||
// This reservation is deliberately eager (done once at startup, before any
|
// This reservation is deliberately eager (done once at startup, before any
|
||||||
// malloc) so the Memory Manager cannot hand the heap span to another
|
// malloc) so the Memory Manager cannot hand the heap span to another
|
||||||
|
|
@ -570,10 +572,10 @@ static void mallocInitOnce(void) {
|
||||||
extern void *NewHandle(unsigned long size, unsigned short userID, unsigned short attr, void *loc);
|
extern void *NewHandle(unsigned long size, unsigned short userID, unsigned short attr, void *loc);
|
||||||
extern void DisposeHandle(void *h);
|
extern void DisposeHandle(void *h);
|
||||||
void __heapInitMM(unsigned short userID) {
|
void __heapInitMM(unsigned short userID) {
|
||||||
// Idempotency guard: keep the first window that was installed. The sole
|
// Idempotency guard: keep the first window that was installed. Each
|
||||||
// caller (crt0Gsos) invokes this once before any malloc, but this keeps
|
// entry crt (crt0Gsos for GS/OS, crt0Gno for GNO) invokes this once
|
||||||
// a second call -- or a future reorder -- from stomping a heap that is
|
// before any malloc, but this keeps a second call -- or a future reorder
|
||||||
// already serving allocations.
|
// -- from stomping a heap that is already serving allocations.
|
||||||
if (bumpPtr) {
|
if (bumpPtr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1367,6 +1369,8 @@ void __assert_fail(const char *expr, const char *file, unsigned int line,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- abort ----
|
// ---- abort ----
|
||||||
|
// Weak: a GNO link (libcGno.o) overrides with a raise(SIGABRT)-based abort.
|
||||||
|
__attribute__((weak))
|
||||||
void abort(void) {
|
void abort(void) {
|
||||||
exit(127);
|
exit(127);
|
||||||
}
|
}
|
||||||
|
|
@ -1410,9 +1414,11 @@ int at_quick_exit(AtexitFn fn) {
|
||||||
|
|
||||||
// ---- getenv / system ----
|
// ---- getenv / system ----
|
||||||
//
|
//
|
||||||
// GS/OS has no environment. getenv always returns NULL. system
|
// Bare GS/OS has no environment. This WEAK getenv returns NULL; a GNO link
|
||||||
// always returns 0 (no command shell available). These exist to
|
// (libcGno.o) overrides it with a shell-variable-table-backed getenv. system
|
||||||
// keep portable code compiling.
|
// always returns 0 (no command shell available). These keep portable code
|
||||||
|
// compiling.
|
||||||
|
char *getenv(const char *name) __attribute__((weak));
|
||||||
char *getenv(const char *name) { (void)name; return (char *)0; }
|
char *getenv(const char *name) { (void)name; return (char *)0; }
|
||||||
int system(const char *cmd) { (void)cmd; return 0; }
|
int system(const char *cmd) { (void)cmd; return 0; }
|
||||||
|
|
||||||
|
|
@ -2223,6 +2229,9 @@ typedef void (*__sighandler_t)(int);
|
||||||
#define _NSIG 16
|
#define _NSIG 16
|
||||||
static __sighandler_t __sigHandlers[_NSIG];
|
static __sighandler_t __sigHandlers[_NSIG];
|
||||||
|
|
||||||
|
// Weak: a GNO link (libcGno.o) overrides with a kernel-backed signal() that
|
||||||
|
// routes real handlers through __sigTrampoline.
|
||||||
|
__attribute__((weak))
|
||||||
__sighandler_t signal(int sig, __sighandler_t handler) {
|
__sighandler_t signal(int sig, __sighandler_t handler) {
|
||||||
if (sig < 0 || sig >= _NSIG) return _SIG_ERR;
|
if (sig < 0 || sig >= _NSIG) return _SIG_ERR;
|
||||||
__sighandler_t prev = __sigHandlers[sig];
|
__sighandler_t prev = __sigHandlers[sig];
|
||||||
|
|
@ -2231,6 +2240,7 @@ __sighandler_t signal(int sig, __sighandler_t handler) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__attribute__((weak))
|
||||||
int raise(int sig) {
|
int raise(int sig) {
|
||||||
if (sig < 0 || sig >= _NSIG) return -1;
|
if (sig < 0 || sig >= _NSIG) return -1;
|
||||||
__sighandler_t h = __sigHandlers[sig];
|
__sighandler_t h = __sigHandlers[sig];
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -43,17 +43,19 @@
|
||||||
; C ABI) survive the indirection unmodified.
|
; C ABI) survive the indirection unmodified.
|
||||||
;
|
;
|
||||||
; Why the SMC dispatch instead of a fixed `jmp ($00B8)`:
|
; Why the SMC dispatch instead of a fixed `jmp ($00B8)`:
|
||||||
; Under bare-metal (crt0.s) and GNO/ME (crt0Gno.s) the runtime sets
|
; Under bare-metal (crt0.s) the runtime leaves DP=0, so the patched
|
||||||
; DP=0, so the patched operand stays $00B8 and the dispatch is byte-
|
; operand stays $00B8 and the dispatch is byte-identical to the legacy
|
||||||
; identical to the legacy fixed form. Under the GS/OS Loader
|
; fixed form. Under the GS/OS Loader (crt0Gsos.s) AND GNO/ME
|
||||||
; (crt0Gsos.s) we now keep DP at the Loader-allocated value rather
|
; (crt0Gno.s) we keep DP at a non-zero value -- the Loader-allocated
|
||||||
; than forcing DP=0 — that keeps every codegen scratch slot (the
|
; page under GS/OS, GNO's per-process page under GNO -- and patch the
|
||||||
|
; operand to (DP + $B8). That keeps every codegen scratch slot (the
|
||||||
; indirect-call anchor at $B8, the PBR stash at $BE, the libcall
|
; indirect-call anchor at $B8, the PBR stash at $BE, the libcall
|
||||||
; scratch at $E0..$FF) in Loader-reserved bank-0 memory instead of
|
; scratch at $E0..$FF) in reserved / process-private bank-0 memory
|
||||||
; on top of system zero page that ROM IRQ handlers and GS/OS
|
; instead of on top of system zero page that ROM IRQ handlers and
|
||||||
; dispatcher state freely scribble. Forcing DP=0 was making clang-
|
; GS/OS dispatcher state freely scribble (and, under GNO's preemptive
|
||||||
; built S16 apps with C++ ctors crash during the init_array
|
; multitasking, that a concurrent second copy of ours would alias).
|
||||||
; indirect-dispatch loop for exactly that reason.
|
; Forcing DP=0 was making clang-built S16 apps with C++ ctors crash
|
||||||
|
; during the init_array indirect-dispatch loop for exactly that reason.
|
||||||
;
|
;
|
||||||
; Constraint preserved: the long-indirect anchor must be in bank 0 (the
|
; Constraint preserved: the long-indirect anchor must be in bank 0 (the
|
||||||
; 65816 JML [abs] opcode 0xDC reads its 24-bit PC vector from bank-0
|
; 65816 JML [abs] opcode 0xDC reads its 24-bit PC vector from bank-0
|
||||||
|
|
@ -87,8 +89,9 @@ __jsl_indir:
|
||||||
.globl __jsl_indir_op
|
.globl __jsl_indir_op
|
||||||
__jsl_indir_op:
|
__jsl_indir_op:
|
||||||
.byte 0xb8, 0x00 ; operand: bank-0 address of the target slot.
|
.byte 0xb8, 0x00 ; operand: bank-0 address of the target slot.
|
||||||
; Patched at startup by crt0 to (DP + $B8); default
|
; Patched at startup by crt0 to (DP + $B8). The
|
||||||
; $00B8 is correct when DP=0 (bare-metal, GNO).
|
; default $00B8 is correct only under bare-metal
|
||||||
|
; DP=0; GS/OS and GNO patch it to their non-zero DP.
|
||||||
|
|
||||||
; --------------------------------------------------------------------
|
; --------------------------------------------------------------------
|
||||||
; __run_cxa_atexit — weak no-op fallback.
|
; __run_cxa_atexit — weak no-op fallback.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
#
|
#
|
||||||
# The GNO kernel is implemented as Apple IIgs user toolset $03. Each
|
# The GNO kernel is implemented as Apple IIgs user toolset $03. Each
|
||||||
# kernel function is identified by a 16-bit number $XX03 where XX is
|
# kernel function is identified by a 16-bit number $XX03 where XX is
|
||||||
# the function index and 03 is the toolset. The dispatcher is the
|
# the function index and 03 is the toolset. Kernel calls dispatch through
|
||||||
# standard IIgs tool dispatcher at $E10000.
|
# GNO's own vector $E10008 (the "udispatch" JSL target GNO installs), NOT the
|
||||||
|
# standard IIgs tool dispatcher $E10000 (toolset $03 there is the Misc Tool Set).
|
||||||
#
|
#
|
||||||
# Function list is hard-coded from include/gno/kerntool.h in the GNO
|
# Function list is hard-coded from include/gno/kerntool.h in the GNO
|
||||||
# Consortium source (https://github.com/GnoConsortium/gno). The
|
# Consortium source (https://github.com/GnoConsortium/gno). The
|
||||||
|
|
@ -13,7 +14,7 @@
|
||||||
# Output: runtime/src/gnoKernel.s — asm wrappers callable from our C
|
# Output: runtime/src/gnoKernel.s — asm wrappers callable from our C
|
||||||
# ABI (arg0 in A, arg0 i32 in A:X, rest pushed RTL on stack). Each
|
# ABI (arg0 in A, arg0 i32 in A:X, rest pushed RTL on stack). Each
|
||||||
# wrapper re-pushes args in toolbox/Pascal order (high-word first for
|
# wrapper re-pushes args in toolbox/Pascal order (high-word first for
|
||||||
# 32-bit values), allocates result space, JSLs $E10000, pops the
|
# 32-bit values), allocates result space, JSLs $E10008, pops the
|
||||||
# result back into A:X if non-void.
|
# result back into A:X if non-void.
|
||||||
#
|
#
|
||||||
# Run after editing the function table:
|
# Run after editing the function table:
|
||||||
|
|
@ -84,7 +85,11 @@ SYSCALLS = [
|
||||||
(0x4103, "KSetGNOQuitRec", 0, [2, 4, 2, 4]), # (pCount, GSStringPtr, flags, *err) -> void
|
(0x4103, "KSetGNOQuitRec", 0, [2, 4, 2, 4]), # (pCount, GSStringPtr, flags, *err) -> void
|
||||||
]
|
]
|
||||||
|
|
||||||
DISPATCHER = 0xE10000
|
# GNO kernel calls dispatch through GNO's OWN vector $E10008 (the "udispatch"
|
||||||
|
# JSL target GNO installs -- lib/libc/sys/trap.asm: `udispatch gequ $E10008`),
|
||||||
|
# NOT the standard IIgs Tool Locator at $E10000 (where toolset $03 is the Misc
|
||||||
|
# Tool Set, so calling it there hangs/misbehaves). Same LDX #funcId convention.
|
||||||
|
DISPATCHER = 0xE10008
|
||||||
|
|
||||||
|
|
||||||
def emitWrapper(funcId, name, retSize, argSizes):
|
def emitWrapper(funcId, name, retSize, argSizes):
|
||||||
|
|
@ -205,7 +210,7 @@ def main():
|
||||||
"; AUTOGENERATED by scripts/genGnoKernel.py — DO NOT EDIT by hand.",
|
"; AUTOGENERATED by scripts/genGnoKernel.py — DO NOT EDIT by hand.",
|
||||||
";",
|
";",
|
||||||
"; GNO/ME kernel toolset $03 wrappers.",
|
"; GNO/ME kernel toolset $03 wrappers.",
|
||||||
"; Dispatcher: JSL $E10000 with LDX #funcId.",
|
"; Dispatcher: JSL $E10008 (GNO's udispatch vector) with LDX #funcId.",
|
||||||
";",
|
";",
|
||||||
"; C ABI: arg0 (i16) in A, arg0 (i32) in A:X, arg1+ on stack",
|
"; C ABI: arg0 (i16) in A, arg0 (i32) in A:X, arg1+ on stack",
|
||||||
"; (caller-pushed, lo-word first for 32-bit values).",
|
"; (caller-pushed, lo-word first for 32-bit values).",
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,13 @@ cp "$PROG" "$WORK/${NAME}#B50100"
|
||||||
cp "$PROG" "$WORK/HELLO#B50100"
|
cp "$PROG" "$WORK/HELLO#B50100"
|
||||||
"$CADIUS" ADDFILE "$DATA" /GNO.BOOT/bin "$WORK/${NAME}#B50100" >/dev/null
|
"$CADIUS" ADDFILE "$DATA" /GNO.BOOT/bin "$WORK/${NAME}#B50100" >/dev/null
|
||||||
"$CADIUS" ADDFILE "$DATA" /GNO.BOOT "$WORK/HELLO#B50100" >/dev/null
|
"$CADIUS" ADDFILE "$DATA" /GNO.BOOT "$WORK/HELLO#B50100" >/dev/null
|
||||||
|
# Optional second executable for concurrency tests: added to /bin so gsh
|
||||||
|
# can run e.g. GNO_RUNCMD="proga & progb". Backward-compatible (unset -> no-op).
|
||||||
|
if [ -n "${GNO_PROG2:-}" ] && [ -f "${GNO_PROG2}" ]; then
|
||||||
|
NAME2=$(basename "$GNO_PROG2" | sed 's/\.[^.]*$//' | tr '[:upper:]' '[:lower:]' | cut -c1-15)
|
||||||
|
cp "$GNO_PROG2" "$WORK/${NAME2}#B50100"
|
||||||
|
"$CADIUS" ADDFILE "$DATA" /GNO.BOOT/bin "$WORK/${NAME2}#B50100" >/dev/null
|
||||||
|
fi
|
||||||
# Optional: drop an extra TXT file in /home/root (the login cwd) for
|
# Optional: drop an extra TXT file in /home/root (the login cwd) for
|
||||||
# stdin-redirect tests. Reference it relatively or as /home/root/<name>.
|
# stdin-redirect tests. Reference it relatively or as /home/root/<name>.
|
||||||
if [ -n "${GNO_ADDFILE:-}" ] && [ -f "${GNO_ADDFILE}" ]; then
|
if [ -n "${GNO_ADDFILE:-}" ] && [ -f "${GNO_ADDFILE}" ]; then
|
||||||
|
|
|
||||||
|
|
@ -6977,6 +6977,177 @@ else
|
||||||
}
|
}
|
||||||
log "OK: cxxChronoProbe steady_clock monotonic + i32 rep verified under GNO"
|
log "OK: cxxChronoProbe steady_clock monotonic + i32 rep verified under GNO"
|
||||||
|
|
||||||
|
# Phase 5.3b heap: gnoHeapProbe -- regression for the bank-0 malloc-heap
|
||||||
|
# collision. crt0Gno now backs malloc with an MM-owned handle
|
||||||
|
# (__heapInitMM); before that, malloc bumped the fixed bank-0 link-time
|
||||||
|
# window [__bss_end,0xBF00) into GNO's kernel heartbeat block -> SysFailMgr
|
||||||
|
# $0308. This mallocs 8 KB (past the old collision point) and verifies
|
||||||
|
# every block survives intact.
|
||||||
|
log "check: gnoHeapProbe (MM-backed malloc heap, 8 KB) runs under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoHeapProbe >/tmp/gnoHeapBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoHeapBuildOut >&2
|
||||||
|
die "buildGno.sh gnoHeapProbe failed"
|
||||||
|
}
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/gnoHeapProbe.omf" \
|
||||||
|
--check 0x025010=BEEF --check 0x025012=0001 --check 0x025000=C0DE \
|
||||||
|
>/tmp/gnoHeapRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoHeapRunOut >&2
|
||||||
|
die "gnoHeapProbe failed: malloc-heap bank-0/heartbeat collision or marker 0xC0DE not reached"
|
||||||
|
}
|
||||||
|
log "OK: gnoHeapProbe MM-backed heap survived 8 KB of allocations under GNO"
|
||||||
|
|
||||||
|
# Phase 5.3c large-BSS: gnoBigBssProbe -- regression for the crt0Gno
|
||||||
|
# BSS-clear heartbeat clobber. A ~6 KB zero-init BSS array pushes
|
||||||
|
# __bss_end past the old ~0xB100 corruption threshold; crt0Gno no longer
|
||||||
|
# clears BSS (the loader fills the embedded zeros), so the program must
|
||||||
|
# run AND read the array back as all-zero.
|
||||||
|
log "check: gnoBigBssProbe (large zero-init BSS) runs under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoBigBssProbe >/tmp/gnoBigBssBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoBigBssBuildOut >&2
|
||||||
|
die "buildGno.sh gnoBigBssProbe failed"
|
||||||
|
}
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/gnoBigBssProbe.omf" \
|
||||||
|
--check 0x025010=BEEF --check 0x025012=0001 --check 0x025000=C0DE \
|
||||||
|
>/tmp/gnoBigBssRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoBigBssRunOut >&2
|
||||||
|
die "gnoBigBssProbe failed: large-BSS heartbeat clobber regressed or BSS not zero-filled"
|
||||||
|
}
|
||||||
|
log "OK: gnoBigBssProbe large BSS zero-filled + no heartbeat clobber under GNO"
|
||||||
|
|
||||||
|
# Phase 5.3d process lifecycle: gnoForkWaitProbe -- the FIRST exercise of the
|
||||||
|
# GNO kernel syscall surface (K* toolset $03). Regression for two fixed bugs:
|
||||||
|
# (1) gnoKernel.s dispatched every K* call via $E10000 (the GS/OS Tool Locator,
|
||||||
|
# where toolset $03 is Misc Tools) instead of GNO's udispatch vector $E10008,
|
||||||
|
# so any kernel call hung -- latent because nothing exercised a K* primitive
|
||||||
|
# until now. (2) _exit recorded 0x2029 as the exit code (__gnoGsosCall left
|
||||||
|
# A = the QUIT callNum at the trap, which StackGSOS stashes as exitCode), so
|
||||||
|
# wait() decoded WEXITSTATUS = 41; __gnoQuit now leaves A = status. This
|
||||||
|
# fork()s a leaf child that _exit(42)s and asserts the parent's wait() reaps
|
||||||
|
# the same pid with WEXITSTATUS == 42 (0x2A), guarding the old 41 (0x29).
|
||||||
|
log "check: gnoForkWaitProbe (fork thread-spawn + wait + _exit status) under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoForkWaitProbe >/tmp/gnoForkWaitBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoForkWaitBuildOut >&2
|
||||||
|
die "buildGno.sh gnoForkWaitProbe failed"
|
||||||
|
}
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/gnoForkWaitProbe.omf" \
|
||||||
|
--check 0x025000=C0DE --check 0x025010=C41D --check 0x025014=002A \
|
||||||
|
--check 0x025016=0001 --check 0x025018=600D \
|
||||||
|
>/tmp/gnoForkWaitRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoForkWaitRunOut >&2
|
||||||
|
die "gnoForkWaitProbe failed: K* dispatch vector, fork thread-spawn, or _exit status regressed"
|
||||||
|
}
|
||||||
|
log "OK: gnoForkWaitProbe fork/wait + _exit status (WEXITSTATUS=42) green under GNO"
|
||||||
|
|
||||||
|
# Phase 5.3e..5.3k POSIX syscall surface (punch-list items 4-12): the GNO
|
||||||
|
# kernel toolset-$03 wrappers (waitpid/stat/isatty/uid/gid/job-control/env/
|
||||||
|
# fd layer/exec/signals) now wired into libc via libcGno.c. Each probe
|
||||||
|
# exercises one subsystem end-to-end under real GNO/MAME. (These all depend
|
||||||
|
# on the $E10008 kernel-dispatch fix -- see Phase 5.3d.)
|
||||||
|
for gnoProbeSpec in \
|
||||||
|
"gnoWaitpidTimesProbe|waitpid + times|0x025000=C0DE 0x025010=C41D 0x025014=0025 0x025016=0001 0x025018=0001" \
|
||||||
|
"gnoStatProbe|stat/fstat/isatty|0x025000=C0DE 0x025010=0001 0x025012=0001 0x025014=0001 0x025016=0001 0x025018=0001" \
|
||||||
|
"gnoIdProbe|uid/gid + job control|0x025000=C0DE 0x025010=0001 0x025012=0001 0x025014=0001 0x025016=0001" \
|
||||||
|
"gnoEnvProbe|environment setenv/getenv|0x025000=C0DE 0x025012=0001 0x025002=0001" \
|
||||||
|
"gnoFdProbe|POSIX fd layer|0x025000=C0DE 0x025002=0001 0x025004=0001 0x025006=0001 0x025008=0001 0x02500A=0001 0x02500C=0001 0x02500E=0001" \
|
||||||
|
"gnoSignalProbe|signal + __sigTrampoline SIGALRM|0x025000=C0DE 0x025010=C41D 0x025012=0001" \
|
||||||
|
; do
|
||||||
|
gnoProbe="${gnoProbeSpec%%|*}"
|
||||||
|
gnoRest="${gnoProbeSpec#*|}"
|
||||||
|
gnoDesc="${gnoRest%%|*}"
|
||||||
|
gnoChecks="${gnoRest##*|}"
|
||||||
|
gnoCheckArgs=""
|
||||||
|
for gnoChk in $gnoChecks; do
|
||||||
|
gnoCheckArgs="$gnoCheckArgs --check $gnoChk"
|
||||||
|
done
|
||||||
|
log "check: $gnoProbe ($gnoDesc) under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" "$gnoProbe" >/tmp/gnoSyscallBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoSyscallBuildOut >&2
|
||||||
|
die "buildGno.sh $gnoProbe failed"
|
||||||
|
}
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/$gnoProbe.omf" $gnoCheckArgs \
|
||||||
|
>/tmp/gnoSyscallRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoSyscallRunOut >&2
|
||||||
|
die "$gnoProbe failed: GNO POSIX syscall wrapper regression ($gnoDesc)"
|
||||||
|
}
|
||||||
|
log "OK: $gnoProbe ($gnoDesc) green under GNO"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Phase 5.3l exec-INTO: gnoExecProbe run WITH a second program deployed on
|
||||||
|
# /bin (GNO_PROG2), so its final execvp("gnoexeced") actually replaces the
|
||||||
|
# process image via GS/OS InitialLoad2. This is the exec keystone the
|
||||||
|
# generic loop above cannot cover (it needs a companion binary on the disk).
|
||||||
|
#
|
||||||
|
# Half-1 markers (02/04/08/0A) come from prog1: access() over Kstat + a
|
||||||
|
# failed $PATH-resolved execvp. The exec-INTO markers are the payoff:
|
||||||
|
# 0x025010 = E0EC gnoExeced ran (K_execve transferred control)
|
||||||
|
# 0x025000 = C0DE reached end, set BY the exec'd 2nd program
|
||||||
|
# Regression guard for the DP/Stack size ceiling: a program's ~Direct $12
|
||||||
|
# segment must be small enough that the caller's AND callee's copies fit in
|
||||||
|
# bank 0 at once during InitialLoad2 (see demos/buildGno.sh --stack-size
|
||||||
|
# note). A 16 KB stack regresses this to EIO / no transfer.
|
||||||
|
log "check: gnoExecProbe exec-INTO (execvp replaces image with gnoExeced) under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoExeced >/tmp/gnoExecedBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoExecedBuildOut >&2
|
||||||
|
die "buildGno.sh gnoExeced failed"
|
||||||
|
}
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoExecProbe >/tmp/gnoExecBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoExecBuildOut >&2
|
||||||
|
die "buildGno.sh gnoExecProbe failed"
|
||||||
|
}
|
||||||
|
GNO_PROG2="$PROJECT_ROOT/demos/gnoExeced.omf" \
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/gnoExecProbe.omf" \
|
||||||
|
--check 0x025000=C0DE --check 0x025002=0001 --check 0x025004=0001 \
|
||||||
|
--check 0x025008=0001 --check 0x02500A=0001 --check 0x025010=E0EC \
|
||||||
|
>/tmp/gnoExecRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoExecRunOut >&2
|
||||||
|
die "gnoExecProbe exec-into failed: K_execve did not transfer control (DP/Stack size ceiling? see buildGno.sh)"
|
||||||
|
}
|
||||||
|
log "OK: gnoExecProbe exec-INTO transferred control to gnoExeced under GNO"
|
||||||
|
|
||||||
|
# Phase 5.3m errno-space consistency: GNO numbers its kernel errno differently
|
||||||
|
# from our <errno.h> (GNO ENOENT=4 vs our 2). The fd layer maps GS/OS errors
|
||||||
|
# via _mapErr; the stat/access/process wrappers translate the RAW kernel errno
|
||||||
|
# via gnoToPosixErrno (libcGno.c). This probe triggers a missing-file error
|
||||||
|
# through open/stat/unlink/access and asserts all four report POSIX ENOENT
|
||||||
|
# (0x02503C=0001) -- guards against the raw GNO errno leaking back through.
|
||||||
|
log "check: gnoErrnoProbe (errno-space: stat/access map GNO errno -> POSIX) under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoErrnoProbe >/tmp/gnoErrnoBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoErrnoBuildOut >&2
|
||||||
|
die "buildGno.sh gnoErrnoProbe failed"
|
||||||
|
}
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/gnoErrnoProbe.omf" \
|
||||||
|
--check 0x025030=0002 --check 0x025032=0002 --check 0x025034=0002 \
|
||||||
|
--check 0x025036=0002 --check 0x025038=0002 --check 0x02503A=0001 \
|
||||||
|
--check 0x02503C=0001 --check 0x025000=C0DE \
|
||||||
|
>/tmp/gnoErrnoRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoErrnoRunOut >&2
|
||||||
|
die "gnoErrnoProbe failed: stat/access errno not mapped to POSIX ENOENT (gnoToPosixErrno regression)"
|
||||||
|
}
|
||||||
|
log "OK: gnoErrnoProbe (stat/access errno == POSIX ENOENT) under GNO"
|
||||||
|
|
||||||
|
# Phase 5.3n composite: ONE process that chains env + file I/O + stat + fork/
|
||||||
|
# waitpid + signals + times + exec-into, in an order that stresses the cross-
|
||||||
|
# subsystem interactions the isolated probes miss (fork AFTER file I/O, signals
|
||||||
|
# AFTER fork, exec AFTER all of it). The end-to-end "real program" smoke: if a
|
||||||
|
# future change breaks an INTERACTION the per-subsystem probes still pass but
|
||||||
|
# this one catches it. (gnoExeced.omf was built by the exec-INTO stage above.)
|
||||||
|
log "check: gnoComposite (env+file+stat+fork+signal+times+exec in one process) under GNO"
|
||||||
|
bash "$PROJECT_ROOT/demos/buildGno.sh" gnoComposite >/tmp/gnoCompositeBuildOut 2>&1 || {
|
||||||
|
cat /tmp/gnoCompositeBuildOut >&2
|
||||||
|
die "buildGno.sh gnoComposite failed"
|
||||||
|
}
|
||||||
|
GNO_PROG2="$PROJECT_ROOT/demos/gnoExeced.omf" \
|
||||||
|
bash "$PROJECT_ROOT/scripts/runInGno.sh" "$PROJECT_ROOT/demos/gnoComposite.omf" \
|
||||||
|
--check 0x025002=0007 --check 0x025020=0001 --check 0x025022=0001 \
|
||||||
|
--check 0x025024=0001 --check 0x025026=0001 --check 0x025028=0001 \
|
||||||
|
--check 0x02502A=0001 --check 0x02502E=C41D --check 0x025010=E0EC \
|
||||||
|
--check 0x025000=C0DE \
|
||||||
|
>/tmp/gnoCompositeRunOut 2>&1 || {
|
||||||
|
cat /tmp/gnoCompositeRunOut >&2
|
||||||
|
die "gnoComposite failed: cross-subsystem composition regression under GNO"
|
||||||
|
}
|
||||||
|
log "OK: gnoComposite (7 subsystems composed in one process) green under GNO"
|
||||||
|
|
||||||
# Phase 5.4 cxxstream+format+path: build cxxStreamProbe and run under
|
# Phase 5.4 cxxstream+format+path: build cxxStreamProbe and run under
|
||||||
# GNO/MAME. Verifies:
|
# GNO/MAME. Verifies:
|
||||||
# - etl::string_stream<<int produces the expected "x=42" — the
|
# - etl::string_stream<<int produces the expected "x=42" — the
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,7 @@ static std::vector<uint8_t> emitDpStackSeg(uint32_t length, uint16_t segNum) {
|
||||||
// bss-as-zeros approach used for the user CODE seg — the Loader's
|
// bss-as-zeros approach used for the user CODE seg — the Loader's
|
||||||
// ExpressLoad fast path can't be trusted to honor RESSPC).
|
// ExpressLoad fast path can't be trusted to honor RESSPC).
|
||||||
const uint32_t RESSPC = 0;
|
const uint32_t RESSPC = 0;
|
||||||
const uint32_t BANKSIZE = 0; // DP/Stack lives in bank 0
|
const uint32_t BANKSIZE = 0x10000; // must fit within one bank (bank 0). BANKSIZE=0 means "may cross banks", which the GS/OS Loader's InitialLoad2 path (used by GNO execve) rejects for a DP/Stack seg; a real GNO ~_STACK uses 0x10000.
|
||||||
const uint32_t ALIGN = 0x100; // page-aligned per spec
|
const uint32_t ALIGN = 0x100; // page-aligned per spec
|
||||||
const uint16_t KIND = OMF_KIND_DPSTACK; // DP/Stack | RELOAD
|
const uint16_t KIND = OMF_KIND_DPSTACK; // DP/Stack | RELOAD
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/Analysis/LoopInfo.h"
|
#include "llvm/Analysis/LoopInfo.h"
|
||||||
#include "llvm/Analysis/ScalarEvolution.h"
|
#include "llvm/Analysis/ScalarEvolution.h"
|
||||||
|
#include "llvm/IR/Dominators.h"
|
||||||
#include "llvm/InitializePasses.h"
|
#include "llvm/InitializePasses.h"
|
||||||
#include "llvm/IR/Constants.h"
|
#include "llvm/IR/Constants.h"
|
||||||
#include "llvm/IR/Function.h"
|
#include "llvm/IR/Function.h"
|
||||||
|
|
@ -72,6 +73,7 @@ public:
|
||||||
|
|
||||||
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
||||||
AU.addRequired<LoopInfoWrapperPass>();
|
AU.addRequired<LoopInfoWrapperPass>();
|
||||||
|
AU.addRequired<DominatorTreeWrapperPass>();
|
||||||
AU.setPreservesCFG();
|
AU.setPreservesCFG();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +81,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool processLoop(Loop *L);
|
bool processLoop(Loop *L);
|
||||||
bool processCounterToPtrPHIs(Loop *L);
|
bool processCounterToPtrPHIs(Loop *L, DominatorTree &DT);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
@ -89,6 +91,7 @@ char W65816UnLSR::ID = 0;
|
||||||
INITIALIZE_PASS_BEGIN(W65816UnLSR, DEBUG_TYPE,
|
INITIALIZE_PASS_BEGIN(W65816UnLSR, DEBUG_TYPE,
|
||||||
"W65816 undo LSR for global-array", false, false)
|
"W65816 undo LSR for global-array", false, false)
|
||||||
INITIALIZE_PASS_DEPENDENCY(LoopInfoWrapperPass)
|
INITIALIZE_PASS_DEPENDENCY(LoopInfoWrapperPass)
|
||||||
|
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
|
||||||
INITIALIZE_PASS_END(W65816UnLSR, DEBUG_TYPE,
|
INITIALIZE_PASS_END(W65816UnLSR, DEBUG_TYPE,
|
||||||
"W65816 undo LSR for global-array", false, false)
|
"W65816 undo LSR for global-array", false, false)
|
||||||
|
|
||||||
|
|
@ -98,15 +101,16 @@ bool W65816UnLSR::runOnFunction(Function &F) {
|
||||||
if (F.hasOptNone())
|
if (F.hasOptNone())
|
||||||
return false;
|
return false;
|
||||||
LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
|
LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
|
||||||
|
DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
|
||||||
bool Changed = false;
|
bool Changed = false;
|
||||||
for (Loop *L : LI) {
|
for (Loop *L : LI) {
|
||||||
Changed |= processLoop(L);
|
Changed |= processLoop(L);
|
||||||
Changed |= processCounterToPtrPHIs(L);
|
Changed |= processCounterToPtrPHIs(L, DT);
|
||||||
SmallVector<Loop *, 4> Worklist(L->begin(), L->end());
|
SmallVector<Loop *, 4> Worklist(L->begin(), L->end());
|
||||||
while (!Worklist.empty()) {
|
while (!Worklist.empty()) {
|
||||||
Loop *Sub = Worklist.pop_back_val();
|
Loop *Sub = Worklist.pop_back_val();
|
||||||
Changed |= processLoop(Sub);
|
Changed |= processLoop(Sub);
|
||||||
Changed |= processCounterToPtrPHIs(Sub);
|
Changed |= processCounterToPtrPHIs(Sub, DT);
|
||||||
Worklist.append(Sub->begin(), Sub->end());
|
Worklist.append(Sub->begin(), Sub->end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +135,7 @@ bool W65816UnLSR::runOnFunction(Function &F) {
|
||||||
// Rewrite: for each base_i, introduce a pointer PHI that strides by 1
|
// Rewrite: for each base_i, introduce a pointer PHI that strides by 1
|
||||||
// per iter. Replace %scevgep_i with the new pointer PHI. If counter
|
// per iter. Replace %scevgep_i with the new pointer PHI. If counter
|
||||||
// has no other uses, eliminate it.
|
// has no other uses, eliminate it.
|
||||||
bool W65816UnLSR::processCounterToPtrPHIs(Loop *L) {
|
bool W65816UnLSR::processCounterToPtrPHIs(Loop *L, DominatorTree &DT) {
|
||||||
BasicBlock *Header = L->getHeader();
|
BasicBlock *Header = L->getHeader();
|
||||||
BasicBlock *Latch = L->getLoopLatch();
|
BasicBlock *Latch = L->getLoopLatch();
|
||||||
BasicBlock *Preheader = L->getLoopPreheader();
|
BasicBlock *Preheader = L->getLoopPreheader();
|
||||||
|
|
@ -179,8 +183,17 @@ bool W65816UnLSR::processCounterToPtrPHIs(Loop *L) {
|
||||||
Value *Base = GEP->getPointerOperand();
|
Value *Base = GEP->getPointerOperand();
|
||||||
// base must be loop-invariant. Instructions inside the loop fail;
|
// base must be loop-invariant. Instructions inside the loop fail;
|
||||||
// arguments and globals are always invariant.
|
// arguments and globals are always invariant.
|
||||||
if (auto *BaseI = dyn_cast<Instruction>(Base))
|
if (auto *BaseI = dyn_cast<Instruction>(Base)) {
|
||||||
if (L->contains(BaseI)) return false;
|
if (L->contains(BaseI)) return false;
|
||||||
|
// Base becomes the pointer-PHI's preheader incoming value, so it must
|
||||||
|
// dominate the preheader's terminator (the edge into the header). A
|
||||||
|
// value merely OUTSIDE the loop is not guaranteed to: a compound loop
|
||||||
|
// guard (a && b) can define an invariant GEP on a sibling path that
|
||||||
|
// reaches the preheader without dominating it. Emitting a PHI with a
|
||||||
|
// non-dominating incoming there fails the verifier ("Instruction does
|
||||||
|
// not dominate all uses"), so skip the transform for this loop instead.
|
||||||
|
if (!DT.dominates(BaseI, Preheader->getTerminator())) return false;
|
||||||
|
}
|
||||||
if (!Base->getType()->isPointerTy()) return false;
|
if (!Base->getType()->isPointerTy()) return false;
|
||||||
// Only handle the i8 element type (byte stride). Other strides
|
// Only handle the i8 element type (byte stride). Other strides
|
||||||
// would need different ptr-PHI step values.
|
// would need different ptr-PHI step values.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue