Skip to content

cmd/link: wrong flag order when checking supported linker flags #69350

@siddharthab

Description

@siddharthab

Go version

1.23.1

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/root/.cache/go-build'
GOENV='/root/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/root/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/root/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/goroot'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/goroot/pkg/tool/linux_arm64'
GOVCS=''
GOVERSION='go1.23.1'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/root/.config/go/telemetry'
GCCGO='gccgo'
GOARM64='v8.0'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/hyperbase/go.mod'
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-build4149942144=/tmp/go-build -gno-record-gcc-switches'

What did you do?

There can probably be a simpler repro, but I am presenting here as I discovered the issue. The context is Bazel rules for Go. As per bazel-contrib/rules_go#4091, I used the repro in https://github.com/DavidZbarsky-at/cgo-repro.

To further investigate the issue, I patched the source code for linkerFlagSupported to print the linker command being executed.

diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index d66027387b..f528400a7e 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -2139,4 +2139,5 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
        cmd.Env = append([]string{"LC_ALL=C"}, os.Environ()...)
        out, err := cmd.CombinedOutput()
+       fmt.Printf("checking %s (%v %v) (%v): %s\n", flag, linker, strings.Join(flags, " "), err, out)
        // GCC says "unrecognized command line option <E2><80><98>-no-pie<E2><80><99>"
        // clang says "unknown argument: '-no-pie'"

What did you see happen?

All invocations of linkerFlagSupported fail because of the wrong order of linker flags:

The patched print statement reveals the following information:

checking -no-pie: external/toolchains_llvm~~llvm~llvm_toolchain/bin/cc_wrapper.sh -fuse-ld=lld -Wl,--build-id=md5 -Wl,--hash-style=gnu -Wl,-z,relro,-z,now --sysroot=external/_main~_repo_rules~org_chromium_sysroot_linux_aarch64/ -fuse-ld=lld -Wl,--build-id=md5 -Wl,--hash-style=gnu -Wl,-z,relro,-z,now --sysroot=__GO_BAZEL_CC_PLACEHOLDER__external/_main~_repo_rules~org_chromium_sysroot_linux_aarch64/ -pthread -fuse-ld=lld -Wl,--build-id=md5 -Wl,--hash-style=gnu -Wl,-z,relro,-z,now --sysroot=__GO_BAZEL_CC_PLACEHOLDER__external/_main~_repo_rules~org_chromium_sysroot_linux_aarch64/ -pthread -o /tmp/go-link-2636548368/a.out -no-pie /tmp/go-link-2636548368/trivial.c (exit status 1): ld.lld: error: cannot open crt1.o: No such file or directory

Notice the multiple values of --sysroot. The first value is coming from extldflags, and the two later values are coming from CGo directives from stdlib objects being linked in.

What did you expect to see?

CGo directives should get less priority than extldflags, and so should be placed before.

This is because of how flags are appended in

moreFlags := trimLinkerArgv(append(flagExtldflags, ldflag...))

Consider the correct behavior in the final link command - extldflags after CGo directives (given by the variable ldflag):

for _, p := range ldflag {
argv = append(argv, p)
checkStatic(p)
}
// When building a program with the default -buildmode=exe the
// gc compiler generates code requires DT_TEXTREL in a
// position independent executable (PIE). On systems where the
// toolchain creates PIEs by default, and where DT_TEXTREL
// does not work, the resulting programs will not run. See
// issue #17847. To avoid this problem pass -no-pie to the
// toolchain if it is supported.
if ctxt.BuildMode == BuildModeExe && !ctxt.linkShared && !(ctxt.IsDarwin() && ctxt.IsARM64()) {
// GCC uses -no-pie, clang uses -nopie.
for _, nopie := range []string{"-no-pie", "-nopie"} {
if linkerFlagSupported(ctxt.Arch, argv[0], altLinker, nopie) {
argv = append(argv, nopie)
break
}
}
}
for _, p := range flagExtldflags {
argv = append(argv, p)
checkStatic(p)
}

The fix can be as simple as to change the above line in linkerFlagSupported to:

moreFlags := []string{}
moreFlags = append(moreFlags, ldflag...)
moreFlags = append(moreFlags, flagExtldflags...)
moreFlags = trimLinkerArgv(moreFlags)

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.help wanted

    Type

    No type

    Projects

    Status

    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions