vms-b93: OVMX 0.1 -- VMS-native x86_64 dynamic linking, boot+install, real DCL.EXE/LOGINOUT.EXE via LINK.EXE - #155
Merged
Merged
Conversation
emit_shareable() in src/vmslink/link.c had two pieces hardcoded to
AArch64 machine code regardless of g_out_machine: the synthesized crt0
entry stub for a main()-based --executable, and the cross-image CALL
PLT stub + import-CALL detection (gated on R_AARCH64_CALL26/JUMP26
only, so R_X86_64_PLT32 references to a producer universal never
routed through the import table). An x86_64 main()-based program, or
any x86_64 image with a cross-image CALL import, linked to garbage --
exactly DCL.EXE's shape, blocking vms-cb5f.
Adds, additively per g_out_machine (mirrors vms-8f5's e_machine gate):
- is_call also covers R_X86_64_PLT32, so a PLT32 reference to an
undefined-locally, --use-producer-exported symbol becomes an
import exactly like aarch64's CALL26/JUMP26.
- the cross-image-call reloc-apply branch gets an x86_64 case: a
PC32-style S+A-P write targeting the PLT stub instead of the
(absent) callee.
- the PLT stub emitter gets an x86_64 case: `jmp *disp32(%rip)`
(FF 25 imm32) through the import-GOT cell -- the one-instruction
analogue of aarch64's adrp/ldr/br page+lo12 GOT load+branch.
- the crt0 emitter gets a real x86_64 stub: mov rdi,[rsp] / lea
rsi,[rsp+8] / lea rdx,[rsi+rdi*8+8] recovers argc/argv/envp per
the SysV process-entry stack layout, `call main` then `mov
edi,eax ; call exit` tails the return value into exit(). Encoding
verified against `as`'s AT&T disassembly byte-for-byte.
New test (src/imgact/test/run_multiobj_exec_x86_64.sh, wired into CI
as multiobj-exec-x86_64): links a two-object main() program against a
hand-written producer shareable via LINK.EXE --executable --use,
activates it by executing the image directly (real kernel PT_INTERP
-> IMGACT.EXE, native x86_64, no emulation), and checks the process
really ran main(), read a real argc/argv off the stack (two runs, two
different exit codes), made a real cross-image CALL through the new
PLT stub into the producer, and exited via a cross-image exit() call.
Verified this test fails with 'unresolved external symbol' before the
fix (confirmed via stash) and passes after.
Regression: aarch64 MVP (run_test.sh), aarch64 crt0/PLT
(run_multiobj_exec.sh) and x86_64 simple-reloc (run_test_x86_64.sh)
harnesses all stay green. Full ctest suite: 106/107 pass; the one
failure (facility_attribution_negctl) is pre-existing and unrelated
(reproduces identically with this change stashed out -- host/container
site-derivation drift in kernel/vms_lock.c attribution, nothing to do
with vmslink).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-206: LINK.EXE emits real x86_64 crt0 + cross-image CALL PLT stubs Clean automatic merge (verified: no conflict markers). vms-206 branched before vms-2e4 landed on main; both modify src/vmslink/link.c's x86_64 relocation/emit_shareable() path (TLSDESC vs crt0/PLT32) but git's 3-way merge resolved it without manual intervention.
…st real musl/libgcc
is_got_reloc() only recognized GOTPCREL (9) and REX_GOTPCRELX (42); plain
GOTPCRELX (41, gas's non-REX relaxable GOT-load variant) hit patch_pcrel's
default die("unsupported .text relocation"). Adds it as a third case
alongside its already-handled siblings in is_got_reloc()/patch_got() --
same flat-disp32-write codegen (S(got)+A-P, addend -4), since LINK.EXE
performs no GOT-load-to-LEA relaxation for any of the three.
Grounded empirically, not from psABI text alone:
- The system's prebuilt musl libc.a carries ZERO GOTPCRELX/REX_GOTPCRELX
(non-PIC static build) -- confirming the gap check needed a real PIC
object set, not just this host's default libc.a.
- A `-fPIC -fno-plt` probe (`extern void f(void); void g(void){f();}`)
reproduces the exact instruction shape: `jmp *sym@GOTPCREL(%rip)` (ff 25
disp32, no REX prefix -- near indirect call/jmp defaults to 64-bit
operand size without REX.W), matching readelf's R_X86_64_GOTPCRELX.
- Alpine x86_64 libgcc.a (the exact toolchain vms-cb5f's DECC$SHR build
uses) carries 1521 real GOTPCRELX occurrences -- e.g. `call *abort@
GOTPCREL(%rip)` in _absvdi2.o -- confirming the gap is in libgcc.a
(GCC's runtime calling abort/etc. through the GOT under -fno-plt), not
libc.a itself, and closely matches vms-cb5f's reported 1449 (after its
TLS-subsystem filter narrows the count).
Proof (done condition):
- Pre-fix LINK.EXE reproducibly dies with "%LINK-F-ERROR, unsupported
.text relocation" on a hand-built intra-image GOTPCRELX call
(caller.o -> callee.o, both defined, forcing the GOT slot to resolve
internally rather than deferring as an import).
- Post-fix LINK.EXE links it; a new REAL-LOAD harness
(src/vmslink/test/gotpcrelx_activate.c) mmaps the shareable at a
genuine non-zero ASLR'd base, applies the .vms$rel load-bias fixup
IMGACT would perform, and calls in -- proving the GOT cell resolves to
the correct address under a real load, not a readelf/byte check.
Wired into run_test_x86_64.sh as a permanent regression case.
- Whole-archiving the real Alpine x86_64 musl libc.a + libgcc.a (TLS
subsystem filtered per vms-cb5f's precedent, an unrelated gap) with
pre-fix LINK.EXE reproduces the exact die(); post-fix it gets past
that point and (with --allow-undefined for legitimately deferred
externals) succeeds end-to-end: 1586 objects, 146 GOT slots, 9610
relocs, EM_X86_64 -- confirming this specific gap is cleared at the
scale vms-cb5f hit it.
Base: work/vms-b93-integration (vms-206 merged). Blocks vms-cb5f.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-e5d: LINK.EXE resolves x86_64 GOTPCRELX (type 41), grounded against real musl/libgcc Clean automatic merge, base was already work/vms-b93-integration.
…RCH=x86_64)
Reproduces vms-b65.6's aarch64 DCL.EXE-through-IMGACT proof for x86_64, per
vms-bdf's own done-condition (all four reloc/crt0/PLT beads merged):
- run_dcl_native.sh / lib_build_graph.sh: ARCH env var (default aarch64,
unchanged) selects the one target-specific codegen flag each producer
needs (-mno-outline-atomics on aarch64 vs -mtls-dialect=gnu2 on x86_64,
the standing precedent from docs/design-link-x86_64-relocs.md) and the
libvmssys arch/<ARCH>/syscall.S. Shared by run_login_native.sh unaffected
(ARCH unset -> identical aarch64 defaults, verified against a real arm64
musl container).
- mk_{vmsprocess,vmslnm,vmsfs,libvms,vmsrms}_shr.sh / mk_dcl.sh: CFLAGS is
now env-overridable (${CFLAGS:-<same aarch64 default>}) so the x86_64
caller can supply target-appropriate flags without a forked copy of each
recipe.
- mk_decc_shr.sh: DECC$SHR must stay a non-TLS producer (LINK.EXE's
one-TLS-object-per-image limit, vms-212 tracks the general fix). aarch64's
libgcc.a empirically carries zero TLS-defining members; x86_64's whole-
archives a dead-for-OVMX subsystem (GCC's IEEE 754-2008 decimal-float
library + -fsplit-stack support) built on the TLSGD general-dynamic model
LINK.EXE's x86_64 path doesn't implement (OVMX standardizes on gnu2/
TLSDESC). Filtered architecture-generically by scanning archive members
for .tdata/.tbss or TLSGD refs, not by hardcoding names -- a no-op on an
archive with neither.
- .github/workflows/ci.yml: new dcl-native-x86_64 job, native amd64 (no
QEMU binfmt needed -- the runner already is x86_64), ARCH=x86_64 through
the same run_dcl_native.sh.
BLOCKED short of a green run: whole-archiving real musl libc.a on x86_64
(1345 objects) hits R_X86_64_GOTPCRELX (type 41), a relocation link.c's
x86_64 path does not recognize (is_got_reloc() only checks GOTPCREL/
REX_GOTPCRELX) -- confirmed empirically (1449 occurrences across ordinary,
load-bearing musl objects, not confinable to dead code the way the TLSGD
subsystem was). link.c is out of this item's file-domain per its own
repeated header comments ("do NOT edit them here"); see the escalation.
Also confirmed (fetched fresh): vms-206 is on work/vms-b93-integration
(16c7965) but NOT yet on main -- a second prerequisite for the executable
(not just shareable) link step once the GOTPCRELX gap is fixed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…hole-archive gap Re-dispatch, continuing from work/vms-cb5f now that vms-e5d (GOTPCRELX) is merged into work/vms-b93-integration. Rebased cleanly onto origin/work/vms-b93-integration (vms-206 crt0/PLT + vms-e5d GOTPCRELX both present); the prior CI-job/ARCH-parameterization/CFLAGS work carried over unchanged. Whole-archiving real musl libc.a + libgcc.a on x86_64 got past GOTPCRELX and hit a NEW gap: mk_decc_shr.sh's TLS filter (vms-cb5f's own prior commit) removes members that directly define/reference TLS storage (bid64_add.o etc, via TLSGD) but left non-TLS "glue" objects in place -- _addsub_dd.o/_addsub_sd.o/... call INTO the removed decimal-float subsystem via a plain GOT reference to e.g. __bid64_add, which no longer has a definer once bid64_add.o is dropped. LINK.EXE's "GOT symbol undefined" strict die() was CORRECT given the archive it was handed -- the gap was in the filter only removing the directly-tainted half of a connected dead-code component, not link.c. Fixed by making the filter a reference-graph fixed-point closure: after seeding the direct TLS-tainted set (unchanged), repeatedly pull in any surviving member whose undefined reference is satisfied ONLY by an already- removed member, until nothing new is added. One nm pass over the whole archive up front (not re-invoked per member per iteration) keeps this cheap on libc.a's 1345 members. Verified as a no-op on aarch64's libgcc.a (366/366 members survive, 0 filtered, byte-identical DECC$SHR before/after) and pulls in exactly the expected 148/241-surviving decimal-float + split-stack subsystem on x86_64's. With that fix, the full six-library producer graph + DCL.EXE (22 objects, 6 GOT, 2 TLS, 1053 ABS64-ptr, 145 imports) now link VMS-native and clean on x86_64. Activation through IMGACT.EXE segfaults -- escalated (see PR/item notes), not patched here: link.c/imgact.c are out of this item's file-domain per the item's own repeated header comments, and the crash is upstream of mk_decc_shr.sh (isolated: run_multiobj_exec_x86_64.sh's small-scale crt0/PLT proof -- 3 imports, 1 GOT, 0 TLS -- still passes natively with correct argc-computed exit codes, so this is scale/shape-specific to DCL.EXE's import/reloc volume, not a general crt0/PLT regression). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DCL.EXE segfaulted the moment it was activated on x86_64. The fault PC sat in
the anonymous RWX region IMGACT.EXE maps for DECC$SHR, executing non-instruction
bytes. Single-stepping from musl's printf_core into pop_arg caught the transfer:
movslq (%rcx,%rsi,4),%rdx ; rdx = jumptable[1]
add %rcx,%rdx ; rdx = table_base + delta
jmp *%rdx
with the table entry ZERO, so the jump landed on the table's own address --
inside .rodata, on the "(null)" string constant.
Root cause: parse_obj() collected relocations only for sections it bucketed
B_TEXT or B_DATA. Every relocation whose target section was B_RODATA was
discarded with no diagnostic. gcc emits each `switch` jump table into a
per-function read-only section as `.long arm - table_base`; the arms are in
.text and the table is not, so the assembler cannot fold the difference and
leaves one real R_X86_64_PC32 per arm. All of them were dropped, so every jump
table in the image came out all zero.
Not a scale bug. Nothing about 145 imports, 6 shareables, 6 GOT slots, 2 TLSDESC
entries or the 6-deep --use chain is involved: vms-206/vms-cd1/vms-2e4 passed
through this because their specimens contained no switch large enough for gcc to
build a table and never called a printf-family function with a conversion. It is
a code SHAPE that first appeared when real musl and the real DCL sources entered
the link -- 902 such relocations in libc.a, 554 in DCL's own objects.
aarch64 was never affected: aarch64 gcc resolves its jump tables at assembly
time, so its only read-only-section relocations are .eh_frame PREL32 (never
executed). Confirmed empirically on both arches.
Fix: collect relocations for B_RODATA as well, via a single bucket_is_patchable()
predicate, and emit %LINK-W-RELSKIP for any RELA section whose target is
allocatable but not flat-placed -- a silent drop is exactly how this survived
four proofs. (That diagnostic immediately surfaces one pre-existing gap:
libgcc's cpuinfo.o .init_array, tracked separately, not executed today.)
Regression gate: src/vmslink/test/run_rodata_reloc_x86_64.sh + its specimen link
a jump-table-bearing image against the real whole-archive musl DECC$SHR, activate
it through a real IMGACT.EXE, and diff the transcript against the SAME source
built by the system toolchain. It asserts the specimen still produces .rela.rodata
(so it cannot rot into a vacuous pass) and fails LOUD -- verified: it dies with
"Illegal instruction" on the pre-fix linker and passes on the fixed one. Wired
into CI as job rodata-reloc-x86_64.
Verified green: dcl-native ARCH=x86_64 (SHOW TIME + A=5, exit 0) and ARCH=aarch64,
run_multiobj_exec_x86_64.sh, src/imgact/test/run_test_x86_64.sh,
src/vmslink/test/run_test_x86_64.sh, run_test_x86_64_tls.sh, run_decc_shr.sh,
run_test.sh + run_multiobj_exec.sh under arm64.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-a66: LINK.EXE was dropping every read-only-section relocation (includes vms-cb5f's commits: DCL.EXE x86_64 ARCH param + DECC$SHR transitive TLS filter -- confirmed via git merge-base --is-ancestor that cb5f is fully contained in a66's history, so cb5f is not merged separately) Clean automatic merge, no conflicts.
…ch64) Wires the mk_*_shr.sh / mk_dcl.sh / mk_loginout.sh recipes (the vms-b65/c39 lib-migration chain) into `cmake --build` via a new OVMX_LINK_NATIVE option that auto-detects on an aarch64 musl toolchain. `cmake -B build && cmake --build build` now produces LIBVMSSYS$SHR.EXE, DECC$SHR.EXE, LIBVMSPROCESS$SHR.EXE, LIBVMSLNM$SHR.EXE, LIBVMSFS$SHR.EXE, LIBVMS$SHR.EXE, LIBVMSRMS$SHR.EXE, DCL.EXE and LOGINOUT.EXE via LINK.EXE -- verified EM_AARCH64 with zero DT_NEEDED entries on all 9 artifacts. Additive to (not a replacement of) each library's existing add_library() target, which host ctest unit tests still link directly; on non-aarch64-musl toolchains OVMX_LINK_NATIVE stays off with no behavior change. Extracted the previously-inlined LIBVMSSYS$SHR recipe (duplicated in lib_build_graph.sh) into mk_vmssys_shr.sh, the one place the mk_*_shr.sh convention keeps it -- lib_build_graph.sh (run_dcl_native.sh/run_login_native.sh) now calls it too, closing the exact drift risk mk_libvms_shr.sh's LIST comment warns about. mk_vmssys_shr.sh exports vms_kif_setident unconditionally (append-only vector), so run_login_native.sh's SYS_VEC_EXTRA override is no longer needed. Both harnesses re-verified green end-to-end in an aarch64 musl container after the refactor. Added CI job link-native-cmake asserting the readelf ground-source condition directly. x86_64 extension is vms-6da (separate item, unblocked). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # src/imgact/test/lib_build_graph.sh
Extends vms-b6a's OVMX_LINK_NATIVE mechanism (auto-detected from the compiler's -dumpmachine triple) to also recognize an x86_64 musl toolchain, not just aarch64 -- same CMake target (link_native_graph), same build_link_native.sh entrypoint, no forked plumbing. ARCH is threaded through as an env var to every mk_*_shr.sh recipe (CFLAGS picks -mtls-dialect=gnu2 on x86_64 vs -mno-outline-atomics on aarch64, the same convention lib_build_graph.sh's build_producer_graph() established for the raw-harness path in vms-cb5f/vms-a66). mk_vmssys_shr.sh (extracted by vms-b6a, so it hadn't picked up the ARCH/CFLAGS env-override convention yet) and mk_loginout.sh (missed by cb5f/a66) are brought in line with the rest of the mk_*_shr.sh recipes. Fixed a real bug hit while proving this: `N=$(... | grep -c NEEDED)` aborts under `set -e` in the CI job's alpine /bin/sh whenever the DT_NEEDED count is legitimately zero (grep -c exits 1 on no match) -- present in both the pre-existing aarch64 job and the new x86_64 one; both fixed. Ground-truth proof, real alpine:3.20 musl containers (arm64 emulated, amd64 native), `cmake --build . --target link_native_graph`: - x86_64: 9/9 artifacts EM_X86_64, zero DT_NEEDED, via LINK.EXE - aarch64: 9/9 artifacts EM_AARCH64, zero DT_NEEDED (regression, unchanged) Adds CI job link-native-cmake-x86_64 alongside link-native-cmake, mirroring dcl-native-x86_64's amd64-native (no QEMU) approach. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-6da: extend the VMS-native LINK.EXE CMake graph to x86_64 Supersedes work/vms-b6a (discarded per operator ruling): vms-6da's implementer independently hit the same lib_build_graph.sh conflict (ARCH/CFLAGS convention vs the inline LIBVMSSYS$SHR recipe), merged work/vms-b6a in, and reconciled it correctly -- keeping the deduped mk_vmssys_shr.sh consistent with the ARCH/CFLAGS convention already integrated via vms-cb5f/vms-a66. Clean automatic merge here, no conflicts.
…to work/vms-d0f5e
…NK.EXE
Replaces vms-913.6 (cancelled -- DT_HASH/ld-based dynamic ELF was a proven
dead end). distro/Dockerfile.bootable now builds a real alpine:3.20 musl
link-native stage that runs `cmake --build --target link_native_graph`
(OVMX_LINK_NATIVE, vms-b6a/vms-6da) to produce the 7 shareables
(DECC$SHR, LIBVMSSYS$SHR, LIBVMSPROCESS$SHR, LIBVMSLNM$SHR, LIBVMSFS$SHR,
LIBVMS$SHR, LIBVMSRMS$SHR) + DCL.EXE + LOGINOUT.EXE via LINK.EXE, plus
IMGACT.EXE (its own standalone Makefile, x86_64). All 9 artifacts are
ET_DYN with a .vms$sv symbol vector, zero DT_NEEDED/DT_HASH -- ground-
truth readelf assertions are baked into the Docker build itself (both in
the link-native stage and against the actually-shipped DCL.EXE in the fat
initramfs), not just asserted by a separate CI job. STARTUP.EXE and
IMGACT.EXE stay static/freestanding; HELP/AUTHORIZE/MAIL/MONITOR/
INITIALIZE ship static for 0.1 (no mk_*.sh recipe yet, scope decision --
DCL is the flagship dynamic proof, not every utility).
Ground-truth verified locally: full `docker build` succeeds, QEMU x86_64
boot reaches login (tests/uat/vms_session_qemu.sh: SYSTEM and GUEST
sessions authenticate, DCL runs 50+ commands to VMS-correct output,
DIRECTORY SYS$SYSTEM: lists the new DCL.EXE/IMGACT.EXE/LOGINOUT.EXE),
and test_executive_integral.sh's negative controls (NOEXEC/NODEV,
rebuilt from the same fat initramfs) still pass 14/14.
KNOWN REGRESSION, not fixed here (out of this item's file-domain --
distro/Dockerfile.bootable + CMake wiring, not src/vmsdcl or
src/kernel/vmsfs): SPAWN's first invocation in a session now fails
(%DCL-E-CREPRC) where it previously succeeded. Root-caused via an A/B
rebuild swapping only DCL.EXE/LOGINOUT.EXE back to static (54/54 UAT
checks pass) vs the VMS-native pair (52/54, this SPAWN check newly red).
cmd_spawn() (src/vmsdcl/dcl_cmd_process.c) re-execs via
readlink("/proc/self/exe"), which resolves to a path vmsfs reports
"(deleted)" -- confirmed present already at the top of DCL's own main(),
before any DCL code runs, so the deletion happens during kernel PT_INTERP
+ IMGACT activation, not in DCL or LOGINOUT. No unlink()/rename() of
SYSEXE exists in the userspace boot path (checked ovmx_init.c,
vms_login.c, vmsfs_translate.c) -- the leading hypothesis is a vmsfs.ko
dentry-lifecycle interaction exposed by IMGACT's longer activation time,
which needs its own investigation. Login and the item's own done
condition are unaffected; test file left unmodified (weakening an
existing UAT assertion is outside this item's authority).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ROOT CAUSE. vmsfs.ko's ->d_revalidate answered "invalid" for EVERY
positive regular-file dentry, unconditionally. That is not revalidation,
it is permanent invalidation: a d_revalidate() of 0 makes the VFS call
d_invalidate(), which UNHASHES the dentry (fs/namei.c lookup_fast() /
lookup_open()); an unhashed non-root dentry satisfies d_unlinked(); and
d_path() renders any d_unlinked() path with a " (deleted)" suffix
(fs/d_path.c path_with_deleted()). /proc/<pid>/exe and /proc/<pid>/fd/<n>
are d_path() readers.
So the FIRST path walk of a running executable that lives on vmsfs made
that program's own /proc/self/exe read ".../DCL.EXE (deleted)" -- with
the file present and unmodified. mm->exe_file pins the dentry, so it
stayed unhashed for the life of the process. DCL's SPAWN re-execs itself
via readlink("/proc/self/exe") (cmd_spawn(), src/vmsdcl/dcl_cmd_process.c),
so it execl()'d a path with " (deleted)" on the end, got ENOENT, and
answered %DCL-E-CREPRC.
That single defect explains BOTH observed shapes, which differ only in
when the second walk happens:
- static DCL.EXE: nothing re-walks the image during startup, so the
first SPAWN's own execl() was the second walk -- spawn #1 worked,
spawn #2 onward failed. (The "SECOND SPAWN fails" defect recorded in
tests/uat/vms_session_qemu.sh, previously blamed on DCL.)
- VMS-native, IMGACT-activated DCL.EXE (vms-d0f5e): IMGACT.EXE re-opens
the image by AT_EXECFN to read its .vms$sv/.vms$imp sections
(activate_symbol_vector(), src/imgact/imgact.c) BEFORE the image runs,
so the dentry was already unhashed at the first line of main() and the
FIRST SPAWN failed. IMGACT did not cause the bug; it reached it one
walk earlier.
Nothing ever unlinked or renamed anything, which is why the userspace
audit of ovmx_init.c / vms_login.c / vmsfs_translate.c found nothing.
FIX. ->d_revalidate now asks the resolver the question a fresh ->lookup
would ask -- "what does this name resolve to right now?" -- and keeps the
dentry when the answer is unchanged:
- block-device mode: re-resolve the name to a FID (vmsfs_blkdev_resolve(),
factored out of vmsfs_blkdev_lookup() with no behaviour change) and
compare against i_ino, which IS the FID (iget_locked(sb, fid)). Exact
identity: catches a newer version AND a deletion.
- overlay mode: compare the current highest version of the base name
against the version this dentry resolved to (overlay mints a fresh
inode per lookup, so version is the only stable identity).
- create intent (LOOKUP_CREATE/LOOKUP_RENAME_TARGET) still returns 0, so
O_CREAT cannot be satisfied from the cache and VMS still cuts a NEW
VERSION rather than reopening the current one.
This is also strictly cheaper than the old behaviour, which paid for the
same resolution and then threw away the dentry and the inode anyway.
GROUND SOURCE, BOTH DIRECTIONS, ON THE REAL RUNTIME (Rule 6 -- no module
was loaded on the host; everything below ran under QEMU).
New suite tests/qemu/test_kmod_vmsfs_exepath.c, 28 assertions:
pre-fix 3 phases red, incl. "child: /proc/self/exe after re-open =
/mnt/.../CHILD.EXE (deleted)" -- the product symptom reproduced
at the kernel layer -- then the kernel OOPSES in __fput()
(NULL d_inode) when the held fd is closed.
post-fix 28/28 green.
Phase 3 execs this program from a real block-device vmsfs and has the
child do exactly what IMGACT does (re-open its own image by path) and
then what SPAWN does (re-exec via /proc/self/exe). Phase order puts it
first precisely so it is REACHED before the pre-fix oops kills the
process. Phase 2 carries the POSITIVE CONTROL that makes the file
non-vacuous: once PROBE.TXT;2 exists, the fd held on ;1 MUST become
"(deleted)", because the unversioned name no longer names it -- a
d_revalidate that just answers "valid" passes everything else here and
goes red on that one.
tests/uat/vms_session_qemu.sh on the vms-d0f5e VMS-native fat initramfs:
pre-fix 52/54 (both SPAWN assertions red)
post-fix 54/54
A SECOND spawn ('SPAWN SHOW TIME') and two assertions on it are added
here, because the long-standing "second SPAWN" defect is fixed by
the same change -- measured, not assumed. With them: 52/56 pre-fix,
56/56 post-fix. The comment block that recorded that defect as a
DCL bug is corrected in place rather than removed.
No regression: full QEMU kernel harness 31/31 suites (incl. the existing
test_kmod_vmsfs and test_kmod_vmsfs_blkdev version-semantics suites),
test_persistent_boot.sh 14/14, test_executive_integral.sh 14/14, Rule 9
runtime-target gate, divider integrity, harness verdict, kif caller
census, identity census, facility manifest selftest+coverage.
tests/qemu/facility_defects.sh: the new suite joins the two existing
vmsfs suites in SCOPE_OUT_SUITES for the reason already stated there --
it never opens /dev/vms, so no executive mutation can turn it red.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-00e: vmsfs must not unhash the dentry of a running image (SPAWN fix) Contains work/vms-d0f5e (fat initramfs ships DCL.EXE/LOGINOUT.EXE VMS-native via LINK.EXE, which itself includes vms-6da/vms-b6a) -- confirmed via git merge-base --is-ancestor that vms-d0f5e is fully contained in vms-00e's history, so vms-d0f5e is not merged separately. Clean automatic merge, no conflicts.
…ge DB Checked current state first (per item instructions): vms-p78.3's install unification and vms-d0f5e's real-file fat initramfs already held, and test_persistent_boot.sh was passing -- but done condition #4 (SYSTARTUP_VMS.COM carrying INSTALL ADD for each shareable) was not met, and fixing it surfaced two real, ground-verified gaps beneath it: 1. DCL's INSTALL builtin (cmd_install) never dispatched to SYS$SYSTEM:INSTALL.EXE (src/install/install.c, vms-913.5's KFE-database utility). It reimplemented its own flat-text SYS$MANAGER:INSTALL_LIST.DAT that nothing ever read -- IMGACT.EXE's known-image search (src/imgact/known_images.c) mmaps the binary VMS$KNOWN_IMAGES.DAT only INSTALL.EXE writes. Fixed by making cmd_install a thin wrapper that re-execs INSTALL.EXE via dcl_exec_utility(), the same pattern already used for ANALYZE/MAIL/SYSGEN/SYSMAN in this file -- matching install.c's own header comment ("deliberately NOT wired as a DCL builtin"). 2. Once wired, real QEMU boot (docker build + test_persistent_boot.sh) caught that INSTALL.EXE was never copied into the fat initramfs, so every SYSTARTUP_VMS.COM INSTALL ADD failed with %INSTALL-F-NOIMG on live boot -- and that failure silently aborted the rest of SYSTARTUP_VMS.COM despite SET NOON, dropping the "site startup ran" banner test_persistent_boot.sh checks for (14/14 -> 12/14). Fixed by adding INSTALL.EXE to Dockerfile.bootable's fat-initramfs SYSEXE copy list, alongside the other build-static utilities. SYSTARTUP_VMS.COM now INSTALL ADDs exactly the 7 shareables the fat initramfs actually ships (DECC$SHR, LIBVMSSYS$SHR, LIBVMS$SHR, LIBVMSPROCESS$SHR, LIBVMSLNM$SHR, LIBVMSFS$SHR, LIBVMSRMS$SHR -- Dockerfile.bootable's own "9 VMS-native LINK.EXE artifacts" gate). LIBVMSQUEUE$SHR is deliberately excluded: it builds via the ordinary CMake add_library() graph, not the VMS-native LINK.EXE graph, and is not shipped in the fat initramfs -- INSTALLing it would FILNOTFND on every boot. Verified live: docker build -f distro/Dockerfile.bootable + test_persistent_boot.sh, 14/14 checks pass across both boots, with all 7 INSTALL-I-ADDED lines visible each time (idempotent re-add on reboot, matching real VMS SYSTARTUP_VMS.COM practice). New test: tests/dcl/test_install_command.sh proves through real vmsdcl (not around it) that INSTALL ADD/LIST/REMOVE write and clear the actual KFE binary database (magic-byte check), not just matching text output -- the old stub could print the same %INSTALL-I-ADDED text without touching the file IMGACT.EXE reads. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-913.7: wire SYSTARTUP_VMS.COM's INSTALL ADD to the real Known Image DB SCHEMA CHANGE: DCL's INSTALL builtin now writes a binary KFE database (VMS$KNOWN_IMAGES.DAT) instead of the old dead text format. Nothing else merged so far touches INSTALL.EXE's database format. Clean automatic merge, no conflicts.
…CI comments Live-verified on this worktree's native x86_64 host: docker build distro/Dockerfile.bootable + tests/uat/vms_session_qemu.sh boots real QEMU (qemu-system-x86_64, no emulation) through IMGACT.EXE's x86_64 relocation path (RELATIVE/GLOB_DAT/JUMP_SLOT/TLSDESC) to a DCL login prompt, drives a full scripted session, 56/56 checks passed. This is already the uat-session CI job (Job 7), unconditional on every push since GH runners are x86_64 natively. Job 8/9 comments still said "aarch64-only until bead vms-913.11" as if x86_64 support were still pending; it has its own native-runner counterpart (Job 9b, imgact-x86_64) and the uat-session boot-to-login proof. Corrected the comments to point at the now-complete state instead of a stale forward reference. Done condition satisfied by existing work (vms-8f5, vms-cd1, vms-2e4, vms-a66, vms-00e, vms-d0f5e chain); no functional code change needed.
…, not just DCL.EXE vms-c39's done condition (STARTUP execs VMS-native LOGINOUT.EXE, which authenticates against SYSUAF and execs VMS-native DCL.EXE, zero ld/ld.so) was structurally unverifiable in the Docker CI container -- no /dev/vms there. This item's job is proving the SUCCESSFUL leg under a real kernel. Verified live, not assumed: that proof already exists and is repeatable. - src/ovmx_init/ovmx_init.c execl()s SYS$SYSTEM:LOGINOUT.EXE. - tools/vms_login.c (LOGINOUT) authenticates against SYSUAF (sysuaf_authenticate), then execl()s DCL.EXE --login. - src/vmslink/link.c sets PT_INTERP=IMGACT.EXE on every LINK.EXE executable image -- ground truth that no ld.so is anywhere in this chain, not an inference. - distro/Dockerfile.bootable's fat initramfs ships exactly one DCL.EXE and one LOGINOUT.EXE, both copied only from the VMS-native /link-native build (no static fallback exists for either anymore). - tests/uat/vms_session_qemu.sh runs this exact chain under real QEMU in CI (job uat-session, .github/workflows/ci.yml), and vms-00e's own commit records a fresh 56/56 pass on this initramfs today. The one real gap: the Dockerfile's own ground-source readelf gate (does the SHIPPED image -- the actual bytes cp'd into the initramfs, not a copy two directories away -- carry zero DT_NEEDED/DT_HASH) only checked DCL.EXE. LOGINOUT.EXE is the FIRST VMS-native image in the login chain and had no equivalent check on its shipped bytes; a regression that silently reintroduced an ld-linked LOGINOUT.EXE ahead of DCL.EXE would not have been caught by this gate (the generic 9-artifact loop in the earlier link-native stage checks a build-output copy, not what actually ships). Fixed by widening the existing gate to loop over both images. Ground-truth verified locally: `docker build -f distro/Dockerfile.bootable --target builder` -- real build, not mocked -- prints "OK: shipped DCL.EXE is VMS-native (EM_X86_64, zero DT_NEEDED/DT_HASH)" and "OK: shipped LOGINOUT.EXE is VMS-native (EM_X86_64, zero DT_NEEDED/DT_HASH)" for the actual artifacts this build produced. The new for-loop's shell logic was also unit-checked standalone: fails correctly against a real dynamically-linked ELF (DT_NEEDED present) and passes correctly against a statically-linked one, before spending a build cycle on it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-913.11: verify x86_64 boot-to-login is already proven, fix stale CI comments CI comment fix only, no functional change. Clean automatic merge.
vms-fbc: shipped-image ground-source gate now covers LOGINOUT.EXE too, not just DCL.EXE Widened a Dockerfile readelf gate; no runtime behavior change. Clean automatic merge (no conflict with vms-913.11's disjoint ci.yml change or vms-913.7's single-line Dockerfile.bootable addition).
The slim initramfs (STARTUP.EXE-only) and boot.sh --slim wiring already
existed (Dockerfile.bootable, boot.sh) but nothing exercised the boot
path: test_persistent_boot.sh's two boots both used the FAT initramfs.
Extends that harness with:
- a static check that the slim initramfs cpio listing carries no
DCL.EXE/LOGINOUT.EXE/IMGACT.EXE/SYSLIB (bootstrap-only, as designed)
- Boot 3: boots the SAME installed disk with the SLIM initramfs, logs
in as SYSTEM/MANAGER over the QEMU serial console (real SHA256-backed
SYSUAF credentials, same as tests/uat/vms_session_qemu.sh), and runs
SHOW TIME to a real DCL prompt
Since the slim initramfs structurally ships none of LOGINOUT.EXE,
IMGACT.EXE, DCL.EXE, or the SYSLIB shareables, a real login reaching a
working DCL prompt is functional proof they resolve from the mounted
system disk's SYS$SYSTEM:/SYS$LIBRARY:, not the initramfs. Measured
against a real QEMU boot: 25/25 checks pass.
Also adds `cpio` to the runner image's apt install list -- needed by the
new static check, absent from the base ubuntu:24.04 image (verified).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-913.10: prove slim-boot login+DCL activates from disk, not initramfs Adds a new Boot 3 (slim-initramfs login proof) to tests/qemu/test_persistent_boot.sh, plus the cpio apt-get dependency in the Dockerfile runner stage. Clean automatic merge despite three branches (vms-913.7, vms-fbc, vms-913.10) all touching distro/Dockerfile.bootable -- disjoint hunks, no conflict markers.
Light release-eng doc scoped to 0.1 (download/build the fat-initramfs image, first-boot install, reboot into the slim initramfs, log in to DCL). Cluster admin / license audit / trademark review stay under vms-d5b R6 for 1.0. Ground-sourced: a real `docker build -f distro/Dockerfile.bootable -t ovmx-boot .` was run on this checkout and reached the builder stage before this shared host's disk filled and the build was aborted for safety. Every command and every piece of documented console output (the %STARTUP-I-* banners, %OVMX-I-EXEC, Username:/Password: prompts, Welcome to OVMX, SHOW TIME) is instead quoted verbatim from GitHub Actions run 31128513528 (commit 8560fa7), where the "Persistent Boot Smoke Test" and "VMS User Acceptance Test" CI jobs build and boot this same image and passed 14/14 + all UAT assertions -- the proven, passing path tests/qemu/test_persistent_boot.sh and tests/uat/vms_session_qemu.sh already exercise on every push. Login credential (SYSTEM/MANAGER) confirmed against the real hash in distro/rootfs/.../SYSUAF.DAT, not invented. No git tag created -- that step is reserved for the operator's final sign-off. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-0c9: docs/install-0.1.md -- 0.1 install/boot/login walkthrough Docs-only, no code. Clean automatic merge. Last item in the vms-b93 closure.
…ng note Run 31128513528 predates the slim-boot work (checked-out test_persistent_boot.sh has zero slim references) and its overall status was FAILURE (Build & Test + attribution negative-control jobs red); only citing the two individually-passing jobs overclaimed coverage. Rescope the note to state what was actually verified: this swarm's own local docker build + real QEMU boots against the merged tree (25/25 checks) for Section 3, and the UAT script's own run for the DCL session content. No CI run number is cited as covering slim-boot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s-913.7 Re-ran tools/survey_x86_64_relocs.sh to refresh the empirical R_X86_64_PC32/ PLT32 counts in src/libvms/descrip.c, no methodology change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vms-9bc: regenerate docs/design-link-x86_64-relocs.md, stale after vms-913.7 Post-merge veracity audit fix: ctest x86_64_reloc_survey_fresh was red because the doc was stale. Clean automatic merge.
vms-ade8: fix overclaimed CI citation in install-0.1.md ground-sourcing note Post-merge veracity audit fix. Clean automatic merge.
Comment on lines
+1147
to
+1195
| name: VMS-native LINK.EXE Graph via CMake (OVMX_LINK_NATIVE) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Register QEMU binfmt handlers (arm64 emulation) | ||
| uses: docker/setup-qemu-action@v3 | ||
| with: | ||
| platforms: arm64 | ||
|
|
||
| - name: cmake -B build && cmake --build build (arm64 musl) -- assert the shipped graph is LINK.EXE-native | ||
| run: | | ||
| docker run --rm --platform linux/arm64 \ | ||
| -v "${{ github.workspace }}":/src -w /src \ | ||
| docker.io/library/alpine:3.20 sh -c ' | ||
| set -e | ||
| apk add --no-cache cmake gcc g++ musl-dev binutils make linux-headers >/dev/null | ||
| cmake -B /tmp/build -DBUILD_TESTS=OFF -DBUILD_TOOLS=ON -DCMAKE_BUILD_TYPE=Release | ||
| cmake --build /tmp/build --target link_native_graph -j"$(nproc)" | ||
| echo "== readelf: EM_AARCH64, zero DT_NEEDED, across all 9 artifacts ==" | ||
| SYSLIB=/tmp/build/link-native/SYSLIB | ||
| SYSEXE=/tmp/build/link-native/SYSEXE | ||
| for f in "$SYSLIB"/*.EXE "$SYSEXE"/*.EXE; do | ||
| echo "-- $f --" | ||
| readelf -h "$f" | grep -q "Machine:.*AArch64" || { echo "FAIL: $f not EM_AARCH64"; exit 1; } | ||
| N=$(readelf -d "$f" 2>/dev/null | grep -c NEEDED || true) | ||
| [ "$N" -eq 0 ] || { echo "FAIL: $f has $N DT_NEEDED entries (ld-linked, not VMS-native)"; exit 1; } | ||
| done | ||
| COUNT=$(ls "$SYSLIB"/*.EXE "$SYSEXE"/*.EXE | wc -l) | ||
| [ "$COUNT" -eq 9 ] || { echo "FAIL: expected 9 artifacts (5 libs + DECC\$SHR + DCL.EXE + LOGINOUT.EXE), got $COUNT"; exit 1; } | ||
| echo "OK: $COUNT VMS-native LINK.EXE artifacts, all EM_AARCH64, zero DT_NEEDED" | ||
| ' | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # link-native-cmake-x86_64: the x86_64 analog of link-native-cmake above | ||
| # (bead vms-6da, epic vms-b93 "0.1 pre-release" -- the direct blocker of | ||
| # vms-d0f5e's fat initramfs). Same CMakeLists.txt/build_link_native.sh | ||
| # mechanism, ARCH auto-detected from the compiler triple (aarch64-musl vs | ||
| # x86_64-musl) -- NOT a duplicated custom target. x86_64 is the project's | ||
| # primary architecture (CLAUDE.md Rule 5) and this is what a shipped fat | ||
| # initramfs actually needs. Asserts, via readelf, that all 9 artifacts are | ||
| # EM_X86_64 with zero DT_NEEDED entries. Unlike the arm64 job, GitHub's | ||
| # runner IS x86_64 (see dcl-native-x86_64 above), so this needs no QEMU | ||
| # binfmt registration -- the alpine:3.20/amd64 container runs natively. | ||
| # ----------------------------------------------------------------------- | ||
| link-native-cmake-x86_64: |
Comment on lines
+1574
to
+1606
| sh src/imgact/test/run_multiobj_exec_x86_64.sh | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # Job: Read-only-section relocations — x86_64 (vms-a66) | ||
| # | ||
| # LINK.EXE used to collect relocations ONLY against sections it bucketed | ||
| # B_TEXT or B_DATA, silently discarding every relocation whose target was a | ||
| # read-only allocatable section. gcc emits each switch jump table into | ||
| # `.rodata` as `.long arm - table_base` (one R_X86_64_PC32 per arm, because | ||
| # the arms are in .text and the table is not), so those tables came out ALL | ||
| # ZERO and the dispatch `jmp *rdx` executed the table's own bytes. That is | ||
| # what made DCL.EXE segfault on x86_64: musl's pop_arg jumped into the | ||
| # "(null)" string in DECC$SHR's .rodata on the first %-conversion. | ||
| # | ||
| # The three jobs that "covered" this path before (multiobj-exec-x86_64, | ||
| # vmslink-x86_64, exec-tls) all passed through the bug because their | ||
| # specimens contained no switch big enough for a jump table and never | ||
| # executed one. This job reproduces the SHAPE directly — it is not a | ||
| # scale/import-count gate — and diffs the activated image's output against | ||
| # the same source built by the system toolchain. | ||
| # ----------------------------------------------------------------------- | ||
| rodata-reloc-x86_64: | ||
| name: Read-only-section relocations (switch jump tables) — x86_64 (vms-a66) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Link + activate a jump-table-bearing image against real musl DECC$SHR (x86_64) | ||
| run: | | ||
| docker run --rm --platform linux/amd64 \ |
| /* Two different kinds of second walk: stat(), then open(). */ | ||
| if (stat(path, &st) != 0) | ||
| break; | ||
| fd2 = open(path, O_RDONLY); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Batched integration PR for the vms-b93 epic (OVMX 0.1 pre-release: Rail B
standalone, VMS-native dynamic linking). ~22 items folded into
work/vms-b93-integration via local merges, each independently tested, plus a
post-merge veracity audit (5 independent adversarial passes against the
actual merged code -- found and fixed 2 real gaps).
Highlights: LINK.EXE gains a full x86_64 relocation path (simple relocs,
GOT-relative DATA imports, gnu2 TLSDESC, GOTPCRELX, crt0 + cross-image CALL
PLT stubs, read-only-section/jump-table relocations), wired into CMake for
both aarch64 and x86_64 (OVMX_LINK_NATIVE), DCL.EXE and LOGINOUT.EXE now
link and activate VMS-native on x86_64, INSTALL's Known Image DB is real
(binary KFE format), a SPAWN vmsfs dentry fix, a fat-initramfs install/boot
chain with a new slim-boot (Boot 3) proof, and doc updates.
Full history, item-by-item detail, and test evidence: rd item
vms-b93.Test plan
Every item tested individually during integration (ctest suites,
src/vmslink/test/* and src/imgact/test/* regressions on both aarch64 and
x86_64, real docker build + QEMU boot for the boot/install chain) --
detail in
vms-b93's history. Final gate before this PR: full ctest109/109, QEMU kernel harness 31/31, bootable docker build clean,
persistent-boot 25/25, UAT session 56/56, executive-integral 14/14.