Source file misc/cgo/testcarchive/carchive_test.go

     1  // Copyright 2016 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 carchive_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"debug/elf"
    11  	"flag"
    12  	"fmt"
    13  	"io"
    14  	"log"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"strconv"
    21  	"strings"
    22  	"syscall"
    23  	"testing"
    24  	"time"
    25  	"unicode"
    26  )
    27  
    28  // Program to run.
    29  var bin []string
    30  
    31  // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
    32  var cc []string
    33  
    34  // ".exe" on Windows.
    35  var exeSuffix string
    36  
    37  var GOOS, GOARCH, GOPATH string
    38  var libgodir string
    39  
    40  var testWork bool // If true, preserve temporary directories.
    41  
    42  func TestMain(m *testing.M) {
    43  	flag.BoolVar(&testWork, "testwork", false, "if true, log and preserve the test's temporary working directory")
    44  	flag.Parse()
    45  	if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
    46  		fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
    47  		os.Exit(0)
    48  	}
    49  	log.SetFlags(log.Lshortfile)
    50  	os.Exit(testMain(m))
    51  }
    52  
    53  func testMain(m *testing.M) int {
    54  	// We need a writable GOPATH in which to run the tests.
    55  	// Construct one in a temporary directory.
    56  	var err error
    57  	GOPATH, err = os.MkdirTemp("", "carchive_test")
    58  	if err != nil {
    59  		log.Panic(err)
    60  	}
    61  	if testWork {
    62  		log.Println(GOPATH)
    63  	} else {
    64  		defer os.RemoveAll(GOPATH)
    65  	}
    66  	os.Setenv("GOPATH", GOPATH)
    67  
    68  	// Copy testdata into GOPATH/src/testarchive, along with a go.mod file
    69  	// declaring the same path.
    70  	modRoot := filepath.Join(GOPATH, "src", "testcarchive")
    71  	if err := overlayDir(modRoot, "testdata"); err != nil {
    72  		log.Panic(err)
    73  	}
    74  	if err := os.Chdir(modRoot); err != nil {
    75  		log.Panic(err)
    76  	}
    77  	os.Setenv("PWD", modRoot)
    78  	if err := os.WriteFile("go.mod", []byte("module testcarchive\n"), 0666); err != nil {
    79  		log.Panic(err)
    80  	}
    81  
    82  	GOOS = goEnv("GOOS")
    83  	GOARCH = goEnv("GOARCH")
    84  	bin = cmdToRun("./testp")
    85  
    86  	ccOut := goEnv("CC")
    87  	cc = []string{string(ccOut)}
    88  
    89  	out := goEnv("GOGCCFLAGS")
    90  	quote := '\000'
    91  	start := 0
    92  	lastSpace := true
    93  	backslash := false
    94  	s := string(out)
    95  	for i, c := range s {
    96  		if quote == '\000' && unicode.IsSpace(c) {
    97  			if !lastSpace {
    98  				cc = append(cc, s[start:i])
    99  				lastSpace = true
   100  			}
   101  		} else {
   102  			if lastSpace {
   103  				start = i
   104  				lastSpace = false
   105  			}
   106  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
   107  				quote = c
   108  				backslash = false
   109  			} else if !backslash && quote == c {
   110  				quote = '\000'
   111  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
   112  				backslash = true
   113  			} else {
   114  				backslash = false
   115  			}
   116  		}
   117  	}
   118  	if !lastSpace {
   119  		cc = append(cc, s[start:])
   120  	}
   121  
   122  	if GOOS == "aix" {
   123  		// -Wl,-bnoobjreorder is mandatory to keep the same layout
   124  		// in .text section.
   125  		cc = append(cc, "-Wl,-bnoobjreorder")
   126  	}
   127  	libbase := GOOS + "_" + GOARCH
   128  	if runtime.Compiler == "gccgo" {
   129  		libbase = "gccgo_" + libgodir + "_fPIC"
   130  	} else {
   131  		switch GOOS {
   132  		case "darwin", "ios":
   133  			if GOARCH == "arm64" {
   134  				libbase += "_shared"
   135  			}
   136  		case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
   137  			libbase += "_shared"
   138  		}
   139  	}
   140  	libgodir = filepath.Join(GOPATH, "pkg", libbase, "testcarchive")
   141  	cc = append(cc, "-I", libgodir)
   142  
   143  	if GOOS == "windows" {
   144  		exeSuffix = ".exe"
   145  	}
   146  
   147  	return m.Run()
   148  }
   149  
   150  func goEnv(key string) string {
   151  	out, err := exec.Command("go", "env", key).Output()
   152  	if err != nil {
   153  		if ee, ok := err.(*exec.ExitError); ok {
   154  			fmt.Fprintf(os.Stderr, "%s", ee.Stderr)
   155  		}
   156  		log.Panicf("go env %s failed:\n%s\n", key, err)
   157  	}
   158  	return strings.TrimSpace(string(out))
   159  }
   160  
   161  func cmdToRun(name string) []string {
   162  	execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec"
   163  	executor, err := exec.LookPath(execScript)
   164  	if err != nil {
   165  		return []string{name}
   166  	}
   167  	return []string{executor, name}
   168  }
   169  
   170  // genHeader writes a C header file for the C-exported declarations found in .go
   171  // source files in dir.
   172  //
   173  // TODO(golang.org/issue/35715): This should be simpler.
   174  func genHeader(t *testing.T, header, dir string) {
   175  	t.Helper()
   176  
   177  	// The 'cgo' command generates a number of additional artifacts,
   178  	// but we're only interested in the header.
   179  	// Shunt the rest of the outputs to a temporary directory.
   180  	objDir, err := os.MkdirTemp(GOPATH, "_obj")
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	defer os.RemoveAll(objDir)
   185  
   186  	files, err := filepath.Glob(filepath.Join(dir, "*.go"))
   187  	if err != nil {
   188  		t.Fatal(err)
   189  	}
   190  
   191  	cmd := exec.Command("go", "tool", "cgo",
   192  		"-objdir", objDir,
   193  		"-exportheader", header)
   194  	cmd.Args = append(cmd.Args, files...)
   195  	t.Log(cmd.Args)
   196  	if out, err := cmd.CombinedOutput(); err != nil {
   197  		t.Logf("%s", out)
   198  		t.Fatal(err)
   199  	}
   200  }
   201  
   202  func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
   203  	t.Helper()
   204  	cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
   205  	t.Log(buildcmd)
   206  	if out, err := cmd.CombinedOutput(); err != nil {
   207  		t.Logf("%s", out)
   208  		t.Fatal(err)
   209  	}
   210  	if !testWork {
   211  		defer func() {
   212  			os.Remove(libgoa)
   213  			os.Remove(libgoh)
   214  		}()
   215  	}
   216  
   217  	ccArgs := append(cc, "-o", exe, "main.c")
   218  	if GOOS == "windows" {
   219  		ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm")
   220  	} else {
   221  		ccArgs = append(ccArgs, "main_unix.c", libgoa)
   222  	}
   223  	if runtime.Compiler == "gccgo" {
   224  		ccArgs = append(ccArgs, "-lgo")
   225  	}
   226  	t.Log(ccArgs)
   227  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   228  		t.Logf("%s", out)
   229  		t.Fatal(err)
   230  	}
   231  	if !testWork {
   232  		defer os.Remove(exe)
   233  	}
   234  
   235  	binArgs := append(cmdToRun(exe), "arg1", "arg2")
   236  	cmd = exec.Command(binArgs[0], binArgs[1:]...)
   237  	if runtime.Compiler == "gccgo" {
   238  		cmd.Env = append(os.Environ(), "GCCGO=1")
   239  	}
   240  	if out, err := cmd.CombinedOutput(); err != nil {
   241  		t.Logf("%s", out)
   242  		t.Fatal(err)
   243  	}
   244  
   245  	checkLineComments(t, libgoh)
   246  }
   247  
   248  var badLineRegexp = regexp.MustCompile(`(?m)^#line [0-9]+ "/.*$`)
   249  
   250  // checkLineComments checks that the export header generated by
   251  // -buildmode=c-archive doesn't have any absolute paths in the #line
   252  // comments. We don't want those paths because they are unhelpful for
   253  // the user and make the files change based on details of the location
   254  // of GOPATH.
   255  func checkLineComments(t *testing.T, hdrname string) {
   256  	hdr, err := os.ReadFile(hdrname)
   257  	if err != nil {
   258  		if !os.IsNotExist(err) {
   259  			t.Error(err)
   260  		}
   261  		return
   262  	}
   263  	if line := badLineRegexp.Find(hdr); line != nil {
   264  		t.Errorf("bad #line directive with absolute path in %s: %q", hdrname, line)
   265  	}
   266  }
   267  
   268  // checkArchive verifies that the created library looks OK.
   269  // We just check a couple of things now, we can add more checks as needed.
   270  func checkArchive(t *testing.T, arname string) {
   271  	t.Helper()
   272  
   273  	switch GOOS {
   274  	case "aix", "darwin", "ios", "windows":
   275  		// We don't have any checks for non-ELF libraries yet.
   276  		if _, err := os.Stat(arname); err != nil {
   277  			t.Errorf("archive %s does not exist: %v", arname, err)
   278  		}
   279  	default:
   280  		checkELFArchive(t, arname)
   281  	}
   282  }
   283  
   284  // checkELFArchive checks an ELF archive.
   285  func checkELFArchive(t *testing.T, arname string) {
   286  	t.Helper()
   287  
   288  	f, err := os.Open(arname)
   289  	if err != nil {
   290  		t.Errorf("archive %s does not exist: %v", arname, err)
   291  		return
   292  	}
   293  	defer f.Close()
   294  
   295  	// TODO(iant): put these in a shared package?  But where?
   296  	const (
   297  		magic = "!<arch>\n"
   298  		fmag  = "`\n"
   299  
   300  		namelen = 16
   301  		datelen = 12
   302  		uidlen  = 6
   303  		gidlen  = 6
   304  		modelen = 8
   305  		sizelen = 10
   306  		fmaglen = 2
   307  		hdrlen  = namelen + datelen + uidlen + gidlen + modelen + sizelen + fmaglen
   308  	)
   309  
   310  	type arhdr struct {
   311  		name string
   312  		date string
   313  		uid  string
   314  		gid  string
   315  		mode string
   316  		size string
   317  		fmag string
   318  	}
   319  
   320  	var magbuf [len(magic)]byte
   321  	if _, err := io.ReadFull(f, magbuf[:]); err != nil {
   322  		t.Errorf("%s: archive too short", arname)
   323  		return
   324  	}
   325  	if string(magbuf[:]) != magic {
   326  		t.Errorf("%s: incorrect archive magic string %q", arname, magbuf)
   327  	}
   328  
   329  	off := int64(len(magic))
   330  	for {
   331  		if off&1 != 0 {
   332  			var b [1]byte
   333  			if _, err := f.Read(b[:]); err != nil {
   334  				if err == io.EOF {
   335  					break
   336  				}
   337  				t.Errorf("%s: error skipping alignment byte at %d: %v", arname, off, err)
   338  			}
   339  			off++
   340  		}
   341  
   342  		var hdrbuf [hdrlen]byte
   343  		if _, err := io.ReadFull(f, hdrbuf[:]); err != nil {
   344  			if err == io.EOF {
   345  				break
   346  			}
   347  			t.Errorf("%s: error reading archive header at %d: %v", arname, off, err)
   348  			return
   349  		}
   350  
   351  		var hdr arhdr
   352  		hdrslice := hdrbuf[:]
   353  		set := func(len int, ps *string) {
   354  			*ps = string(bytes.TrimSpace(hdrslice[:len]))
   355  			hdrslice = hdrslice[len:]
   356  		}
   357  		set(namelen, &hdr.name)
   358  		set(datelen, &hdr.date)
   359  		set(uidlen, &hdr.uid)
   360  		set(gidlen, &hdr.gid)
   361  		set(modelen, &hdr.mode)
   362  		set(sizelen, &hdr.size)
   363  		hdr.fmag = string(hdrslice[:fmaglen])
   364  		hdrslice = hdrslice[fmaglen:]
   365  		if len(hdrslice) != 0 {
   366  			t.Fatalf("internal error: len(hdrslice) == %d", len(hdrslice))
   367  		}
   368  
   369  		if hdr.fmag != fmag {
   370  			t.Errorf("%s: invalid fmagic value %q at %d", arname, hdr.fmag, off)
   371  			return
   372  		}
   373  
   374  		size, err := strconv.ParseInt(hdr.size, 10, 64)
   375  		if err != nil {
   376  			t.Errorf("%s: error parsing size %q at %d: %v", arname, hdr.size, off, err)
   377  			return
   378  		}
   379  
   380  		off += hdrlen
   381  
   382  		switch hdr.name {
   383  		case "__.SYMDEF", "/", "/SYM64/":
   384  			// The archive symbol map.
   385  		case "//", "ARFILENAMES/":
   386  			// The extended name table.
   387  		default:
   388  			// This should be an ELF object.
   389  			checkELFArchiveObject(t, arname, off, io.NewSectionReader(f, off, size))
   390  		}
   391  
   392  		off += size
   393  		if _, err := f.Seek(off, os.SEEK_SET); err != nil {
   394  			t.Errorf("%s: failed to seek to %d: %v", arname, off, err)
   395  		}
   396  	}
   397  }
   398  
   399  // checkELFArchiveObject checks an object in an ELF archive.
   400  func checkELFArchiveObject(t *testing.T, arname string, off int64, obj io.ReaderAt) {
   401  	t.Helper()
   402  
   403  	ef, err := elf.NewFile(obj)
   404  	if err != nil {
   405  		t.Errorf("%s: failed to open ELF file at %d: %v", arname, off, err)
   406  		return
   407  	}
   408  	defer ef.Close()
   409  
   410  	// Verify section types.
   411  	for _, sec := range ef.Sections {
   412  		want := elf.SHT_NULL
   413  		switch sec.Name {
   414  		case ".text", ".data":
   415  			want = elf.SHT_PROGBITS
   416  		case ".bss":
   417  			want = elf.SHT_NOBITS
   418  		case ".symtab":
   419  			want = elf.SHT_SYMTAB
   420  		case ".strtab":
   421  			want = elf.SHT_STRTAB
   422  		case ".init_array":
   423  			want = elf.SHT_INIT_ARRAY
   424  		case ".fini_array":
   425  			want = elf.SHT_FINI_ARRAY
   426  		case ".preinit_array":
   427  			want = elf.SHT_PREINIT_ARRAY
   428  		}
   429  		if want != elf.SHT_NULL && sec.Type != want {
   430  			t.Errorf("%s: incorrect section type in elf file at %d for section %q: got %v want %v", arname, off, sec.Name, sec.Type, want)
   431  		}
   432  	}
   433  }
   434  
   435  func TestInstall(t *testing.T) {
   436  	if !testWork {
   437  		defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   438  	}
   439  
   440  	libgoa := "libgo.a"
   441  	if runtime.Compiler == "gccgo" {
   442  		libgoa = "liblibgo.a"
   443  	}
   444  
   445  	// Generate the p.h header file.
   446  	//
   447  	// 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
   448  	// would also attempt to install transitive standard-library dependencies to
   449  	// GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
   450  	// be running this test in a GOROOT owned by root.)
   451  	genHeader(t, "p.h", "./p")
   452  
   453  	testInstall(t, "./testp1"+exeSuffix,
   454  		filepath.Join(libgodir, libgoa),
   455  		filepath.Join(libgodir, "libgo.h"),
   456  		"go", "install", "-buildmode=c-archive", "./libgo")
   457  
   458  	// Test building libgo other than installing it.
   459  	// Header files are now present.
   460  	testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h",
   461  		"go", "build", "-buildmode=c-archive", filepath.Join(".", "libgo", "libgo.go"))
   462  
   463  	testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h",
   464  		"go", "build", "-buildmode=c-archive", "-o", "libgo.a", "./libgo")
   465  }
   466  
   467  func TestEarlySignalHandler(t *testing.T) {
   468  	switch GOOS {
   469  	case "darwin", "ios":
   470  		switch GOARCH {
   471  		case "arm64":
   472  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   473  		}
   474  	case "windows":
   475  		t.Skip("skipping signal test on Windows")
   476  	}
   477  
   478  	if !testWork {
   479  		defer func() {
   480  			os.Remove("libgo2.a")
   481  			os.Remove("libgo2.h")
   482  			os.Remove("testp")
   483  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   484  		}()
   485  	}
   486  
   487  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
   488  	if out, err := cmd.CombinedOutput(); err != nil {
   489  		t.Logf("%s", out)
   490  		t.Fatal(err)
   491  	}
   492  	checkLineComments(t, "libgo2.h")
   493  	checkArchive(t, "libgo2.a")
   494  
   495  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a")
   496  	if runtime.Compiler == "gccgo" {
   497  		ccArgs = append(ccArgs, "-lgo")
   498  	}
   499  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   500  		t.Logf("%s", out)
   501  		t.Fatal(err)
   502  	}
   503  
   504  	darwin := "0"
   505  	if runtime.GOOS == "darwin" {
   506  		darwin = "1"
   507  	}
   508  	cmd = exec.Command(bin[0], append(bin[1:], darwin)...)
   509  
   510  	if out, err := cmd.CombinedOutput(); err != nil {
   511  		t.Logf("%s", out)
   512  		t.Fatal(err)
   513  	}
   514  }
   515  
   516  func TestSignalForwarding(t *testing.T) {
   517  	checkSignalForwardingTest(t)
   518  
   519  	if !testWork {
   520  		defer func() {
   521  			os.Remove("libgo2.a")
   522  			os.Remove("libgo2.h")
   523  			os.Remove("testp")
   524  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   525  		}()
   526  	}
   527  
   528  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
   529  	if out, err := cmd.CombinedOutput(); err != nil {
   530  		t.Logf("%s", out)
   531  		t.Fatal(err)
   532  	}
   533  	checkLineComments(t, "libgo2.h")
   534  	checkArchive(t, "libgo2.a")
   535  
   536  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   537  	if runtime.Compiler == "gccgo" {
   538  		ccArgs = append(ccArgs, "-lgo")
   539  	}
   540  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   541  		t.Logf("%s", out)
   542  		t.Fatal(err)
   543  	}
   544  
   545  	cmd = exec.Command(bin[0], append(bin[1:], "1")...)
   546  
   547  	out, err := cmd.CombinedOutput()
   548  	t.Logf("%s", out)
   549  	expectSignal(t, err, syscall.SIGSEGV)
   550  
   551  	// SIGPIPE is never forwarded on darwin. See golang.org/issue/33384.
   552  	if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
   553  		// Test SIGPIPE forwarding
   554  		cmd = exec.Command(bin[0], append(bin[1:], "3")...)
   555  
   556  		out, err = cmd.CombinedOutput()
   557  		t.Logf("%s", out)
   558  		expectSignal(t, err, syscall.SIGPIPE)
   559  	}
   560  }
   561  
   562  func TestSignalForwardingExternal(t *testing.T) {
   563  	if GOOS == "freebsd" || GOOS == "aix" {
   564  		t.Skipf("skipping on %s/%s; signal always goes to the Go runtime", GOOS, GOARCH)
   565  	} else if GOOS == "darwin" && GOARCH == "amd64" {
   566  		t.Skipf("skipping on %s/%s: runtime does not permit SI_USER SIGSEGV", GOOS, GOARCH)
   567  	}
   568  	checkSignalForwardingTest(t)
   569  
   570  	if !testWork {
   571  		defer func() {
   572  			os.Remove("libgo2.a")
   573  			os.Remove("libgo2.h")
   574  			os.Remove("testp")
   575  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   576  		}()
   577  	}
   578  
   579  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
   580  	if out, err := cmd.CombinedOutput(); err != nil {
   581  		t.Logf("%s", out)
   582  		t.Fatal(err)
   583  	}
   584  	checkLineComments(t, "libgo2.h")
   585  	checkArchive(t, "libgo2.a")
   586  
   587  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   588  	if runtime.Compiler == "gccgo" {
   589  		ccArgs = append(ccArgs, "-lgo")
   590  	}
   591  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   592  		t.Logf("%s", out)
   593  		t.Fatal(err)
   594  	}
   595  
   596  	// We want to send the process a signal and see if it dies.
   597  	// Normally the signal goes to the C thread, the Go signal
   598  	// handler picks it up, sees that it is running in a C thread,
   599  	// and the program dies. Unfortunately, occasionally the
   600  	// signal is delivered to a Go thread, which winds up
   601  	// discarding it because it was sent by another program and
   602  	// there is no Go handler for it. To avoid this, run the
   603  	// program several times in the hopes that it will eventually
   604  	// fail.
   605  	const tries = 20
   606  	for i := 0; i < tries; i++ {
   607  		cmd = exec.Command(bin[0], append(bin[1:], "2")...)
   608  
   609  		stderr, err := cmd.StderrPipe()
   610  		if err != nil {
   611  			t.Fatal(err)
   612  		}
   613  		defer stderr.Close()
   614  
   615  		r := bufio.NewReader(stderr)
   616  
   617  		err = cmd.Start()
   618  
   619  		if err != nil {
   620  			t.Fatal(err)
   621  		}
   622  
   623  		// Wait for trigger to ensure that the process is started.
   624  		ok, err := r.ReadString('\n')
   625  
   626  		// Verify trigger.
   627  		if err != nil || ok != "OK\n" {
   628  			t.Fatalf("Did not receive OK signal")
   629  		}
   630  
   631  		// Give the program a chance to enter the sleep function.
   632  		time.Sleep(time.Millisecond)
   633  
   634  		cmd.Process.Signal(syscall.SIGSEGV)
   635  
   636  		err = cmd.Wait()
   637  
   638  		if err == nil {
   639  			continue
   640  		}
   641  
   642  		if expectSignal(t, err, syscall.SIGSEGV) {
   643  			return
   644  		}
   645  	}
   646  
   647  	t.Errorf("program succeeded unexpectedly %d times", tries)
   648  }
   649  
   650  // checkSignalForwardingTest calls t.Skip if the SignalForwarding test
   651  // doesn't work on this platform.
   652  func checkSignalForwardingTest(t *testing.T) {
   653  	switch GOOS {
   654  	case "darwin", "ios":
   655  		switch GOARCH {
   656  		case "arm64":
   657  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   658  		}
   659  	case "windows":
   660  		t.Skip("skipping signal test on Windows")
   661  	}
   662  }
   663  
   664  // expectSignal checks that err, the exit status of a test program,
   665  // shows a failure due to a specific signal. Returns whether we found
   666  // the expected signal.
   667  func expectSignal(t *testing.T, err error, sig syscall.Signal) bool {
   668  	if err == nil {
   669  		t.Error("test program succeeded unexpectedly")
   670  	} else if ee, ok := err.(*exec.ExitError); !ok {
   671  		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   672  	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   673  		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   674  	} else if !ws.Signaled() || ws.Signal() != sig {
   675  		t.Errorf("got %v; expected signal %v", ee, sig)
   676  	} else {
   677  		return true
   678  	}
   679  	return false
   680  }
   681  
   682  func TestOsSignal(t *testing.T) {
   683  	switch GOOS {
   684  	case "windows":
   685  		t.Skip("skipping signal test on Windows")
   686  	}
   687  
   688  	if !testWork {
   689  		defer func() {
   690  			os.Remove("libgo3.a")
   691  			os.Remove("libgo3.h")
   692  			os.Remove("testp")
   693  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   694  		}()
   695  	}
   696  
   697  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "./libgo3")
   698  	if out, err := cmd.CombinedOutput(); err != nil {
   699  		t.Logf("%s", out)
   700  		t.Fatal(err)
   701  	}
   702  	checkLineComments(t, "libgo3.h")
   703  	checkArchive(t, "libgo3.a")
   704  
   705  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
   706  	if runtime.Compiler == "gccgo" {
   707  		ccArgs = append(ccArgs, "-lgo")
   708  	}
   709  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   710  		t.Logf("%s", out)
   711  		t.Fatal(err)
   712  	}
   713  
   714  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   715  		t.Logf("%s", out)
   716  		t.Fatal(err)
   717  	}
   718  }
   719  
   720  func TestSigaltstack(t *testing.T) {
   721  	switch GOOS {
   722  	case "windows":
   723  		t.Skip("skipping signal test on Windows")
   724  	}
   725  
   726  	if !testWork {
   727  		defer func() {
   728  			os.Remove("libgo4.a")
   729  			os.Remove("libgo4.h")
   730  			os.Remove("testp")
   731  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   732  		}()
   733  	}
   734  
   735  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "./libgo4")
   736  	if out, err := cmd.CombinedOutput(); err != nil {
   737  		t.Logf("%s", out)
   738  		t.Fatal(err)
   739  	}
   740  	checkLineComments(t, "libgo4.h")
   741  	checkArchive(t, "libgo4.a")
   742  
   743  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
   744  	if runtime.Compiler == "gccgo" {
   745  		ccArgs = append(ccArgs, "-lgo")
   746  	}
   747  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   748  		t.Logf("%s", out)
   749  		t.Fatal(err)
   750  	}
   751  
   752  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   753  		t.Logf("%s", out)
   754  		t.Fatal(err)
   755  	}
   756  }
   757  
   758  const testar = `#!/usr/bin/env bash
   759  while [[ $1 == -* ]] >/dev/null; do
   760    shift
   761  done
   762  echo "testar" > $1
   763  echo "testar" > PWD/testar.ran
   764  `
   765  
   766  func TestExtar(t *testing.T) {
   767  	switch GOOS {
   768  	case "windows":
   769  		t.Skip("skipping signal test on Windows")
   770  	}
   771  	if runtime.Compiler == "gccgo" {
   772  		t.Skip("skipping -extar test when using gccgo")
   773  	}
   774  	if runtime.GOOS == "ios" {
   775  		t.Skip("shell scripts are not executable on iOS hosts")
   776  	}
   777  
   778  	if !testWork {
   779  		defer func() {
   780  			os.Remove("libgo4.a")
   781  			os.Remove("libgo4.h")
   782  			os.Remove("testar")
   783  			os.Remove("testar.ran")
   784  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   785  		}()
   786  	}
   787  
   788  	os.Remove("testar")
   789  	dir, err := os.Getwd()
   790  	if err != nil {
   791  		t.Fatal(err)
   792  	}
   793  	s := strings.Replace(testar, "PWD", dir, 1)
   794  	if err := os.WriteFile("testar", []byte(s), 0777); err != nil {
   795  		t.Fatal(err)
   796  	}
   797  
   798  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "./libgo4")
   799  	if out, err := cmd.CombinedOutput(); err != nil {
   800  		t.Logf("%s", out)
   801  		t.Fatal(err)
   802  	}
   803  	checkLineComments(t, "libgo4.h")
   804  
   805  	if _, err := os.Stat("testar.ran"); err != nil {
   806  		if os.IsNotExist(err) {
   807  			t.Error("testar does not exist after go build")
   808  		} else {
   809  			t.Errorf("error checking testar: %v", err)
   810  		}
   811  	}
   812  }
   813  
   814  func TestPIE(t *testing.T) {
   815  	switch GOOS {
   816  	case "windows", "darwin", "ios", "plan9":
   817  		t.Skipf("skipping PIE test on %s", GOOS)
   818  	}
   819  
   820  	if !testWork {
   821  		defer func() {
   822  			os.Remove("testp" + exeSuffix)
   823  			os.RemoveAll(filepath.Join(GOPATH, "pkg"))
   824  		}()
   825  	}
   826  
   827  	// Generate the p.h header file.
   828  	//
   829  	// 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
   830  	// would also attempt to install transitive standard-library dependencies to
   831  	// GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
   832  	// be running this test in a GOROOT owned by root.)
   833  	genHeader(t, "p.h", "./p")
   834  
   835  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "./libgo")
   836  	if out, err := cmd.CombinedOutput(); err != nil {
   837  		t.Logf("%s", out)
   838  		t.Fatal(err)
   839  	}
   840  
   841  	libgoa := "libgo.a"
   842  	if runtime.Compiler == "gccgo" {
   843  		libgoa = "liblibgo.a"
   844  	}
   845  
   846  	ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join(libgodir, libgoa))
   847  	if runtime.Compiler == "gccgo" {
   848  		ccArgs = append(ccArgs, "-lgo")
   849  	}
   850  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   851  		t.Logf("%s", out)
   852  		t.Fatal(err)
   853  	}
   854  
   855  	binArgs := append(bin, "arg1", "arg2")
   856  	cmd = exec.Command(binArgs[0], binArgs[1:]...)
   857  	if runtime.Compiler == "gccgo" {
   858  		cmd.Env = append(os.Environ(), "GCCGO=1")
   859  	}
   860  	if out, err := cmd.CombinedOutput(); err != nil {
   861  		t.Logf("%s", out)
   862  		t.Fatal(err)
   863  	}
   864  
   865  	if GOOS != "aix" {
   866  		f, err := elf.Open("testp" + exeSuffix)
   867  		if err != nil {
   868  			t.Fatal("elf.Open failed: ", err)
   869  		}
   870  		defer f.Close()
   871  		if hasDynTag(t, f, elf.DT_TEXTREL) {
   872  			t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix)
   873  		}
   874  	}
   875  }
   876  
   877  func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
   878  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   879  	if ds == nil {
   880  		t.Error("no SHT_DYNAMIC section")
   881  		return false
   882  	}
   883  	d, err := ds.Data()
   884  	if err != nil {
   885  		t.Errorf("can't read SHT_DYNAMIC contents: %v", err)
   886  		return false
   887  	}
   888  	for len(d) > 0 {
   889  		var t elf.DynTag
   890  		switch f.Class {
   891  		case elf.ELFCLASS32:
   892  			t = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   893  			d = d[8:]
   894  		case elf.ELFCLASS64:
   895  			t = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   896  			d = d[16:]
   897  		}
   898  		if t == tag {
   899  			return true
   900  		}
   901  	}
   902  	return false
   903  }
   904  
   905  func TestSIGPROF(t *testing.T) {
   906  	switch GOOS {
   907  	case "windows", "plan9":
   908  		t.Skipf("skipping SIGPROF test on %s", GOOS)
   909  	case "darwin", "ios":
   910  		t.Skipf("skipping SIGPROF test on %s; see https://golang.org/issue/19320", GOOS)
   911  	}
   912  
   913  	t.Parallel()
   914  
   915  	if !testWork {
   916  		defer func() {
   917  			os.Remove("testp6" + exeSuffix)
   918  			os.Remove("libgo6.a")
   919  			os.Remove("libgo6.h")
   920  		}()
   921  	}
   922  
   923  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "./libgo6")
   924  	if out, err := cmd.CombinedOutput(); err != nil {
   925  		t.Logf("%s", out)
   926  		t.Fatal(err)
   927  	}
   928  	checkLineComments(t, "libgo6.h")
   929  	checkArchive(t, "libgo6.a")
   930  
   931  	ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a")
   932  	if runtime.Compiler == "gccgo" {
   933  		ccArgs = append(ccArgs, "-lgo")
   934  	}
   935  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   936  		t.Logf("%s", out)
   937  		t.Fatal(err)
   938  	}
   939  
   940  	argv := cmdToRun("./testp6")
   941  	cmd = exec.Command(argv[0], argv[1:]...)
   942  	if out, err := cmd.CombinedOutput(); err != nil {
   943  		t.Logf("%s", out)
   944  		t.Fatal(err)
   945  	}
   946  }
   947  
   948  // TestCompileWithoutShared tests that if we compile code without the
   949  // -shared option, we can put it into an archive. When we use the go
   950  // tool with -buildmode=c-archive, it passes -shared to the compiler,
   951  // so we override that. The go tool doesn't work this way, but Bazel
   952  // will likely do it in the future. And it ought to work. This test
   953  // was added because at one time it did not work on PPC Linux.
   954  func TestCompileWithoutShared(t *testing.T) {
   955  	// For simplicity, reuse the signal forwarding test.
   956  	checkSignalForwardingTest(t)
   957  
   958  	if !testWork {
   959  		defer func() {
   960  			os.Remove("libgo2.a")
   961  			os.Remove("libgo2.h")
   962  		}()
   963  	}
   964  
   965  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "./libgo2")
   966  	t.Log(cmd.Args)
   967  	out, err := cmd.CombinedOutput()
   968  	t.Logf("%s", out)
   969  	if err != nil {
   970  		t.Fatal(err)
   971  	}
   972  	checkLineComments(t, "libgo2.h")
   973  	checkArchive(t, "libgo2.a")
   974  
   975  	exe := "./testnoshared" + exeSuffix
   976  
   977  	// In some cases, -no-pie is needed here, but not accepted everywhere. First try
   978  	// if -no-pie is accepted. See #22126.
   979  	ccArgs := append(cc, "-o", exe, "-no-pie", "main5.c", "libgo2.a")
   980  	if runtime.Compiler == "gccgo" {
   981  		ccArgs = append(ccArgs, "-lgo")
   982  	}
   983  	t.Log(ccArgs)
   984  	out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
   985  
   986  	// If -no-pie unrecognized, try -nopie if this is possibly clang
   987  	if err != nil && bytes.Contains(out, []byte("unknown")) && !strings.Contains(cc[0], "gcc") {
   988  		ccArgs = append(cc, "-o", exe, "-nopie", "main5.c", "libgo2.a")
   989  		t.Log(ccArgs)
   990  		out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
   991  	}
   992  
   993  	// Don't use either -no-pie or -nopie
   994  	if err != nil && bytes.Contains(out, []byte("unrecognized")) {
   995  		ccArgs := append(cc, "-o", exe, "main5.c", "libgo2.a")
   996  		t.Log(ccArgs)
   997  		out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
   998  	}
   999  	t.Logf("%s", out)
  1000  	if err != nil {
  1001  		t.Fatal(err)
  1002  	}
  1003  	if !testWork {
  1004  		defer os.Remove(exe)
  1005  	}
  1006  
  1007  	binArgs := append(cmdToRun(exe), "1")
  1008  	t.Log(binArgs)
  1009  	out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
  1010  	t.Logf("%s", out)
  1011  	expectSignal(t, err, syscall.SIGSEGV)
  1012  
  1013  	// SIGPIPE is never forwarded on darwin. See golang.org/issue/33384.
  1014  	if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
  1015  		binArgs := append(cmdToRun(exe), "3")
  1016  		t.Log(binArgs)
  1017  		out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
  1018  		t.Logf("%s", out)
  1019  		expectSignal(t, err, syscall.SIGPIPE)
  1020  	}
  1021  }
  1022  
  1023  // Test that installing a second time recreates the header file.
  1024  func TestCachedInstall(t *testing.T) {
  1025  	if !testWork {
  1026  		defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
  1027  	}
  1028  
  1029  	h := filepath.Join(libgodir, "libgo.h")
  1030  
  1031  	buildcmd := []string{"go", "install", "-buildmode=c-archive", "./libgo"}
  1032  
  1033  	cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
  1034  	t.Log(buildcmd)
  1035  	if out, err := cmd.CombinedOutput(); err != nil {
  1036  		t.Logf("%s", out)
  1037  		t.Fatal(err)
  1038  	}
  1039  
  1040  	if _, err := os.Stat(h); err != nil {
  1041  		t.Errorf("libgo.h not installed: %v", err)
  1042  	}
  1043  
  1044  	if err := os.Remove(h); err != nil {
  1045  		t.Fatal(err)
  1046  	}
  1047  
  1048  	cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
  1049  	t.Log(buildcmd)
  1050  	if out, err := cmd.CombinedOutput(); err != nil {
  1051  		t.Logf("%s", out)
  1052  		t.Fatal(err)
  1053  	}
  1054  
  1055  	if _, err := os.Stat(h); err != nil {
  1056  		t.Errorf("libgo.h not installed in second run: %v", err)
  1057  	}
  1058  }
  1059  
  1060  // Issue 35294.
  1061  func TestManyCalls(t *testing.T) {
  1062  	t.Parallel()
  1063  
  1064  	if !testWork {
  1065  		defer func() {
  1066  			os.Remove("testp7" + exeSuffix)
  1067  			os.Remove("libgo7.a")
  1068  			os.Remove("libgo7.h")
  1069  		}()
  1070  	}
  1071  
  1072  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo7.a", "./libgo7")
  1073  	if out, err := cmd.CombinedOutput(); err != nil {
  1074  		t.Logf("%s", out)
  1075  		t.Fatal(err)
  1076  	}
  1077  	checkLineComments(t, "libgo7.h")
  1078  	checkArchive(t, "libgo7.a")
  1079  
  1080  	ccArgs := append(cc, "-o", "testp7"+exeSuffix, "main7.c", "libgo7.a")
  1081  	if runtime.Compiler == "gccgo" {
  1082  		ccArgs = append(ccArgs, "-lgo")
  1083  	}
  1084  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
  1085  		t.Logf("%s", out)
  1086  		t.Fatal(err)
  1087  	}
  1088  
  1089  	argv := cmdToRun("./testp7")
  1090  	cmd = exec.Command(argv[0], argv[1:]...)
  1091  	var sb strings.Builder
  1092  	cmd.Stdout = &sb
  1093  	cmd.Stderr = &sb
  1094  	if err := cmd.Start(); err != nil {
  1095  		t.Fatal(err)
  1096  	}
  1097  
  1098  	timer := time.AfterFunc(time.Minute,
  1099  		func() {
  1100  			t.Error("test program timed out")
  1101  			cmd.Process.Kill()
  1102  		},
  1103  	)
  1104  	defer timer.Stop()
  1105  
  1106  	if err := cmd.Wait(); err != nil {
  1107  		t.Log(sb.String())
  1108  		t.Error(err)
  1109  	}
  1110  }
  1111  

View as plain text