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

View as plain text