Source file src/cmd/link/elf_test.go

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

View as plain text