]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: add -randlayout flag to randomize function ordering
authorCherry Mui <cherryyz@google.com>
Tue, 6 Feb 2024 23:08:34 +0000 (18:08 -0500)
committerCherry Mui <cherryyz@google.com>
Fri, 16 Feb 2024 14:59:05 +0000 (14:59 +0000)
Sometimes we found that benchmark results may strongly depend on
the ordering of functions laid out in the binary. This CL adds a
flag -randlayout=seed, which randomizes the function layout (in a
deterministic way), so can verify the benchmark results against
different function ordering.

Change-Id: I85f33881bbfd4ca6812fbd4bec00bf475755a09e
Reviewed-on: https://go-review.googlesource.com/c/go/+/562157
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
src/cmd/link/internal/ld/data.go
src/cmd/link/internal/ld/main.go
src/cmd/link/internal/ld/pcln.go
src/cmd/link/link_test.go

index 896d7731246e4107d53bf56e23193b624af97d9b..b4930277e40da740e5fafc0463a9e1ed5d446d67 100644 (file)
@@ -45,6 +45,7 @@ import (
        "fmt"
        "internal/abi"
        "log"
+       "math/rand"
        "os"
        "sort"
        "strconv"
@@ -122,10 +123,11 @@ func trampoline(ctxt *Link, s loader.Sym) {
                }
 
                if ldr.SymValue(rs) == 0 && ldr.SymType(rs) != sym.SDYNIMPORT && ldr.SymType(rs) != sym.SUNDEFEXT {
-                       // Symbols in the same package are laid out together.
+                       // Symbols in the same package are laid out together (if we
+                       // don't randomize the function order).
                        // Except that if SymPkg(s) == "", it is a host object symbol
                        // which may call an external symbol via PLT.
-                       if ldr.SymPkg(s) != "" && ldr.SymPkg(rs) == ldr.SymPkg(s) {
+                       if ldr.SymPkg(s) != "" && ldr.SymPkg(rs) == ldr.SymPkg(s) && *flagRandLayout == 0 {
                                // RISC-V is only able to reach +/-1MiB via a JAL instruction.
                                // We need to generate a trampoline when an address is
                                // currently unknown.
@@ -134,7 +136,7 @@ func trampoline(ctxt *Link, s loader.Sym) {
                                }
                        }
                        // Runtime packages are laid out together.
-                       if isRuntimeDepPkg(ldr.SymPkg(s)) && isRuntimeDepPkg(ldr.SymPkg(rs)) {
+                       if isRuntimeDepPkg(ldr.SymPkg(s)) && isRuntimeDepPkg(ldr.SymPkg(rs)) && *flagRandLayout == 0 {
                                continue
                        }
                }
@@ -2397,6 +2399,26 @@ func (ctxt *Link) textaddress() {
 
        ldr := ctxt.loader
 
+       if *flagRandLayout != 0 {
+               r := rand.New(rand.NewSource(*flagRandLayout))
+               textp := ctxt.Textp
+               i := 0
+               // don't move the buildid symbol
+               if len(textp) > 0 && ldr.SymName(textp[0]) == "go:buildid" {
+                       i++
+               }
+               // Skip over C symbols, as functions in a (C object) section must stay together.
+               // TODO: maybe we can move a section as a whole.
+               // Note: we load C symbols before Go symbols, so we can scan from the start.
+               for i < len(textp) && (ldr.SubSym(textp[i]) != 0 || ldr.AttrSubSymbol(textp[i])) {
+                       i++
+               }
+               textp = textp[i:]
+               r.Shuffle(len(textp), func(i, j int) {
+                       textp[i], textp[j] = textp[j], textp[i]
+               })
+       }
+
        text := ctxt.xdefine("runtime.text", sym.STEXT, 0)
        etext := ctxt.xdefine("runtime.etext", sym.STEXT, 0)
        ldr.SetSymSect(text, sect)
index feb4ba5c1725f1654a188642791230a4fce700fe..877b3a6be844fece3571d98eb88ed58f984d981d 100644 (file)
@@ -102,6 +102,7 @@ var (
        FlagTextAddr      = flag.Int64("T", -1, "set the start address of text symbols")
        flagEntrySymbol   = flag.String("E", "", "set `entry` symbol name")
        flagPruneWeakMap  = flag.Bool("pruneweakmap", true, "prune weak mapinit refs")
+       flagRandLayout    = flag.Int64("randlayout", 0, "randomize function layout")
        cpuprofile        = flag.String("cpuprofile", "", "write cpu profile to `file`")
        memprofile        = flag.String("memprofile", "", "write memory profile to `file`")
        memprofilerate    = flag.Int64("memprofilerate", 0, "set runtime.MemProfileRate to `rate`")
index df06084352942065df6dafe68646107183817d87..c5996f11d32cdd13f66a4dd723454f80f8093b4c 100644 (file)
@@ -877,7 +877,7 @@ func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
                                q = ldr.SymValue(e)
                        }
 
-                       //print("%d: [%lld %lld] %s\n", idx, p, q, s->name);
+                       //fmt.Printf("%d: [%x %x] %s\n", idx, p, q, ldr.SymName(s))
                        for ; p < q; p += SUBBUCKETSIZE {
                                i = int((p - min) / SUBBUCKETSIZE)
                                if indexes[i] > idx {
index 7029d3213fa7a7cf9e7409f9c9d3a93029471218..6afde4b085db8a06964683b9650b9e1d03f1bb98 100644 (file)
@@ -348,7 +348,7 @@ func TestXFlag(t *testing.T) {
        }
 }
 
-var testMachOBuildVersionSrc = `
+var trivialSrc = `
 package main
 func main() { }
 `
@@ -361,7 +361,7 @@ func TestMachOBuildVersion(t *testing.T) {
        tmpdir := t.TempDir()
 
        src := filepath.Join(tmpdir, "main.go")
-       err := os.WriteFile(src, []byte(testMachOBuildVersionSrc), 0666)
+       err := os.WriteFile(src, []byte(trivialSrc), 0666)
        if err != nil {
                t.Fatal(err)
        }
@@ -1375,3 +1375,43 @@ func TestFlagS(t *testing.T) {
                }
        }
 }
+
+func TestRandLayout(t *testing.T) {
+       // Test that the -randlayout flag randomizes function order and
+       // generates a working binary.
+       testenv.MustHaveGoBuild(t)
+
+       t.Parallel()
+
+       tmpdir := t.TempDir()
+
+       src := filepath.Join(tmpdir, "hello.go")
+       err := os.WriteFile(src, []byte(trivialSrc), 0666)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       var syms [2]string
+       for i, seed := range []string{"123", "456"} {
+               exe := filepath.Join(tmpdir, "hello"+seed+".exe")
+               cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-randlayout="+seed, "-o", exe, src)
+               out, err := cmd.CombinedOutput()
+               if err != nil {
+                       t.Fatalf("build failed: %v\n%s", err, out)
+               }
+               cmd = testenv.Command(t, exe)
+               err = cmd.Run()
+               if err != nil {
+                       t.Fatalf("executable failed to run: %v\n%s", err, out)
+               }
+               cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe)
+               out, err = cmd.CombinedOutput()
+               if err != nil {
+                       t.Fatalf("fail to run \"go tool nm\": %v\n%s", err, out)
+               }
+               syms[i] = string(out)
+       }
+       if syms[0] == syms[1] {
+               t.Errorf("randlayout with different seeds produced same layout:\n%s\n===\n\n%s", syms[0], syms[1])
+       }
+}