-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
Go version
go version go1.24.2 linux/amd64
Output of go env in your module/workspace:
(Some output here and below manually modified to hide internal configuration)
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='$HOME/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='$HOME/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build353822596=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='$HOME/tmp/go-cross/go.mod'
GOMODCACHE='$HOME/go/pkg/mod'
GONOPROXY=''
GONOSUMDB='$INTERNAL'
GOOS='linux'
GOPATH='$HOME/go'
GOPRIVATE=''
GOPROXY='$INTERNAL,direct'
GOROOT='$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='$HOME/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK=''
PKG_CONFIG='pkg-config'What did you do?
Attempted to cross-compile a cgo binary from amd64 to arm64 using a private LLVM/Clang-based toolchain and sysroot. I was able to reproduce this from arm64 crossing to amd64 as well.
Minimal example (which doesn't run in the playground due to needing cgo): https://go.dev/play/p/a0qGj4Vakf-
Built with:
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=/path/to/private/clang CGO_CFLAGS="--sysroot=/path/to/private/sysroot-arm --target=aarch64-unknown-linux-gnu" \
go build \
-ldflags="-v -extld C/path/to/private/clang -extldflags '-fuse-ld=lld --sysroot=/path/to/private/sysroot-arm --target=aarch64-unknown-linux-gnu --verbose'" \
-gcflags=-vThe output binary can be run on an ARM host, or via qemu with something like:
qemu-aarch64 -L /path/to/private/sysroot-arm ./go-crossWhat did you see happen?
The go tool link output from the above go build command contained a linker invocation like this:
(The "host link" outputs below are particularly long, and is more readable in an editor)
host link: "/path/to/private/clang" "-o" "/tmp/go-build481106842/b001/exe/a.out" "-rdynamic" "/tmp/go-link-168810886/go.o" "/tmp/go-link-168810886/000000.o" "/tmp/go-link-168810886/000001.o" "/tmp/go-link-168810886/000002.o" "/tmp/go-link-168810886/000003.o" "/tmp/go-link-168810886/000004.o" "/tmp/go-link-168810886/000005.o" "/tmp/go-link-168810886/000006.o" "/tmp/go-link-168810886/000007.o" "/tmp/go-link-168810886/000008.o" "/tmp/go-link-168810886/000009.o" "/tmp/go-link-168810886/000010.o" "/tmp/go-link-168810886/000011.o" "/tmp/go-link-168810886/000012.o" "/tmp/go-link-168810886/000013.o" "/tmp/go-link-168810886/000014.o" "/tmp/go-link-168810886/000015.o" "-O2" "-g" "-O2" "-g" "-lpthread" "-fuse-ld=lld" "--sysroot=/path/to/private/sysroot-arm" "--target=aarch64-unknown-linux-gnu" "--verbose"
If I use the same compiler targeting the host architecture, using a sysroot compiled for it (in this case amd64/x86_64), the output is different:
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=/path/to/private/clang CGO_CFLAGS="--sysroot=/path/to/private/sysroot-amd64 --target=x86_64-unknown-linux-gnu" \
go build \
-ldflags="-v -extld C/path/to/private/clang -extldflags '-fuse-ld=lld --sysroot=/path/to/private/sysroot-amd64 --target=x86_64-unknown-linux-gnu --verbose'" \
-gcflags=-v
...
host link: "$HOME/tmp/llvm-17.0.6-dist-x86_64/bin/clang" "-m64" "-Wl,--build-id=0xe3c881e6feaf37cd5c2a7e4e643c549ff859ee17" "-o" "/tmp/go-build3873136084/b001/exe/a.out" "-Wl,--export-dynamic-symbol=_cgo_panic" "-Wl,--export-dynamic-symbol=_cgo_topofstack" "-Wl,--export-dynamic-symbol=crosscall2" "-Qunused-arguments" "-Wl,--compress-debug-sections=zlib" "/tmp/go-link-1498717480/go.o" "/tmp/go-link-1498717480/000000.o" "/tmp/go-link-1498717480/000001.o" "/tmp/go-link-1498717480/000002.o" "/tmp/go-link-1498717480/000003.o" "/tmp/go-link-1498717480/000004.o" "/tmp/go-link-1498717480/000005.o" "/tmp/go-link-1498717480/000006.o" "/tmp/go-link-1498717480/000007.o" "/tmp/go-link-1498717480/000008.o" "/tmp/go-link-1498717480/000009.o" "/tmp/go-link-1498717480/000010.o" "/tmp/go-link-1498717480/000011.o" "/tmp/go-link-1498717480/000012.o" "/tmp/go-link-1498717480/000013.o" "/tmp/go-link-1498717480/000014.o" "/tmp/go-link-1498717480/000015.o" "-O2" "-g" "-O2" "-g" "-lpthread" "-no-pie" "-fuse-ld=lld" "--sysroot=/path/to/private/sysroot-amd64" "--target=x86_64-unknown-linux-gnu" "--verbose"Both commands succeed, and produce a binary.
These flag differences are numerous and consequential: running the cross-compiled binary (in this particular case, using qemu) crashes at startup like this:
> qemu-aarch64 -L /path/to/private/sysroot-arm ./go-cross
runtime: pcHeader: magic= 0xfffffff1 pad1= 0 pad2= 0 minLC= 4 ptrSize= 8 pcHeader.textStart= 0xd9d20 text= 0x7f051fca9d20 pluginpath=
fatal error: invalid function symbol table
runtime: panic before malloc heap initialized
runtime stack:
runtime.throw({0x7f051fbff423?, 0x0?})
$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/src/runtime/panic.go:1101 +0x38 fp=0x4000008000b0 sp=0x400000800080 pc=0x7f051fd11208
runtime.moduledataverify1(0x7f051fd8d5e0?)
$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/src/runtime/symtab.go:623 +0x644 fp=0x4000008001d0 sp=0x4000008000b0 pc=0x7f051fd13704
runtime.moduledataverify(...)
$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/src/runtime/symtab.go:599
runtime.schedinit()
$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/src/runtime/proc.go:834 +0x88 fp=0x400000800260 sp=0x4000008001d0 pc=0x7f051fce2fd8
runtime.rt0_go()
$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/src/runtime/asm_arm64.s:86 +0xa4 fp=0x400000800290 sp=0x400000800260 pc=0x7f051fd15154The binary fails similarly on an actual ARM host as well. When I investigated this, it seems that the critical difference is the -no-pie flag in the second (amd64, same as the build host) output, as adding -buildmode pie to the cross-compiling go build prevents the binary from crashing like this.
What did you expect to see?
The flags in the two "host link" invocations above should be more similar (aside from arch-specific flags), and the cross-compiled program should not crash as mentioned above when run in an emulator or a system with that architecture.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status