Skip to content

fix(cuda): guard darwin dlopen probe against SIGSEGV (zerfoo T137.1)#172

Merged
dndungu merged 1 commit into
mainfrom
wave-1-task-T137.1
Jul 3, 2026
Merged

fix(cuda): guard darwin dlopen probe against SIGSEGV (zerfoo T137.1)#172
dndungu merged 1 commit into
mainfrom
wave-1-task-T137.1

Conversation

@dndungu

@dndungu dndungu commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

On darwin dev hosts (Go 1.26.2, ztensor v1.19.0), any test binary that
transitively imports the compute/device path SIGSEGVs at process startup inside
device.init — before any test runs, so even GPU-gated tests that would Skip
cannot run. zerfoo/tests/training is fully broken on macs. Fixes #171
(zerfoo T137.1, verifies UC-H2-006: darwin dev hosts can run tests — a Phase 2
external-contributor prerequisite).

Crash path: device.initcuda.GetDeviceCountcuda.OpendlopenImpl
syscall.syscall6 → SIGSEGV.

Root cause

The darwin dlopen/dlsym shim obtained each libc trampoline's address by taking
the assembly trampoline as a func() value and double-dereferencing it
(funcPC), then passed that to syscall.syscall6. On darwin/amd64 a func()
value of an ABI0 assembly function points at the compiler-generated
ABIInternal wrapper, not the raw ABI0 JMP dlopen trampoline. Measured on
the affected host: funcPC(libc_dlopen_trampoline) = 0x…de0 vs the true
trampoline 0x…da0 — 64 bytes off. syscall.syscall6 then CALLs that wrong PC
as if it were a C trampoline and control lands on a non-executable address →
SIGSEGV. On Linux the same path is clean because runtime.dlopen is used
instead. (The linux/arm64 file already abandoned this trampoline+funcPC
mechanism for the same reason; the darwin file still used it.)

Fix

Adopt the golang.org/x/sys/unix darwin idiom: make each trampoline a
file-local (<>) symbol and export its true ABI0 address to Go via an assembly
DATA directive (libc_dlopen_trampoline_addr etc.), then hand that address to
syscall.syscall6. Contract-level, not consumer-level: device probing on any
darwin host without CUDA now returns a clean "cuda not available" error instead
of crashing. No behavior change on Linux (only //go:build darwin files
touched); backward compatible (no exported API change). Zero CGo preserved.

Tests

New //go:build darwin regression tests (purego_darwin_test.go):

Verified on darwin/amd64 (this host): go test -race ./internal/cuda/ ./device/
passes (previously SIGSEGV at init). go build ./... green on
darwin/amd64, darwin/arm64, linux/amd64. CI-equivalent go vet clean.

Note (out of scope, tracked separately)

With the probe fixed, internal/metal tests now proceed past dlsym and reach a
genuine Metal dispatch that faults on darwin/amd64 — a pre-existing latent
Metal-binding defect previously masked by this dlopen crash, not the probe path.
It is darwin-only (excluded from linux CI) and not an import-time crash. Filed
as a follow-up issue rather than expanding this PR's scope.

The darwin dlopen/dlsym shim handed syscall.syscall6 a func-value PC
obtained via funcPC(trampoline) (a double-dereferenced func() value).
On darwin/amd64 that resolves to the compiler-generated ABIInternal
wrapper, not the raw ABI0 JMP trampoline, so syscall.syscall6 transferred
control to a bad PC and the process SIGSEGV'd inside dlopen during
package init -- taking down every test binary that imports the device
path (device.init -> GetDeviceCount -> Open -> dlopenImpl).

Adopt the golang.org/x/sys/unix darwin idiom: make each trampoline a
file-local (<>) symbol and export its true ABI0 address to Go through an
assembly DATA directive, then pass that address to syscall.syscall6.
The probe now returns a clean "cuda not available" on macOS instead of
crashing. Applies to both amd64 and arm64 dev hosts; no special-casing.

Add darwin regression tests exercising the probe path: trampoline
addresses resolve non-zero, GetDeviceCount/Available return cleanly, and
dlopen/dlsym/ccall round-trip against libSystem.

fixes #171
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.

device.init purego dlopen SIGSEGV on darwin crashes any importing test binary (Go 1.26.2, ztensor v1.19.0)

1 participant