From 43788f9d1755bf261934ab3e6e4399d82ad1e8ac Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 21 Oct 2026 17:38:21 +0000 Subject: [PATCH 1/5] vms-599: DEC C exit() and __posix_exit() record the VMS completion status OVMX's DECC$SHR exit() was musl's. It ran the atexit handlers, flushed stdio and exit_group()ed a POSIX code, but nothing recorded the status. DCL then read the image's exit(1), which is a success on VMS, as "%DCL-E-ABORT ... exited with error status %X00000001". That was the wall on #1810: the GCC port's ar ends with exit(system(...)). Measured on OpenVMS Alpha V8.4 with MACRO-32 probes that call the C RTL directly (tests/lab/captures/decc-exit-alpha-20261021): - DECC$EXIT maps 0 to %X00000001 and passes every other value through. - DECC$__POSIX_EXIT keeps n&255. It maps 0 to %X00000001, 1 to %X1035A00A, and any other n to %X0035A001+(n<<3). What changed: - crtl_process.c: decc$exit is rebound to ovmx_crtl_exit, and decc$__posix_exit (ovmx_crtl_posix_exit) is a new tail universal. Both record the status and then run musl's exit. - crtl_rms_fd.c: the system-call hook ends the image with SYS$EXIT of that status at the final exit_group, after the handlers and the stdio flush. - build-musl.sh: follows DEC C. _POSIX_EXIT makes exit __posix_exit, which is what the GCC port's xm-vms.h asks for. Otherwise EXIT_FAILURE is 2. Test: the crtl-fd gate runs the image as a foreign command, through 11 exit and __posix_exit cases, and checks each $STATUS against the oracle. It also checks that an unflushed line is printed. Three new selftest negative controls cover musl's exit, a raw __posix_exit and a skipped flush. Co-Authored-By: Claude Opus 5.5 --- src/vmslink/mk_decc_shr.sh | 18 +++- src/vmsrms/crtl_process.c | 60 +++++++++++++ src/vmsrms/crtl_rms_fd.c | 7 ++ .../decc-exit-alpha-20261021/CONSOLE.txt | 87 +++++++++++++++++++ .../decc-exit-alpha-20261021/EXS0.MAR | 7 ++ .../decc-exit-alpha-20261021/EXS1.MAR | 7 ++ .../decc-exit-alpha-20261021/EXS2.MAR | 7 ++ .../decc-exit-alpha-20261021/EXS3.MAR | 7 ++ .../decc-exit-alpha-20261021/EXS4.MAR | 7 ++ .../decc-exit-alpha-20261021/EXSBIG.MAR | 7 ++ .../decc-exit-alpha-20261021/PXS0.MAR | 7 ++ .../decc-exit-alpha-20261021/PXS1.MAR | 7 ++ .../decc-exit-alpha-20261021/PXS2.MAR | 7 ++ .../decc-exit-alpha-20261021/PXS255.MAR | 7 ++ .../decc-exit-alpha-20261021/PXS256.MAR | 7 ++ .../decc-exit-alpha-20261021/PXS3.MAR | 7 ++ .../decc-exit-alpha-20261021/README.md | 37 ++++++++ .../cross-alpha-vms/joint-e2e/crtl_fd_test.c | 18 +++- tools/cross-alpha-vms/musl-arch/build-musl.sh | 10 +++ .../SYSTARTUP_VMS_CRTLFD_PROOF.COM | 28 ++++++ .../run-module-gp-activation-alpha.sh | 26 +++++- 21 files changed, 371 insertions(+), 4 deletions(-) create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/CONSOLE.txt create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/EXS0.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/EXS1.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/EXS2.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/EXS3.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/EXS4.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/EXSBIG.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/PXS0.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/PXS1.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/PXS2.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/PXS255.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/PXS256.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/PXS3.MAR create mode 100644 tests/lab/captures/decc-exit-alpha-20261021/README.md diff --git a/src/vmslink/mk_decc_shr.sh b/src/vmslink/mk_decc_shr.sh index 7632ecc10..5be4656db 100755 --- a/src/vmslink/mk_decc_shr.sh +++ b/src/vmslink/mk_decc_shr.sh @@ -430,8 +430,24 @@ if [ "$OVMX_DECC_ARCH" = alpha ]; then esac done VEC=$(printf '%s' ",$VEC," | sed 's/,decc\$_getcwd64=PROCEDURE,/,decc$_getcwd64\/ovmx_crtl_getcwd=PROCEDURE,/; s/,decc\$getcwd\/decc\$_getcwd64=PROCEDURE,/,decc$getcwd\/ovmx_crtl_getcwd=PROCEDURE,/; s/^,//; s/,$//') + # vms-599: DEC C exit() records the image's VMS completion status (SYS$EXIT in + # LIBVMS$SHR) after musl's atexit handlers and stdio flush. decc$exit is rebound + # in place; __posix_exit (exit() under _POSIX_EXIT) is a new universal at the tail. + for _ex in ovmx_crtl_exit ovmx_crtl_posix_exit; do + "$NM" --defined-only "$SYS_OBJ" 2>/dev/null | awk '{print $NF}' | grep -qxF "$_ex" \ + || { echo "mk_decc_shr: FAIL crtl_process.c did not define $_ex" >&2; exit 2; } + done + case ",$VEC," in + *',decc$exit=PROCEDURE,'*) ;; + *) echo "mk_decc_shr: FAIL decc\$exit is not a PROCEDURE universal to rebind" >&2; exit 2;; + esac + VEC=$(printf '%s' ",$VEC," | sed 's/,decc\$exit=PROCEDURE,/,decc$exit\/ovmx_crtl_exit=PROCEDURE,/; s/^,//; s/,$//') + case ",$VEC,${PASS2_TAIL:-}," in + *',decc$__posix_exit'[=/]*) echo "mk_decc_shr: FAIL decc\$__posix_exit is already in the vector" >&2; exit 2;; + esac + PASS2_TAIL="${PASS2_TAIL:-},decc\$__posix_exit/ovmx_crtl_posix_exit=PROCEDURE" ALPHA_VENEER_OBJ="$ALPHA_VENEER_OBJ $SYS_OBJ" - echo "mk_decc_shr: DEC C system() wired (vms-65a): decc\$system -> ovmx_crtl_system (LIB\$SPAWN, --use $ALPHA_CRTL_VMS_USE)" + echo "mk_decc_shr: DEC C system() wired (vms-65a): decc\$system -> ovmx_crtl_system (LIB\$SPAWN, --use $ALPHA_CRTL_VMS_USE); exit/__posix_exit record \$STATUS (vms-599)" fi # Plain (non-decc$-decorated) names the decc$ filter above cannot catch, diff --git a/src/vmsrms/crtl_process.c b/src/vmsrms/crtl_process.c index c6164084b..275e38c47 100644 --- a/src/vmsrms/crtl_process.c +++ b/src/vmsrms/crtl_process.c @@ -23,6 +23,21 @@ * image. The musl system() the archive also carries (a clone with CLONE_VFORK, * which the executive refuses) stays an internal symbol: decc$system is rebound * in place to ovmx_crtl_system by mk_decc_shr.sh. + * + * exit() and __posix_exit() (vms-599): the value an image hands exit() is its VMS + * completion status, which DCL reads as $STATUS. Measured on OpenVMS Alpha V8.4 + * (tests/lab/captures/decc-exit-alpha-20261021, MACRO-32 calling the C RTL entry + * points): DECC$EXIT turns 0 into %X00000001 (SS$_NORMAL) and passes every other + * value through unchanged (1, 2, 3, %X1000002C). DECC$__POSIX_EXIT (what exit() + * is under _POSIX_EXIT) keeps the low eight bits, turns 0 into %X00000001, and any + * other n into a C-facility message: %X0035A001 + (n << 3), with 1 made an error + * whose message is inhibited, %X1035A00A (2 -> %X0035A011, 255 -> %X0035A7F9, + * 256 -> %X00000001). musl's exit() ran the atexit handlers and flushed stdio, then + * exit_group()ed a POSIX code, and nothing recorded the status: DCL saw exit(1), + * a success, as "exited with error status %X00000001". Both entry points record + * the status, then run musl's exit; the C RTL file layer's system-call hook + * (crtl_rms_fd.c) sees the final exit_group and ends the image with SYS$EXIT of + * that status, after the handlers and the flush, as the DEC C RTL does. */ #include #include @@ -97,3 +112,48 @@ char *ovmx_crtl_getcwd(char *buf, unsigned long size, int vms_flag) memcpy(buf, out, need); return buf; } + +/* ------------------------------------------------------- exit (vms-599) ---- */ + +extern unsigned int sys$exit(unsigned int code); /* LIBVMS$SHR */ +extern void (*__ovmx_crtl_exit_record)(void); /* crtl_rms_fd.c */ + +static unsigned int crtl_exit_cond; + +/* Called from the system-call hook at the image's final exit_group: end the + * image with SYS$EXIT of the recorded status (it runs the exit handlers, records + * the status with the executive and terminates). The cell is cleared first, so + * SYS$EXIT's own exit_group passes straight through the hook. */ +static void crtl_exit_record(void) +{ + __ovmx_crtl_exit_record = 0; + (void)sys$exit(crtl_exit_cond); +} + +static void crtl_exit_with(unsigned int cond) +{ + crtl_exit_cond = cond; + __ovmx_crtl_exit_record = crtl_exit_record; + exit((cond & 1u) ? 0 : 1); /* musl: atexit, stdio flush */ +} + +/* DEC C exit(): 0 is SS$_NORMAL, any other value is the status itself. */ +void ovmx_crtl_exit(int status) +{ + crtl_exit_with(status == 0 ? 1u : (unsigned int)status); +} + +/* DEC C __posix_exit() (exit() under _POSIX_EXIT): a POSIX code 0-255 as a + * C-facility condition value (see the measurement above). */ +void ovmx_crtl_posix_exit(int status) +{ + unsigned int n = (unsigned int)status & 255u; + unsigned int cond; + if (n == 0) + cond = 1u; + else if (n == 1) + cond = 0x1035A00Au; + else + cond = 0x0035A001u + (n << 3); + crtl_exit_with(cond); +} diff --git a/src/vmsrms/crtl_rms_fd.c b/src/vmsrms/crtl_rms_fd.c index 32bc6683b..335991a4a 100644 --- a/src/vmsrms/crtl_rms_fd.c +++ b/src/vmsrms/crtl_rms_fd.c @@ -1489,10 +1489,17 @@ static long long rms_hook_body(long long n, long long a1, long long a2, } } +/* Set by the DEC C exit()/__posix_exit() (crtl_process.c, vms-599) to the routine + * that ends the image with SYS$EXIT of the status it was given; called at the + * final exit_group, after musl ran the atexit handlers and flushed stdio. */ +void (*__ovmx_crtl_exit_record)(void); + static long long rms_hook(long long n, long long a1, long long a2, long long a3, long long a4, long long a5, long long a6, int *handled) { (void)a6; + if (n == SYS_exit_group && __ovmx_crtl_exit_record) + __ovmx_crtl_exit_record(); /* SYS$EXIT: does not return */ if (in_rms) return 0; /* RMS's own system calls */ in_rms = 1; diff --git a/tests/lab/captures/decc-exit-alpha-20261021/CONSOLE.txt b/tests/lab/captures/decc-exit-alpha-20261021/CONSOLE.txt new file mode 100644 index 000000000..5367c4b1d --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/CONSOLE.txt @@ -0,0 +1,87 @@ +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:EXS0.OBJ SYS$SCRATCH:EXS0.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:EXS0.EXE SYS$SCRATCH:EXS0.OBJ +$ RUN SYS$SCRATCH:EXS0.EXE +$ WRITE SYS$OUTPUT "PROBE EXS0 STATUS=",F$STRING($STATUS) +PROBE EXS0 STATUS=%X00000001 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:EXS1.OBJ SYS$SCRATCH:EXS1.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:EXS1.EXE SYS$SCRATCH:EXS1.OBJ +$ RUN SYS$SCRATCH:EXS1.EXE +$ WRITE SYS$OUTPUT "PROBE EXS1 STATUS=",F$STRING($STATUS) +PROBE EXS1 STATUS=%X00000001 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:EXS2.OBJ SYS$SCRATCH:EXS2.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:EXS2.EXE SYS$SCRATCH:EXS2.OBJ +$ RUN SYS$SCRATCH:EXS2.EXE +%NONAME-E-NOMSG, Message number 00000002 +$ WRITE SYS$OUTPUT "PROBE EXS2 STATUS=",F$STRING($STATUS) +PROBE EXS2 STATUS=%X00000002 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:EXS3.OBJ SYS$SCRATCH:EXS3.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:EXS3.EXE SYS$SCRATCH:EXS3.OBJ +$ RUN SYS$SCRATCH:EXS3.EXE +$ WRITE SYS$OUTPUT "PROBE EXS3 STATUS=",F$STRING($STATUS) +PROBE EXS3 STATUS=%X00000003 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:EXS4.OBJ SYS$SCRATCH:EXS4.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:EXS4.EXE SYS$SCRATCH:EXS4.OBJ +$ RUN SYS$SCRATCH:EXS4.EXE +%NONAME-F-NOMSG, Message number 00000004 +$ WRITE SYS$OUTPUT "PROBE EXS4 STATUS=",F$STRING($STATUS) +PROBE EXS4 STATUS=%X00000004 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:EXSBIG.OBJ SYS$SCRATCH:EXSBIG.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:EXSBIG.EXE SYS$SCRATCH:EXSBIG.OBJ +$ RUN SYS$SCRATCH:EXSBIG.EXE +$ WRITE SYS$OUTPUT "PROBE EXSBIG STATUS=",F$STRING($STATUS) +PROBE EXSBIG STATUS=%X1000002C +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:PXS0.OBJ SYS$SCRATCH:PXS0.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:PXS0.EXE SYS$SCRATCH:PXS0.OBJ +$ RUN SYS$SCRATCH:PXS0.EXE +$ WRITE SYS$OUTPUT "PROBE PXS0 STATUS=",F$STRING($STATUS) +PROBE PXS0 STATUS=%X00000001 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:PXS1.OBJ SYS$SCRATCH:PXS1.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:PXS1.EXE SYS$SCRATCH:PXS1.OBJ +$ RUN SYS$SCRATCH:PXS1.EXE +$ WRITE SYS$OUTPUT "PROBE PXS1 STATUS=",F$STRING($STATUS) +PROBE PXS1 STATUS=%X1035A00A +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:PXS2.OBJ SYS$SCRATCH:PXS2.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:PXS2.EXE SYS$SCRATCH:PXS2.OBJ +$ RUN SYS$SCRATCH:PXS2.EXE +$ WRITE SYS$OUTPUT "PROBE PXS2 STATUS=",F$STRING($STATUS) +PROBE PXS2 STATUS=%X0035A011 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:PXS3.OBJ SYS$SCRATCH:PXS3.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:PXS3.EXE SYS$SCRATCH:PXS3.OBJ +$ RUN SYS$SCRATCH:PXS3.EXE +$ WRITE SYS$OUTPUT "PROBE PXS3 STATUS=",F$STRING($STATUS) +PROBE PXS3 STATUS=%X0035A019 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:PXS255.OBJ SYS$SCRATCH:PXS255.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:PXS255.EXE SYS$SCRATCH:PXS255.OBJ +$ RUN SYS$SCRATCH:PXS255.EXE +$ WRITE SYS$OUTPUT "PROBE PXS255 STATUS=",F$STRING($STATUS) +PROBE PXS255 STATUS=%X0035A7F9 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ MACRO/NOLIST/OBJECT=SYS$SCRATCH:PXS256.OBJ SYS$SCRATCH:PXS256.MAR +$ LINK/EXECUTABLE=SYS$SCRATCH:PXS256.EXE SYS$SCRATCH:PXS256.OBJ +$ RUN SYS$SCRATCH:PXS256.EXE +$ WRITE SYS$OUTPUT "PROBE PXS256 STATUS=",F$STRING($STATUS) +PROBE PXS256 STATUS=%X00000001 +$ SHOW SYMBOL $STATUS + $STATUS == "%X00010001" +$ diff --git a/tests/lab/captures/decc-exit-alpha-20261021/EXS0.MAR b/tests/lab/captures/decc-exit-alpha-20261021/EXS0.MAR new file mode 100644 index 000000000..4dde406c8 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/EXS0.MAR @@ -0,0 +1,7 @@ + .TITLE EXS0 OVMX exit-status probe: DECC$EXIT(0) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY EXS0,^M<> + PUSHL #0 + CALLS #1,G^DECC$EXIT + RET + .END EXS0 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/EXS1.MAR b/tests/lab/captures/decc-exit-alpha-20261021/EXS1.MAR new file mode 100644 index 000000000..07519cca2 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/EXS1.MAR @@ -0,0 +1,7 @@ + .TITLE EXS1 OVMX exit-status probe: DECC$EXIT(1) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY EXS1,^M<> + PUSHL #1 + CALLS #1,G^DECC$EXIT + RET + .END EXS1 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/EXS2.MAR b/tests/lab/captures/decc-exit-alpha-20261021/EXS2.MAR new file mode 100644 index 000000000..eab782015 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/EXS2.MAR @@ -0,0 +1,7 @@ + .TITLE EXS2 OVMX exit-status probe: DECC$EXIT(2) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY EXS2,^M<> + PUSHL #2 + CALLS #1,G^DECC$EXIT + RET + .END EXS2 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/EXS3.MAR b/tests/lab/captures/decc-exit-alpha-20261021/EXS3.MAR new file mode 100644 index 000000000..39ab1212c --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/EXS3.MAR @@ -0,0 +1,7 @@ + .TITLE EXS3 OVMX exit-status probe: DECC$EXIT(3) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY EXS3,^M<> + PUSHL #3 + CALLS #1,G^DECC$EXIT + RET + .END EXS3 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/EXS4.MAR b/tests/lab/captures/decc-exit-alpha-20261021/EXS4.MAR new file mode 100644 index 000000000..4318ed06a --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/EXS4.MAR @@ -0,0 +1,7 @@ + .TITLE EXS4 OVMX exit-status probe: DECC$EXIT(4) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY EXS4,^M<> + PUSHL #4 + CALLS #1,G^DECC$EXIT + RET + .END EXS4 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/EXSBIG.MAR b/tests/lab/captures/decc-exit-alpha-20261021/EXSBIG.MAR new file mode 100644 index 000000000..e74d9b911 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/EXSBIG.MAR @@ -0,0 +1,7 @@ + .TITLE EXSBIG OVMX exit-status probe: DECC$EXIT(%X1000002C) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY EXSBIG,^M<> + PUSHL #^X1000002C + CALLS #1,G^DECC$EXIT + RET + .END EXSBIG diff --git a/tests/lab/captures/decc-exit-alpha-20261021/PXS0.MAR b/tests/lab/captures/decc-exit-alpha-20261021/PXS0.MAR new file mode 100644 index 000000000..10fb7f829 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/PXS0.MAR @@ -0,0 +1,7 @@ + .TITLE PXS0 OVMX exit-status probe: DECC$__POSIX_EXIT(0) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY PXS0,^M<> + PUSHL #0 + CALLS #1,G^DECC$__POSIX_EXIT + RET + .END PXS0 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/PXS1.MAR b/tests/lab/captures/decc-exit-alpha-20261021/PXS1.MAR new file mode 100644 index 000000000..4ddb5d316 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/PXS1.MAR @@ -0,0 +1,7 @@ + .TITLE PXS1 OVMX exit-status probe: DECC$__POSIX_EXIT(1) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY PXS1,^M<> + PUSHL #1 + CALLS #1,G^DECC$__POSIX_EXIT + RET + .END PXS1 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/PXS2.MAR b/tests/lab/captures/decc-exit-alpha-20261021/PXS2.MAR new file mode 100644 index 000000000..7b8f73797 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/PXS2.MAR @@ -0,0 +1,7 @@ + .TITLE PXS2 OVMX exit-status probe: DECC$__POSIX_EXIT(2) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY PXS2,^M<> + PUSHL #2 + CALLS #1,G^DECC$__POSIX_EXIT + RET + .END PXS2 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/PXS255.MAR b/tests/lab/captures/decc-exit-alpha-20261021/PXS255.MAR new file mode 100644 index 000000000..7645d9b71 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/PXS255.MAR @@ -0,0 +1,7 @@ + .TITLE PXS255 OVMX exit-status probe: DECC$__POSIX_EXIT(255) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY PXS255,^M<> + PUSHL #255 + CALLS #1,G^DECC$__POSIX_EXIT + RET + .END PXS255 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/PXS256.MAR b/tests/lab/captures/decc-exit-alpha-20261021/PXS256.MAR new file mode 100644 index 000000000..233776d2c --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/PXS256.MAR @@ -0,0 +1,7 @@ + .TITLE PXS256 OVMX exit-status probe: DECC$__POSIX_EXIT(256) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY PXS256,^M<> + PUSHL #256 + CALLS #1,G^DECC$__POSIX_EXIT + RET + .END PXS256 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/PXS3.MAR b/tests/lab/captures/decc-exit-alpha-20261021/PXS3.MAR new file mode 100644 index 000000000..e11e40f8e --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/PXS3.MAR @@ -0,0 +1,7 @@ + .TITLE PXS3 OVMX exit-status probe: DECC$__POSIX_EXIT(3) + .PSECT CCODE,EXE,NOWRT,QUAD + .ENTRY PXS3,^M<> + PUSHL #3 + CALLS #1,G^DECC$__POSIX_EXIT + RET + .END PXS3 diff --git a/tests/lab/captures/decc-exit-alpha-20261021/README.md b/tests/lab/captures/decc-exit-alpha-20261021/README.md new file mode 100644 index 000000000..0ea848bd0 --- /dev/null +++ b/tests/lab/captures/decc-exit-alpha-20261021/README.md @@ -0,0 +1,37 @@ +# DEC C exit() and __posix_exit() completion status on OpenVMS Alpha V8.4 (2026-10-21) + +Evidence for rd vms-599. Captured on a disposable lab pod (`exitalpha-1`, node `ALPHA1`, +OpenVMS Alpha V8.4 on AXPbox), driven over the console FIFO with `tools/lab-alpha/`. +The pod and its lab directory were deleted afterwards. + +The node has `MACRO` and `LINK` but no C compiler. Each probe is our own MACRO-32 +program, which calls the C RTL entry point directly with one value: +`PUSHL #n` / `CALLS #1,G^DECC$EXIT` (or `DECC$__POSIX_EXIT`). The program is then +`MACRO`ed, `LINK`ed and `RUN`, and `F$STRING($STATUS)` is printed (`CONSOLE.txt`). + +| call | `$STATUS` | +|---|---| +| `DECC$EXIT(0)` | `%X00000001` | +| `DECC$EXIT(1)` | `%X00000001` | +| `DECC$EXIT(2)` | `%X00000002` (DCL: `%NONAME-E-NOMSG`) | +| `DECC$EXIT(3)` | `%X00000003` | +| `DECC$EXIT(4)` | `%X00000004` (DCL: `%NONAME-F-NOMSG`) | +| `DECC$EXIT(%X1000002C)` | `%X1000002C` | +| `DECC$__POSIX_EXIT(0)` | `%X00000001` | +| `DECC$__POSIX_EXIT(1)` | `%X1035A00A` | +| `DECC$__POSIX_EXIT(2)` | `%X0035A011` | +| `DECC$__POSIX_EXIT(3)` | `%X0035A019` | +| `DECC$__POSIX_EXIT(255)` | `%X0035A7F9` | +| `DECC$__POSIX_EXIT(256)` | `%X00000001` | + +So `exit()` turns 0 into SS$_NORMAL and passes every other value through unchanged. +`__posix_exit()` keeps the low eight bits. It turns 0 into `%X00000001` and turns any +other n into `%X0035A001 + (n << 3)`, a C-facility message with success severity. +The one exception is 1: it becomes an error with the message inhibited, `%X1035A00A`. + +`EXIT_FAILURE` is a header constant and cannot be observed without a compiler. The VSI +C RTL Reference (exit) groups "a status value of 2 or EXIT_FAILURE" as the error-level +value, which matches the `%X00000002` measured above. + +Clean-room: observation only. Nothing was disassembled, and no VSI/HPE source or header +was read. diff --git a/tools/cross-alpha-vms/joint-e2e/crtl_fd_test.c b/tools/cross-alpha-vms/joint-e2e/crtl_fd_test.c index 8f727c4ae..c2ee9f7c0 100644 --- a/tools/cross-alpha-vms/joint-e2e/crtl_fd_test.c +++ b/tools/cross-alpha-vms/joint-e2e/crtl_fd_test.c @@ -14,11 +14,17 @@ * struct stat is the DEC C RTL's default layout (vms-28d part 2): st_ino[3] is * the File ID and st_fab_rfm/rat/mrs the file's RMS record attributes -- what * GCC's libcpp (STAT_SIZE_RELIABLE) and incpath (INO_T_EQ) read under VMS. + * + * Run as a foreign command with "exit N" or "pexit N" (vms-599), it instead ends + * through DEC C exit(N) or __posix_exit(N), after printing a line it does not + * flush; the SYSTARTUP prints the $STATUS DCL got, which the gate compares with + * what OpenVMS Alpha V8.4 gives (tests/lab/captures/decc-exit-alpha-20261021). */ #include #include #include #include +#include #include #include #include @@ -32,6 +38,8 @@ #define FAB_C_STMLF 5 #define STAT_SIZE_RELIABLE(ST) ((ST).st_fab_rfm != FAB_C_VAR) +void __posix_exit(int); /* DEC C; declares it only under _POSIX_EXIT */ + static int fails; static int first_fail; @@ -54,7 +62,15 @@ int main(int argc, char **argv) { char want[128], got[256]; long total = 0; - (void)argv; + if (argc >= 3 && (strcmp(argv[1], "exit") == 0 || strcmp(argv[1], "pexit") == 0)) { + int n = (int)strtol(argv[2], NULL, 0); + /* fully buffered and no newline: only exit()'s stdio flush prints it */ + setvbuf(stdout, NULL, _IOFBF, BUFSIZ); + printf("CFDX: %s %s flushed-by-exit", argv[1], argv[2]); + if (argv[1][0] == 'p') + __posix_exit(n); + exit(n); + } setvbuf(stdout, NULL, _IONBF, 0); /* every line reaches the console */ /* 1. stdio writes a multi-block file of unaligned lines. */ diff --git a/tools/cross-alpha-vms/musl-arch/build-musl.sh b/tools/cross-alpha-vms/musl-arch/build-musl.sh index ca0d49e20..2b9867887 100755 --- a/tools/cross-alpha-vms/musl-arch/build-musl.sh +++ b/tools/cross-alpha-vms/musl-arch/build-musl.sh @@ -236,6 +236,16 @@ if ! grep -q 'vms-28d: psignal' include/signal.h; then perl -0pi -e 's/^void psiginfo\(const siginfo_t \*, const char \*\);\nvoid psignal\(int, const char \*\);\n/#if !defined(__VMS) || defined(__OVMX_LIBC_BUILD) \/* vms-28d: psignal not in the DEC C RTL *\/\nvoid psiginfo(const siginfo_t *, const char *);\nvoid psignal(int, const char *);\n#endif\n/m' include/signal.h fi grep -q 'vms-28d: psignal' include/signal.h || { echo "vms-28d PATCH FAIL: psignal in include/signal.h" >&2; exit 7; } +# vms-599: DEC C exit. Under the _POSIX_EXIT feature-test macro exit() is +# __posix_exit() (a POSIX code 0-255 as a C-facility condition value); otherwise +# exit()'s value is the VMS status, so EXIT_FAILURE is 2, an error severity +# (VSI C RTL Reference, exit: "2 or EXIT_FAILURE"; exit(2) measured as %X00000002 +# on OpenVMS Alpha V8.4, tests/lab/captures/decc-exit-alpha-20261021). The GCC +# port's own programs define _POSIX_EXIT (gcc/config/vms/xm-vms.h). +if ! grep -q 'vms-599: DEC C exit' include/stdlib.h; then + perl -0pi -e 's/^#define EXIT_SUCCESS 0\n/$&#if defined(__VMS) \&\& !defined(__OVMX_LIBC_BUILD) \/* vms-599: DEC C exit *\/\n#ifdef _POSIX_EXIT\n_Noreturn void __posix_exit (int);\n#define exit __posix_exit\n#else\n#undef EXIT_FAILURE\n#define EXIT_FAILURE 2\n#endif\n#endif\n/m' include/stdlib.h +fi +grep -q 'vms-599: DEC C exit' include/stdlib.h || { echo "vms-599 PATCH FAIL: exit in include/stdlib.h" >&2; exit 7; } # vms-4ba3: DEC C fgetname(FILE *, char *, ...) -- the file specification of an # open stream, served by the RMS file layer (src/vmsrms/crtl_rms_fd.c). A DEC C # client form only; musl has no such routine. diff --git a/tools/cross-alpha/SYSTARTUP_VMS_CRTLFD_PROOF.COM b/tools/cross-alpha/SYSTARTUP_VMS_CRTLFD_PROOF.COM index 8436ba2fc..73e91210f 100644 --- a/tools/cross-alpha/SYSTARTUP_VMS_CRTLFD_PROOF.COM +++ b/tools/cross-alpha/SYSTARTUP_VMS_CRTLFD_PROOF.COM @@ -37,4 +37,32 @@ $ WRITE SYS$OUTPUT "CRTLFD-ATTR: CFDFIX.DAT RFM=", F$FILE_ATTRIBUTES("VDA0:[SYST $ WRITE SYS$OUTPUT "CRTLFD-ATTR: CFDSLF.DAT RFM=", F$FILE_ATTRIBUTES("VDA0:[SYSTMP]CFDSLF.DAT","RFM"), " RAT=", F$FILE_ATTRIBUTES("VDA0:[SYSTMP]CFDSLF.DAT","RAT"), "." $ WRITE SYS$OUTPUT "CRTLFD-PROOF: === END INDEPENDENT READER ===" $! +$! vms-599: the image ends through DEC C exit(N) / __posix_exit(N); $STATUS is what +$! DCL got. The gate compares each with OpenVMS Alpha V8.4 +$! (tests/lab/captures/decc-exit-alpha-20261021). The image's last line has no +$! newline (only exit's flush prints it), so each status line may follow it. +$ CFD := $SYS$SYSTEM:JOINT_E2E +$ CFD exit 0 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: exit 0 STATUS=''$STATUS'" +$ CFD exit 1 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: exit 1 STATUS=''$STATUS'" +$ CFD exit 2 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: exit 2 STATUS=''$STATUS'" +$ CFD exit 3 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: exit 3 STATUS=''$STATUS'" +$ CFD exit 0x1000002C +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: exit 0x1000002C STATUS=''$STATUS'" +$ CFD pexit 0 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: pexit 0 STATUS=''$STATUS'" +$ CFD pexit 1 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: pexit 1 STATUS=''$STATUS'" +$ CFD pexit 2 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: pexit 2 STATUS=''$STATUS'" +$ CFD pexit 3 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: pexit 3 STATUS=''$STATUS'" +$ CFD pexit 255 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: pexit 255 STATUS=''$STATUS'" +$ CFD pexit 256 +$ WRITE SYS$OUTPUT "CRTLFD-EXIT: pexit 256 STATUS=''$STATUS'" +$! $ EXIT diff --git a/tools/cross-alpha/run-module-gp-activation-alpha.sh b/tools/cross-alpha/run-module-gp-activation-alpha.sh index f446d8c5c..8643c89a4 100755 --- a/tools/cross-alpha/run-module-gp-activation-alpha.sh +++ b/tools/cross-alpha/run-module-gp-activation-alpha.sh @@ -865,6 +865,18 @@ assert_crtlfd() { printf '%s\n' "$region" | grep >/dev/null -aE "CRTLFD-ATTR: CFDUDF.DAT RFM=UDF$" || { echo " assert_crtlfd: F\$FILE_ATTRIBUTES does not show CFDUDF.DAT RFM=UDF" >&2; return 1; } printf '%s\n' "$region" | grep >/dev/null -aE "CRTLFD-ATTR: CFDFIX.DAT RFM=FIX MRS=20$" || { echo " assert_crtlfd: F\$FILE_ATTRIBUTES does not show CFDFIX.DAT RFM=FIX MRS=20" >&2; return 1; } printf '%s\n' "$region" | grep >/dev/null -aE "CRTLFD-ATTR: CFDSLF.DAT RFM=STMLF RAT=\.$" || { echo " assert_crtlfd: F\$FILE_ATTRIBUTES does not show CFDSLF.DAT RFM=STMLF with no record attributes" >&2; return 1; } + # vms-599: DEC C exit(N) / __posix_exit(N) -> the $STATUS OpenVMS Alpha V8.4 gives + # (tests/lab/captures/decc-exit-alpha-20261021), and the unflushed line exit printed. + local _x _call _want + for _x in "exit 0:%X00000001" "exit 1:%X00000001" "exit 2:%X00000002" "exit 3:%X00000003" \ + "exit 0x1000002C:%X1000002C" "pexit 0:%X00000001" "pexit 1:%X1035A00A" \ + "pexit 2:%X0035A011" "pexit 3:%X0035A019" "pexit 255:%X0035A7F9" "pexit 256:%X00000001"; do + _call=${_x%%:*}; _want=${_x##*:} + tr -d '\r' < "$log" | grep -aqF "CRTLFD-EXIT: $_call STATUS=$_want" \ + || { echo " assert_crtlfd: $_call did not give \$STATUS $_want" >&2; return 1; } + tr -d '\r' < "$log" | grep -aqF "CFDX: $_call flushed-by-exit" \ + || { echo " assert_crtlfd: $_call: exit did not flush stdio" >&2; return 1; } + done return 0 } @@ -2759,13 +2771,23 @@ EOF echo "CRTLFD-ATTR: CFDFIX.DAT RFM=FIX MRS=20" echo "CRTLFD-ATTR: CFDSLF.DAT RFM=STMLF RAT=." echo "CRTLFD-PROOF: === END INDEPENDENT READER ===" + for _x in "exit 0:%X00000001" "exit 1:%X00000001" "exit 2:%X00000002" "exit 3:%X00000003" \ + "exit 0x1000002C:%X1000002C" "pexit 0:%X00000001" "pexit 1:%X1035A00A" \ + "pexit 2:%X0035A011" "pexit 3:%X0035A019" "pexit 255:%X0035A7F9" "pexit 256:%X00000001"; do + echo "CFDX: ${_x%%:*} flushed-by-exitCRTLFD-EXIT: ${_x%%:*} STATUS=${_x##*:}" + done } > "$_st/pass.log" + # vms-599 negative controls: musl's exit (exit(1) a POSIX failure, nothing recorded), + # a __posix_exit that does not encode, and an exit that skips the stdio flush + sed 's/CRTLFD-EXIT: exit 1 STATUS=%X00000001/CRTLFD-EXIT: exit 1 STATUS=%X0000002C/' "$_st/pass.log" > "$_st/muslexit.log" + sed 's/CRTLFD-EXIT: pexit 3 STATUS=%X0035A019/CRTLFD-EXIT: pexit 3 STATUS=%X00000003/' "$_st/pass.log" > "$_st/pexitraw.log" + sed 's/CFDX: exit 3 flushed-by-exit//' "$_st/pass.log" > "$_st/noflush.log" sed 's/CFDUDF.DAT RFM=UDF/CFDUDF.DAT RFM=STMLF/' "$_st/pass.log" > "$_st/kwignored.log" sed 's/CFDSLF.DAT RFM=STMLF RAT=\./CFDSLF.DAT RFM=STMLF RAT=C./' "$_st/pass.log" > "$_st/ratignored.log" grep -v "APPENDED BY" "$_st/pass.log" > "$_st/noappend.log" sed 's/TYPE-STATUS=%X00000001/TYPE-STATUS=%X00018292/' "$_st/pass.log" > "$_st/notype.log" sed 's/test: OK (stdio/test: 1 check(s) FAILED (first 14) (stdio/' "$_st/pass.log" > "$_st/checkfail.log" - for _c in "pass:0" "noappend:1" "notype:1" "checkfail:1" "kwignored:1" "ratignored:1"; do + for _c in "pass:0" "noappend:1" "notype:1" "checkfail:1" "kwignored:1" "ratignored:1" "muslexit:1" "pexitraw:1" "noflush:1"; do _n=${_c%%:*}; _want=${_c##*:} if assert_crtlfd "$_st/$_n.log" >/dev/null 2>&1; then _got=0; else _got=1; fi if [ "$_got" = "$_want" ]; then echo " crtlfd selftest $_n: PASS"; else echo " crtlfd selftest $_n: FAIL"; _fails=$((_fails+1)); fi @@ -2777,7 +2799,7 @@ EOF assemble_boot_image log "step 3: BOOT A -- the C RTL file layer over RMS on the REAL executive" run_boot_a - grep -aE "CFD:|OVMX CRTL-FD|CRTLFD-PROOF:|line (001|120) of|APPENDED BY|%DCL-|%IMGACT|%RMS-" "$WORK/modgpA.log" 2>/dev/null | sed 's/^/ | /' || true + grep -aE "CFD:|CFDX:|CRTLFD-EXIT:|OVMX CRTL-FD|CRTLFD-PROOF:|line (001|120) of|APPENDED BY|%DCL-|%IMGACT|%RMS-" "$WORK/modgpA.log" 2>/dev/null | sed 's/^/ | /' || true if assert_crtlfd "$WORK/modgpA.log"; then echo "" echo "PASS: a 32-bit DEC C program's stdio and descriptor calls ran over RMS on the" From b5c4377885dd374516a75436c9cf8b1d638483a9 Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 21 Oct 2026 17:40:16 +0000 Subject: [PATCH 2/5] vms-599: the exit assertions use grep >/dev/null under pipefail (CI lint) Co-Authored-By: Claude Opus 5.5 --- tools/cross-alpha/run-module-gp-activation-alpha.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cross-alpha/run-module-gp-activation-alpha.sh b/tools/cross-alpha/run-module-gp-activation-alpha.sh index 8643c89a4..bf757ee22 100755 --- a/tools/cross-alpha/run-module-gp-activation-alpha.sh +++ b/tools/cross-alpha/run-module-gp-activation-alpha.sh @@ -872,9 +872,9 @@ assert_crtlfd() { "exit 0x1000002C:%X1000002C" "pexit 0:%X00000001" "pexit 1:%X1035A00A" \ "pexit 2:%X0035A011" "pexit 3:%X0035A019" "pexit 255:%X0035A7F9" "pexit 256:%X00000001"; do _call=${_x%%:*}; _want=${_x##*:} - tr -d '\r' < "$log" | grep -aqF "CRTLFD-EXIT: $_call STATUS=$_want" \ + tr -d '\r' < "$log" | grep >/dev/null -aF "CRTLFD-EXIT: $_call STATUS=$_want" \ || { echo " assert_crtlfd: $_call did not give \$STATUS $_want" >&2; return 1; } - tr -d '\r' < "$log" | grep -aqF "CFDX: $_call flushed-by-exit" \ + tr -d '\r' < "$log" | grep >/dev/null -aF "CFDX: $_call flushed-by-exit" \ || { echo " assert_crtlfd: $_call: exit did not flush stdio" >&2; return 1; } done return 0 From 723f130d056be5f536f6d830aa88811180b9a0b4 Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 21 Oct 2026 17:54:00 +0000 Subject: [PATCH 3/5] vms-599: exit() runs the atexit handlers and flushes stdio on alpha-dec-vms The crtl-fd gate's new check found this on the qemu-system-alpha boot: a line printed without a newline and then exit()ed never reached the console. Generic musl exit.c reaches __funcs_on_exit and __stdio_exit through local weak aliases of a no-op. The alpha-dec-vms back end binds same-TU calls to those aliases section-relatively (the vms-032 lite_malloc case), so the strong definitions in atexit.c and __stdio_exit.c never ran. An arch override (src/exit/alpha-dec-vms/exit.c) now calls both by name. A build-time gate in build-musl.sh fails if exit.o defines either symbol or does not reference it. Co-Authored-By: Claude Opus 5.5 --- tools/cross-alpha-vms/musl-arch/build-musl.sh | 30 +++++++++++ .../musl-arch/src/exit/alpha-dec-vms/exit.c | 50 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c diff --git a/tools/cross-alpha-vms/musl-arch/build-musl.sh b/tools/cross-alpha-vms/musl-arch/build-musl.sh index 2b9867887..c88895871 100755 --- a/tools/cross-alpha-vms/musl-arch/build-musl.sh +++ b/tools/cross-alpha-vms/musl-arch/build-musl.sh @@ -75,6 +75,11 @@ cp -v "${OVERLAY}/src/internal/vms_alpha_syscall.c" "src/internal/" # header of src/malloc/alpha-dec-vms/lite_malloc.c. mkdir -p "src/malloc/${TARGET}" cp -v "${OVERLAY}/src/malloc/${TARGET}/"* "src/malloc/${TARGET}/" +# vms-599: exit() runs the atexit handlers and flushes stdio. Replace generic +# exit.c (which reaches both through local weak aliases the back end self-binds) +# with one that calls them by name. See src/exit/alpha-dec-vms/exit.c. +mkdir -p "src/exit/${TARGET}" +cp -v "${OVERLAY}/src/exit/${TARGET}/"* "src/exit/${TARGET}/" # vms-122: VMS P0 placement. An address-less mmap (the heap's, and mremap's # moves) is placed in the P0 region below 0x40000000, as the VMS heap grows P0, # so every malloc result is a valid 32-bit pointer. See @@ -636,6 +641,31 @@ if ! grep -qE '[[:space:]]U[[:space:]]+__libc_malloc_impl$' /tmp/lmck/nm.out; th fi echo " OK lite_malloc.o: U __libc_malloc_impl, no local allocator -> one heap (mallocng)" +# -------------------------------------------------------------------------- +# EXIT GATE (vms-599): exit.o must be the alpha-dec-vms override -- it names +# __funcs_on_exit (atexit handlers) and __stdio_exit (stdio flush) as undefined +# references and defines neither, so no local weak no-op can absorb the calls. +# -------------------------------------------------------------------------- +echo "== exit gate (vms-599): exit.o calls the atexit handlers and the stdio flush by name ==" +rm -rf /tmp/exck && mkdir -p /tmp/exck +EXMEMB=$(grep -m1 -E '(^|/)exit\.' /tmp/libc.members || true) +[ -n "${EXMEMB}" ] || { echo "VERIFY FAIL (vms-599): no exit member in libc.a" >&2; exit 8; } +( cd /tmp/exck && ar x "${LIBCA}" "${EXMEMB}" ) 2>/dev/null || true +"${NM}" "/tmp/exck/${EXMEMB}" >/tmp/exck/nm.out 2>&1 || { cat /tmp/exck/nm.out >&2; exit 8; } +for _sym in __funcs_on_exit __stdio_exit; do + if grep -qE "^[0-9a-fA-F]+[[:space:]]+[A-TV-Za-tv-z][[:space:]]+${_sym}\$" /tmp/exck/nm.out; then + echo "VERIFY FAIL (vms-599): exit.o DEFINES ${_sym} (generic exit.c's weak no-op, not the alpha-dec-vms override):" >&2 + cat /tmp/exck/nm.out >&2 + exit 8 + fi + if ! grep -qE "[[:space:]]U[[:space:]]+${_sym}\$" /tmp/exck/nm.out; then + echo "VERIFY FAIL (vms-599): exit.o does not reference ${_sym} by name:" >&2 + cat /tmp/exck/nm.out >&2 + exit 8 + fi +done +echo " OK exit.o: U __funcs_on_exit, U __stdio_exit -> exit() runs atexit and flushes stdio" + # -------------------------------------------------------------------------- # SIZE_T MODEL GATE (vms-537): through musl's own installed header set, ::size_t # must BE the compiler's __SIZE_TYPE__ (32-bit, DEC C), ssize_t 32-bit, pointers diff --git a/tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c b/tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c new file mode 100644 index 000000000..49957e7f8 --- /dev/null +++ b/tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c @@ -0,0 +1,50 @@ +/* + * exit.c -- alpha-dec-vms arch override of musl's src/exit/exit.c (vms-599). + * musl's Makefile replaces the generic file with this one for ARCH=alpha-dec-vms + * (src/exit/$(ARCH)/*.c -> REPLACED_OBJS). + * + * Why: the generic exit.c reaches the atexit handlers (__funcs_on_exit, atexit.c) + * and the stdio flush (__stdio_exit, __stdio_exit.c) through WEAK aliases of a + * local no-op, which the strong definitions override only when the linker sees + * every reference by name. The alpha-dec-vms back end binds same-TU calls to a + * local weak alias section-relatively instead, never naming the symbol -- the + * vms-032 lite_malloc case -- so exit() ran neither: an image's buffered output + * and its atexit handlers were lost (observed on the qemu-system-alpha boot of + * the crtl-fd gate: a line printed without a newline and then exit()ed never + * reached the console). The DEC C RTL's exit() runs the atexit handlers and + * flushes and closes the files before SYS$EXIT. + * + * DECC$SHR always links atexit.c and __stdio_exit.c, so this override calls both + * by NAME, with no local definition for a self-bind to land on. _fini and + * __libc_exit_fini keep musl's local forms: nothing overrides them here. + */ +#include +#include +#include "libc.h" +#include "stdio_impl.h" + +static void dummy() +{ +} + +weak_alias(dummy, _fini); + +extern weak hidden void (*const __fini_array_start)(void), (*const __fini_array_end)(void); + +static void libc_exit_fini(void) +{ + uintptr_t a = (uintptr_t)&__fini_array_end; + for (; a>(uintptr_t)&__fini_array_start; a-=sizeof(void(*)())) + (*(void (**)())(a-sizeof(void(*)())))(); + _fini(); +} + +weak_alias(libc_exit_fini, __libc_exit_fini); + +_Noreturn void exit(int code) +{ + __funcs_on_exit(); + __libc_exit_fini(); + __stdio_exit(); + _Exit(code); +} From 55f6356f0748295b25c60bc9baa41c9b12c3438b Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 21 Oct 2026 17:58:27 +0000 Subject: [PATCH 4/5] vms-599: the host symbol scan excludes the musl exit.c arch override (musl-internal headers), like lite_malloc.c and _Fork.c Co-Authored-By: Claude Opus 5.5 --- tests/integration/test_userspace_service_register.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_userspace_service_register.sh b/tests/integration/test_userspace_service_register.sh index 2c0ea8f99..ff0c0060d 100755 --- a/tests/integration/test_userspace_service_register.sh +++ b/tests/integration/test_userspace_service_register.sh @@ -892,6 +892,11 @@ grep -E '\.[sS]$' "$WORK/buildset" > "$WORK/prodasm" 2>/dev/null || : > "$WORK/p # and only compiles inside the extracted musl tree. Its real compile runs in # build-musl.sh and its behaviour is proven by the alpha vfork gate. Anchored + # file-level, like lite_malloc.c above. +# tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c (vms-599) is the +# same kind again (src/exit/$(ARCH)/ replaces the generic exit.c): it includes +# musl's INTERNAL "libc.h"/"stdio_impl.h" and defines only exit(), no sys$ symbol. +# Its real compile + the exit nm gate run in build-musl.sh; the alpha crtl-fd gate +# proves the behaviour. Anchored + file-level. # src/vmsrms/crtl_rms_fd.c (vms-b90) is the C RTL file layer DECC$SHR's # RMS-backed pass links: it hooks the musl-arch syscall funnel and fills musl's # arch-internal struct kstat (musl-arch kstat.h), so it only compiles against @@ -917,7 +922,7 @@ grep -E '\.[sS]$' "$WORK/buildset" > "$WORK/prodasm" 2>/dev/null || : > "$WORK/p # the alpha-dec-vms client headers (musl-arch bits/stat.h: st_ino[3], st_fab_*, # the __OVMX_DECC_STAT_FIELDS list); a host libc has neither. They define no # sys$ service; their real compiles are mk_decc_shr.sh and the crtl-fd gate. -SYMSCAN_EXCLUDE_RE="^src/kernel/|^src/kernel-netbsd/|^src/kernel-core/|^src/vmsfs/ods2/ods2_block_kern\.c$|^tools/cross-alpha-vms/musl-arch/src/internal/vms_alpha_syscall\.c$|^tools/cross-alpha-vms/musl-arch/src/thread/alpha-dec-vms/__set_thread_area\.c$|^tools/cross-alpha-vms/include-surface/compile_vms_unwind\.c$|^tools/cross-alpha-vms/musl-arch/src/malloc/alpha-dec-vms/lite_malloc\.c$|^tools/cross-alpha-vms/musl-arch/src/process/alpha-dec-vms/_Fork\.c$|^src/vmsrms/crtl_rms_fd\.c$|^src/vmsrms/rms_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/vms_abi_headers\.c$|^tools/cross-alpha-vms/joint-e2e/vmsabi_rms_test\.c$|^src/vmslink/ovmx_decc_stat\.c$|^tools/cross-alpha-vms/joint-e2e/crtl_fd_test\.c$|^src/libvms/syssvc/sys_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/hostabi_bfd_shapes\.c$" +SYMSCAN_EXCLUDE_RE="^src/kernel/|^src/kernel-netbsd/|^src/kernel-core/|^src/vmsfs/ods2/ods2_block_kern\.c$|^tools/cross-alpha-vms/musl-arch/src/internal/vms_alpha_syscall\.c$|^tools/cross-alpha-vms/musl-arch/src/thread/alpha-dec-vms/__set_thread_area\.c$|^tools/cross-alpha-vms/include-surface/compile_vms_unwind\.c$|^tools/cross-alpha-vms/musl-arch/src/malloc/alpha-dec-vms/lite_malloc\.c$|^tools/cross-alpha-vms/musl-arch/src/process/alpha-dec-vms/_Fork\.c$|^tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit\.c$|^src/vmsrms/crtl_rms_fd\.c$|^src/vmsrms/rms_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/vms_abi_headers\.c$|^tools/cross-alpha-vms/joint-e2e/vmsabi_rms_test\.c$|^src/vmslink/ovmx_decc_stat\.c$|^tools/cross-alpha-vms/joint-e2e/crtl_fd_test\.c$|^src/libvms/syssvc/sys_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/hostabi_bfd_shapes\.c$" SYMCC="" for _c in cc gcc; do From a6f8d682a60b85b3de3504db281e85d0103e6974 Mon Sep 17 00:00:00 2001 From: alice Date: Wed, 21 Oct 2026 18:11:32 +0000 Subject: [PATCH 5/5] vms-599: __stdio_exit flushes the real stdin/stdout/stderr on alpha-dec-vms The exit() override ran __stdio_exit, but the crtl-fd gate's unflushed line was still lost. musl's __stdio_exit.c reaches the three standard FILEs through weak aliases of a local NULL. The back end self-binds those aliases, so the flush saw no FILE. This adds an arch override that names __stdin_used, __stdout_used and __stderr_used, and extends build-musl.sh's nm gate to check it. Co-Authored-By: Claude Opus 5.5 --- .../test_userspace_service_register.sh | 9 ++-- tools/cross-alpha-vms/musl-arch/build-musl.sh | 41 +++++++++++-------- .../src/stdio/alpha-dec-vms/__stdio_exit.c | 34 +++++++++++++++ 3 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 tools/cross-alpha-vms/musl-arch/src/stdio/alpha-dec-vms/__stdio_exit.c diff --git a/tests/integration/test_userspace_service_register.sh b/tests/integration/test_userspace_service_register.sh index ff0c0060d..37aed2d66 100755 --- a/tests/integration/test_userspace_service_register.sh +++ b/tests/integration/test_userspace_service_register.sh @@ -892,9 +892,10 @@ grep -E '\.[sS]$' "$WORK/buildset" > "$WORK/prodasm" 2>/dev/null || : > "$WORK/p # and only compiles inside the extracted musl tree. Its real compile runs in # build-musl.sh and its behaviour is proven by the alpha vfork gate. Anchored + # file-level, like lite_malloc.c above. -# tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c (vms-599) is the -# same kind again (src/exit/$(ARCH)/ replaces the generic exit.c): it includes -# musl's INTERNAL "libc.h"/"stdio_impl.h" and defines only exit(), no sys$ symbol. +# tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit.c and +# .../src/stdio/alpha-dec-vms/__stdio_exit.c (vms-599) are the same kind again +# (src//$(ARCH)/ replaces the generic file): they include musl's INTERNAL +# "libc.h"/"stdio_impl.h" and define only exit() / __stdio_exit(), no sys$ symbol. # Its real compile + the exit nm gate run in build-musl.sh; the alpha crtl-fd gate # proves the behaviour. Anchored + file-level. # src/vmsrms/crtl_rms_fd.c (vms-b90) is the C RTL file layer DECC$SHR's @@ -922,7 +923,7 @@ grep -E '\.[sS]$' "$WORK/buildset" > "$WORK/prodasm" 2>/dev/null || : > "$WORK/p # the alpha-dec-vms client headers (musl-arch bits/stat.h: st_ino[3], st_fab_*, # the __OVMX_DECC_STAT_FIELDS list); a host libc has neither. They define no # sys$ service; their real compiles are mk_decc_shr.sh and the crtl-fd gate. -SYMSCAN_EXCLUDE_RE="^src/kernel/|^src/kernel-netbsd/|^src/kernel-core/|^src/vmsfs/ods2/ods2_block_kern\.c$|^tools/cross-alpha-vms/musl-arch/src/internal/vms_alpha_syscall\.c$|^tools/cross-alpha-vms/musl-arch/src/thread/alpha-dec-vms/__set_thread_area\.c$|^tools/cross-alpha-vms/include-surface/compile_vms_unwind\.c$|^tools/cross-alpha-vms/musl-arch/src/malloc/alpha-dec-vms/lite_malloc\.c$|^tools/cross-alpha-vms/musl-arch/src/process/alpha-dec-vms/_Fork\.c$|^tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit\.c$|^src/vmsrms/crtl_rms_fd\.c$|^src/vmsrms/rms_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/vms_abi_headers\.c$|^tools/cross-alpha-vms/joint-e2e/vmsabi_rms_test\.c$|^src/vmslink/ovmx_decc_stat\.c$|^tools/cross-alpha-vms/joint-e2e/crtl_fd_test\.c$|^src/libvms/syssvc/sys_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/hostabi_bfd_shapes\.c$" +SYMSCAN_EXCLUDE_RE="^src/kernel/|^src/kernel-netbsd/|^src/kernel-core/|^src/vmsfs/ods2/ods2_block_kern\.c$|^tools/cross-alpha-vms/musl-arch/src/internal/vms_alpha_syscall\.c$|^tools/cross-alpha-vms/musl-arch/src/thread/alpha-dec-vms/__set_thread_area\.c$|^tools/cross-alpha-vms/include-surface/compile_vms_unwind\.c$|^tools/cross-alpha-vms/musl-arch/src/malloc/alpha-dec-vms/lite_malloc\.c$|^tools/cross-alpha-vms/musl-arch/src/process/alpha-dec-vms/_Fork\.c$|^tools/cross-alpha-vms/musl-arch/src/exit/alpha-dec-vms/exit\.c$|^tools/cross-alpha-vms/musl-arch/src/stdio/alpha-dec-vms/__stdio_exit\.c$|^src/vmsrms/crtl_rms_fd\.c$|^src/vmsrms/rms_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/vms_abi_headers\.c$|^tools/cross-alpha-vms/joint-e2e/vmsabi_rms_test\.c$|^src/vmslink/ovmx_decc_stat\.c$|^tools/cross-alpha-vms/joint-e2e/crtl_fd_test\.c$|^src/libvms/syssvc/sys_vmsabi\.c$|^tools/cross-alpha-vms/include-surface/hostabi_bfd_shapes\.c$" SYMCC="" for _c in cc gcc; do diff --git a/tools/cross-alpha-vms/musl-arch/build-musl.sh b/tools/cross-alpha-vms/musl-arch/build-musl.sh index c88895871..15375a541 100755 --- a/tools/cross-alpha-vms/musl-arch/build-musl.sh +++ b/tools/cross-alpha-vms/musl-arch/build-musl.sh @@ -80,6 +80,10 @@ cp -v "${OVERLAY}/src/malloc/${TARGET}/"* "src/malloc/${TARGET}/" # with one that calls them by name. See src/exit/alpha-dec-vms/exit.c. mkdir -p "src/exit/${TARGET}" cp -v "${OVERLAY}/src/exit/${TARGET}/"* "src/exit/${TARGET}/" +# ... and __stdio_exit.c, which reaches stdin/stdout/stderr through the same kind +# of local weak alias (a NULL), so the flush saw no FILE. See its header. +mkdir -p "src/stdio/${TARGET}" +cp -v "${OVERLAY}/src/stdio/${TARGET}/"* "src/stdio/${TARGET}/" # vms-122: VMS P0 placement. An address-less mmap (the heap's, and mremap's # moves) is placed in the P0 region below 0x40000000, as the VMS heap grows P0, # so every malloc result is a valid 32-bit pointer. See @@ -646,25 +650,28 @@ echo " OK lite_malloc.o: U __libc_malloc_impl, no local allocator -> one h # __funcs_on_exit (atexit handlers) and __stdio_exit (stdio flush) as undefined # references and defines neither, so no local weak no-op can absorb the calls. # -------------------------------------------------------------------------- -echo "== exit gate (vms-599): exit.o calls the atexit handlers and the stdio flush by name ==" +echo "== exit gate (vms-599): exit() runs atexit and flushes stdio, every link by name ==" rm -rf /tmp/exck && mkdir -p /tmp/exck -EXMEMB=$(grep -m1 -E '(^|/)exit\.' /tmp/libc.members || true) -[ -n "${EXMEMB}" ] || { echo "VERIFY FAIL (vms-599): no exit member in libc.a" >&2; exit 8; } -( cd /tmp/exck && ar x "${LIBCA}" "${EXMEMB}" ) 2>/dev/null || true -"${NM}" "/tmp/exck/${EXMEMB}" >/tmp/exck/nm.out 2>&1 || { cat /tmp/exck/nm.out >&2; exit 8; } -for _sym in __funcs_on_exit __stdio_exit; do - if grep -qE "^[0-9a-fA-F]+[[:space:]]+[A-TV-Za-tv-z][[:space:]]+${_sym}\$" /tmp/exck/nm.out; then - echo "VERIFY FAIL (vms-599): exit.o DEFINES ${_sym} (generic exit.c's weak no-op, not the alpha-dec-vms override):" >&2 - cat /tmp/exck/nm.out >&2 - exit 8 - fi - if ! grep -qE "[[:space:]]U[[:space:]]+${_sym}\$" /tmp/exck/nm.out; then - echo "VERIFY FAIL (vms-599): exit.o does not reference ${_sym} by name:" >&2 - cat /tmp/exck/nm.out >&2 - exit 8 - fi +for _spec in "exit:__funcs_on_exit __stdio_exit" "__stdio_exit:__stdin_used __stdout_used __stderr_used"; do + _m=${_spec%%:*}; _syms=${_spec#*:} + EXMEMB=$(grep -m1 -E "(^|/)${_m}\\." /tmp/libc.members || true) + [ -n "${EXMEMB}" ] || { echo "VERIFY FAIL (vms-599): no ${_m} member in libc.a" >&2; exit 8; } + ( cd /tmp/exck && ar x "${LIBCA}" "${EXMEMB}" ) 2>/dev/null || true + "${NM}" "/tmp/exck/${EXMEMB}" >/tmp/exck/nm.out 2>&1 || { cat /tmp/exck/nm.out >&2; exit 8; } + for _sym in ${_syms}; do + if grep -qE "^[0-9a-fA-F]+[[:space:]]+[A-TV-Za-tv-z][[:space:]]+${_sym}\$" /tmp/exck/nm.out; then + echo "VERIFY FAIL (vms-599): ${_m}.o DEFINES ${_sym} (musl's local weak alias, not the alpha-dec-vms override):" >&2 + cat /tmp/exck/nm.out >&2 + exit 8 + fi + if ! grep -qE "[[:space:]]U[[:space:]]+${_sym}\$" /tmp/exck/nm.out; then + echo "VERIFY FAIL (vms-599): ${_m}.o does not reference ${_sym} by name:" >&2 + cat /tmp/exck/nm.out >&2 + exit 8 + fi + done + echo " OK ${_m}.o: U ${_syms}" done -echo " OK exit.o: U __funcs_on_exit, U __stdio_exit -> exit() runs atexit and flushes stdio" # -------------------------------------------------------------------------- # SIZE_T MODEL GATE (vms-537): through musl's own installed header set, ::size_t diff --git a/tools/cross-alpha-vms/musl-arch/src/stdio/alpha-dec-vms/__stdio_exit.c b/tools/cross-alpha-vms/musl-arch/src/stdio/alpha-dec-vms/__stdio_exit.c new file mode 100644 index 000000000..8d6d5e596 --- /dev/null +++ b/tools/cross-alpha-vms/musl-arch/src/stdio/alpha-dec-vms/__stdio_exit.c @@ -0,0 +1,34 @@ +/* + * __stdio_exit.c -- alpha-dec-vms arch override of musl's + * src/stdio/__stdio_exit.c (vms-599). musl's Makefile replaces the generic file + * with this one for ARCH=alpha-dec-vms (src/stdio/$(ARCH)/*.c -> REPLACED_OBJS). + * + * Why: the generic file reaches stdin/stdout/stderr through WEAK aliases of a + * local NULL (dummy_file), which stdin.c/stdout.c/stderr.c override with the real + * FILEs only when the linker sees every reference by name. The alpha-dec-vms back + * end binds same-TU references to a local weak alias section-relatively (the + * vms-032 lite_malloc case), so exit() flushed nothing: a line an image printed + * without a newline was lost at exit (the crtl-fd gate, qemu-system-alpha boot). + * DECC$SHR always links stdin.c, stdout.c and stderr.c, so this override names + * the three FILEs and defines none of them. The body is musl's. + */ +#include "stdio_impl.h" + +static void close_file(FILE *f) +{ + if (!f) return; + FFINALLOCK(f); + if (f->wpos != f->wbase) f->write(f, 0, 0); + if (f->rpos != f->rend) f->seek(f, f->rpos-f->rend, SEEK_CUR); +} + +void __stdio_exit(void) +{ + FILE *f; + for (f=*__ofl_lock(); f; f=f->next) close_file(f); + close_file(__stdin_used); + close_file(__stdout_used); + close_file(__stderr_used); +} + +weak_alias(__stdio_exit, __stdio_exit_needed);