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  				testenv.MustInternalLink(t, test.mustHaveCGO)
   282  			}
   283  			if test.mustHaveCGO {
   284  				testenv.MustHaveCGO(t)
   285  			}
   286  			if test.mustHaveBuildModePIE {
   287  				testenv.MustHaveBuildMode(t, "pie")
   288  			}
   289  			if test.mustHaveBuildModePIE && test.mustInternalLink {
   290  				testenv.MustInternalLinkPIE(t)
   291  			}
   292  
   293  			var (
   294  				dir     = t.TempDir()
   295  				src     = filepath.Join(dir, fmt.Sprintf("elf_%s.go", test.name))
   296  				binFile = filepath.Join(dir, test.name)
   297  			)
   298  
   299  			if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil {
   300  				t.Fatal(err)
   301  			}
   302  
   303  			cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...)
   304  			cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
   305  
   306  			if out, err := cmd.CombinedOutput(); err != nil {
   307  				t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
   308  			}
   309  
   310  			fi, err := os.Open(binFile)
   311  			if err != nil {
   312  				t.Fatalf("failed to open built file: %v", err)
   313  			}
   314  			defer fi.Close()
   315  
   316  			elfFile, err := elf.NewFile(fi)
   317  			if err != nil {
   318  				t.Skip("The system may not support ELF, skipped.")
   319  			}
   320  			defer elfFile.Close()
   321  
   322  			flags, err := elfFile.DynValue(elf.DT_FLAGS)
   323  			if err != nil {
   324  				t.Fatalf("failed to get DT_FLAGS: %v", err)
   325  			}
   326  
   327  			flags1, err := elfFile.DynValue(elf.DT_FLAGS_1)
   328  			if err != nil {
   329  				t.Fatalf("failed to get DT_FLAGS_1: %v", err)
   330  			}
   331  
   332  			gotDfBindNow := gotDynFlag(flags, uint64(elf.DF_BIND_NOW))
   333  			gotDf1Now := gotDynFlag(flags1, uint64(elf.DF_1_NOW))
   334  
   335  			bindNowFlagsMatch := gotDfBindNow == test.wantDfBindNow && gotDf1Now == test.wantDf1Now
   336  
   337  			// some external linkers may set one of the two flags but not both.
   338  			if !test.mustInternalLink {
   339  				bindNowFlagsMatch = gotDfBindNow == test.wantDfBindNow || gotDf1Now == test.wantDf1Now
   340  			}
   341  
   342  			if !bindNowFlagsMatch {
   343  				t.Fatalf("Dynamic flags mismatch:\n"+
   344  					"DT_FLAGS BIND_NOW	got: %v,	want: %v\n"+
   345  					"DT_FLAGS_1 DF_1_NOW	got: %v,	want: %v",
   346  					gotDfBindNow, test.wantDfBindNow, gotDf1Now, test.wantDf1Now)
   347  			}
   348  
   349  			if gotDf1Pie := gotDynFlag(flags1, uint64(elf.DF_1_PIE)); gotDf1Pie != test.wantDf1Pie {
   350  				t.Fatalf("DT_FLAGS_1 DF_1_PIE got: %v, want: %v", gotDf1Pie, test.wantDf1Pie)
   351  			}
   352  
   353  			wsrolists := [][]string{test.wantSecsRO, test.wantSecsROIfPresent}
   354  			for k, wsrolist := range wsrolists {
   355  				for _, wsroname := range wsrolist {
   356  					// Locate section of interest.
   357  					var wsro *elf.Section
   358  					for _, s := range elfFile.Sections {
   359  						if s.Name == wsroname {
   360  							wsro = s
   361  							break
   362  						}
   363  					}
   364  					if wsro == nil {
   365  						if k == 0 {
   366  							t.Fatalf("test %s: can't locate %q section",
   367  								test.name, wsroname)
   368  						}
   369  						continue
   370  					}
   371  
   372  					// Now walk the program headers. Section should be part of
   373  					// some segment that is readonly.
   374  					foundRO := false
   375  					foundSegs := []*elf.Prog{}
   376  					for _, p := range elfFile.Progs {
   377  						if segContainsSec(p, wsro) {
   378  							foundSegs = append(foundSegs, p)
   379  							if p.Flags == elf.PF_R {
   380  								foundRO = true
   381  							}
   382  						}
   383  					}
   384  					if !foundRO {
   385  						// Things went off the rails. Write out some
   386  						// useful information for a human looking at the
   387  						// test failure.
   388  						t.Logf("test %s: %q section not in readonly segment",
   389  							wsro.Name, test.name)
   390  						t.Logf("section %s location: st=0x%x en=0x%x\n",
   391  							wsro.Name, wsro.Addr, wsro.Addr+wsro.FileSize)
   392  						t.Logf("sec %s found in these segments: ", wsro.Name)
   393  						for _, p := range foundSegs {
   394  							t.Logf(" %q", p.Type)
   395  						}
   396  						t.Logf("\nall segments: \n")
   397  						for k, p := range elfFile.Progs {
   398  							t.Logf("%d t=%s fl=%s st=0x%x en=0x%x\n",
   399  								k, p.Type, p.Flags, p.Vaddr, p.Vaddr+p.Filesz)
   400  						}
   401  						t.Fatalf("test %s failed", test.name)
   402  					}
   403  				}
   404  			}
   405  		})
   406  	}
   407  }
   408  
   409  // This program is intended to be just big/complicated enough that
   410  // we wind up with decent-sized .data.rel.ro.{typelink,itablink,gopclntab}
   411  // sections.
   412  const ifacecallsProg = `
   413  package main
   414  
   415  import "reflect"
   416  
   417  type A string
   418  type B int
   419  type C float64
   420  
   421  type describer interface{ What() string }
   422  type timer interface{ When() int }
   423  type rationale interface{ Why() error }
   424  
   425  func (a *A) What() string { return "string" }
   426  func (b *B) What() string { return "int" }
   427  func (b *B) When() int    { return int(*b) }
   428  func (b *B) Why() error   { return nil }
   429  func (c *C) What() string { return "float64" }
   430  
   431  func i_am_dead(c C) {
   432  	var d describer = &c
   433  	println(d.What())
   434  }
   435  
   436  func example(a A, b B) describer {
   437  	if b == 1 {
   438  		return &a
   439  	}
   440  	return &b
   441  }
   442  
   443  func ouch(a any, what string) string {
   444  	cv := reflect.ValueOf(a).MethodByName(what).Call(nil)
   445  	return cv[0].String()
   446  }
   447  
   448  func main() {
   449  	println(example("", 1).What())
   450  	println(ouch(example("", 1), "What"))
   451  }
   452  
   453  `
   454  
   455  func TestRelroSectionOverlapIssue67261(t *testing.T) {
   456  	t.Parallel()
   457  	testenv.MustHaveGoBuild(t)
   458  	testenv.MustHaveBuildMode(t, "pie")
   459  	testenv.MustInternalLinkPIE(t)
   460  
   461  	// This test case inspired by issue 67261, in which the linker
   462  	// produces a set of sections for -buildmode=pie that confuse the
   463  	// "strip" command, due to overlapping extents. The test first
   464  	// verifies that we don't have any overlapping PROGBITS/DYNAMIC
   465  	// sections, then runs "strip" on the resulting binary.
   466  
   467  	dir := t.TempDir()
   468  	src := filepath.Join(dir, "e.go")
   469  	binFile := filepath.Join(dir, "e.exe")
   470  
   471  	if err := os.WriteFile(src, []byte(ifacecallsProg), 0666); err != nil {
   472  		t.Fatal(err)
   473  	}
   474  
   475  	cmdArgs := []string{"build", "-o", binFile, "-buildmode=pie", "-ldflags=linkmode=internal", src}
   476  	cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
   477  
   478  	if out, err := cmd.CombinedOutput(); err != nil {
   479  		t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
   480  	}
   481  
   482  	fi, err := os.Open(binFile)
   483  	if err != nil {
   484  		t.Fatalf("failed to open built file: %v", err)
   485  	}
   486  	defer fi.Close()
   487  
   488  	elfFile, err := elf.NewFile(fi)
   489  	if err != nil {
   490  		t.Skip("The system may not support ELF, skipped.")
   491  	}
   492  	defer elfFile.Close()
   493  
   494  	// List of interesting sections. Here "interesting" means progbits/dynamic
   495  	// and loadable (has an address), nonzero size.
   496  	secs := []*elf.Section{}
   497  	for _, s := range elfFile.Sections {
   498  		if s.Type != elf.SHT_PROGBITS && s.Type != elf.SHT_DYNAMIC {
   499  			continue
   500  		}
   501  		if s.Addr == 0 || s.Size == 0 {
   502  			continue
   503  		}
   504  		secs = append(secs, s)
   505  	}
   506  
   507  	secOverlaps := func(s1, s2 *elf.Section) bool {
   508  		st1 := s1.Addr
   509  		st2 := s2.Addr
   510  		en1 := s1.Addr + s1.Size
   511  		en2 := s2.Addr + s2.Size
   512  		return max(st1, st2) < min(en1, en2)
   513  	}
   514  
   515  	// Sort by address
   516  	sort.SliceStable(secs, func(i, j int) bool {
   517  		return secs[i].Addr < secs[j].Addr
   518  	})
   519  
   520  	// Check to make sure we don't have any overlaps.
   521  	foundOverlap := false
   522  	for i := 0; i < len(secs)-1; i++ {
   523  		for j := i + 1; j < len(secs); j++ {
   524  			s := secs[i]
   525  			sn := secs[j]
   526  			if secOverlaps(s, sn) {
   527  				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)
   528  				foundOverlap = true
   529  			}
   530  		}
   531  	}
   532  	if foundOverlap {
   533  		// Print some additional info for human inspection.
   534  		t.Logf("** section list follows\n")
   535  		for i := range secs {
   536  			s := secs[i]
   537  			fmt.Printf(" | %2d: ad=0x%08x en=0x%08x sz=0x%08x t=%s %q\n",
   538  				i, s.Addr, s.Addr+s.Size, s.Size, s.Type, s.Name)
   539  		}
   540  	}
   541  
   542  	// We need CGO / c-compiler for the next bit.
   543  	testenv.MustHaveCGO(t)
   544  
   545  	// Make sure that the resulting binary can be put through strip.
   546  	// Try both "strip" and "llvm-strip"; in each case ask out CC
   547  	// command where to find the tool with "-print-prog-name" (meaning
   548  	// that if CC is gcc, we typically won't be able to find llvm-strip).
   549  	//
   550  	// Interestingly, binutils version of strip will (unfortunately)
   551  	// print error messages if there is a problem but will not return
   552  	// a non-zero exit status (?why?), so we consider any output a
   553  	// failure here.
   554  	stripExecs := []string{}
   555  	ecmd := testenv.Command(t, testenv.GoToolPath(t), "env", "CC")
   556  	if out, err := ecmd.CombinedOutput(); err != nil {
   557  		t.Fatalf("go env CC failed: %v:\n%s", err, out)
   558  	} else {
   559  		ccprog := strings.TrimSpace(string(out))
   560  		tries := []string{"strip", "llvm-strip"}
   561  		for _, try := range tries {
   562  			cmd := testenv.Command(t, ccprog, "-print-prog-name="+try)
   563  			if out, err := cmd.CombinedOutput(); err != nil {
   564  				t.Fatalf("print-prog-name failed: %+v %v:\n%s",
   565  					cmd.Args, err, out)
   566  			} else {
   567  				sprog := strings.TrimSpace(string(out))
   568  				stripExecs = append(stripExecs, sprog)
   569  			}
   570  		}
   571  	}
   572  
   573  	// Run strip on our Go PIE binary, making sure that the strip
   574  	// succeeds and we get no output from strip, then run the resulting
   575  	// stripped binary.
   576  	for k, sprog := range stripExecs {
   577  		if _, err := os.Stat(sprog); err != nil {
   578  			sp1, err := exec.LookPath(sprog)
   579  			if err != nil || sp1 == "" {
   580  				continue
   581  			}
   582  			sprog = sp1
   583  		}
   584  		targ := fmt.Sprintf("p%d.exe", k)
   585  		scmd := testenv.Command(t, sprog, "-o", targ, binFile)
   586  		scmd.Dir = dir
   587  		if sout, serr := scmd.CombinedOutput(); serr != nil {
   588  			t.Fatalf("failed to strip %v: %v:\n%s", scmd.Args, serr, sout)
   589  		} else {
   590  			// Non-empty output indicates failure, as mentioned above.
   591  			if len(string(sout)) != 0 {
   592  				t.Errorf("unexpected output from %s:\n%s\n", sprog, string(sout))
   593  			}
   594  		}
   595  		rcmd := testenv.Command(t, filepath.Join(dir, targ))
   596  		if out, err := rcmd.CombinedOutput(); err != nil {
   597  			t.Errorf("binary stripped by %s failed: %v:\n%s",
   598  				scmd.Args, err, string(out))
   599  		}
   600  	}
   601  
   602  }
   603  

View as plain text