]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/link: pass flags to external linker in deterministic order
authorCherry Mui <cherryyz@google.com>
Wed, 12 Jul 2023 20:54:32 +0000 (16:54 -0400)
committerCherry Mui <cherryyz@google.com>
Thu, 20 Jul 2023 20:54:38 +0000 (20:54 +0000)
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>

src/cmd/link/internal/ld/lib.go
src/cmd/link/link_test.go

index 91e2d5149ce4daf3fae9ce910d84ee537dd448f6..6c03072160945e6b2d061eaf20c6ad3e053270f7 100644 (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 {
index 7059af7cad89c623e906909ff9bbfff5cfd90cd3..346dde05eb0e589b1d56e0012bc345e0671b306d 100644 (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) {