github.com/sbinet/go@v0.0.0-20160827155028-54d7de7dd62b/misc/cgo/testcarchive/carchive_test.go (about)

     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  	"debug/elf"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  	"time"
    19  	"unicode"
    20  )
    21  
    22  // Program to run.
    23  var bin []string
    24  
    25  // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
    26  var cc []string
    27  
    28  // An environment with GOPATH=$(pwd).
    29  var gopathEnv []string
    30  
    31  // ".exe" on Windows.
    32  var exeSuffix string
    33  
    34  var GOOS, GOARCH string
    35  var libgodir string
    36  
    37  func init() {
    38  	bin = []string{"./testp"}
    39  	GOOS = goEnv("GOOS")
    40  	GOARCH = goEnv("GOARCH")
    41  	execScript := "go_" + GOOS + "_" + GOARCH + "_exec"
    42  	if executor, err := exec.LookPath(execScript); err == nil {
    43  		bin = []string{executor, "./testp"}
    44  	}
    45  
    46  	ccOut := goEnv("CC")
    47  	cc = []string{string(ccOut)}
    48  
    49  	out := goEnv("GOGCCFLAGS")
    50  	quote := '\000'
    51  	start := 0
    52  	lastSpace := true
    53  	backslash := false
    54  	s := string(out)
    55  	for i, c := range s {
    56  		if quote == '\000' && unicode.IsSpace(c) {
    57  			if !lastSpace {
    58  				cc = append(cc, s[start:i])
    59  				lastSpace = true
    60  			}
    61  		} else {
    62  			if lastSpace {
    63  				start = i
    64  				lastSpace = false
    65  			}
    66  			if quote == '\000' && !backslash && (c == '"' || c == '\'') {
    67  				quote = c
    68  				backslash = false
    69  			} else if !backslash && quote == c {
    70  				quote = '\000'
    71  			} else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
    72  				backslash = true
    73  			} else {
    74  				backslash = false
    75  			}
    76  		}
    77  	}
    78  	if !lastSpace {
    79  		cc = append(cc, s[start:])
    80  	}
    81  
    82  	if GOOS == "darwin" {
    83  		// For Darwin/ARM.
    84  		// TODO(crawshaw): can we do better?
    85  		cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
    86  	}
    87  	libgodir = GOOS + "_" + GOARCH
    88  	switch GOOS {
    89  	case "darwin":
    90  		if GOARCH == "arm" || GOARCH == "arm64" {
    91  			libgodir += "_shared"
    92  		}
    93  	case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
    94  		libgodir += "_shared"
    95  	}
    96  	cc = append(cc, "-I", filepath.Join("pkg", libgodir))
    97  
    98  	// Build an environment with GOPATH=$(pwd)
    99  	env := os.Environ()
   100  	var n []string
   101  	for _, e := range env {
   102  		if !strings.HasPrefix(e, "GOPATH=") {
   103  			n = append(n, e)
   104  		}
   105  	}
   106  	dir, err := os.Getwd()
   107  	if err != nil {
   108  		fmt.Fprintln(os.Stderr, err)
   109  		os.Exit(2)
   110  	}
   111  	n = append(n, "GOPATH="+dir)
   112  	gopathEnv = n
   113  
   114  	if GOOS == "windows" {
   115  		exeSuffix = ".exe"
   116  	}
   117  }
   118  
   119  func goEnv(key string) string {
   120  	out, err := exec.Command("go", "env", key).Output()
   121  	if err != nil {
   122  		fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err)
   123  		fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr)
   124  		os.Exit(2)
   125  	}
   126  	return strings.TrimSpace(string(out))
   127  }
   128  
   129  func compilemain(t *testing.T, libgo string) {
   130  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main.c")
   131  	if GOOS == "windows" {
   132  		ccArgs = append(ccArgs, "main_windows.c", libgo, "-lntdll", "-lws2_32", "-lwinmm")
   133  	} else {
   134  		ccArgs = append(ccArgs, "main_unix.c", libgo)
   135  	}
   136  	t.Log(ccArgs)
   137  
   138  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   139  		t.Logf("%s", out)
   140  		t.Fatal(err)
   141  	}
   142  }
   143  
   144  func TestInstall(t *testing.T) {
   145  	defer func() {
   146  		os.Remove("libgo.a")
   147  		os.Remove("libgo.h")
   148  		os.Remove("testp")
   149  		os.RemoveAll("pkg")
   150  	}()
   151  
   152  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo")
   153  	cmd.Env = gopathEnv
   154  	if out, err := cmd.CombinedOutput(); err != nil {
   155  		t.Logf("%s", out)
   156  		t.Fatal(err)
   157  	}
   158  
   159  	compilemain(t, filepath.Join("pkg", libgodir, "libgo.a"))
   160  
   161  	binArgs := append(bin, "arg1", "arg2")
   162  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   163  		t.Logf("%s", out)
   164  		t.Fatal(err)
   165  	}
   166  
   167  	os.Remove("libgo.a")
   168  	os.Remove("libgo.h")
   169  	os.Remove("testp")
   170  
   171  	// Test building libgo other than installing it.
   172  	// Header files are now present.
   173  	cmd = exec.Command("go", "build", "-buildmode=c-archive", filepath.Join("src", "libgo", "libgo.go"))
   174  	cmd.Env = gopathEnv
   175  	if out, err := cmd.CombinedOutput(); err != nil {
   176  		t.Logf("%s", out)
   177  		t.Fatal(err)
   178  	}
   179  
   180  	compilemain(t, "libgo.a")
   181  
   182  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   183  		t.Logf("%s", out)
   184  		t.Fatal(err)
   185  	}
   186  
   187  	os.Remove("libgo.a")
   188  	os.Remove("libgo.h")
   189  	os.Remove("testp")
   190  
   191  	cmd = exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo.a", "libgo")
   192  	cmd.Env = gopathEnv
   193  	if out, err := cmd.CombinedOutput(); err != nil {
   194  		t.Logf("%s", out)
   195  		t.Fatal(err)
   196  	}
   197  
   198  	compilemain(t, "libgo.a")
   199  
   200  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   201  		t.Logf("%s", out)
   202  		t.Fatal(err)
   203  	}
   204  }
   205  
   206  func TestEarlySignalHandler(t *testing.T) {
   207  	switch GOOS {
   208  	case "darwin":
   209  		switch GOARCH {
   210  		case "arm", "arm64":
   211  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   212  		}
   213  	case "windows":
   214  		t.Skip("skipping signal test on Windows")
   215  	}
   216  
   217  	defer func() {
   218  		os.Remove("libgo2.a")
   219  		os.Remove("libgo2.h")
   220  		os.Remove("testp")
   221  		os.RemoveAll("pkg")
   222  	}()
   223  
   224  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   225  	cmd.Env = gopathEnv
   226  	if out, err := cmd.CombinedOutput(); err != nil {
   227  		t.Logf("%s", out)
   228  		t.Fatal(err)
   229  	}
   230  
   231  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a")
   232  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   233  		t.Logf("%s", out)
   234  		t.Fatal(err)
   235  	}
   236  
   237  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   238  		t.Logf("%s", out)
   239  		t.Fatal(err)
   240  	}
   241  }
   242  
   243  func TestSignalForwarding(t *testing.T) {
   244  	switch GOOS {
   245  	case "darwin":
   246  		switch GOARCH {
   247  		case "arm", "arm64":
   248  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   249  		}
   250  	case "windows":
   251  		t.Skip("skipping signal test on Windows")
   252  	}
   253  
   254  	defer func() {
   255  		os.Remove("libgo2.a")
   256  		os.Remove("libgo2.h")
   257  		os.Remove("testp")
   258  		os.RemoveAll("pkg")
   259  	}()
   260  
   261  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   262  	cmd.Env = gopathEnv
   263  	if out, err := cmd.CombinedOutput(); err != nil {
   264  		t.Logf("%s", out)
   265  		t.Fatal(err)
   266  	}
   267  
   268  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   269  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   270  		t.Logf("%s", out)
   271  		t.Fatal(err)
   272  	}
   273  
   274  	cmd = exec.Command(bin[0], append(bin[1:], "1")...)
   275  
   276  	out, err := cmd.CombinedOutput()
   277  
   278  	if err == nil {
   279  		t.Logf("%s", out)
   280  		t.Error("test program succeeded unexpectedly")
   281  	} else if ee, ok := err.(*exec.ExitError); !ok {
   282  		t.Logf("%s", out)
   283  		t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   284  	} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   285  		t.Logf("%s", out)
   286  		t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   287  	} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   288  		t.Logf("%s", out)
   289  		t.Errorf("got %v; expected SIGSEGV", ee)
   290  	}
   291  }
   292  
   293  func TestSignalForwardingExternal(t *testing.T) {
   294  	switch GOOS {
   295  	case "darwin":
   296  		switch GOARCH {
   297  		case "arm", "arm64":
   298  			t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
   299  		}
   300  	case "windows":
   301  		t.Skip("skipping signal test on Windows")
   302  	}
   303  
   304  	defer func() {
   305  		os.Remove("libgo2.a")
   306  		os.Remove("libgo2.h")
   307  		os.Remove("testp")
   308  		os.RemoveAll("pkg")
   309  	}()
   310  
   311  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2")
   312  	cmd.Env = gopathEnv
   313  	if out, err := cmd.CombinedOutput(); err != nil {
   314  		t.Logf("%s", out)
   315  		t.Fatal(err)
   316  	}
   317  
   318  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
   319  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   320  		t.Logf("%s", out)
   321  		t.Fatal(err)
   322  	}
   323  
   324  	// We want to send the process a signal and see if it dies.
   325  	// Normally the signal goes to the C thread, the Go signal
   326  	// handler picks it up, sees that it is running in a C thread,
   327  	// and the program dies. Unfortunately, occasionally the
   328  	// signal is delivered to a Go thread, which winds up
   329  	// discarding it because it was sent by another program and
   330  	// there is no Go handler for it. To avoid this, run the
   331  	// program several times in the hopes that it will eventually
   332  	// fail.
   333  	const tries = 20
   334  	for i := 0; i < tries; i++ {
   335  		cmd = exec.Command(bin[0], append(bin[1:], "2")...)
   336  
   337  		stderr, err := cmd.StderrPipe()
   338  		if err != nil {
   339  			t.Fatal(err)
   340  		}
   341  		defer stderr.Close()
   342  
   343  		r := bufio.NewReader(stderr)
   344  
   345  		err = cmd.Start()
   346  
   347  		if err != nil {
   348  			t.Fatal(err)
   349  		}
   350  
   351  		// Wait for trigger to ensure that the process is started.
   352  		ok, err := r.ReadString('\n')
   353  
   354  		// Verify trigger.
   355  		if err != nil || ok != "OK\n" {
   356  			t.Fatalf("Did not receive OK signal")
   357  		}
   358  
   359  		// Give the program a chance to enter the sleep function.
   360  		time.Sleep(time.Millisecond)
   361  
   362  		cmd.Process.Signal(syscall.SIGSEGV)
   363  
   364  		err = cmd.Wait()
   365  
   366  		if err == nil {
   367  			continue
   368  		}
   369  
   370  		if ee, ok := err.(*exec.ExitError); !ok {
   371  			t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
   372  		} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
   373  			t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
   374  		} else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV {
   375  			t.Errorf("got %v; expected SIGSEGV", ee)
   376  		} else {
   377  			// We got the error we expected.
   378  			return
   379  		}
   380  	}
   381  
   382  	t.Errorf("program succeeded unexpectedly %d times", tries)
   383  }
   384  
   385  func TestOsSignal(t *testing.T) {
   386  	switch GOOS {
   387  	case "windows":
   388  		t.Skip("skipping signal test on Windows")
   389  	}
   390  
   391  	defer func() {
   392  		os.Remove("libgo3.a")
   393  		os.Remove("libgo3.h")
   394  		os.Remove("testp")
   395  		os.RemoveAll("pkg")
   396  	}()
   397  
   398  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3")
   399  	cmd.Env = gopathEnv
   400  	if out, err := cmd.CombinedOutput(); err != nil {
   401  		t.Logf("%s", out)
   402  		t.Fatal(err)
   403  	}
   404  
   405  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
   406  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   407  		t.Logf("%s", out)
   408  		t.Fatal(err)
   409  	}
   410  
   411  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   412  		t.Logf("%s", out)
   413  		t.Fatal(err)
   414  	}
   415  }
   416  
   417  func TestSigaltstack(t *testing.T) {
   418  	switch GOOS {
   419  	case "windows":
   420  		t.Skip("skipping signal test on Windows")
   421  	}
   422  
   423  	defer func() {
   424  		os.Remove("libgo4.a")
   425  		os.Remove("libgo4.h")
   426  		os.Remove("testp")
   427  		os.RemoveAll("pkg")
   428  	}()
   429  
   430  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4")
   431  	cmd.Env = gopathEnv
   432  	if out, err := cmd.CombinedOutput(); err != nil {
   433  		t.Logf("%s", out)
   434  		t.Fatal(err)
   435  	}
   436  
   437  	ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
   438  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   439  		t.Logf("%s", out)
   440  		t.Fatal(err)
   441  	}
   442  
   443  	if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
   444  		t.Logf("%s", out)
   445  		t.Fatal(err)
   446  	}
   447  }
   448  
   449  const testar = `#!/usr/bin/env bash
   450  while expr $1 : '[-]' >/dev/null; do
   451    shift
   452  done
   453  echo "testar" > $1
   454  echo "testar" > PWD/testar.ran
   455  `
   456  
   457  func TestExtar(t *testing.T) {
   458  	switch GOOS {
   459  	case "windows":
   460  		t.Skip("skipping signal test on Windows")
   461  	}
   462  
   463  	defer func() {
   464  		os.Remove("libgo4.a")
   465  		os.Remove("libgo4.h")
   466  		os.Remove("testar")
   467  		os.Remove("testar.ran")
   468  		os.RemoveAll("pkg")
   469  	}()
   470  
   471  	os.Remove("testar")
   472  	dir, err := os.Getwd()
   473  	if err != nil {
   474  		t.Fatal(err)
   475  	}
   476  	s := strings.Replace(testar, "PWD", dir, 1)
   477  	if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil {
   478  		t.Fatal(err)
   479  	}
   480  
   481  	cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4")
   482  	cmd.Env = gopathEnv
   483  	if out, err := cmd.CombinedOutput(); err != nil {
   484  		t.Logf("%s", out)
   485  		t.Fatal(err)
   486  	}
   487  
   488  	if _, err := os.Stat("testar.ran"); err != nil {
   489  		if os.IsNotExist(err) {
   490  			t.Error("testar does not exist after go build")
   491  		} else {
   492  			t.Errorf("error checking testar: %v", err)
   493  		}
   494  	}
   495  }
   496  
   497  func TestPIE(t *testing.T) {
   498  	switch GOOS {
   499  	case "windows", "darwin", "plan9":
   500  		t.Skipf("skipping PIE test on %s", GOOS)
   501  	}
   502  
   503  	defer func() {
   504  		os.Remove("testp" + exeSuffix)
   505  		os.RemoveAll("pkg")
   506  	}()
   507  
   508  	cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo")
   509  	cmd.Env = gopathEnv
   510  	if out, err := cmd.CombinedOutput(); err != nil {
   511  		t.Logf("%s", out)
   512  		t.Fatal(err)
   513  	}
   514  
   515  	ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join("pkg", libgodir, "libgo.a"))
   516  	if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
   517  		t.Logf("%s", out)
   518  		t.Fatal(err)
   519  	}
   520  
   521  	binArgs := append(bin, "arg1", "arg2")
   522  	if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil {
   523  		t.Logf("%s", out)
   524  		t.Fatal(err)
   525  	}
   526  
   527  	f, err := elf.Open("testp" + exeSuffix)
   528  	if err != nil {
   529  		t.Fatal("elf.Open failed: ", err)
   530  	}
   531  	defer f.Close()
   532  	if hasDynTag(t, f, elf.DT_TEXTREL) {
   533  		t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix)
   534  	}
   535  }
   536  
   537  func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
   538  	ds := f.SectionByType(elf.SHT_DYNAMIC)
   539  	if ds == nil {
   540  		t.Error("no SHT_DYNAMIC section")
   541  		return false
   542  	}
   543  	d, err := ds.Data()
   544  	if err != nil {
   545  		t.Errorf("can't read SHT_DYNAMIC contents: %v", err)
   546  		return false
   547  	}
   548  	for len(d) > 0 {
   549  		var t elf.DynTag
   550  		switch f.Class {
   551  		case elf.ELFCLASS32:
   552  			t = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
   553  			d = d[8:]
   554  		case elf.ELFCLASS64:
   555  			t = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
   556  			d = d[16:]
   557  		}
   558  		if t == tag {
   559  			return true
   560  		}
   561  	}
   562  	return false
   563  }