aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorCherry Mui <cherryyz@google.com>2023-07-12 16:54:32 -0400
committerCherry Mui <cherryyz@google.com>2023-07-20 20:54:38 +0000
commit57e2eb64ebdd441356eb3fb57bc06636cbfa58c0 (patch)
tree84efab16029e2c081912a0a8b97c18f57a2cb916 /src/cmd
parente201ff2b98981807c4d57ed15d42d308817e138a (diff)
downloadgo-57e2eb64ebdd441356eb3fb57bc06636cbfa58c0.tar.xz
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>
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/link/internal/ld/lib.go6
-rw-r--r--src/cmd/link/link_test.go80
2 files changed, 85 insertions, 1 deletions
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 91e2d5149c..6c03072160 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -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 {
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index 7059af7cad..346dde05eb 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -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) {