1
0
mirror of https://github.com/golang/go synced 2024-11-23 00:40:08 -07:00

cmd/link: add -randlayout flag to randomize function ordering

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>
This commit is contained in:
Cherry Mui 2024-02-06 18:08:34 -05:00
parent 284e553035
commit 5258d4ed60
4 changed files with 69 additions and 6 deletions

View 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)

View 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`")

View 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 {

View 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])
}
}