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  	"encoding/binary"
    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  	"unsafe"
    27  )
    28  
    29  func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) {
    30  	goTool := testenv.GoToolPath(t)
    31  	cmd := testenv.Command(t, goTool, "env", "CC")
    32  	cmd.Env = env
    33  	ccb, err := cmd.Output()
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  	cc := strings.TrimSpace(string(ccb))
    38  
    39  	cmd = testenv.Command(t, goTool, "env", "GOGCCFLAGS")
    40  	cmd.Env = env
    41  	cflagsb, err := cmd.Output()
    42  	if err != nil {
    43  		t.Fatal(err)
    44  	}
    45  	cflags := strings.Fields(string(cflagsb))
    46  
    47  	return cc, cflags
    48  }
    49  
    50  var asmSource = `
    51  	.section .text1,"ax"
    52  s1:
    53  	.byte 0
    54  	.section .text2,"ax"
    55  s2:
    56  	.byte 0
    57  `
    58  
    59  var goSource = `
    60  package main
    61  func main() {}
    62  `
    63  
    64  var goSourceWithData = `
    65  package main
    66  var globalVar = 42
    67  func main() { println(&globalVar) }
    68  `
    69  
    70  // The linker used to crash if an ELF input file had multiple text sections
    71  // with the same name.
    72  func TestSectionsWithSameName(t *testing.T) {
    73  	testenv.MustHaveGoBuild(t)
    74  	testenv.MustHaveCGO(t)
    75  	t.Parallel()
    76  
    77  	objcopy, err := exec.LookPath("objcopy")
    78  	if err != nil {
    79  		t.Skipf("can't find objcopy: %v", err)
    80  	}
    81  
    82  	dir := t.TempDir()
    83  
    84  	gopath := filepath.Join(dir, "GOPATH")
    85  	gopathEnv := "GOPATH=" + gopath
    86  	env := append(os.Environ(), gopathEnv)
    87  
    88  	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
    89  		t.Fatal(err)
    90  	}
    91  
    92  	asmFile := filepath.Join(dir, "x.s")
    93  	if err := os.WriteFile(asmFile, []byte(asmSource), 0444); err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	cc, cflags := getCCAndCCFLAGS(t, env)
    98  
    99  	asmObj := filepath.Join(dir, "x.o")
   100  	t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile)
   101  	if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", asmObj, asmFile)...).CombinedOutput(); err != nil {
   102  		t.Logf("%s", out)
   103  		t.Fatal(err)
   104  	}
   105  
   106  	asm2Obj := filepath.Join(dir, "x2.syso")
   107  	t.Logf("%s --rename-section .text2=.text1 %s %s", objcopy, asmObj, asm2Obj)
   108  	if out, err := testenv.Command(t, objcopy, "--rename-section", ".text2=.text1", asmObj, asm2Obj).CombinedOutput(); err != nil {
   109  		t.Logf("%s", out)
   110  		t.Fatal(err)
   111  	}
   112  
   113  	for _, s := range []string{asmFile, asmObj} {
   114  		if err := os.Remove(s); err != nil {
   115  			t.Fatal(err)
   116  		}
   117  	}
   118  
   119  	goFile := filepath.Join(dir, "main.go")
   120  	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   121  		t.Fatal(err)
   122  	}
   123  
   124  	cmd := goCmd(t, "build")
   125  	cmd.Dir = dir
   126  	cmd.Env = append(cmd.Env, gopathEnv)
   127  	t.Logf("%s build", testenv.GoToolPath(t))
   128  	if out, err := cmd.CombinedOutput(); err != nil {
   129  		t.Logf("%s", out)
   130  		t.Fatal(err)
   131  	}
   132  }
   133  
   134  var cSources35779 = []string{`
   135  static int blah() { return 42; }
   136  int Cfunc1() { return blah(); }
   137  `, `
   138  static int blah() { return 42; }
   139  int Cfunc2() { return blah(); }
   140  `,
   141  }
   142  
   143  // TestMinusRSymsWithSameName tests a corner case in the new
   144  // loader. Prior to the fix this failed with the error 'loadelf:
   145  // $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in
   146  // both main(.text) and main(.text)'. See issue #35779.
   147  func TestMinusRSymsWithSameName(t *testing.T) {
   148  	testenv.MustHaveGoBuild(t)
   149  	testenv.MustHaveCGO(t)
   150  	t.Parallel()
   151  
   152  	dir := t.TempDir()
   153  
   154  	gopath := filepath.Join(dir, "GOPATH")
   155  	gopathEnv := "GOPATH=" + gopath
   156  	env := append(os.Environ(), gopathEnv)
   157  
   158  	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
   159  		t.Fatal(err)
   160  	}
   161  
   162  	cc, cflags := getCCAndCCFLAGS(t, env)
   163  
   164  	objs := []string{}
   165  	csrcs := []string{}
   166  	for i, content := range cSources35779 {
   167  		csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i))
   168  		csrcs = append(csrcs, csrcFile)
   169  		if err := os.WriteFile(csrcFile, []byte(content), 0444); err != nil {
   170  			t.Fatal(err)
   171  		}
   172  
   173  		obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i))
   174  		objs = append(objs, obj)
   175  		t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile)
   176  		if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil {
   177  			t.Logf("%s", out)
   178  			t.Fatal(err)
   179  		}
   180  	}
   181  
   182  	sysoObj := filepath.Join(dir, "ldr.syso")
   183  	t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs)
   184  	if out, err := testenv.Command(t, cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil {
   185  		t.Logf("%s", out)
   186  		t.Fatal(err)
   187  	}
   188  
   189  	cruft := [][]string{objs, csrcs}
   190  	for _, sl := range cruft {
   191  		for _, s := range sl {
   192  			if err := os.Remove(s); err != nil {
   193  				t.Fatal(err)
   194  			}
   195  		}
   196  	}
   197  
   198  	goFile := filepath.Join(dir, "main.go")
   199  	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   200  		t.Fatal(err)
   201  	}
   202  
   203  	t.Logf("%s build", testenv.GoToolPath(t))
   204  	cmd := goCmd(t, "build")
   205  	cmd.Dir = dir
   206  	cmd.Env = append(cmd.Env, gopathEnv)
   207  	if out, err := cmd.CombinedOutput(); err != nil {
   208  		t.Logf("%s", out)
   209  		t.Fatal(err)
   210  	}
   211  }
   212  
   213  func TestGNUBuildID(t *testing.T) {
   214  	testenv.MustHaveGoBuild(t)
   215  
   216  	t.Parallel()
   217  
   218  	tmpdir := t.TempDir()
   219  	goFile := filepath.Join(tmpdir, "notes.go")
   220  	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	// Use a specific Go buildid for testing.
   225  	const gobuildid = "testbuildid"
   226  	h := hash.Sum32([]byte(gobuildid))
   227  	gobuildidHash := string(h[:20])
   228  
   229  	tests := []struct{ name, ldflags, expect string }{
   230  		{"default", "", gobuildidHash},
   231  		{"gobuildid", "-B=gobuildid", gobuildidHash},
   232  		{"specific", "-B=0x0123456789abcdef", "\x01\x23\x45\x67\x89\xab\xcd\xef"},
   233  		{"none", "-B=none", ""},
   234  	}
   235  	if testenv.HasCGO() && runtime.GOOS != "solaris" && runtime.GOOS != "illumos" {
   236  		// Solaris ld doesn't support --build-id. So we don't
   237  		// add it in external linking mode.
   238  		for _, test := range tests {
   239  			t1 := test
   240  			t1.name += "_external"
   241  			t1.ldflags += " -linkmode=external"
   242  			tests = append(tests, t1)
   243  		}
   244  	}
   245  	for _, test := range tests {
   246  		t.Run(test.name, func(t *testing.T) {
   247  			exe := filepath.Join(tmpdir, test.name)
   248  			cmd := goCmd(t, "build", "-ldflags=-buildid="+gobuildid+" "+test.ldflags, "-o", exe, goFile)
   249  			if out, err := cmd.CombinedOutput(); err != nil {
   250  				t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
   251  			}
   252  			gnuBuildID, err := buildid.ReadELFNote(exe, string(ld.ELF_NOTE_BUILDINFO_NAME), ld.ELF_NOTE_BUILDINFO_TAG)
   253  			if err != nil {
   254  				t.Fatalf("can't read GNU build ID")
   255  			}
   256  			if string(gnuBuildID) != test.expect {
   257  				t.Errorf("build id mismatch: got %x, want %x", gnuBuildID, test.expect)
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  func TestMergeNoteSections(t *testing.T) {
   264  	testenv.MustHaveGoBuild(t)
   265  	expected := 1
   266  
   267  	switch runtime.GOOS {
   268  	case "linux", "dragonfly":
   269  	case "openbsd", "netbsd", "freebsd":
   270  		// These OSes require independent segment
   271  		expected = 2
   272  	default:
   273  		t.Skip("We should only test on elf output.")
   274  	}
   275  	t.Parallel()
   276  
   277  	goFile := filepath.Join(t.TempDir(), "notes.go")
   278  	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	outFile := filepath.Join(t.TempDir(), "notes.exe")
   282  	// sha1sum of "gopher"
   283  	id := "0xf4e8cd51ce8bae2996dc3b74639cdeaa1f7fee5f"
   284  	cmd := goCmd(t, "build", "-o", outFile, "-ldflags", "-B "+id, goFile)
   285  	cmd.Dir = t.TempDir()
   286  	if out, err := cmd.CombinedOutput(); err != nil {
   287  		t.Logf("%s", out)
   288  		t.Fatal(err)
   289  	}
   290  
   291  	ef, err := elf.Open(outFile)
   292  	if err != nil {
   293  		t.Fatalf("open elf file failed:%v", err)
   294  	}
   295  	defer ef.Close()
   296  	sec := ef.Section(".note.gnu.build-id")
   297  	if sec == nil {
   298  		t.Fatalf("can't find gnu build id")
   299  	}
   300  
   301  	sec = ef.Section(".note.go.buildid")
   302  	if sec == nil {
   303  		t.Fatalf("can't find go build id")
   304  	}
   305  	cnt := 0
   306  	for _, ph := range ef.Progs {
   307  		if ph.Type == elf.PT_NOTE {
   308  			cnt += 1
   309  		}
   310  	}
   311  	if cnt != expected {
   312  		t.Fatalf("want %d PT_NOTE segment, got %d", expected, cnt)
   313  	}
   314  }
   315  
   316  const pieSourceTemplate = `
   317  package main
   318  
   319  import "fmt"
   320  
   321  // Force the creation of a lot of type descriptors that will go into
   322  // the .data.rel.ro section.
   323  {{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{}
   324  {{end}}
   325  
   326  func main() {
   327  {{range $index, $element := .}}	fmt.Println(V{{$index}})
   328  {{end}}
   329  }
   330  `
   331  
   332  func TestPIESize(t *testing.T) {
   333  	testenv.MustHaveGoBuild(t)
   334  
   335  	// We don't want to test -linkmode=external if cgo is not supported.
   336  	// On some systems -buildmode=pie implies -linkmode=external, so just
   337  	// always skip the test if cgo is not supported.
   338  	testenv.MustHaveCGO(t)
   339  
   340  	if !platform.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) {
   341  		t.Skip("-buildmode=pie not supported")
   342  	}
   343  
   344  	t.Parallel()
   345  
   346  	tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate))
   347  
   348  	writeGo := func(t *testing.T, dir string) {
   349  		f, err := os.Create(filepath.Join(dir, "pie.go"))
   350  		if err != nil {
   351  			t.Fatal(err)
   352  		}
   353  
   354  		// Passing a 100-element slice here will cause
   355  		// pieSourceTemplate to create 100 variables with
   356  		// different types.
   357  		if err := tmpl.Execute(f, make([]byte, 100)); err != nil {
   358  			t.Fatal(err)
   359  		}
   360  
   361  		if err := f.Close(); err != nil {
   362  			t.Fatal(err)
   363  		}
   364  	}
   365  
   366  	var linkmodes []string
   367  	if platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
   368  		linkmodes = append(linkmodes, "internal")
   369  	}
   370  	linkmodes = append(linkmodes, "external")
   371  
   372  	for _, linkmode := range linkmodes {
   373  		t.Run(fmt.Sprintf("TestPieSize-%v", linkmode), func(t *testing.T) {
   374  			t.Parallel()
   375  
   376  			dir := t.TempDir()
   377  
   378  			writeGo(t, dir)
   379  
   380  			binexe := filepath.Join(dir, "exe")
   381  			binpie := filepath.Join(dir, "pie")
   382  			binexe += linkmode
   383  			binpie += linkmode
   384  
   385  			build := func(bin, mode string) error {
   386  				cmd := goCmd(t, "build", "-o", bin, "-buildmode="+mode, "-ldflags=-linkmode="+linkmode)
   387  				cmd.Args = append(cmd.Args, "pie.go")
   388  				cmd.Dir = dir
   389  				t.Logf("%v", cmd.Args)
   390  				out, err := cmd.CombinedOutput()
   391  				if len(out) > 0 {
   392  					t.Logf("%s", out)
   393  				}
   394  				if err != nil {
   395  					t.Log(err)
   396  				}
   397  				return err
   398  			}
   399  
   400  			var errexe, errpie error
   401  			var wg sync.WaitGroup
   402  			wg.Add(2)
   403  			go func() {
   404  				defer wg.Done()
   405  				errexe = build(binexe, "exe")
   406  			}()
   407  			go func() {
   408  				defer wg.Done()
   409  				errpie = build(binpie, "pie")
   410  			}()
   411  			wg.Wait()
   412  			if errexe != nil || errpie != nil {
   413  				if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
   414  					testenv.SkipFlaky(t, 58806)
   415  				}
   416  				t.Fatal("link failed")
   417  			}
   418  
   419  			var sizeexe, sizepie uint64
   420  			if fi, err := os.Stat(binexe); err != nil {
   421  				t.Fatal(err)
   422  			} else {
   423  				sizeexe = uint64(fi.Size())
   424  			}
   425  			if fi, err := os.Stat(binpie); err != nil {
   426  				t.Fatal(err)
   427  			} else {
   428  				sizepie = uint64(fi.Size())
   429  			}
   430  
   431  			elfexe, err := elf.Open(binexe)
   432  			if err != nil {
   433  				t.Fatal(err)
   434  			}
   435  			defer elfexe.Close()
   436  
   437  			elfpie, err := elf.Open(binpie)
   438  			if err != nil {
   439  				t.Fatal(err)
   440  			}
   441  			defer elfpie.Close()
   442  
   443  			// The difference in size between exe and PIE
   444  			// should be approximately the difference in
   445  			// size of the .text section plus the size of
   446  			// the PIE dynamic data sections plus the
   447  			// difference in size of the .got and .plt
   448  			// sections if they exist.
   449  			// We ignore unallocated sections.
   450  			// There may be gaps between non-writeable and
   451  			// writable PT_LOAD segments. We also skip those
   452  			// gaps (see issue #36023).
   453  
   454  			textsize := func(ef *elf.File, name string) uint64 {
   455  				for _, s := range ef.Sections {
   456  					if s.Name == ".text" {
   457  						return s.Size
   458  					}
   459  				}
   460  				t.Fatalf("%s: no .text section", name)
   461  				return 0
   462  			}
   463  			textexe := textsize(elfexe, binexe)
   464  			textpie := textsize(elfpie, binpie)
   465  
   466  			dynsize := func(ef *elf.File) uint64 {
   467  				var ret uint64
   468  				for _, s := range ef.Sections {
   469  					if s.Flags&elf.SHF_ALLOC == 0 {
   470  						continue
   471  					}
   472  					switch s.Type {
   473  					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:
   474  						ret += s.Size
   475  					}
   476  					if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) {
   477  						ret += s.Size
   478  					}
   479  				}
   480  				return ret
   481  			}
   482  
   483  			dynexe := dynsize(elfexe)
   484  			dynpie := dynsize(elfpie)
   485  
   486  			extrasize := func(ef *elf.File) uint64 {
   487  				var ret uint64
   488  				// skip unallocated sections
   489  				for _, s := range ef.Sections {
   490  					if s.Flags&elf.SHF_ALLOC == 0 {
   491  						ret += s.Size
   492  					}
   493  				}
   494  				// also skip gaps between PT_LOAD segments
   495  				var prev *elf.Prog
   496  				for _, seg := range ef.Progs {
   497  					if seg.Type != elf.PT_LOAD {
   498  						continue
   499  					}
   500  					if prev != nil {
   501  						ret += seg.Off - prev.Off - prev.Filesz
   502  					}
   503  					prev = seg
   504  				}
   505  				return ret
   506  			}
   507  
   508  			extraexe := extrasize(elfexe)
   509  			extrapie := extrasize(elfpie)
   510  
   511  			if sizepie < sizeexe || sizepie-extrapie < sizeexe-extraexe {
   512  				return
   513  			}
   514  			diffReal := (sizepie - extrapie) - (sizeexe - extraexe)
   515  			diffExpected := (textpie + dynpie) - (textexe + dynexe)
   516  
   517  			t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected)
   518  
   519  			if diffReal > (diffExpected + diffExpected/10) {
   520  				t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected)
   521  			}
   522  		})
   523  	}
   524  }
   525  
   526  func TestIssue51939(t *testing.T) {
   527  	testenv.MustHaveGoBuild(t)
   528  	t.Parallel()
   529  	td := t.TempDir()
   530  	goFile := filepath.Join(td, "issue51939.go")
   531  	if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	outFile := filepath.Join(td, "issue51939.exe")
   535  	cmd := goCmd(t, "build", "-o", outFile, goFile)
   536  	if out, err := cmd.CombinedOutput(); err != nil {
   537  		t.Logf("%s", out)
   538  		t.Fatal(err)
   539  	}
   540  
   541  	ef, err := elf.Open(outFile)
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  
   546  	for _, s := range ef.Sections {
   547  		if s.Flags&elf.SHF_ALLOC == 0 && s.Addr != 0 {
   548  			t.Errorf("section %s should not allocated with addr %x", s.Name, s.Addr)
   549  		}
   550  	}
   551  }
   552  
   553  func TestFlagR(t *testing.T) {
   554  	// Test that using the -R flag to specify a (large) alignment generates
   555  	// a working binary.
   556  	// (Test only on ELF for now. The alignment allowed differs from platform
   557  	// to platform.)
   558  	testenv.MustHaveGoBuild(t)
   559  	t.Parallel()
   560  	tmpdir := t.TempDir()
   561  	src := filepath.Join(tmpdir, "x.go")
   562  	if err := os.WriteFile(src, []byte(goSource), 0444); err != nil {
   563  		t.Fatal(err)
   564  	}
   565  	exe := filepath.Join(tmpdir, "x.exe")
   566  
   567  	cmd := goCmd(t, "build", "-ldflags=-R=0x100000", "-o", exe, src)
   568  	if out, err := cmd.CombinedOutput(); err != nil {
   569  		t.Fatalf("build failed: %v, output:\n%s", err, out)
   570  	}
   571  
   572  	cmd = testenv.Command(t, exe)
   573  	if out, err := cmd.CombinedOutput(); err != nil {
   574  		t.Errorf("executable failed to run: %v\n%s", err, out)
   575  	}
   576  }
   577  
   578  func TestFlagD(t *testing.T) {
   579  	// Test that using the -D flag to specify data section address generates
   580  	// a working binary with data at the specified address.
   581  	t.Parallel()
   582  	testFlagD(t, "0x10000000", "", 0x10000000)
   583  }
   584  
   585  func TestFlagDUnaligned(t *testing.T) {
   586  	// Test that using the -D flag with an unaligned address errors out
   587  	t.Parallel()
   588  	testFlagDError(t, "0x10000123", "", "invalid -D value 0x10000123")
   589  }
   590  
   591  func TestFlagDWithR(t *testing.T) {
   592  	// Test that using the -D flag with -R flag errors on unaligned address.
   593  	t.Parallel()
   594  	testFlagDError(t, "0x30001234", "8192", "invalid -D value 0x30001234")
   595  }
   596  
   597  func testFlagD(t *testing.T, dataAddr string, roundQuantum string, expectedAddr uint64) {
   598  	testenv.MustHaveGoBuild(t)
   599  	tmpdir := t.TempDir()
   600  	src := filepath.Join(tmpdir, "x.go")
   601  	if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil {
   602  		t.Fatal(err)
   603  	}
   604  	exe := filepath.Join(tmpdir, "x.exe")
   605  
   606  	// Build linker flags
   607  	ldflags := "-D=" + dataAddr
   608  	if roundQuantum != "" {
   609  		ldflags += " -R=" + roundQuantum
   610  	}
   611  
   612  	cmd := goCmd(t, "build", "-ldflags="+ldflags, "-o", exe, src)
   613  	if out, err := cmd.CombinedOutput(); err != nil {
   614  		t.Fatalf("build failed: %v, output:\n%s", err, out)
   615  	}
   616  
   617  	cmd = testenv.Command(t, exe)
   618  	if out, err := cmd.CombinedOutput(); err != nil {
   619  		t.Errorf("executable failed to run: %v\n%s", err, out)
   620  	}
   621  
   622  	ef, err := elf.Open(exe)
   623  	if err != nil {
   624  		t.Fatalf("open elf file failed: %v", err)
   625  	}
   626  	defer ef.Close()
   627  
   628  	// Find the first data-related section to verify segment placement
   629  	var firstDataSection *elf.Section
   630  	for _, sec := range ef.Sections {
   631  		if sec.Type == elf.SHT_PROGBITS || sec.Type == elf.SHT_NOBITS {
   632  			// These sections are writable, allocated at runtime, but not executable
   633  			// nor TLS.
   634  			isWrite := sec.Flags&elf.SHF_WRITE != 0
   635  			isExec := sec.Flags&elf.SHF_EXECINSTR != 0
   636  			isAlloc := sec.Flags&elf.SHF_ALLOC != 0
   637  			isTLS := sec.Flags&elf.SHF_TLS != 0
   638  
   639  			if isWrite && !isExec && isAlloc && !isTLS {
   640  				if firstDataSection == nil || sec.Addr < firstDataSection.Addr {
   641  					firstDataSection = sec
   642  				}
   643  			}
   644  		}
   645  	}
   646  
   647  	if firstDataSection == nil {
   648  		t.Fatalf("can't find any writable data sections")
   649  	}
   650  	if firstDataSection.Addr != expectedAddr {
   651  		t.Errorf("data section starts at 0x%x for section %s, expected 0x%x",
   652  			firstDataSection.Addr, firstDataSection.Name, expectedAddr)
   653  	}
   654  }
   655  
   656  func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expectedError string) {
   657  	testenv.MustHaveGoBuild(t)
   658  	tmpdir := t.TempDir()
   659  	src := filepath.Join(tmpdir, "x.go")
   660  	if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil {
   661  		t.Fatal(err)
   662  	}
   663  	exe := filepath.Join(tmpdir, "x.exe")
   664  
   665  	// Build linker flags
   666  	ldflags := "-D=" + dataAddr
   667  	if roundQuantum != "" {
   668  		ldflags += " -R=" + roundQuantum
   669  	}
   670  
   671  	cmd := goCmd(t, "build", "-ldflags="+ldflags, "-o", exe, src)
   672  	out, err := cmd.CombinedOutput()
   673  	if err == nil {
   674  		t.Fatalf("expected build to fail with unaligned data address, but it succeeded")
   675  	}
   676  	if !strings.Contains(string(out), expectedError) {
   677  		t.Errorf("expected error message to contain %q, got:\n%s", expectedError, out)
   678  	}
   679  }
   680  
   681  func TestELFHeadersSorted(t *testing.T) {
   682  	for _, buildmode := range []string{"exe", "pie"} {
   683  		t.Run(buildmode, func(t *testing.T) {
   684  			testELFHeadersSorted(t, buildmode)
   685  		})
   686  	}
   687  }
   688  
   689  func testELFHeadersSorted(t *testing.T, buildmode string) {
   690  	testenv.MustHaveGoBuild(t)
   691  
   692  	// We can only test this for internal linking mode.
   693  	// For external linking the external linker will
   694  	// decide how to sort the sections.
   695  	testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
   696  	if buildmode == "pie" {
   697  		testenv.MustInternalLinkPIE(t)
   698  	}
   699  
   700  	t.Parallel()
   701  
   702  	tmpdir := t.TempDir()
   703  	src := filepath.Join(tmpdir, "x.go")
   704  	if err := os.WriteFile(src, []byte(goSourceWithData), 0o444); err != nil {
   705  		t.Fatal(err)
   706  	}
   707  
   708  	exe := filepath.Join(tmpdir, "x.exe")
   709  	cmd := goCmd(t, "build", "-buildmode="+buildmode, "-ldflags=-linkmode=internal", "-o", exe, src)
   710  	if out, err := cmd.CombinedOutput(); err != nil {
   711  		t.Fatalf("build failed: %v, output:\n%s", err, out)
   712  	}
   713  
   714  	// Check that the first section header is all zeroes.
   715  	f, err := os.Open(exe)
   716  	if err != nil {
   717  		t.Fatal(err)
   718  	}
   719  	defer f.Close()
   720  
   721  	var ident [elf.EI_NIDENT]byte
   722  	if _, err := f.Read(ident[:]); err != nil {
   723  		t.Fatal(err)
   724  	}
   725  
   726  	var bo binary.ByteOrder
   727  	switch elf.Data(ident[elf.EI_DATA]) {
   728  	case elf.ELFDATA2LSB:
   729  		bo = binary.LittleEndian
   730  	case elf.ELFDATA2MSB:
   731  		bo = binary.BigEndian
   732  	default:
   733  		t.Fatalf("unrecognized data encoding %d", ident[elf.EI_DATA])
   734  	}
   735  
   736  	var shoff int64
   737  	var shsize int
   738  	switch elf.Class(ident[elf.EI_CLASS]) {
   739  	case elf.ELFCLASS32:
   740  		var hdr elf.Header32
   741  		data := make([]byte, unsafe.Sizeof(hdr))
   742  		if _, err := f.ReadAt(data, 0); err != nil {
   743  			t.Fatal(err)
   744  		}
   745  		shoff = int64(bo.Uint32(data[unsafe.Offsetof(hdr.Shoff):]))
   746  		shsize = int(unsafe.Sizeof(elf.Section32{}))
   747  
   748  	case elf.ELFCLASS64:
   749  		var hdr elf.Header64
   750  		data := make([]byte, unsafe.Sizeof(hdr))
   751  		if _, err := f.ReadAt(data, 0); err != nil {
   752  			t.Fatal(err)
   753  		}
   754  		shoff = int64(bo.Uint64(data[unsafe.Offsetof(hdr.Shoff):]))
   755  		shsize = int(unsafe.Sizeof(elf.Section64{}))
   756  
   757  	default:
   758  		t.Fatalf("unrecognized class %d", ident[elf.EI_CLASS])
   759  	}
   760  
   761  	if shoff > 0 {
   762  		data := make([]byte, shsize)
   763  		if _, err := f.ReadAt(data, shoff); err != nil {
   764  			t.Fatal(err)
   765  		}
   766  		for i, c := range data {
   767  			if c != 0 {
   768  				t.Errorf("section header 0 byte %d is %d, should be zero", i, c)
   769  			}
   770  		}
   771  	}
   772  
   773  	ef, err := elf.NewFile(f)
   774  	if err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	defer ef.Close()
   778  
   779  	// After the first zero section header,
   780  	// we should see allocated sections,
   781  	// then unallocated sections.
   782  	// The allocated sections should be sorted by address.
   783  	i := 1
   784  	lastAddr := uint64(0)
   785  	for i < len(ef.Sections) {
   786  		sec := ef.Sections[i]
   787  		if sec.Flags&elf.SHF_ALLOC == 0 {
   788  			break
   789  		}
   790  		if sec.Addr < lastAddr {
   791  			t.Errorf("section %d %q address %#x less than previous address %#x", i, sec.Name, sec.Addr, lastAddr)
   792  		}
   793  		lastAddr = sec.Addr
   794  		i++
   795  	}
   796  
   797  	firstUnalc := i
   798  	for i < len(ef.Sections) {
   799  		sec := ef.Sections[i]
   800  		if sec.Flags&elf.SHF_ALLOC != 0 {
   801  			t.Errorf("allocated section %d %q follows first unallocated section %d %q", i, sec.Name, firstUnalc, ef.Sections[firstUnalc].Name)
   802  		}
   803  		i++
   804  	}
   805  }
   806  

View as plain text