cmd/link: when changing to Segrelrodata, reset datsize
Otherwise we leave a gap at the start of Segrelrodata equal to the
size of the read-only non-relro data, which causes -buildmode=pie
executables to be noticeably larger than -buildmode=exe executables.
Change-Id: I98956ef29d5b7a57ad8e633c823ac09d9ca36a45
Reviewed-on: https://go-review.googlesource.com/c/go/+/208897
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go
index e9f727e..f0c7872 100644
--- a/src/cmd/link/elf_test.go
+++ b/src/cmd/link/elf_test.go
@@ -7,6 +7,8 @@
package main
import (
+ "cmd/internal/sys"
+ "debug/elf"
"fmt"
"internal/testenv"
"io/ioutil"
@@ -15,7 +17,9 @@
"path/filepath"
"runtime"
"strings"
+ "sync"
"testing"
+ "text/template"
)
func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) {
@@ -209,3 +213,195 @@
t.Fatal(err)
}
}
+
+const pieSourceTemplate = `
+package main
+
+import "fmt"
+
+// Force the creation of a lot of type descriptors that will go into
+// the .data.rel.ro section.
+{{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{}
+{{end}}
+
+func main() {
+{{range $index, $element := .}} fmt.Println(V{{$index}})
+{{end}}
+}
+`
+
+func TestPIESize(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ if !sys.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) {
+ t.Skip("-buildmode=pie not supported")
+ }
+
+ tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate))
+
+ writeGo := func(t *testing.T, dir string) {
+ f, err := os.Create(filepath.Join(dir, "pie.go"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Passing a 100-element slice here will cause
+ // pieSourceTemplate to create 100 variables with
+ // different types.
+ if err := tmpl.Execute(f, make([]byte, 100)); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ for _, external := range []bool{false, true} {
+ external := external
+
+ name := "TestPieSize-"
+ if external {
+ name += "external"
+ } else {
+ name += "internal"
+ }
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ dir, err := ioutil.TempDir("", "go-link-"+name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ writeGo(t, dir)
+
+ binexe := filepath.Join(dir, "exe")
+ binpie := filepath.Join(dir, "pie")
+ if external {
+ binexe += "external"
+ binpie += "external"
+ }
+
+ build := func(bin, mode string) error {
+ cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode)
+ if external {
+ cmd.Args = append(cmd.Args, "-ldflags=-linkmode=external")
+ }
+ cmd.Args = append(cmd.Args, "pie.go")
+ cmd.Dir = dir
+ t.Logf("%v", cmd.Args)
+ out, err := cmd.CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Error(err)
+ }
+ return err
+ }
+
+ var errexe, errpie error
+ var wg sync.WaitGroup
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ errexe = build(binexe, "exe")
+ }()
+ go func() {
+ defer wg.Done()
+ errpie = build(binpie, "pie")
+ }()
+ wg.Wait()
+ if errexe != nil || errpie != nil {
+ t.Fatal("link failed")
+ }
+
+ var sizeexe, sizepie uint64
+ if fi, err := os.Stat(binexe); err != nil {
+ t.Fatal(err)
+ } else {
+ sizeexe = uint64(fi.Size())
+ }
+ if fi, err := os.Stat(binpie); err != nil {
+ t.Fatal(err)
+ } else {
+ sizepie = uint64(fi.Size())
+ }
+
+ elfexe, err := elf.Open(binexe)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer elfexe.Close()
+
+ elfpie, err := elf.Open(binpie)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer elfpie.Close()
+
+ // The difference in size between exe and PIE
+ // should be approximately the difference in
+ // size of the .text section plus the size of
+ // the PIE dynamic data sections plus the
+ // difference in size of the .got and .plt
+ // sections if they exist.
+ // We ignore unallocated sections.
+
+ textsize := func(ef *elf.File, name string) uint64 {
+ for _, s := range ef.Sections {
+ if s.Name == ".text" {
+ return s.Size
+ }
+ }
+ t.Fatalf("%s: no .text section", name)
+ return 0
+ }
+ textexe := textsize(elfexe, binexe)
+ textpie := textsize(elfpie, binpie)
+
+ dynsize := func(ef *elf.File) uint64 {
+ var ret uint64
+ for _, s := range ef.Sections {
+ if s.Flags&elf.SHF_ALLOC == 0 {
+ continue
+ }
+ switch s.Type {
+ case elf.SHT_DYNSYM, elf.SHT_STRTAB, elf.SHT_REL, elf.SHT_RELA, elf.SHT_HASH, elf.SHT_GNU_HASH, elf.SHT_GNU_VERDEF, elf.SHT_GNU_VERNEED, elf.SHT_GNU_VERSYM:
+ ret += s.Size
+ }
+ if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) {
+ ret += s.Size
+ }
+ }
+ return ret
+ }
+
+ dynexe := dynsize(elfexe)
+ dynpie := dynsize(elfpie)
+
+ extrasize := func(ef *elf.File) uint64 {
+ var ret uint64
+ for _, s := range ef.Sections {
+ if s.Flags&elf.SHF_ALLOC == 0 {
+ ret += s.Size
+ }
+ }
+ return ret
+ }
+
+ extraexe := extrasize(elfexe)
+ extrapie := extrasize(elfpie)
+
+ diffReal := (sizepie - extrapie) - (sizeexe - extraexe)
+ diffExpected := (textpie + dynpie) - (textexe + dynexe)
+
+ t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected)
+
+ if diffReal > (diffExpected + diffExpected/10) {
+ t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected)
+ }
+ })
+ }
+}
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index 32d1111..7ca01c8 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1625,29 +1625,27 @@
}
if ctxt.UseRelro() {
- addrelrosection = func(suffix string) *sym.Section {
- seg := &Segrelrodata
- if ctxt.LinkMode == LinkExternal && ctxt.HeadType != objabi.Haix {
- // Using a separate segment with an external
- // linker results in some programs moving
- // their data sections unexpectedly, which
- // corrupts the moduledata. So we use the
- // rodata segment and let the external linker
- // sort out a rel.ro segment.
- seg = &Segrodata
- }
- return addsection(ctxt.Arch, seg, ".data.rel.ro"+suffix, 06)
- }
- /* data only written by relocations */
- sect = addrelrosection("")
-
- sect.Vaddr = 0
- if ctxt.HeadType == objabi.Haix {
- // datsize must be reset because relro datas will end up
- // in data segment.
+ segrelro := &Segrelrodata
+ if ctxt.LinkMode == LinkExternal && ctxt.HeadType != objabi.Haix {
+ // Using a separate segment with an external
+ // linker results in some programs moving
+ // their data sections unexpectedly, which
+ // corrupts the moduledata. So we use the
+ // rodata segment and let the external linker
+ // sort out a rel.ro segment.
+ segrelro = segro
+ } else {
+ // Reset datsize for new segment.
datsize = 0
}
+ addrelrosection = func(suffix string) *sym.Section {
+ return addsection(ctxt.Arch, segrelro, ".data.rel.ro"+suffix, 06)
+ }
+
+ /* data only written by relocations */
+ sect = addrelrosection("")
+
ctxt.Syms.Lookup("runtime.types", 0).Sect = sect
ctxt.Syms.Lookup("runtime.etypes", 0).Sect = sect
@@ -1659,7 +1657,17 @@
}
}
datsize = Rnd(datsize, int64(sect.Align))
- for _, symnro := range sym.ReadOnly {
+ sect.Vaddr = uint64(datsize)
+
+ for i, symnro := range sym.ReadOnly {
+ if i == 0 && symnro == sym.STYPE && ctxt.HeadType != objabi.Haix {
+ // Skip forward so that no type
+ // reference uses a zero offset.
+ // This is unlikely but possible in small
+ // programs with no other read-only data.
+ datsize++
+ }
+
symn := sym.RelROMap[symnro]
symnStartValue := datsize
for _, s := range data[symn] {