Emit GNU symbol versioning in the synthetic vDSO

Closed
CH
5mo ago
+219-24

Problem

Static Go aarch64 binaries (GOOS=linux GOARCH=arm64 CGO_ENABLED=0) SIGSEGV under elfuse during runtime startup, before the first syscall: a data abort at FAR=0x2, PC in runtime.vdsoFindVersion.

The Go runtime parses the kernel vDSO (vdsoInitFromSysinfoEhdr) and treats any image with a hash table, symtab and strtab as valid, then calls vdsoFindVersion guarded only by that flag. vdsoFindVersion dereferences info.verdef unconditionally. The synthetic vDSO advertised DT_HASH/DT_SYMTAB/DT_STRTAB but no DT_VERDEF, so verdef was NULL and Go read vd_flags at struct offset 2 -> address 0x2. Real Linux vDSOs always ship .gnu.version_d (LINUX_2.6.39); glibc and musl tolerate the omission, which is why only Go was affected.

Fix

Emit .gnu.version (DT_VERSYM) and .gnu.version_d (DT_VERDEF): a base version node plus LINUX_2.6.39, with each exported symbol bound to it. The page layout shifts to make room (dynsym and hash move; versym and verdef are inserted before the dynamic table). This matches the real arm64 vDSO and keeps the vDSO fast path live for glibc/musl, which look up the same version.

Test

tests/test-go.sh (wired into make check) builds a tiny linux/arm64 Go binary and runs it under elfuse, asserting it reaches main and that time.Now() (the resolved vDSO clock_gettime) returns a sane value. It skips (exit 77) when the Go toolchain is unavailable, mirroring the other optional checks.

Verification

  • Before: the Go binary exits 139 (SIGSEGV, FAR=0x2). After: it prints and exits 0; make test-go passes, and skips cleanly with no go on PATH.
  • Ran tests/driver.sh against a prebuilt guest-binary corpus before and after the change: identical pass/fail set, no regression. (The local toolchain could not cross-build the in-tree guest tests, so the full make check plus test-go will exercise on the CI Apple Silicon runner.)
  • clang-format-22, cppcheck (.ci/check-cppcheck.sh), shellcheck, gofmt: clean.

Closes #49


Summary by cubic

Adds GNU symbol versioning to the synthetic vDSO to fix startup SIGSEGVs in static linux/arm64 Go binaries. The vDSO now matches Linux (with LINUX_2.6.39) and keeps the glibc/musl fast path.

  • Bug Fixes
    • Emit .gnu.version (DT_VERSYM) and .gnu.version_d (DT_VERDEF) with a base node and LINUX_2.6.39; bind all exported symbols; add DT_VERDEFNUM.
    • Adjust vDSO layout and section counts to include the new tables; behavior matches the real arm64 vDSO for glibc/musl.
    • Add regression test tests/test-go.sh wired to make check (target test-go): builds a tiny static linux/arm64 Go binary, runs it under elfuse, and skips when the Go toolchain is absent.