1
0
mirror of https://github.com/golang/go synced 2024-11-22 22:00:02 -07:00

cmd/link: pass flags to external linker in deterministic order

Currently we may pass C linker flags in nondeterministic order,
as on ELf systems we pass --export-dynamic-symbol for symbols from
a map. This is usally not a big problem because even if the flags
are passed in nondeterministic order the resulting binary is
probably still deterministic. This CL makes it pass them in a
deterministic order to be extra sure. This also helps build
systems where e.g. there is a build cache for the C linking action.

Change-Id: I930524dd2c3387f49d62be7ad2cef937cb2c2238
Reviewed-on: https://go-review.googlesource.com/c/go/+/509215
Reviewed-by: Than McIntosh <thanm@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Cherry Mui 2023-07-12 16:54:32 -04:00
parent e201ff2b98
commit 57e2eb64eb
2 changed files with 85 additions and 1 deletions

View File

@ -44,6 +44,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
@ -1670,9 +1671,12 @@ func (ctxt *Link) hostlink() {
if ctxt.DynlinkingGo() || ctxt.BuildMode == BuildModeCShared || !linkerFlagSupported(ctxt.Arch, argv[0], altLinker, "-Wl,--export-dynamic-symbol=main") {
argv = append(argv, "-rdynamic")
} else {
var exports []string
ctxt.loader.ForAllCgoExportDynamic(func(s loader.Sym) {
argv = append(argv, "-Wl,--export-dynamic-symbol="+ctxt.loader.SymExtname(s))
exports = append(exports, "-Wl,--export-dynamic-symbol="+ctxt.loader.SymExtname(s))
})
sort.Strings(exports)
argv = append(argv, exports...)
}
}
if ctxt.HeadType == objabi.Haix {

View File

@ -1167,6 +1167,86 @@ func TestUnlinkableObj(t *testing.T) {
}
}
func TestExtLinkCmdlineDeterminism(t *testing.T) {
// Test that we pass flags in deterministic order to the external linker
testenv.MustHaveGoBuild(t)
testenv.MustHaveCGO(t) // this test requires -linkmode=external
t.Parallel()
// test source code, with some cgo exports
testSrc := `
package main
import "C"
//export F1
func F1() {}
//export F2
func F2() {}
//export F3
func F3() {}
func main() {}
`
tmpdir := t.TempDir()
src := filepath.Join(tmpdir, "x.go")
if err := os.WriteFile(src, []byte(testSrc), 0666); err != nil {
t.Fatal(err)
}
exe := filepath.Join(tmpdir, "x.exe")
// Use a deterministc tmp directory so the temporary file paths are
// deterministc.
linktmp := filepath.Join(tmpdir, "linktmp")
if err := os.Mkdir(linktmp, 0777); err != nil {
t.Fatal(err)
}
// Link with -v -linkmode=external to see the flags we pass to the
// external linker.
ldflags := "-ldflags=-v -linkmode=external -tmpdir=" + linktmp
var out0 []byte
for i := 0; i < 5; i++ {
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", ldflags, "-o", exe, src)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("build failed: %v, output:\n%s", err, out)
}
if err := os.Remove(exe); err != nil {
t.Fatal(err)
}
// extract the "host link" invocaton
j := bytes.Index(out, []byte("\nhost link:"))
if j == -1 {
t.Fatalf("host link step not found, output:\n%s", out)
}
out = out[j+1:]
k := bytes.Index(out, []byte("\n"))
if k == -1 {
t.Fatalf("no newline after host link, output:\n%s", out)
}
out = out[:k]
// filter out output file name, which is passed by the go
// command and is nondeterministic.
fs := bytes.Fields(out)
for i, f := range fs {
if bytes.Equal(f, []byte(`"-o"`)) && i+1 < len(fs) {
fs[i+1] = []byte("a.out")
break
}
}
out = bytes.Join(fs, []byte{' '})
if i == 0 {
out0 = out
continue
}
if !bytes.Equal(out0, out) {
t.Fatalf("output differ:\n%s\n==========\n%s", out0, out)
}
}
}
// TestResponseFile tests that creating a response file to pass to the
// external linker works correctly.
func TestResponseFile(t *testing.T) {