go: cmd/link: missing CGO symbols in `.dynsym` on Go >= 1.21 on arm64

What version of Go are you using (go version)?

$ go version
go version go1.21.1 linux/arm64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/home/mahe.linux/.cache/go-build'
GOENV='/home/mahe.linux/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/mahe.linux/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/mahe.linux/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_arm64'
GOVCS=''
GOVERSION='go1.21.1'
GCCGO='gccgo'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1359563043=/tmp/go-build -gno-record-gcc-switches'

What did you do?

So the issue is a difference of behavior from Go 1.20 to Go 1.21 on arm64 (and not on amd64).

Let’s make a minimal reproducing setup:

  1. Put that in a file, for example main.go and go build main.

    //go:build linux
    // +build linux
    
    package main
    
    /*
    int symbol_test(void)
    {
            return 0;
    }
    */
    import "C"
    
    func main() {
            C.symbol_test()
    }
    
  2. Use a binary to read the symbols, for example readelf -s main | grep symbol_test, you can use less as well to see:

    • With Go 1.20.8 (and before), there is a symbol_test in .dynsym and symbol_test in .symtab.
    • With Go 1.21.0 and 1.21.1, there is only a symbol_test in .symtab, it was removed from .dynsym.

So I cloned Go and did a git bissect to see which commit was the culprit and found that it was 1f29f39795e736238200840c368c4e0c6edbfbae -> https://go-review.googlesource.com/c/go/+/414654. Unfortunately, I lack context and understanding of the Go codebase to understand why this changed the behavior like that on arm64 specifically and if this is unintended.

For context, I was using uprobes on a Go binary, and for a reason that is not entirely clear for now, the lib I was using to load the probe is only reading .dynsym.

What did you expect to see?

I expected this behavior not to change, and especially not to change only on arm64 while amd64 is still behaving the same as in the past.

What did you see instead?

I saw that the symbol was missing in .dynsym using Go >= 1.21.

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 16 (11 by maintainers)

Commits related to this issue

Most upvoted comments

on arm64 (and not on amd64).

This seems to be true. On my Linux/arm64 machine:

$ go build -o main2 main2.go
$ readelf -s main2 | grep symbol_test
  1780: 0000000000459e90     8 FUNC    GLOBAL DEFAULT   14 symbol_test

$ go env

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/home/erifan01/.cache/go-build'
GOENV='/home/erifan01/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/erifan01/gopath/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/erifan01/gopath'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/erifan01/go-master'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/erifan01/go-master/pkg/tool/linux_arm64'
GOVCS=''
GOVERSION='devel go1.22-5eb382fc08 Sun Sep 10 23:21:34 2023 +0000'
GCCGO='/usr/bin/gccgo'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3909050461=/tmp/go-build -gno-record-gcc-switches'

gcc version 13.1.0 (Ubuntu 13.1.0-8ubuntu1~22.04)

On my Linux/amd64 machine:

$ go build -o main2 main2.go
$ readelf -s main2 | grep "symbol_test"
    61: 000000000045ab30     7 FUNC    GLOBAL DEFAULT   14 symbol_test
  1174: 000000000045aaa0    84 FUNC    LOCAL  DEFAULT   14 main._Cfunc_symbol_test.a
  1723: 000000000045ab30     7 FUNC    GLOBAL DEFAULT   14 symbol_test

$go env

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/erifan02/.cache/go-build'
GOENV='/home/erifan02/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/erifan02/gopath/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/erifan02/gopath'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/erifan02/go-master'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/erifan02/go-master/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='devel go1.22-5eb382fc08 Sun Sep 10 23:21:34 2023 +0000'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3754892114=/tmp/go-build -gno-record-gcc-switches'

gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)

I don’t know if this is caused by the difference in GCC version, but this difference does exist.

Thanks for the report. Previously we dynamically export ~all symbols, which is too many and unnecessary for many of them. With that CL, we only export Go symbols that are dynamically exported to C. I think we probably should also export C symbols (that are not marked as static or hidden visibility) per C semantics. @ianlancetaylor what do you think?