Source file misc/cgo/testshared/shared_test.go

     1  // Copyright 2015 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  package shared_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"debug/elf"
    11  	"encoding/binary"
    12  	"flag"
    13  	"fmt"
    14  	"go/build"
    15  	"io"
    16  	"log"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"regexp"
    21  	"runtime"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  )
    27  
    28  var gopathInstallDir, gorootInstallDir string
    29  
    30  // This is the smallest set of packages we can link into a shared
    31  // library (runtime/cgo is built implicitly).
    32  var minpkgs = []string{"runtime", "sync/atomic"}
    33  var soname = "libruntime,sync-atomic.so"
    34  
    35  var testX = flag.Bool("testx", false, "if true, pass -x to 'go' subcommands invoked by the test")
    36  var testWork = flag.Bool("testwork", false, "if true, log and do not delete the temporary working directory")
    37  
    38  // run runs a command and calls t.Errorf if it fails.
    39  func run(t *testing.T, msg string, args ...string) {
    40  	runWithEnv(t, msg, nil, args...)
    41  }
    42  
    43  // runWithEnv runs a command under the given environment and calls t.Errorf if it fails.
    44  func runWithEnv(t *testing.T, msg string, env []string, args ...string) {
    45  	c := exec.Command(args[0], args[1:]...)
    46  	if len(env) != 0 {
    47  		c.Env = append(os.Environ(), env...)
    48  	}
    49  	if output, err := c.CombinedOutput(); err != nil {
    50  		t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output)
    51  	}
    52  }
    53  
    54  // goCmd invokes the go tool with the installsuffix set up by TestMain. It calls
    55  // t.Fatalf if the command fails.
    56  func goCmd(t *testing.T, args ...string) string {
    57  	newargs := []string{args[0]}
    58  	if *testX {
    59  		newargs = append(newargs, "-x")
    60  	}
    61  	newargs = append(newargs, args[1:]...)
    62  	c := exec.Command("go", newargs...)
    63  	stderr := new(strings.Builder)
    64  	c.Stderr = stderr
    65  
    66  	if testing.Verbose() && t == nil {
    67  		fmt.Fprintf(os.Stderr, "+ go %s\n", strings.Join(args, " "))
    68  		c.Stderr = os.Stderr
    69  	}
    70  	output, err := c.Output()
    71  
    72  	if err != nil {
    73  		if t != nil {
    74  			t.Helper()
    75  			t.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr)
    76  		} else {
    77  			// Panic instead of using log.Fatalf so that deferred cleanup may run in testMain.
    78  			log.Panicf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, stderr)
    79  		}
    80  	}
    81  	if testing.Verbose() && t != nil {
    82  		t.Logf("go %s", strings.Join(args, " "))
    83  		if stderr.Len() > 0 {
    84  			t.Logf("%s", stderr)
    85  		}
    86  	}
    87  	return string(bytes.TrimSpace(output))
    88  }
    89  
    90  // TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit).
    91  func testMain(m *testing.M) (int, error) {
    92  	workDir, err := os.MkdirTemp("", "shared_test")
    93  	if err != nil {
    94  		return 0, err
    95  	}
    96  	if *testWork || testing.Verbose() {
    97  		fmt.Printf("+ mkdir -p %s\n", workDir)
    98  	}
    99  	if !*testWork {
   100  		defer os.RemoveAll(workDir)
   101  	}
   102  
   103  	// Some tests need to edit the source in GOPATH, so copy this directory to a
   104  	// temporary directory and chdir to that.
   105  	gopath := filepath.Join(workDir, "gopath")
   106  	modRoot, err := cloneTestdataModule(gopath)
   107  	if err != nil {
   108  		return 0, err
   109  	}
   110  	if testing.Verbose() {
   111  		fmt.Printf("+ export GOPATH=%s\n", gopath)
   112  		fmt.Printf("+ cd %s\n", modRoot)
   113  	}
   114  	os.Setenv("GOPATH", gopath)
   115  	// Explicitly override GOBIN as well, in case it was set through a GOENV file.
   116  	os.Setenv("GOBIN", filepath.Join(gopath, "bin"))
   117  	os.Chdir(modRoot)
   118  	os.Setenv("PWD", modRoot)
   119  
   120  	// The test also needs to install libraries into GOROOT/pkg, so copy the
   121  	// subset of GOROOT that we need.
   122  	//
   123  	// TODO(golang.org/issue/28553): Rework -buildmode=shared so that it does not
   124  	// need to write to GOROOT.
   125  	goroot := filepath.Join(workDir, "goroot")
   126  	if err := cloneGOROOTDeps(goroot); err != nil {
   127  		return 0, err
   128  	}
   129  	if testing.Verbose() {
   130  		fmt.Fprintf(os.Stderr, "+ export GOROOT=%s\n", goroot)
   131  	}
   132  	os.Setenv("GOROOT", goroot)
   133  
   134  	myContext := build.Default
   135  	myContext.GOROOT = goroot
   136  	myContext.GOPATH = gopath
   137  	runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
   138  	if err != nil {
   139  		return 0, fmt.Errorf("import failed: %v", err)
   140  	}
   141  	gorootInstallDir = runtimeP.PkgTargetRoot + "_dynlink"
   142  
   143  	// All tests depend on runtime being built into a shared library. Because
   144  	// that takes a few seconds, do it here and have all tests use the version
   145  	// built here.
   146  	goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
   147  
   148  	myContext.InstallSuffix = "_dynlink"
   149  	depP, err := myContext.Import("./depBase", ".", build.ImportComment)
   150  	if err != nil {
   151  		return 0, fmt.Errorf("import failed: %v", err)
   152  	}
   153  	if depP.PkgTargetRoot == "" {
   154  		gopathInstallDir = filepath.Dir(goCmd(nil, "list", "-buildmode=shared", "-f", "{{.Target}}", "./depBase"))
   155  	} else {
   156  		gopathInstallDir = filepath.Join(depP.PkgTargetRoot, "testshared")
   157  	}
   158  	return m.Run(), nil
   159  }
   160  
   161  func TestMain(m *testing.M) {
   162  	log.SetFlags(log.Lshortfile)
   163  	flag.Parse()
   164  
   165  	exitCode, err := testMain(m)
   166  	if err != nil {
   167  		log.Fatal(err)
   168  	}
   169  	os.Exit(exitCode)
   170  }
   171  
   172  // cloneTestdataModule clones the packages from src/testshared into gopath.
   173  // It returns the directory within gopath at which the module root is located.
   174  func cloneTestdataModule(gopath string) (string, error) {
   175  	modRoot := filepath.Join(gopath, "src", "testshared")
   176  	if err := overlayDir(modRoot, "testdata"); err != nil {
   177  		return "", err
   178  	}
   179  	if err := os.WriteFile(filepath.Join(modRoot, "go.mod"), []byte("module testshared\n"), 0644); err != nil {
   180  		return "", err
   181  	}
   182  	return modRoot, nil
   183  }
   184  
   185  // cloneGOROOTDeps copies (or symlinks) the portions of GOROOT/src and
   186  // GOROOT/pkg relevant to this test into the given directory.
   187  // It must be run from within the testdata module.
   188  func cloneGOROOTDeps(goroot string) error {
   189  	oldGOROOT := strings.TrimSpace(goCmd(nil, "env", "GOROOT"))
   190  	if oldGOROOT == "" {
   191  		return fmt.Errorf("go env GOROOT returned an empty string")
   192  	}
   193  
   194  	// Before we clone GOROOT, figure out which packages we need to copy over.
   195  	listArgs := []string{
   196  		"list",
   197  		"-deps",
   198  		"-f", "{{if and .Standard (not .ForTest)}}{{.ImportPath}}{{end}}",
   199  	}
   200  	stdDeps := goCmd(nil, append(listArgs, minpkgs...)...)
   201  	testdataDeps := goCmd(nil, append(listArgs, "-test", "./...")...)
   202  
   203  	pkgs := append(strings.Split(strings.TrimSpace(stdDeps), "\n"),
   204  		strings.Split(strings.TrimSpace(testdataDeps), "\n")...)
   205  	sort.Strings(pkgs)
   206  	var pkgRoots []string
   207  	for _, pkg := range pkgs {
   208  		parentFound := false
   209  		for _, prev := range pkgRoots {
   210  			if strings.HasPrefix(pkg, prev) {
   211  				// We will copy in the source for pkg when we copy in prev.
   212  				parentFound = true
   213  				break
   214  			}
   215  		}
   216  		if !parentFound {
   217  			pkgRoots = append(pkgRoots, pkg)
   218  		}
   219  	}
   220  
   221  	gorootDirs := []string{
   222  		"pkg/tool",
   223  		"pkg/include",
   224  	}
   225  	for _, pkg := range pkgRoots {
   226  		gorootDirs = append(gorootDirs, filepath.Join("src", pkg))
   227  	}
   228  
   229  	for _, dir := range gorootDirs {
   230  		if testing.Verbose() {
   231  			fmt.Fprintf(os.Stderr, "+ cp -r %s %s\n", filepath.Join(oldGOROOT, dir), filepath.Join(goroot, dir))
   232  		}
   233  		if err := overlayDir(filepath.Join(goroot, dir), filepath.Join(oldGOROOT, dir)); err != nil {
   234  			return err
   235  		}
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  // The shared library was built at the expected location.
   242  func TestSOBuilt(t *testing.T) {
   243  	_, err := os.Stat(filepath.Join(gorootInstallDir, soname))
   244  	if err != nil {
   245  		t.Error(err)
   246  	}
   247  }
   248  
   249  func hasDynTag(f *elf.File, tag elf.DynTag) bool {
   250  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   251  	if ds == nil {
   252  		return false
   253  	}
   254  	d, err := ds.Data()
   255  	if err != nil {
   256  		return false
   257  	}
   258  	for len(d) > 0 {
   259  		var t elf.DynTag
   260  		switch f.Class {
   261  		case elf.ELFCLASS32:
   262  			t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
   263  			d = d[8:]
   264  		case elf.ELFCLASS64:
   265  			t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
   266  			d = d[16:]
   267  		}
   268  		if t == tag {
   269  			return true
   270  		}
   271  	}
   272  	return false
   273  }
   274  
   275  // The shared library does not have relocations against the text segment.
   276  func TestNoTextrel(t *testing.T) {
   277  	sopath := filepath.Join(gorootInstallDir, soname)
   278  	f, err := elf.Open(sopath)
   279  	if err != nil {
   280  		t.Fatal("elf.Open failed: ", err)
   281  	}
   282  	defer f.Close()
   283  	if hasDynTag(f, elf.DT_TEXTREL) {
   284  		t.Errorf("%s has DT_TEXTREL set", soname)
   285  	}
   286  }
   287  
   288  // The shared library does not contain symbols called ".dup"
   289  // (See golang.org/issue/14841.)
   290  func TestNoDupSymbols(t *testing.T) {
   291  	sopath := filepath.Join(gorootInstallDir, soname)
   292  	f, err := elf.Open(sopath)
   293  	if err != nil {
   294  		t.Fatal("elf.Open failed: ", err)
   295  	}
   296  	defer f.Close()
   297  	syms, err := f.Symbols()
   298  	if err != nil {
   299  		t.Errorf("error reading symbols %v", err)
   300  		return
   301  	}
   302  	for _, s := range syms {
   303  		if s.Name == ".dup" {
   304  			t.Fatalf("%s contains symbol called .dup", sopath)
   305  		}
   306  	}
   307  }
   308  
   309  // The install command should have created a "shlibname" file for the
   310  // listed packages (and runtime/cgo, and math on arm) indicating the
   311  // name of the shared library containing it.
   312  func TestShlibnameFiles(t *testing.T) {
   313  	pkgs := append([]string{}, minpkgs...)
   314  	pkgs = append(pkgs, "runtime/cgo")
   315  	if runtime.GOARCH == "arm" {
   316  		pkgs = append(pkgs, "math")
   317  	}
   318  	for _, pkg := range pkgs {
   319  		shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname")
   320  		contentsb, err := os.ReadFile(shlibnamefile)
   321  		if err != nil {
   322  			t.Errorf("error reading shlibnamefile for %s: %v", pkg, err)
   323  			continue
   324  		}
   325  		contents := strings.TrimSpace(string(contentsb))
   326  		if contents != soname {
   327  			t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents)
   328  		}
   329  	}
   330  }
   331  
   332  // Is a given offset into the file contained in a loaded segment?
   333  func isOffsetLoaded(f *elf.File, offset uint64) bool {
   334  	for _, prog := range f.Progs {
   335  		if prog.Type == elf.PT_LOAD {
   336  			if prog.Off <= offset && offset < prog.Off+prog.Filesz {
   337  				return true
   338  			}
   339  		}
   340  	}
   341  	return false
   342  }
   343  
   344  func rnd(v int32, r int32) int32 {
   345  	if r <= 0 {
   346  		return v
   347  	}
   348  	v += r - 1
   349  	c := v % r
   350  	if c < 0 {
   351  		c += r
   352  	}
   353  	v -= c
   354  	return v
   355  }
   356  
   357  func readwithpad(r io.Reader, sz int32) ([]byte, error) {
   358  	data := make([]byte, rnd(sz, 4))
   359  	_, err := io.ReadFull(r, data)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	data = data[:sz]
   364  	return data, nil
   365  }
   366  
   367  type note struct {
   368  	name    string
   369  	tag     int32
   370  	desc    string
   371  	section *elf.Section
   372  }
   373  
   374  // Read all notes from f. As ELF section names are not supposed to be special, one
   375  // looks for a particular note by scanning all SHT_NOTE sections looking for a note
   376  // with a particular "name" and "tag".
   377  func readNotes(f *elf.File) ([]*note, error) {
   378  	var notes []*note
   379  	for _, sect := range f.Sections {
   380  		if sect.Type != elf.SHT_NOTE {
   381  			continue
   382  		}
   383  		r := sect.Open()
   384  		for {
   385  			var namesize, descsize, tag int32
   386  			err := binary.Read(r, f.ByteOrder, &namesize)
   387  			if err != nil {
   388  				if err == io.EOF {
   389  					break
   390  				}
   391  				return nil, fmt.Errorf("read namesize failed: %v", err)
   392  			}
   393  			err = binary.Read(r, f.ByteOrder, &descsize)
   394  			if err != nil {
   395  				return nil, fmt.Errorf("read descsize failed: %v", err)
   396  			}
   397  			err = binary.Read(r, f.ByteOrder, &tag)
   398  			if err != nil {
   399  				return nil, fmt.Errorf("read type failed: %v", err)
   400  			}
   401  			name, err := readwithpad(r, namesize)
   402  			if err != nil {
   403  				return nil, fmt.Errorf("read name failed: %v", err)
   404  			}
   405  			desc, err := readwithpad(r, descsize)
   406  			if err != nil {
   407  				return nil, fmt.Errorf("read desc failed: %v", err)
   408  			}
   409  			notes = append(notes, &note{name: string(name), tag: tag, desc: string(desc), section: sect})
   410  		}
   411  	}
   412  	return notes, nil
   413  }
   414  
   415  func dynStrings(t *testing.T, path string, flag elf.DynTag) []string {
   416  	t.Helper()
   417  	f, err := elf.Open(path)
   418  	if err != nil {
   419  		t.Fatalf("elf.Open(%q) failed: %v", path, err)
   420  	}
   421  	defer f.Close()
   422  	dynstrings, err := f.DynString(flag)
   423  	if err != nil {
   424  		t.Fatalf("DynString(%s) failed on %s: %v", flag, path, err)
   425  	}
   426  	return dynstrings
   427  }
   428  
   429  func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) {
   430  	t.Helper()
   431  	for _, dynstring := range dynStrings(t, path, elf.DT_NEEDED) {
   432  		if re.MatchString(dynstring) {
   433  			return
   434  		}
   435  	}
   436  	t.Errorf("%s is not linked to anything matching %v", path, re)
   437  }
   438  
   439  func AssertIsLinkedTo(t *testing.T, path, lib string) {
   440  	t.Helper()
   441  	AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
   442  }
   443  
   444  func AssertHasRPath(t *testing.T, path, dir string) {
   445  	t.Helper()
   446  	for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} {
   447  		for _, dynstring := range dynStrings(t, path, tag) {
   448  			for _, rpath := range strings.Split(dynstring, ":") {
   449  				if filepath.Clean(rpath) == filepath.Clean(dir) {
   450  					return
   451  				}
   452  			}
   453  		}
   454  	}
   455  	t.Errorf("%s does not have rpath %s", path, dir)
   456  }
   457  
   458  // Build a trivial program that links against the shared runtime and check it runs.
   459  func TestTrivialExecutable(t *testing.T) {
   460  	goCmd(t, "install", "-linkshared", "./trivial")
   461  	run(t, "trivial executable", "../../bin/trivial")
   462  	AssertIsLinkedTo(t, "../../bin/trivial", soname)
   463  	AssertHasRPath(t, "../../bin/trivial", gorootInstallDir)
   464  	checkSize(t, "../../bin/trivial", 100000) // it is 19K on linux/amd64, 100K should be enough
   465  }
   466  
   467  // Build a trivial program in PIE mode that links against the shared runtime and check it runs.
   468  func TestTrivialExecutablePIE(t *testing.T) {
   469  	goCmd(t, "build", "-buildmode=pie", "-o", "trivial.pie", "-linkshared", "./trivial")
   470  	run(t, "trivial executable", "./trivial.pie")
   471  	AssertIsLinkedTo(t, "./trivial.pie", soname)
   472  	AssertHasRPath(t, "./trivial.pie", gorootInstallDir)
   473  	checkSize(t, "./trivial.pie", 100000) // it is 19K on linux/amd64, 100K should be enough
   474  }
   475  
   476  // Check that the file size does not exceed a limit.
   477  func checkSize(t *testing.T, f string, limit int64) {
   478  	fi, err := os.Stat(f)
   479  	if err != nil {
   480  		t.Fatalf("stat failed: %v", err)
   481  	}
   482  	if sz := fi.Size(); sz > limit {
   483  		t.Errorf("file too large: got %d, want <= %d", sz, limit)
   484  	}
   485  }
   486  
   487  // Build a division test program and check it runs.
   488  func TestDivisionExecutable(t *testing.T) {
   489  	goCmd(t, "install", "-linkshared", "./division")
   490  	run(t, "division executable", "../../bin/division")
   491  }
   492  
   493  // Build an executable that uses cgo linked against the shared runtime and check it
   494  // runs.
   495  func TestCgoExecutable(t *testing.T) {
   496  	goCmd(t, "install", "-linkshared", "./execgo")
   497  	run(t, "cgo executable", "../../bin/execgo")
   498  }
   499  
   500  func checkPIE(t *testing.T, name string) {
   501  	f, err := elf.Open(name)
   502  	if err != nil {
   503  		t.Fatal("elf.Open failed: ", err)
   504  	}
   505  	defer f.Close()
   506  	if f.Type != elf.ET_DYN {
   507  		t.Errorf("%s has type %v, want ET_DYN", name, f.Type)
   508  	}
   509  	if hasDynTag(f, elf.DT_TEXTREL) {
   510  		t.Errorf("%s has DT_TEXTREL set", name)
   511  	}
   512  }
   513  
   514  func TestTrivialPIE(t *testing.T) {
   515  	name := "trivial_pie"
   516  	goCmd(t, "build", "-buildmode=pie", "-o="+name, "./trivial")
   517  	defer os.Remove(name)
   518  	run(t, name, "./"+name)
   519  	checkPIE(t, name)
   520  }
   521  
   522  func TestCgoPIE(t *testing.T) {
   523  	name := "cgo_pie"
   524  	goCmd(t, "build", "-buildmode=pie", "-o="+name, "./execgo")
   525  	defer os.Remove(name)
   526  	run(t, name, "./"+name)
   527  	checkPIE(t, name)
   528  }
   529  
   530  // Build a GOPATH package into a shared library that links against the goroot runtime
   531  // and an executable that links against both.
   532  func TestGopathShlib(t *testing.T) {
   533  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   534  	shlib := goCmd(t, "list", "-f", "{{.Shlib}}", "-buildmode=shared", "-linkshared", "./depBase")
   535  	AssertIsLinkedTo(t, shlib, soname)
   536  	goCmd(t, "install", "-linkshared", "./exe")
   537  	AssertIsLinkedTo(t, "../../bin/exe", soname)
   538  	AssertIsLinkedTo(t, "../../bin/exe", filepath.Base(shlib))
   539  	AssertHasRPath(t, "../../bin/exe", gorootInstallDir)
   540  	AssertHasRPath(t, "../../bin/exe", filepath.Dir(gopathInstallDir))
   541  	// And check it runs.
   542  	run(t, "executable linked to GOPATH library", "../../bin/exe")
   543  }
   544  
   545  // The shared library contains a note listing the packages it contains in a section
   546  // that is not mapped into memory.
   547  func testPkgListNote(t *testing.T, f *elf.File, note *note) {
   548  	if note.section.Flags != 0 {
   549  		t.Errorf("package list section has flags %v, want 0", note.section.Flags)
   550  	}
   551  	if isOffsetLoaded(f, note.section.Offset) {
   552  		t.Errorf("package list section contained in PT_LOAD segment")
   553  	}
   554  	if note.desc != "testshared/depBase\n" {
   555  		t.Errorf("incorrect package list %q, want %q", note.desc, "testshared/depBase\n")
   556  	}
   557  }
   558  
   559  // The shared library contains a note containing the ABI hash that is mapped into
   560  // memory and there is a local symbol called go.link.abihashbytes that points 16
   561  // bytes into it.
   562  func testABIHashNote(t *testing.T, f *elf.File, note *note) {
   563  	if note.section.Flags != elf.SHF_ALLOC {
   564  		t.Errorf("abi hash section has flags %v, want SHF_ALLOC", note.section.Flags)
   565  	}
   566  	if !isOffsetLoaded(f, note.section.Offset) {
   567  		t.Errorf("abihash section not contained in PT_LOAD segment")
   568  	}
   569  	var hashbytes elf.Symbol
   570  	symbols, err := f.Symbols()
   571  	if err != nil {
   572  		t.Errorf("error reading symbols %v", err)
   573  		return
   574  	}
   575  	for _, sym := range symbols {
   576  		if sym.Name == "go.link.abihashbytes" {
   577  			hashbytes = sym
   578  		}
   579  	}
   580  	if hashbytes.Name == "" {
   581  		t.Errorf("no symbol called go.link.abihashbytes")
   582  		return
   583  	}
   584  	if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
   585  		t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
   586  	}
   587  	if f.Sections[hashbytes.Section] != note.section {
   588  		t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section.Name)
   589  	}
   590  	if hashbytes.Value-note.section.Addr != 16 {
   591  		t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr)
   592  	}
   593  }
   594  
   595  // A Go shared library contains a note indicating which other Go shared libraries it
   596  // was linked against in an unmapped section.
   597  func testDepsNote(t *testing.T, f *elf.File, note *note) {
   598  	if note.section.Flags != 0 {
   599  		t.Errorf("package list section has flags %v, want 0", note.section.Flags)
   600  	}
   601  	if isOffsetLoaded(f, note.section.Offset) {
   602  		t.Errorf("package list section contained in PT_LOAD segment")
   603  	}
   604  	// libdepBase.so just links against the lib containing the runtime.
   605  	if note.desc != soname {
   606  		t.Errorf("incorrect dependency list %q, want %q", note.desc, soname)
   607  	}
   608  }
   609  
   610  // The shared library contains notes with defined contents; see above.
   611  func TestNotes(t *testing.T) {
   612  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   613  	shlib := goCmd(t, "list", "-f", "{{.Shlib}}", "-buildmode=shared", "-linkshared", "./depBase")
   614  	f, err := elf.Open(shlib)
   615  	if err != nil {
   616  		t.Fatal(err)
   617  	}
   618  	defer f.Close()
   619  	notes, err := readNotes(f)
   620  	if err != nil {
   621  		t.Fatal(err)
   622  	}
   623  	pkgListNoteFound := false
   624  	abiHashNoteFound := false
   625  	depsNoteFound := false
   626  	for _, note := range notes {
   627  		if note.name != "Go\x00\x00" {
   628  			continue
   629  		}
   630  		switch note.tag {
   631  		case 1: // ELF_NOTE_GOPKGLIST_TAG
   632  			if pkgListNoteFound {
   633  				t.Error("multiple package list notes")
   634  			}
   635  			testPkgListNote(t, f, note)
   636  			pkgListNoteFound = true
   637  		case 2: // ELF_NOTE_GOABIHASH_TAG
   638  			if abiHashNoteFound {
   639  				t.Error("multiple abi hash notes")
   640  			}
   641  			testABIHashNote(t, f, note)
   642  			abiHashNoteFound = true
   643  		case 3: // ELF_NOTE_GODEPS_TAG
   644  			if depsNoteFound {
   645  				t.Error("multiple dependency list notes")
   646  			}
   647  			testDepsNote(t, f, note)
   648  			depsNoteFound = true
   649  		}
   650  	}
   651  	if !pkgListNoteFound {
   652  		t.Error("package list note not found")
   653  	}
   654  	if !abiHashNoteFound {
   655  		t.Error("abi hash note not found")
   656  	}
   657  	if !depsNoteFound {
   658  		t.Error("deps note not found")
   659  	}
   660  }
   661  
   662  // Build a GOPATH package (depBase) into a shared library that links against the goroot
   663  // runtime, another package (dep2) that links against the first, and an
   664  // executable that links against dep2.
   665  func TestTwoGopathShlibs(t *testing.T) {
   666  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   667  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./dep2")
   668  	goCmd(t, "install", "-linkshared", "./exe2")
   669  	run(t, "executable linked to GOPATH library", "../../bin/exe2")
   670  }
   671  
   672  func TestThreeGopathShlibs(t *testing.T) {
   673  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   674  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./dep2")
   675  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./dep3")
   676  	goCmd(t, "install", "-linkshared", "./exe3")
   677  	run(t, "executable linked to GOPATH library", "../../bin/exe3")
   678  }
   679  
   680  // If gccgo is not available or not new enough, call t.Skip.
   681  func requireGccgo(t *testing.T) {
   682  	t.Helper()
   683  
   684  	gccgoName := os.Getenv("GCCGO")
   685  	if gccgoName == "" {
   686  		gccgoName = "gccgo"
   687  	}
   688  	gccgoPath, err := exec.LookPath(gccgoName)
   689  	if err != nil {
   690  		t.Skip("gccgo not found")
   691  	}
   692  	cmd := exec.Command(gccgoPath, "-dumpversion")
   693  	output, err := cmd.CombinedOutput()
   694  	if err != nil {
   695  		t.Fatalf("%s -dumpversion failed: %v\n%s", gccgoPath, err, output)
   696  	}
   697  	if string(output) < "5" {
   698  		t.Skipf("gccgo too old (%s)", strings.TrimSpace(string(output)))
   699  	}
   700  
   701  	gomod, err := exec.Command("go", "env", "GOMOD").Output()
   702  	if err != nil {
   703  		t.Fatalf("go env GOMOD: %v", err)
   704  	}
   705  	if len(bytes.TrimSpace(gomod)) > 0 {
   706  		t.Skipf("gccgo not supported in module mode; see golang.org/issue/30344")
   707  	}
   708  }
   709  
   710  // Build a GOPATH package into a shared library with gccgo and an executable that
   711  // links against it.
   712  func TestGoPathShlibGccgo(t *testing.T) {
   713  	requireGccgo(t)
   714  
   715  	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
   716  
   717  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "./depBase")
   718  
   719  	// Run 'go list' after 'go install': with gccgo, we apparently don't know the
   720  	// shlib location until after we've installed it.
   721  	shlib := goCmd(t, "list", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "-f", "{{.Shlib}}", "./depBase")
   722  
   723  	AssertIsLinkedToRegexp(t, shlib, libgoRE)
   724  	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "./exe")
   725  	AssertIsLinkedToRegexp(t, "../../bin/exe", libgoRE)
   726  	AssertIsLinkedTo(t, "../../bin/exe", filepath.Base(shlib))
   727  	AssertHasRPath(t, "../../bin/exe", filepath.Dir(shlib))
   728  	// And check it runs.
   729  	run(t, "gccgo-built", "../../bin/exe")
   730  }
   731  
   732  // The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared
   733  // library with gccgo, another GOPATH package that depends on the first and an
   734  // executable that links the second library.
   735  func TestTwoGopathShlibsGccgo(t *testing.T) {
   736  	requireGccgo(t)
   737  
   738  	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
   739  
   740  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "./depBase")
   741  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "./dep2")
   742  	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "./exe2")
   743  
   744  	// Run 'go list' after 'go install': with gccgo, we apparently don't know the
   745  	// shlib location until after we've installed it.
   746  	dep2 := goCmd(t, "list", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "-f", "{{.Shlib}}", "./dep2")
   747  	depBase := goCmd(t, "list", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "-f", "{{.Shlib}}", "./depBase")
   748  
   749  	AssertIsLinkedToRegexp(t, depBase, libgoRE)
   750  	AssertIsLinkedToRegexp(t, dep2, libgoRE)
   751  	AssertIsLinkedTo(t, dep2, filepath.Base(depBase))
   752  	AssertIsLinkedToRegexp(t, "../../bin/exe2", libgoRE)
   753  	AssertIsLinkedTo(t, "../../bin/exe2", filepath.Base(dep2))
   754  	AssertIsLinkedTo(t, "../../bin/exe2", filepath.Base(depBase))
   755  
   756  	// And check it runs.
   757  	run(t, "gccgo-built", "../../bin/exe2")
   758  }
   759  
   760  // Testing rebuilding of shared libraries when they are stale is a bit more
   761  // complicated that it seems like it should be. First, we make everything "old": but
   762  // only a few seconds old, or it might be older than gc (or the runtime source) and
   763  // everything will get rebuilt. Then define a timestamp slightly newer than this
   764  // time, which is what we set the mtime to of a file to cause it to be seen as new,
   765  // and finally another slightly even newer one that we can compare files against to
   766  // see if they have been rebuilt.
   767  var oldTime = time.Now().Add(-9 * time.Second)
   768  var nearlyNew = time.Now().Add(-6 * time.Second)
   769  var stampTime = time.Now().Add(-3 * time.Second)
   770  
   771  // resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the
   772  // test-specific parts of GOROOT) appear old.
   773  func resetFileStamps() {
   774  	chtime := func(path string, info os.FileInfo, err error) error {
   775  		return os.Chtimes(path, oldTime, oldTime)
   776  	}
   777  	reset := func(path string) {
   778  		if err := filepath.Walk(path, chtime); err != nil {
   779  			log.Panicf("resetFileStamps failed: %v", err)
   780  		}
   781  
   782  	}
   783  	reset("../../bin")
   784  	reset("../../pkg")
   785  	reset("../../src")
   786  	reset(gorootInstallDir)
   787  }
   788  
   789  // touch changes path and returns a function that changes it back.
   790  // It also sets the time of the file, so that we can see if it is rewritten.
   791  func touch(t *testing.T, path string) (cleanup func()) {
   792  	t.Helper()
   793  	data, err := os.ReadFile(path)
   794  	if err != nil {
   795  		t.Fatal(err)
   796  	}
   797  	old := make([]byte, len(data))
   798  	copy(old, data)
   799  	if bytes.HasPrefix(data, []byte("!<arch>\n")) {
   800  		// Change last digit of build ID.
   801  		// (Content ID in the new content-based build IDs.)
   802  		const marker = `build id "`
   803  		i := bytes.Index(data, []byte(marker))
   804  		if i < 0 {
   805  			t.Fatal("cannot find build id in archive")
   806  		}
   807  		j := bytes.IndexByte(data[i+len(marker):], '"')
   808  		if j < 0 {
   809  			t.Fatal("cannot find build id in archive")
   810  		}
   811  		i += len(marker) + j - 1
   812  		if data[i] == 'a' {
   813  			data[i] = 'b'
   814  		} else {
   815  			data[i] = 'a'
   816  		}
   817  	} else {
   818  		// assume it's a text file
   819  		data = append(data, '\n')
   820  	}
   821  
   822  	// If the file is still a symlink from an overlay, delete it so that we will
   823  	// replace it with a regular file instead of overwriting the symlinked one.
   824  	fi, err := os.Lstat(path)
   825  	if err == nil && !fi.Mode().IsRegular() {
   826  		fi, err = os.Stat(path)
   827  		if err := os.Remove(path); err != nil {
   828  			t.Fatal(err)
   829  		}
   830  	}
   831  	if err != nil {
   832  		t.Fatal(err)
   833  	}
   834  
   835  	// If we're replacing a symlink to a read-only file, make the new file
   836  	// user-writable.
   837  	perm := fi.Mode().Perm() | 0200
   838  
   839  	if err := os.WriteFile(path, data, perm); err != nil {
   840  		t.Fatal(err)
   841  	}
   842  	if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil {
   843  		t.Fatal(err)
   844  	}
   845  	return func() {
   846  		if err := os.WriteFile(path, old, perm); err != nil {
   847  			t.Fatal(err)
   848  		}
   849  	}
   850  }
   851  
   852  // isNew returns if the path is newer than the time stamp used by touch.
   853  func isNew(t *testing.T, path string) bool {
   854  	t.Helper()
   855  	fi, err := os.Stat(path)
   856  	if err != nil {
   857  		t.Fatal(err)
   858  	}
   859  	return fi.ModTime().After(stampTime)
   860  }
   861  
   862  // Fail unless path has been rebuilt (i.e. is newer than the time stamp used by
   863  // isNew)
   864  func AssertRebuilt(t *testing.T, msg, path string) {
   865  	t.Helper()
   866  	if !isNew(t, path) {
   867  		t.Errorf("%s was not rebuilt (%s)", msg, path)
   868  	}
   869  }
   870  
   871  // Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew)
   872  func AssertNotRebuilt(t *testing.T, msg, path string) {
   873  	t.Helper()
   874  	if isNew(t, path) {
   875  		t.Errorf("%s was rebuilt (%s)", msg, path)
   876  	}
   877  }
   878  
   879  func TestRebuilding(t *testing.T) {
   880  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   881  	goCmd(t, "install", "-linkshared", "./exe")
   882  	info := strings.Fields(goCmd(t, "list", "-buildmode=shared", "-linkshared", "-f", "{{.Target}} {{.Shlib}}", "./depBase"))
   883  	if len(info) != 2 {
   884  		t.Fatalf("go list failed to report Target and/or Shlib")
   885  	}
   886  	target := info[0]
   887  	shlib := info[1]
   888  
   889  	// If the source is newer than both the .a file and the .so, both are rebuilt.
   890  	t.Run("newsource", func(t *testing.T) {
   891  		resetFileStamps()
   892  		cleanup := touch(t, "./depBase/dep.go")
   893  		defer func() {
   894  			cleanup()
   895  			goCmd(t, "install", "-linkshared", "./exe")
   896  		}()
   897  		goCmd(t, "install", "-linkshared", "./exe")
   898  		AssertRebuilt(t, "new source", target)
   899  		AssertRebuilt(t, "new source", shlib)
   900  	})
   901  
   902  	// If the .a file is newer than the .so, the .so is rebuilt (but not the .a)
   903  	t.Run("newarchive", func(t *testing.T) {
   904  		resetFileStamps()
   905  		AssertNotRebuilt(t, "new .a file before build", target)
   906  		goCmd(t, "list", "-linkshared", "-f={{.ImportPath}} {{.Stale}} {{.StaleReason}} {{.Target}}", "./depBase")
   907  		AssertNotRebuilt(t, "new .a file before build", target)
   908  		cleanup := touch(t, target)
   909  		defer func() {
   910  			cleanup()
   911  			goCmd(t, "install", "-v", "-linkshared", "./exe")
   912  		}()
   913  		goCmd(t, "install", "-v", "-linkshared", "./exe")
   914  		AssertNotRebuilt(t, "new .a file", target)
   915  		AssertRebuilt(t, "new .a file", shlib)
   916  	})
   917  }
   918  
   919  func appendFile(t *testing.T, path, content string) {
   920  	t.Helper()
   921  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660)
   922  	if err != nil {
   923  		t.Fatalf("os.OpenFile failed: %v", err)
   924  	}
   925  	defer func() {
   926  		err := f.Close()
   927  		if err != nil {
   928  			t.Fatalf("f.Close failed: %v", err)
   929  		}
   930  	}()
   931  	_, err = f.WriteString(content)
   932  	if err != nil {
   933  		t.Fatalf("f.WriteString failed: %v", err)
   934  	}
   935  }
   936  
   937  func createFile(t *testing.T, path, content string) {
   938  	t.Helper()
   939  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
   940  	if err != nil {
   941  		t.Fatalf("os.OpenFile failed: %v", err)
   942  	}
   943  	_, err = f.WriteString(content)
   944  	if closeErr := f.Close(); err == nil {
   945  		err = closeErr
   946  	}
   947  	if err != nil {
   948  		t.Fatalf("WriteString failed: %v", err)
   949  	}
   950  }
   951  
   952  func TestABIChecking(t *testing.T) {
   953  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   954  	goCmd(t, "install", "-linkshared", "./exe")
   955  
   956  	// If we make an ABI-breaking change to depBase and rebuild libp.so but not exe,
   957  	// exe will abort with a complaint on startup.
   958  	// This assumes adding an exported function breaks ABI, which is not true in
   959  	// some senses but suffices for the narrow definition of ABI compatibility the
   960  	// toolchain uses today.
   961  	resetFileStamps()
   962  
   963  	createFile(t, "./depBase/break.go", "package depBase\nfunc ABIBreak() {}\n")
   964  	defer os.Remove("./depBase/break.go")
   965  
   966  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   967  	c := exec.Command("../../bin/exe")
   968  	output, err := c.CombinedOutput()
   969  	if err == nil {
   970  		t.Fatal("executing exe did not fail after ABI break")
   971  	}
   972  	scanner := bufio.NewScanner(bytes.NewReader(output))
   973  	foundMsg := false
   974  	const wantPrefix = "abi mismatch detected between the executable and lib"
   975  	for scanner.Scan() {
   976  		if strings.HasPrefix(scanner.Text(), wantPrefix) {
   977  			foundMsg = true
   978  			break
   979  		}
   980  	}
   981  	if err = scanner.Err(); err != nil {
   982  		t.Errorf("scanner encountered error: %v", err)
   983  	}
   984  	if !foundMsg {
   985  		t.Fatalf("exe failed, but without line %q; got output:\n%s", wantPrefix, output)
   986  	}
   987  
   988  	// Rebuilding exe makes it work again.
   989  	goCmd(t, "install", "-linkshared", "./exe")
   990  	run(t, "rebuilt exe", "../../bin/exe")
   991  
   992  	// If we make a change which does not break ABI (such as adding an unexported
   993  	// function) and rebuild libdepBase.so, exe still works, even if new function
   994  	// is in a file by itself.
   995  	resetFileStamps()
   996  	createFile(t, "./depBase/dep2.go", "package depBase\nfunc noABIBreak() {}\n")
   997  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./depBase")
   998  	run(t, "after non-ABI breaking change", "../../bin/exe")
   999  }
  1000  
  1001  // If a package 'explicit' imports a package 'implicit', building
  1002  // 'explicit' into a shared library implicitly includes implicit in
  1003  // the shared library. Building an executable that imports both
  1004  // explicit and implicit builds the code from implicit into the
  1005  // executable rather than fetching it from the shared library. The
  1006  // link still succeeds and the executable still runs though.
  1007  func TestImplicitInclusion(t *testing.T) {
  1008  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./explicit")
  1009  	goCmd(t, "install", "-linkshared", "./implicitcmd")
  1010  	run(t, "running executable linked against library that contains same package as it", "../../bin/implicitcmd")
  1011  }
  1012  
  1013  // Tests to make sure that the type fields of empty interfaces and itab
  1014  // fields of nonempty interfaces are unique even across modules,
  1015  // so that interface equality works correctly.
  1016  func TestInterface(t *testing.T) {
  1017  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./iface_a")
  1018  	// Note: iface_i gets installed implicitly as a dependency of iface_a.
  1019  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./iface_b")
  1020  	goCmd(t, "install", "-linkshared", "./iface")
  1021  	run(t, "running type/itab uniqueness tester", "../../bin/iface")
  1022  }
  1023  
  1024  // Access a global variable from a library.
  1025  func TestGlobal(t *testing.T) {
  1026  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./globallib")
  1027  	goCmd(t, "install", "-linkshared", "./global")
  1028  	run(t, "global executable", "../../bin/global")
  1029  	AssertIsLinkedTo(t, "../../bin/global", soname)
  1030  	AssertHasRPath(t, "../../bin/global", gorootInstallDir)
  1031  }
  1032  
  1033  // Run a test using -linkshared of an installed shared package.
  1034  // Issue 26400.
  1035  func TestTestInstalledShared(t *testing.T) {
  1036  	goCmd(nil, "test", "-linkshared", "-test.short", "sync/atomic")
  1037  }
  1038  
  1039  // Test generated pointer method with -linkshared.
  1040  // Issue 25065.
  1041  func TestGeneratedMethod(t *testing.T) {
  1042  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue25065")
  1043  }
  1044  
  1045  // Test use of shared library struct with generated hash function.
  1046  // Issue 30768.
  1047  func TestGeneratedHash(t *testing.T) {
  1048  	goCmd(nil, "install", "-buildmode=shared", "-linkshared", "./issue30768/issue30768lib")
  1049  	goCmd(nil, "test", "-linkshared", "./issue30768")
  1050  }
  1051  
  1052  // Test that packages can be added not in dependency order (here a depends on b, and a adds
  1053  // before b). This could happen with e.g. go build -buildmode=shared std. See issue 39777.
  1054  func TestPackageOrder(t *testing.T) {
  1055  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue39777/a", "./issue39777/b")
  1056  }
  1057  
  1058  // Test that GC data are generated correctly by the linker when it needs a type defined in
  1059  // a shared library. See issue 39927.
  1060  func TestGCData(t *testing.T) {
  1061  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./gcdata/p")
  1062  	goCmd(t, "build", "-linkshared", "./gcdata/main")
  1063  	runWithEnv(t, "running gcdata/main", []string{"GODEBUG=clobberfree=1"}, "./main")
  1064  }
  1065  
  1066  // Test that we don't decode type symbols from shared libraries (which has no data,
  1067  // causing panic). See issue 44031.
  1068  func TestIssue44031(t *testing.T) {
  1069  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue44031/a")
  1070  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue44031/b")
  1071  	goCmd(t, "run", "-linkshared", "./issue44031/main")
  1072  }
  1073  
  1074  // Test that we use a variable from shared libraries (which implement an
  1075  // interface in shared libraries.). A weak reference is used in the itab
  1076  // in main process. It can cause unreacheble panic. See issue 47873.
  1077  func TestIssue47873(t *testing.T) {
  1078  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "./issue47837/a")
  1079  	goCmd(t, "run", "-linkshared", "./issue47837/main")
  1080  }
  1081  

View as plain text