"fmt"
"internal/abi"
"log"
+ "math/rand"
"os"
"sort"
"strconv"
}
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.
}
}
// 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
}
}
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)
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`")
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 {
}
}
-var testMachOBuildVersionSrc = `
+var trivialSrc = `
package main
func main() { }
`
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)
}
}
}
}
+
+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])
+ }
+}