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

View as plain text