Skip to content

vms-83e8: classic GD/LD TLS relaxation — drop dead __tls_get_addr GOT slot + standing guard - #683

Open
baron-3dl wants to merge 2 commits into
work/vms-ad70-decc-deltafrom
work/vms-83e8-ldtls
Open

vms-83e8: classic GD/LD TLS relaxation — drop dead __tls_get_addr GOT slot + standing guard#683
baron-3dl wants to merge 2 commits into
work/vms-ad70-decc-deltafrom
work/vms-83e8-ldtls

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

What / why

Investigating the reported residual __tls_get_addr SIGSEGV in C++ OVMX images (vms-83e8, epic vms-da0 F2b).

Ground-truth finding: at base work/vms-ad70-decc-delta @ 39dadb0, the classic GD/LD → Local-Exec relaxation (vms-76a: 16cf1f5 + 1f733a7) is complete. Linking the real cpptest.o against the whole upstream libstdc++/libsupc++/libgcc archives (525 objects) yields 1772 mov %fs:0 LE landings and ZERO surviving classic TLS sequences — no data16 GD lea/call, no direct GD call, no symbolic/deferred __tls_get_addr. The un-relaxed-lea-crashes-in-__tls_get_addr bug does not reproduce here; it was fixed by the two vms-76a commits already in the base. Both hypothesized residual forms (GD data16-indirect from libgcc bid*.o, LD-indirect from libsupc++ eh_globals.o) are handled.

The one real gap found + fixed

The classic GD/LD __tls_get_addr call, under -fno-plt (how Alpine's libstdc++/libgcc are built), reaches its target via an R_X86_64_GOTPCRELX cell. patch_tls_le relaxes the call away, so that cell is dead — but the GOT-collection pass still collected a slot for it, and GOT-fill then died GOT symbol undefined (__tls_get_addr is defined nowhere in a static image). So a clean classic-GD/LD link needed --allow-undefined purely to defer one dead slot. The import loop (vms-e65) and datum loop (vms-76a) already guard __tls_get_addr; the GOT-collection pass was the one place missing it. Added the matching guard (one continue).

Effect on the real cpptest link: GOT 2047→2046, deferred externals 184→183, otherwise byte-identical.

New standing guard

src/vmslink/test/run_test_x86_64_classic_tls.sh — the classic-dialect sibling of run_test_x86_64_tls.sh (which only covers gnu2 TLSDESC and never emits a TLSGD/TLSLD/__tls_get_addr, so the classic path had no end-to-end coverage). Compiles a freestanding program with a cross-TU __thread (GD, data16 lea + -fno-plt indirect call) and a function-local static _Thread_local pair (LD, TLSLD + DTPOFF32) — the exact shapes of bid*.o / eh_globals.o — links with no --allow-undefined, asserts at byte level that no classic GD/LD sequence survives, then activates through the real x86_64 IMGACT.EXE and proves both variables read/write correctly (exit 110). Wired into the vmslink-x86_64 CI job.

Verified

  • New classic-TLS test: PASS (host x86_64, real activation).
  • gnu2 TLSDESC test (run_test_x86_64_tls.sh): PASS (no regression).
  • Base x86_64 additive-gate test (run_test_x86_64.sh): PASS.
  • AS.EXE no-regression (run_as_native.sh): running.

x86_64-only instruction rewriting; offset math unchanged (Alpha/VAX untouched, per the conductor's gate).

Routes to the conductor's batched post-0.5 gate.

🤖 Generated with Claude Code

baron-3dl and others added 2 commits August 21, 2026 02:58
…E relaxation

The classic general-/local-dynamic TLS access model calls __tls_get_addr; when
the object was built -fno-plt (Alpine's libstdc++/libgcc, whole-archived into
every C++ OVMX image) that call reaches its target through an R_X86_64_GOTPCRELX
cell against __tls_get_addr. LINK relaxes every classic GD/LD access to
Local-Exec (patch_tls_le, vms-76a), overwriting the call, so no patched
instruction ever reads that cell -- it is dead.

The GOT-collection pass still collected a slot for it, and the GOT-fill pass
then tried to resolve __tls_get_addr (defined nowhere in a static image) and
died "GOT symbol undefined", so an otherwise clean classic-GD/LD link needed
--allow-undefined purely to defer one dead slot. The import loop (vms-e65) and
the datum loop (vms-76a) already skip __tls_get_addr for the same reason; the
GOT-collection pass was the one place missing the guard. Add it.

Effect on the real cpptest link (whole libstdc++/libgcc): GOT 2047->2046,
deferred externals 184->183, output byte-identical otherwise; 1772 mov %fs LE
landings, zero surviving __tls_get_addr / data16-GD sequences (verified).

New standing guard: src/vmslink/test/run_test_x86_64_classic_tls.sh -- the
classic-dialect sibling of run_test_x86_64_tls.sh (which only covers gnu2
TLSDESC and never emits a TLSGD/TLSLD/__tls_get_addr). It compiles a
freestanding program with a cross-TU __thread (GD, data16 lea + -fno-plt
indirect call) and a function-local static _Thread_local pair (LD, TLSLD +
DTPOFF32) -- the exact shapes of libgcc's bid*.o and libsupc++'s eh_globals.o --
links it with NO --allow-undefined, asserts at byte level that no classic GD/LD
sequence survives, then ACTIVATES it through the real x86_64 IMGACT.EXE and
proves both variables read/write correctly through the relaxed LE sequences
(exit 110).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-x86_64 CI job

Runs src/vmslink/test/run_test_x86_64_classic_tls.sh alongside the gnu2
TLSDESC step, reusing the same /vms PT_INTERP setup. Native x86_64, no
emulation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant