...

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

View as plain text