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

View as plain text