Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/link/elf_test.go

Documentation: cmd/link

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build dragonfly freebsd linux netbsd openbsd
     6  
     7  package main
     8  
     9  import (
    10  	"cmd/internal/sys"
    11  	"debug/elf"
    12  	"fmt"
    13  	"internal/testenv"
    14  	"io/ioutil"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"runtime"
    19  	"strings"
    20  	"sync"
    21  	"testing"
    22  	"text/template"
    23  )
    24  
    25  func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) {
    26  	goTool := testenv.GoToolPath(t)
    27  	cmd := exec.Command(goTool, "env", "CC")
    28  	cmd.Env = env
    29  	ccb, err := cmd.Output()
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	cc := strings.TrimSpace(string(ccb))
    34  
    35  	cmd = exec.Command(goTool, "env", "GOGCCFLAGS")
    36  	cmd.Env = env
    37  	cflagsb, err := cmd.Output()
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	cflags := strings.Fields(string(cflagsb))
    42  
    43  	return cc, cflags
    44  }
    45  
    46  var asmSource = `
    47  	.section .text1,"ax"
    48  s1:
    49  	.byte 0
    50  	.section .text2,"ax"
    51  s2:
    52  	.byte 0
    53  `
    54  
    55  var goSource = `
    56  package main
    57  func main() {}
    58  `
    59  
    60  // The linker used to crash if an ELF input file had multiple text sections
    61  // with the same name.
    62  func TestSectionsWithSameName(t *testing.T) {
    63  	testenv.MustHaveGoBuild(t)
    64  	testenv.MustHaveCGO(t)
    65  	t.Parallel()
    66  
    67  	objcopy, err := exec.LookPath("objcopy")
    68  	if err != nil {
    69  		t.Skipf("can't find objcopy: %v", err)
    70  	}
    71  
    72  	dir, err := ioutil.TempDir("", "go-link-TestSectionsWithSameName")
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  	defer os.RemoveAll(dir)
    77  
    78  	gopath := filepath.Join(dir, "GOPATH")
    79  	env := append(os.Environ(), "GOPATH="+gopath)
    80  
    81  	if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	asmFile := filepath.Join(dir, "x.s")
    86  	if err := ioutil.WriteFile(asmFile, []byte(asmSource), 0444); err != nil {
    87  		t.Fatal(err)
    88  	}
    89  
    90  	goTool := testenv.GoToolPath(t)
    91  	cc, cflags := getCCAndCCFLAGS(t, env)
    92  
    93  	asmObj := filepath.Join(dir, "x.o")
    94  	t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile)
    95  	if out, err := exec.Command(cc, append(cflags, "-c", "-o", asmObj, asmFile)...).CombinedOutput(); err != nil {
    96  		t.Logf("%s", out)
    97  		t.Fatal(err)
    98  	}
    99  
   100  	asm2Obj := filepath.Join(dir, "x2.syso")
   101  	t.Logf("%s --rename-section .text2=.text1 %s %s", objcopy, asmObj, asm2Obj)
   102  	if out, err := exec.Command(objcopy, "--rename-section", ".text2=.text1", asmObj, asm2Obj).CombinedOutput(); err != nil {
   103  		t.Logf("%s", out)
   104  		t.Fatal(err)
   105  	}
   106  
   107  	for _, s := range []string{asmFile, asmObj} {
   108  		if err := os.Remove(s); err != nil {
   109  			t.Fatal(err)
   110  		}
   111  	}
   112  
   113  	goFile := filepath.Join(dir, "main.go")
   114  	if err := ioutil.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   115  		t.Fatal(err)
   116  	}
   117  
   118  	cmd := exec.Command(goTool, "build")
   119  	cmd.Dir = dir
   120  	cmd.Env = env
   121  	t.Logf("%s build", goTool)
   122  	if out, err := cmd.CombinedOutput(); err != nil {
   123  		t.Logf("%s", out)
   124  		t.Fatal(err)
   125  	}
   126  }
   127  
   128  var cSources35779 = []string{`
   129  static int blah() { return 42; }
   130  int Cfunc1() { return blah(); }
   131  `, `
   132  static int blah() { return 42; }
   133  int Cfunc2() { return blah(); }
   134  `,
   135  }
   136  
   137  // TestMinusRSymsWithSameName tests a corner case in the new
   138  // loader. Prior to the fix this failed with the error 'loadelf:
   139  // $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in
   140  // both main(.text) and main(.text)'. See issue #35779.
   141  func TestMinusRSymsWithSameName(t *testing.T) {
   142  	testenv.MustHaveGoBuild(t)
   143  	testenv.MustHaveCGO(t)
   144  	t.Parallel()
   145  
   146  	dir, err := ioutil.TempDir("", "go-link-TestMinusRSymsWithSameName")
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	defer os.RemoveAll(dir)
   151  
   152  	gopath := filepath.Join(dir, "GOPATH")
   153  	env := append(os.Environ(), "GOPATH="+gopath)
   154  
   155  	if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
   156  		t.Fatal(err)
   157  	}
   158  
   159  	goTool := testenv.GoToolPath(t)
   160  	cc, cflags := getCCAndCCFLAGS(t, env)
   161  
   162  	objs := []string{}
   163  	csrcs := []string{}
   164  	for i, content := range cSources35779 {
   165  		csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i))
   166  		csrcs = append(csrcs, csrcFile)
   167  		if err := ioutil.WriteFile(csrcFile, []byte(content), 0444); err != nil {
   168  			t.Fatal(err)
   169  		}
   170  
   171  		obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i))
   172  		objs = append(objs, obj)
   173  		t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile)
   174  		if out, err := exec.Command(cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil {
   175  			t.Logf("%s", out)
   176  			t.Fatal(err)
   177  		}
   178  	}
   179  
   180  	sysoObj := filepath.Join(dir, "ldr.syso")
   181  	t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs)
   182  	if out, err := exec.Command(cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil {
   183  		t.Logf("%s", out)
   184  		t.Fatal(err)
   185  	}
   186  
   187  	cruft := [][]string{objs, csrcs}
   188  	for _, sl := range cruft {
   189  		for _, s := range sl {
   190  			if err := os.Remove(s); err != nil {
   191  				t.Fatal(err)
   192  			}
   193  		}
   194  	}
   195  
   196  	goFile := filepath.Join(dir, "main.go")
   197  	if err := ioutil.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   198  		t.Fatal(err)
   199  	}
   200  
   201  	t.Logf("%s build", goTool)
   202  	cmd := exec.Command(goTool, "build")
   203  	cmd.Dir = dir
   204  	cmd.Env = env
   205  	if out, err := cmd.CombinedOutput(); err != nil {
   206  		t.Logf("%s", out)
   207  		t.Fatal(err)
   208  	}
   209  }
   210  
   211  const pieSourceTemplate = `
   212  package main
   213  
   214  import "fmt"
   215  
   216  // Force the creation of a lot of type descriptors that will go into
   217  // the .data.rel.ro section.
   218  {{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{}
   219  {{end}}
   220  
   221  func main() {
   222  {{range $index, $element := .}}	fmt.Println(V{{$index}})
   223  {{end}}
   224  }
   225  `
   226  
   227  func TestPIESize(t *testing.T) {
   228  	testenv.MustHaveGoBuild(t)
   229  	if !sys.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) {
   230  		t.Skip("-buildmode=pie not supported")
   231  	}
   232  
   233  	tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate))
   234  
   235  	writeGo := func(t *testing.T, dir string) {
   236  		f, err := os.Create(filepath.Join(dir, "pie.go"))
   237  		if err != nil {
   238  			t.Fatal(err)
   239  		}
   240  
   241  		// Passing a 100-element slice here will cause
   242  		// pieSourceTemplate to create 100 variables with
   243  		// different types.
   244  		if err := tmpl.Execute(f, make([]byte, 100)); err != nil {
   245  			t.Fatal(err)
   246  		}
   247  
   248  		if err := f.Close(); err != nil {
   249  			t.Fatal(err)
   250  		}
   251  	}
   252  
   253  	for _, external := range []bool{false, true} {
   254  		external := external
   255  
   256  		name := "TestPieSize-"
   257  		if external {
   258  			name += "external"
   259  		} else {
   260  			name += "internal"
   261  		}
   262  		t.Run(name, func(t *testing.T) {
   263  			t.Parallel()
   264  
   265  			dir, err := ioutil.TempDir("", "go-link-"+name)
   266  			if err != nil {
   267  				t.Fatal(err)
   268  			}
   269  			defer os.RemoveAll(dir)
   270  
   271  			writeGo(t, dir)
   272  
   273  			binexe := filepath.Join(dir, "exe")
   274  			binpie := filepath.Join(dir, "pie")
   275  			if external {
   276  				binexe += "external"
   277  				binpie += "external"
   278  			}
   279  
   280  			build := func(bin, mode string) error {
   281  				cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode)
   282  				if external {
   283  					cmd.Args = append(cmd.Args, "-ldflags=-linkmode=external")
   284  				}
   285  				cmd.Args = append(cmd.Args, "pie.go")
   286  				cmd.Dir = dir
   287  				t.Logf("%v", cmd.Args)
   288  				out, err := cmd.CombinedOutput()
   289  				if len(out) > 0 {
   290  					t.Logf("%s", out)
   291  				}
   292  				if err != nil {
   293  					t.Error(err)
   294  				}
   295  				return err
   296  			}
   297  
   298  			var errexe, errpie error
   299  			var wg sync.WaitGroup
   300  			wg.Add(2)
   301  			go func() {
   302  				defer wg.Done()
   303  				errexe = build(binexe, "exe")
   304  			}()
   305  			go func() {
   306  				defer wg.Done()
   307  				errpie = build(binpie, "pie")
   308  			}()
   309  			wg.Wait()
   310  			if errexe != nil || errpie != nil {
   311  				t.Fatal("link failed")
   312  			}
   313  
   314  			var sizeexe, sizepie uint64
   315  			if fi, err := os.Stat(binexe); err != nil {
   316  				t.Fatal(err)
   317  			} else {
   318  				sizeexe = uint64(fi.Size())
   319  			}
   320  			if fi, err := os.Stat(binpie); err != nil {
   321  				t.Fatal(err)
   322  			} else {
   323  				sizepie = uint64(fi.Size())
   324  			}
   325  
   326  			elfexe, err := elf.Open(binexe)
   327  			if err != nil {
   328  				t.Fatal(err)
   329  			}
   330  			defer elfexe.Close()
   331  
   332  			elfpie, err := elf.Open(binpie)
   333  			if err != nil {
   334  				t.Fatal(err)
   335  			}
   336  			defer elfpie.Close()
   337  
   338  			// The difference in size between exe and PIE
   339  			// should be approximately the difference in
   340  			// size of the .text section plus the size of
   341  			// the PIE dynamic data sections plus the
   342  			// difference in size of the .got and .plt
   343  			// sections if they exist.
   344  			// We ignore unallocated sections.
   345  			// There may be gaps between non-writeable and
   346  			// writable PT_LOAD segments. We also skip those
   347  			// gaps (see issue #36023).
   348  
   349  			textsize := func(ef *elf.File, name string) uint64 {
   350  				for _, s := range ef.Sections {
   351  					if s.Name == ".text" {
   352  						return s.Size
   353  					}
   354  				}
   355  				t.Fatalf("%s: no .text section", name)
   356  				return 0
   357  			}
   358  			textexe := textsize(elfexe, binexe)
   359  			textpie := textsize(elfpie, binpie)
   360  
   361  			dynsize := func(ef *elf.File) uint64 {
   362  				var ret uint64
   363  				for _, s := range ef.Sections {
   364  					if s.Flags&elf.SHF_ALLOC == 0 {
   365  						continue
   366  					}
   367  					switch s.Type {
   368  					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:
   369  						ret += s.Size
   370  					}
   371  					if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) {
   372  						ret += s.Size
   373  					}
   374  				}
   375  				return ret
   376  			}
   377  
   378  			dynexe := dynsize(elfexe)
   379  			dynpie := dynsize(elfpie)
   380  
   381  			extrasize := func(ef *elf.File) uint64 {
   382  				var ret uint64
   383  				// skip unallocated sections
   384  				for _, s := range ef.Sections {
   385  					if s.Flags&elf.SHF_ALLOC == 0 {
   386  						ret += s.Size
   387  					}
   388  				}
   389  				// also skip gaps between PT_LOAD segments
   390  				for i := range ef.Progs {
   391  					if i == 0 {
   392  						continue
   393  					}
   394  					p1 := ef.Progs[i-1]
   395  					p2 := ef.Progs[i]
   396  					if p1.Type == elf.PT_LOAD && p2.Type == elf.PT_LOAD {
   397  						ret += p2.Off - p1.Off - p1.Filesz
   398  					}
   399  				}
   400  				return ret
   401  			}
   402  
   403  			extraexe := extrasize(elfexe)
   404  			extrapie := extrasize(elfpie)
   405  
   406  			diffReal := (sizepie - extrapie) - (sizeexe - extraexe)
   407  			diffExpected := (textpie + dynpie) - (textexe + dynexe)
   408  
   409  			t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected)
   410  
   411  			if diffReal > (diffExpected + diffExpected/10) {
   412  				t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected)
   413  			}
   414  		})
   415  	}
   416  }
   417  

View as plain text