Skip to content

Go 1.27 linker rejects assembly references to runtime.load_g and runtime.cgocallback #455

@gdams

Description

@gdams

Summary

Go 1.27 (tip) introduces stricter checklinkname enforcement for assembly symbols via golang/go@aee6009. This causes linker errors when building with CGO_ENABLED=0 on all architectures, because the fakecgo assembly files reference runtime.load_g and runtime.cgocallback, which are now //go:linknamestd (std-only).

Error

# example.com/linkname-repro
link: main: invalid reference to runtime.load_g

After fixing load_g (which can be removed from crosscall2 since cgocallback calls it internally), the next error is:

link: main: invalid reference to runtime.cgocallback

Affected symbols

  • runtime.load_g — referenced from crosscall2 in asm_{arm64,arm,riscv64,loong64,ppc64le,s390x}.s, and from setg_trampoline in trampolines_{ppc64le,s390x}.s
  • runtime.cgocallback — referenced from crosscall2 in all asm_*.s files (all 8 architectures)

Both are declared with //go:linknamestd in the Go runtime, meaning only standard library packages can reference them. The runtime/cgo package (which has identical crosscall2 code) works because it is in std.

Minimal reproducer

go.mod:

module example.com/linkname-repro
go 1.24

main.go:

package main

func main() {
	crosscall2(0, 0, 0, 0)
}

func crosscall2(fn, a, n, ctxt uintptr)

asm_arm64.s:

#include "textflag.h"

TEXT ·crosscall2(SB), NOSPLIT|NOFRAME, $0
	BL runtime·load_g(SB)
	BL runtime·cgocallback(SB)
	RET

Reproduce:

# Passes on Go 1.26:
docker run --rm --platform linux/arm64 -v "$PWD":/repro -w /repro golang:1.26 go build .

# Fails on Go tip (go1.27-devel):
docker run --rm --platform linux/arm64 -v "$PWD":/repro -w /repro golang:tip go build .
# link: main: invalid reference to runtime.load_g

# Workaround:
docker run --rm --platform linux/arm64 -v "$PWD":/repro -w /repro golang:tip \
  go build -ldflags='-checklinkname=0' .

Analysis

The load_g issue can be worked around in the assembly:

  • In crosscall2: remove the load_g call — cgocallback calls it internally
  • In setg_trampoline (ppc64le/s390x only): save the g pointer before calling setg_gcc and assign directly to the g register afterward, instead of round-tripping through TLS via load_g

The cgocallback issue has no assembly-level workaround — it is the core call that crosscall2 must make. Options:

  1. Request Go upstream change cgocallback to //go:linkname (open to all) or add an allowlist exception
  2. Use -ldflags='-checklinkname=0' as a temporary workaround
  3. Find an indirect calling mechanism

Cause

golang/go@aee6009cmd/link: check linkname access to assembly symbols (cherrymui, May 2026)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions