Source file src/cmd/link/internal/ld/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 cgo
     6  
     7  package ld
     8  
     9  import (
    10  	"debug/elf"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"runtime"
    17  	"sort"
    18  	"strings"
    19  	"testing"
    20  )
    21  
    22  func TestDynSymShInfo(t *testing.T) {
    23  	t.Parallel()
    24  	testenv.MustHaveGoBuild(t)
    25  	dir := t.TempDir()
    26  
    27  	const prog = `
    28  package main
    29  
    30  import "net"
    31  
    32  func main() {
    33  	net.Dial("", "")
    34  }
    35  `
    36  	src := filepath.Join(dir, "issue33358.go")
    37  	if err := os.WriteFile(src, []byte(prog), 0666); err != nil {
    38  		t.Fatal(err)
    39  	}
    40  
    41  	binFile := filepath.Join(dir, "issue33358")
    42  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, src)
    43  	if out, err := cmd.CombinedOutput(); err != nil {
    44  		t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
    45  	}
    46  
    47  	fi, err := os.Open(binFile)
    48  	if err != nil {
    49  		t.Fatalf("failed to open built file: %v", err)
    50  	}
    51  	defer fi.Close()
    52  
    53  	elfFile, err := elf.NewFile(fi)
    54  	if err != nil {
    55  		t.Skip("The system may not support ELF, skipped.")
    56  	}
    57  
    58  	section := elfFile.Section(".dynsym")
    59  	if section == nil {
    60  		t.Fatal("no dynsym")
    61  	}
    62  
    63  	symbols, err := elfFile.DynamicSymbols()
    64  	if err != nil {
    65  		t.Fatalf("failed to get dynamic symbols: %v", err)
    66  	}
    67  
    68  	var numLocalSymbols uint32
    69  	for i, s := range symbols {
    70  		if elf.ST_BIND(s.Info) != elf.STB_LOCAL {
    71  			numLocalSymbols = uint32(i + 1)
    72  			break
    73  		}
    74  	}
    75  
    76  	if section.Info != numLocalSymbols {
    77  		t.Fatalf("Unexpected sh info, want greater than 0, got: %d", section.Info)
    78  	}
    79  }
    80  
    81  func TestNoDuplicateNeededEntries(t *testing.T) {
    82  	testenv.MustHaveGoBuild(t)
    83  	testenv.MustHaveCGO(t)
    84  
    85  	// run this test on just a small set of platforms (no need to test it
    86  	// across the board given the nature of the test).
    87  	pair := runtime.GOOS + "-" + runtime.GOARCH
    88  	switch pair {
    89  	case "linux-amd64", "linux-arm64", "freebsd-amd64", "openbsd-amd64":
    90  	default:
    91  		t.Skip("no need for test on " + pair)
    92  	}
    93  
    94  	t.Parallel()
    95  
    96  	dir := t.TempDir()
    97  	path := filepath.Join(dir, "x")
    98  	argv := []string{"build", "-o", path, "./testdata/issue39256"}
    99  	out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
   100  	if err != nil {
   101  		t.Fatalf("Build failure: %s\n%s\n", err, string(out))
   102  	}
   103  
   104  	f, err := elf.Open(path)
   105  	if err != nil {
   106  		t.Fatalf("Failed to open ELF file: %v", err)
   107  	}
   108  	libs, err := f.ImportedLibraries()
   109  	if err != nil {
   110  		t.Fatalf("Failed to read imported libraries: %v", err)
   111  	}
   112  
   113  	var count int
   114  	for _, lib := range libs {
   115  		if lib == "libc.so" || strings.HasPrefix(lib, "libc.so.") {
   116  			count++
   117  		}
   118  	}
   119  
   120  	if got, want := count, 1; got != want {
   121  		t.Errorf("Got %d entries for `libc.so`, want %d", got, want)
   122  	}
   123  }
   124  
   125  func TestShStrTabAttributesIssue62600(t *testing.T) {
   126  	t.Parallel()
   127  	testenv.MustHaveGoBuild(t)
   128  	dir := t.TempDir()
   129  
   130  	const prog = `
   131  package main
   132  
   133  func main() {
   134  	println("whee")
   135  }
   136  `
   137  	src := filepath.Join(dir, "issue62600.go")
   138  	if err := os.WriteFile(src, []byte(prog), 0666); err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	binFile := filepath.Join(dir, "issue62600")
   143  	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, src)
   144  	if out, err := cmd.CombinedOutput(); err != nil {
   145  		t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
   146  	}
   147  
   148  	fi, err := os.Open(binFile)
   149  	if err != nil {
   150  		t.Fatalf("failed to open built file: %v", err)
   151  	}
   152  	defer fi.Close()
   153  
   154  	elfFile, err := elf.NewFile(fi)
   155  	if err != nil {
   156  		t.Skip("The system may not support ELF, skipped.")
   157  	}
   158  
   159  	section := elfFile.Section(".shstrtab")
   160  	if section == nil {
   161  		t.Fatal("no .shstrtab")
   162  	}
   163  
   164  	// The .shstrtab section should have a zero address, non-zero
   165  	// size, no ALLOC flag, and the offset should not fall into any of
   166  	// the segments defined by the program headers.
   167  	if section.Addr != 0 {
   168  		t.Fatalf("expected Addr == 0 for .shstrtab got %x", section.Addr)
   169  	}
   170  	if section.Size == 0 {
   171  		t.Fatal("expected nonzero Size for .shstrtab got 0")
   172  	}
   173  	if section.Flags&elf.SHF_ALLOC != 0 {
   174  		t.Fatal("expected zero alloc flag got nonzero for .shstrtab")
   175  	}
   176  	for idx, p := range elfFile.Progs {
   177  		if section.Offset >= p.Off && section.Offset < p.Off+p.Filesz {
   178  			t.Fatalf("badly formed .shstrtab, is contained in segment %d", idx)
   179  		}
   180  	}
   181  }
   182  
   183  func TestElfBindNow(t *testing.T) {
   184  	t.Parallel()
   185  	testenv.MustHaveGoBuild(t)
   186  
   187  	const (
   188  		prog = `package main; func main() {}`
   189  		// with default buildmode code compiles in a statically linked binary, hence CGO
   190  		progC = `package main; import "C"; func main() {}`
   191  	)
   192  
   193  	// Notes:
   194  	// - for linux/amd64 and linux/arm64, for relro we'll always see a
   195  	//   .got section when building with -buildmode=pie (in addition
   196  	//   to .dynamic); for some other less mainstream archs (ppc64le,
   197  	//   s390) this is not the case (on ppc64le for example we only
   198  	//   see got refs from C objects). Hence we put ".dynamic" in the
   199  	//   'want RO' list below and ".got" in the 'want RO if present".
   200  	// - when using the external linker, checking for read-only ".got"
   201  	//   is problematic since some linkers will only make the .got
   202  	//   read-only if its size is above a specific threshold, e.g.
   203  	//   https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=ld/scripttempl/elf.sc;h=d5022fa502f24db23f396f337a6c8978fbc8415b;hb=6fde04116b4b835fa9ec3b3497fcac4e4a0637e2#l74 . For this reason, don't try to verify read-only .got
   204  	//   in the external linking case.
   205  
   206  	tests := []struct {
   207  		name                 string
   208  		args                 []string
   209  		prog                 string
   210  		wantSecsRO           []string
   211  		wantSecsROIfPresent  []string
   212  		mustHaveBuildModePIE bool
   213  		mustHaveCGO          bool
   214  		mustInternalLink     bool
   215  		wantDfBindNow        bool
   216  		wantDf1Now           bool
   217  		wantDf1Pie           bool
   218  	}{
   219  		{name: "default", prog: prog},
   220  		{
   221  			name:                 "pie-linkmode-internal",
   222  			args:                 []string{"-buildmode=pie", "-ldflags", "-linkmode=internal"},
   223  			prog:                 prog,
   224  			mustHaveBuildModePIE: true,
   225  			mustInternalLink:     true,
   226  			wantDf1Pie:           true,
   227  			wantSecsRO:           []string{".dynamic"},
   228  			wantSecsROIfPresent:  []string{".got"},
   229  		},
   230  		{
   231  			name:             "bindnow-linkmode-internal",
   232  			args:             []string{"-ldflags", "-bindnow -linkmode=internal"},
   233  			prog:             progC,
   234  			mustHaveCGO:      true,
   235  			mustInternalLink: true,
   236  			wantDfBindNow:    true,
   237  			wantDf1Now:       true,
   238  		},
   239  		{
   240  			name:                 "bindnow-pie-linkmode-internal",
   241  			args:                 []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=internal"},
   242  			prog:                 prog,
   243  			mustHaveBuildModePIE: true,
   244  			mustInternalLink:     true,
   245  			wantDfBindNow:        true,
   246  			wantDf1Now:           true,
   247  			wantDf1Pie:           true,
   248  			wantSecsRO:           []string{".dynamic"},
   249  			wantSecsROIfPresent:  []string{".got", ".got.plt"},
   250  		},
   251  		{
   252  			name:                 "bindnow-pie-linkmode-external",
   253  			args:                 []string{"-buildmode=pie", "-ldflags", "-bindnow -linkmode=external"},
   254  			prog:                 prog,
   255  			mustHaveBuildModePIE: true,
   256  			mustHaveCGO:          true,
   257  			wantDfBindNow:        true,
   258  			wantDf1Now:           true,
   259  			wantDf1Pie:           true,
   260  			wantSecsRO:           []string{".dynamic"},
   261  		},
   262  	}
   263  
   264  	gotDynFlag := func(flags []uint64, dynFlag uint64) bool {
   265  		for _, flag := range flags {
   266  			if gotFlag := dynFlag&flag != 0; gotFlag {
   267  				return true
   268  			}
   269  		}
   270  		return false
   271  	}
   272  
   273  	segContainsSec := func(p *elf.Prog, s *elf.Section) bool {
   274  		return s.Addr >= p.Vaddr &&
   275  			s.Addr+s.FileSize <= p.Vaddr+p.Filesz
   276  	}
   277  
   278  	for _, test := range tests {
   279  		t.Run(test.name, func(t *testing.T) {
   280  			if test.mustInternalLink {
   281  				// N.B. none of the tests pass -asan/-msan/-asan.
   282  				testenv.MustInternalLink(t, testenv.SpecialBuildTypes{Cgo: test.mustHaveCGO})
   283  			}
   284  			if test.mustHaveCGO {
   285  				testenv.MustHaveCGO(t)
   286  			}
   287  			if test.mustHaveBuildModePIE {
   288  				testenv.MustHaveBuildMode(t, "pie")
   289  			}
   290  			if test.mustHaveBuildModePIE && test.mustInternalLink {
   291  				testenv.MustInternalLinkPIE(t)
   292  			}
   293  
   294  			var (
   295  				dir     = t.TempDir()
   296  				src     = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name))
   297  				binFile = filepath.Join(dir, test.name)
   298  			)
   299  
   300  			if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil {
   301  				t.Fatal(err)
   302  			}
   303  
   304  			cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...)
   305  			cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
   306  
   307  			if out, err := cmd.CombinedOutput(); err != nil {
   308  				t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
   309  			}
   310  
   311  			fi, err := os.Open(binFile)
   312  			if err != nil {
   313  				t.Fatalf("failed to open built file: %v", err)
   314  			}
   315  			defer fi.Close()
   316  
   317  			elfFile, err := elf.NewFile(fi)
   318  			if err != nil {
   319  				t.Skip("The system may not support ELF, skipped.")
   320  			}
   321  			defer elfFile.Close()
   322  
   323  			flags, err := elfFile.DynValue(elf.DT_FLAGS)
   324  			if err != nil {
   325  				t.Fatalf("failed to get DT_FLAGS: %v", err)
   326  			}
   327  
   328  			flags1, err := elfFile.DynValue(elf.DT_FLAGS_1)
   329  			if err != nil {
   330  				t.Fatalf("failed to get DT_FLAGS_1: %v", err)
   331  			}
   332  
   333  			gotDfBindNow := gotDynFlag(flags, uint64(elf.DF_BIND_NOW))
   334  			gotDf1Now := gotDynFlag(flags1, uint64(elf.DF_1_NOW))
   335  
   336  			bindNowFlagsMatch := gotDfBindNow == test.wantDfBindNow && gotDf1Now == test.wantDf1Now
   337  
   338  			// some external linkers may set one of the two flags but not both.
   339  			if !test.mustInternalLink {
   340  				bindNowFlagsMatch = gotDfBindNow == test.wantDfBindNow || gotDf1Now == test.wantDf1Now
   341  			}
   342  
   343  			if !bindNowFlagsMatch {
   344  				t.Fatalf("Dynamic flags mismatch:\n"+
   345  					"DT_FLAGS BIND_NOW	got: %v,	want: %v\n"+
   346  					"DT_FLAGS_1 DF_1_NOW	got: %v,	want: %v",
   347  					gotDfBindNow, test.wantDfBindNow, gotDf1Now, test.wantDf1Now)
   348  			}
   349  
   350  			if gotDf1Pie := gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie {
   351  				t.Fatalf("DT_FLAGS_1 DF_1_PIE got: %v, want: %v", gotDf1Pie, test.wantDf1Pie)
   352  			}
   353  
   354  			wsrolists := [][]string{test.wantSecsRO, test.wantSecsROIfPresent}
   355  			for k, wsrolist := range wsrolists {
   356  				for _, wsroname := range wsrolist {
   357  					// Locate section of interest.
   358  					var wsro *elf.Section
   359  					for _, s := range elfFile.Sections {
   360  						if s.Name == wsroname {
   361  							wsro = s
   362  							break
   363  						}
   364  					}
   365  					if wsro == nil {
   366  						if k == 0 {
   367  							t.Fatalf("test %s: can't locate %q section",
   368  								test.name, wsroname)
   369  						}
   370  						continue
   371  					}
   372  
   373  					// Now walk the program headers. Section should be part of
   374  					// some segment that is readonly.
   375  					foundRO := false
   376  					foundSegs := []*elf.Prog{}
   377  					for _, p := range elfFile.Progs {
   378  						if segContainsSec(p, wsro) {
   379  							foundSegs = append(foundSegs, p)
   380  							if p.Flags == elf.PF_R {
   381  								foundRO = true
   382  							}
   383  						}
   384  					}
   385  					if !foundRO {
   386  						// Things went off the rails. Write out some
   387  						// useful information for a human looking at the
   388  						// test failure.
   389  						t.Logf("test %s: %q section not in readonly segment",
   390  							wsro.Name, test.name)
   391  						t.Logf("section %s location: st=0x%x en=0x%x\n",
   392  							wsro.Name, wsro.Addr, wsro.Addr+wsro.FileSize)
   393  						t.Logf("sec %s found in these segments: ", wsro.Name)
   394  						for _, p := range foundSegs {
   395  							t.Logf(" %q", p.Type)
   396  						}
   397  						t.Logf("\nall segments: \n")
   398  						for k, p := range elfFile.Progs {
   399  							t.Logf("%d t=%s fl=%s st=0x%x en=0x%x\n",
   400  								k, p.Type, p.Flags, p.Vaddr, p.Vaddr+p.Filesz)
   401  						}
   402  						t.Fatalf("test %s failed", test.name)
   403  					}
   404  				}
   405  			}
   406  		})
   407  	}
   408  }
   409  
   410  // This program is intended to be just big/complicated enough that
   411  // we wind up with decent-sized .data.rel.ro.{typelink,itablink,gopclntab}
   412  // sections.
   413  const ifacecallsProg = `
   414  package main
   415  
   416  import "reflect"
   417  
   418  type A string
   419  type B int
   420  type C float64
   421  
   422  type describer interface{ What() string }
   423  type timer interface{ When() int }
   424  type rationale interface{ Why() error }
   425  
   426  func (a *A) What() string { return "string" }
   427  func (b *B) What() string { return "int" }
   428  func (b *B) When() int    { return int(*b) }
   429  func (b *B) Why() error   { return nil }
   430  func (c *C) What() string { return "float64" }
   431  
   432  func i_am_dead(c C) {
   433  	var d describer = &c
   434  	println(d.What())
   435  }
   436  
   437  func example(a A, b B) describer {
   438  	if b == 1 {
   439  		return &a
   440  	}
   441  	return &b
   442  }
   443  
   444  func ouch(a any, what string) string {
   445  	cv := reflect.ValueOf(a).MethodByName(what).Call(nil)
   446  	return cv[0].String()
   447  }
   448  
   449  func main() {
   450  	println(example("", 1).What())
   451  	println(ouch(example("", 1), "What"))
   452  }
   453  
   454  `
   455  
   456  func TestRelroSectionOverlapIssue67261(t *testing.T) {
   457  	t.Parallel()
   458  	testenv.MustHaveGoBuild(t)
   459  	testenv.MustHaveBuildMode(t, "pie")
   460  	testenv.MustInternalLinkPIE(t)
   461  
   462  	// This test case inspired by issue 67261, in which the linker
   463  	// produces a set of sections for -buildmode=pie that confuse the
   464  	// "strip" command, due to overlapping extents. The test first
   465  	// verifies that we don't have any overlapping PROGBITS/DYNAMIC
   466  	// sections, then runs "strip" on the resulting binary.
   467  
   468  	dir := t.TempDir()
   469  	src := filepath.Join(dir, "e.go")
   470  	binFile := filepath.Join(dir, "e.exe")
   471  
   472  	if err := os.WriteFile(src, []byte(ifacecallsProg), 0666); err != nil {
   473  		t.Fatal(err)
   474  	}
   475  
   476  	cmdArgs := []string{"build", "-o", binFile, "-buildmode=pie", "-ldflags=linkmode=internal", src}
   477  	cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
   478  
   479  	if out, err := cmd.CombinedOutput(); err != nil {
   480  		t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
   481  	}
   482  
   483  	fi, err := os.Open(binFile)
   484  	if err != nil {
   485  		t.Fatalf("failed to open built file: %v", err)
   486  	}
   487  	defer fi.Close()
   488  
   489  	elfFile, err := elf.NewFile(fi)
   490  	if err != nil {
   491  		t.Skip("The system may not support ELF, skipped.")
   492  	}
   493  	defer elfFile.Close()
   494  
   495  	// List of interesting sections. Here "interesting" means progbits/dynamic
   496  	// and loadable (has an address), nonzero size.
   497  	secs := []*elf.Section{}
   498  	for _, s := range elfFile.Sections {
   499  		if s.Type != elf.SHT_PROGBITS && s.Type != elf.SHT_DYNAMIC {
   500  			continue
   501  		}
   502  		if s.Addr == 0 || s.Size == 0 {
   503  			continue
   504  		}
   505  		secs = append(secs, s)
   506  	}
   507  
   508  	secOverlaps := func(s1, s2 *elf.Section) bool {
   509  		st1 := s1.Addr
   510  		st2 := s2.Addr
   511  		en1 := s1.Addr + s1.Size
   512  		en2 := s2.Addr + s2.Size
   513  		return max(st1, st2) < min(en1, en2)
   514  	}
   515  
   516  	// Sort by address
   517  	sort.SliceStable(secs, func(i, j int) bool {
   518  		return secs[i].Addr < secs[j].Addr
   519  	})
   520  
   521  	// Check to make sure we don't have any overlaps.
   522  	foundOverlap := false
   523  	for i := 0; i < len(secs)-1; i++ {
   524  		for j := i + 1; j < len(secs); j++ {
   525  			s := secs[i]
   526  			sn := secs[j]
   527  			if secOverlaps(s, sn) {
   528  				t.Errorf("unexpected: section %d:%q (addr=%x size=%x) overlaps section %d:%q (addr=%x size=%x)", i, s.Name, s.Addr, s.Size, i+1, sn.Name, sn.Addr, sn.Size)
   529  				foundOverlap = true
   530  			}
   531  		}
   532  	}
   533  	if foundOverlap {
   534  		// Print some additional info for human inspection.
   535  		t.Logf("** section list follows\n")
   536  		for i := range secs {
   537  			s := secs[i]
   538  			fmt.Printf(" | %2d: ad=0x%08x en=0x%08x sz=0x%08x t=%s %q\n",
   539  				i, s.Addr, s.Addr+s.Size, s.Size, s.Type, s.Name)
   540  		}
   541  	}
   542  
   543  	// We need CGO / c-compiler for the next bit.
   544  	testenv.MustHaveCGO(t)
   545  
   546  	// Make sure that the resulting binary can be put through strip.
   547  	// Try both "strip" and "llvm-strip"; in each case ask out CC
   548  	// command where to find the tool with "-print-prog-name" (meaning
   549  	// that if CC is gcc, we typically won't be able to find llvm-strip).
   550  	//
   551  	// Interestingly, binutils version of strip will (unfortunately)
   552  	// print error messages if there is a problem but will not return
   553  	// a non-zero exit status (?why?), so we consider any output a
   554  	// failure here.
   555  	stripExecs := []string{}
   556  	ecmd := testenv.Command(t, testenv.GoToolPath(t), "env", "CC")
   557  	if out, err := ecmd.CombinedOutput(); err != nil {
   558  		t.Fatalf("go env CC failed: %v:\n%s", err, out)
   559  	} else {
   560  		ccprog := strings.TrimSpace(string(out))
   561  		tries := []string{"strip", "llvm-strip"}
   562  		for _, try := range tries {
   563  			cmd := testenv.Command(t, ccprog, "-print-prog-name="+try)
   564  			if out, err := cmd.CombinedOutput(); err != nil {
   565  				t.Fatalf("print-prog-name failed: %+v %v:\n%s",
   566  					cmd.Args, err, out)
   567  			} else {
   568  				sprog := strings.TrimSpace(string(out))
   569  				stripExecs = append(stripExecs, sprog)
   570  			}
   571  		}
   572  	}
   573  
   574  	// Run strip on our Go PIE binary, making sure that the strip
   575  	// succeeds and we get no output from strip, then run the resulting
   576  	// stripped binary.
   577  	for k, sprog := range stripExecs {
   578  		if _, err := os.Stat(sprog); err != nil {
   579  			sp1, err := exec.LookPath(sprog)
   580  			if err != nil || sp1 == "" {
   581  				continue
   582  			}
   583  			sprog = sp1
   584  		}
   585  		targ := fmt.Sprintf("p%d.exe", k)
   586  		scmd := testenv.Command(t, sprog, "-o", targ, binFile)
   587  		scmd.Dir = dir
   588  		if sout, serr := scmd.CombinedOutput(); serr != nil {
   589  			t.Fatalf("failed to strip %v: %v:\n%s", scmd.Args, serr, sout)
   590  		} else {
   591  			// Non-empty output indicates failure, as mentioned above.
   592  			if len(string(sout)) != 0 {
   593  				t.Errorf("unexpected output from %s:\n%s\n", sprog, string(sout))
   594  			}
   595  		}
   596  		rcmd := testenv.Command(t, filepath.Join(dir, targ))
   597  		if out, err := rcmd.CombinedOutput(); err != nil {
   598  			t.Errorf("binary stripped by %s failed: %v:\n%s",
   599  				scmd.Args, err, string(out))
   600  		}
   601  	}
   602  
   603  }
   604  

View as plain text