go: cmd/cgo, cmd/link: with zig as CC/CXX, Go linker does not put libc onto the linker line, causing undefined symbol errors

Corresponding Zig issue: https://github.com/ziglang/zig/issues/11398

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

$ go version
go version go1.18.1 linux/amd64

Does this issue reproduce with the latest release?

I only tested with go1.18.1.

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

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/andy/.cache/go-build"
GOENV="/home/andy/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/andy/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/andy/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/nix/store/qyc0w8vsikzmbdy97gb68l2ri1jzqp9v-go-1.18.1/share/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/nix/store/qyc0w8vsikzmbdy97gb68l2ri1jzqp9v-go-1.18.1/share/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.18.1"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/run/user/1000/go-build3829224601=/tmp/go-build -gno-record-gcc-switches"

What did you do?

go.mod:

module test

go 1.18

foo.go:

package test

func foo() {}

foo_test.go:

package test

import (
	"testing"
)

func TestFoo(t *testing.T) {
	foo()
}

Then use zig cc as the C toolchain for go test with race detection:

CGO_ENABLED=1 CC="zig cc" go test -race test

Zig Version 0.10.0-dev.2052+3cfde183f

What did you expect to see?

This should work, as it does without -race.

What did you see instead?

We get errors from the Go linker:

# test.test
runtime/race(.text): relocation target getuid not defined
runtime/race(.text): relocation target pthread_self not defined
runtime/race(.text): relocation target sleep not defined
runtime/race(.text): relocation target usleep not defined
runtime/race(.text): relocation target abort not defined
runtime/race(.text): relocation target isatty not defined
runtime/race(.text): relocation target pthread_attr_getstack not defined
runtime/race(.text): relocation target sigaction not defined
runtime/race(.text): relocation target getrusage not defined
runtime/race(.text): relocation target syslog not defined
runtime/race(.text): relocation target confstr not defined
runtime/race(.text): relocation target getrlimit not defined
runtime/race(.text): relocation target pipe not defined
runtime/race(.text): relocation target sched_getaffinity not defined
runtime/race(.text): relocation target __sched_cpucount not defined
runtime/race(.text): relocation target pthread_attr_init not defined
runtime/race(.text): relocation target pthread_getattr_np not defined
runtime/race(.text): relocation target pthread_attr_destroy not defined
runtime/race(.text): relocation target exit not defined
runtime/race(.text): relocation target sysconf not defined
runtime/race(.text): relocation target setrlimit not defined
/home/kmicklas/go/pkg/tool/linux_amd64/link: too many errors
FAIL	test [build failed]
FAIL

Notes

Related issue: #44695

It appears that cgo has asked the C compiler to compile C source files which contain dependencies on libc symbols, however the Go linker is not actually putting libc onto the linker line, causing these undefined symbol errors.

It does compile and run successfully using -linkmode external, however, I don’t see why it shouldn’t work with the Go linker as well, given that zig cc produces standard ELF object files (same as Clang and GCC).

It looks to me like the fix is simple: the Go linker needs to put libc onto the linker line when it is linking objects that contain libc dependencies.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 3
  • Comments: 31 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Would you be able to link the memset/memcpy fix here? Was this recent? @motiejus

Sure, here it is: https://github.com/ziglang/zig/commit/b3f4e0d091d83874af849f1d1a2328ad7c154d42

I attempted to workaround res_search issue it in bazel-zig-cc (https://git.sr.ht/~motiejus/bazel-zig-cc/commit/7b0de33070bef14265d7ec560fca43f5e132eea4 and https://git.sr.ht/~motiejus/bazel-zig-cc/commit/8d1e1c9fa66712c8f7da3634990ab4ccd2aa47c9), but that doesn’t always work, as you can see.

We agreed with @andrewrk that we will change the offending headers in upsteam zig (add ifdefs on glibc version). To my latest knowledge, @sywhang is working on that.

zig cc follows the same static/dynamic linking rules as clang and gcc; it shares the exact same command line API. On glibc-based systems, GCC, Clang, and zig cc all link dynamically against libc.so by default:

andy@ark ~/tmp> gcc -o hello hello.c
andy@ark ~/tmp> ldd hello
	linux-vdso.so.1 (0x00007ffc9f3df000)
	libc.so.6 => /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib/libc.so.6 (0x00007f13b9dec000)
	/nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib/ld-linux-x86-64.so.2 => /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib64/ld-linux-x86-64.so.2 (0x00007f13b9fc3000)
andy@ark ~/tmp> clang -o hello hello.c
andy@ark ~/tmp> ldd hello
	linux-vdso.so.1 (0x00007ffd177ab000)
	libc.so.6 => /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib/libc.so.6 (0x00007fc1ef774000)
	/nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib/ld-linux-x86-64.so.2 => /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib64/ld-linux-x86-64.so.2 (0x00007fc1ef94b000)
andy@ark ~/tmp> zig cc -o hello hello.c
andy@ark ~/tmp> ldd hello
	linux-vdso.so.1 (0x00007fff1d5f7000)
	libc.so.6 => /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib/libc.so.6 (0x00007ff3ec3ae000)
	/nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib/ld-linux-x86-64.so.2 => /nix/store/g02b1lpbddhymmcjb923kf0l7s9nww58-glibc-2.33-123/lib64/ld-linux-x86-64.so.2 (0x00007ff3ec585000)

A typical linker line for any C compiler will look something like this:

ld -m elf_x86_64 -o hello Scrt1.o crti.o hello.o libcompiler_rt.a libm.so.6 libpthread.so.0 libc.so.6 libdl.so.2 librt.so.1 libld.so.2 libutil.so.1 libc_nonshared.a crtn.o

In this example, hello.o contains libc dependencies, but it is the linker which satisfies them by putting libc on the linker line, as you can see.

I’m not sure what you mean when you say the Go linker never links directly against the C library. Indeed, getuid and friends come from the libc.so shared C library, as you can see above, but in this issue, the Go linker is omitting libc.so from the list of objects to link, causing the undefined symbol errors.

We can simulate this same problem with any C compiler by passing -nostdlib, causing the linker driver to omit libc.so from the linker line, as Go is doing:

andy@ark ~/tmp> clang -o hello hello.c -nostdlib
/nix/store/b987cpz6k72fkp05ycbhvk635y46m3cc-binutils-2.35.2/bin/ld: /tmp/hello-d74816.o: in function `main':
hello.c:(.text+0x15): undefined reference to `printf'
clang-7: error: linker command failed with exit code 1 (use -v to see invocation)

Ultimately, the Go toolchain is compiling C source files into object files which contain libc dependencies. If any C compiler is used to then link these together, everything works fine, because C compilers put libc onto the linker line. However, when the Go linker is used, which is the default, it does not satisfy its own dependencies.

I think one thing that would shed some light on this issue would be seeing, side by side, the Go linker line when CC=clang is used vs CC="zig cc". I spent a couple hours trying to obtain this information but did not figure out how to do it.