fix(cuda): guard darwin dlopen probe against SIGSEGV (zerfoo T137.1)#172
Merged
Conversation
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
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.
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 wouldSkipcannot run.
zerfoo/tests/trainingis 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.init→cuda.GetDeviceCount→cuda.Open→dlopenImpl→
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 tosyscall.syscall6. On darwin/amd64 afunc()value of an ABI0 assembly function points at the compiler-generated
ABIInternal wrapper, not the raw ABI0
JMP dlopentrampoline. Measured onthe affected host:
funcPC(libc_dlopen_trampoline) = 0x…de0vs the truetrampoline
0x…da0— 64 bytes off.syscall.syscall6then CALLs that wrong PCas if it were a C trampoline and control lands on a non-executable address →
SIGSEGV. On Linux the same path is clean because
runtime.dlopenis usedinstead. (The
linux/arm64file already abandoned this trampoline+funcPCmechanism for the same reason; the darwin file still used it.)
Fix
Adopt the
golang.org/x/sys/unixdarwin idiom: make each trampoline afile-local (
<>) symbol and export its true ABI0 address to Go via an assemblyDATAdirective (libc_dlopen_trampoline_addretc.), then hand that address tosyscall.syscall6. Contract-level, not consumer-level: device probing on anydarwin host without CUDA now returns a clean "cuda not available" error instead
of crashing. No behavior change on Linux (only
//go:build darwinfilestouched); backward compatible (no exported API change). Zero CGo preserved.
Tests
New
//go:build darwinregression tests (purego_darwin_test.go):TestDarwinTrampolineAddrsResolved— the four_addrvars resolve non-zero(guards against the exact func-value defect).
TestDarwinProbeDoesNotCrash— the device.init purego dlopen SIGSEGV on darwin crashes any importing test binary (Go 1.26.2, ztensor v1.19.0) #171 init path (GetDeviceCount/Available) returns cleanly on a non-CUDA mac.TestDarwinDlopenLibSystem— dlopen/dlsym/ccall round-trip against libSystem.Verified on darwin/amd64 (this host):
go test -race ./internal/cuda/ ./device/passes (previously SIGSEGV at init).
go build ./...green ondarwin/amd64, darwin/arm64, linux/amd64. CI-equivalent
go vetclean.Note (out of scope, tracked separately)
With the probe fixed,
internal/metaltests now proceed past dlsym and reach agenuine 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.