diff options
Diffstat (limited to 'misc/cgo/testcarchive')
-rw-r--r-- | misc/cgo/testcarchive/carchive_test.go | 934 | ||||
-rw-r--r-- | misc/cgo/testcarchive/overlaydir_test.go | 78 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/libgo/libgo.go | 53 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/libgo2/libgo2.go | 80 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/libgo3/libgo3.go | 56 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/libgo4/libgo4.go | 52 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/libgo6/sigprof.go | 25 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/libgo7/sink.go | 17 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main.c | 48 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main2.c | 239 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main3.c | 210 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main4.c | 204 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main5.c | 100 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main6.c | 34 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main7.c | 18 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main_unix.c | 59 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/main_windows.c | 17 | ||||
-rw-r--r-- | misc/cgo/testcarchive/testdata/p/p.go | 10 |
18 files changed, 2234 insertions, 0 deletions
diff --git a/misc/cgo/testcarchive/carchive_test.go b/misc/cgo/testcarchive/carchive_test.go new file mode 100644 index 0000000..6a5adf7 --- /dev/null +++ b/misc/cgo/testcarchive/carchive_test.go @@ -0,0 +1,934 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package carchive_test + +import ( + "bufio" + "bytes" + "debug/elf" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" + "syscall" + "testing" + "time" + "unicode" +) + +// Program to run. +var bin []string + +// C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)). +var cc []string + +// ".exe" on Windows. +var exeSuffix string + +var GOOS, GOARCH, GOPATH string +var libgodir string + +var testWork bool // If true, preserve temporary directories. + +func TestMain(m *testing.M) { + flag.BoolVar(&testWork, "testwork", false, "if true, log and preserve the test's temporary working directory") + flag.Parse() + if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" { + fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n") + os.Exit(0) + } + log.SetFlags(log.Lshortfile) + os.Exit(testMain(m)) +} + +func testMain(m *testing.M) int { + // We need a writable GOPATH in which to run the tests. + // Construct one in a temporary directory. + var err error + GOPATH, err = ioutil.TempDir("", "carchive_test") + if err != nil { + log.Panic(err) + } + if testWork { + log.Println(GOPATH) + } else { + defer os.RemoveAll(GOPATH) + } + os.Setenv("GOPATH", GOPATH) + + // Copy testdata into GOPATH/src/testarchive, along with a go.mod file + // declaring the same path. + modRoot := filepath.Join(GOPATH, "src", "testcarchive") + if err := overlayDir(modRoot, "testdata"); err != nil { + log.Panic(err) + } + if err := os.Chdir(modRoot); err != nil { + log.Panic(err) + } + os.Setenv("PWD", modRoot) + if err := ioutil.WriteFile("go.mod", []byte("module testcarchive\n"), 0666); err != nil { + log.Panic(err) + } + + GOOS = goEnv("GOOS") + GOARCH = goEnv("GOARCH") + bin = cmdToRun("./testp") + + ccOut := goEnv("CC") + cc = []string{string(ccOut)} + + out := goEnv("GOGCCFLAGS") + quote := '\000' + start := 0 + lastSpace := true + backslash := false + s := string(out) + for i, c := range s { + if quote == '\000' && unicode.IsSpace(c) { + if !lastSpace { + cc = append(cc, s[start:i]) + lastSpace = true + } + } else { + if lastSpace { + start = i + lastSpace = false + } + if quote == '\000' && !backslash && (c == '"' || c == '\'') { + quote = c + backslash = false + } else if !backslash && quote == c { + quote = '\000' + } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { + backslash = true + } else { + backslash = false + } + } + } + if !lastSpace { + cc = append(cc, s[start:]) + } + + if GOOS == "aix" { + // -Wl,-bnoobjreorder is mandatory to keep the same layout + // in .text section. + cc = append(cc, "-Wl,-bnoobjreorder") + } + libbase := GOOS + "_" + GOARCH + if runtime.Compiler == "gccgo" { + libbase = "gccgo_" + libgodir + "_fPIC" + } else { + switch GOOS { + case "darwin", "ios": + if GOARCH == "arm64" { + libbase += "_shared" + } + case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos": + libbase += "_shared" + } + } + libgodir = filepath.Join(GOPATH, "pkg", libbase, "testcarchive") + cc = append(cc, "-I", libgodir) + + if GOOS == "windows" { + exeSuffix = ".exe" + } + + return m.Run() +} + +func goEnv(key string) string { + out, err := exec.Command("go", "env", key).Output() + if err != nil { + if ee, ok := err.(*exec.ExitError); ok { + fmt.Fprintf(os.Stderr, "%s", ee.Stderr) + } + log.Panicf("go env %s failed:\n%s\n", key, err) + } + return strings.TrimSpace(string(out)) +} + +func cmdToRun(name string) []string { + execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec" + executor, err := exec.LookPath(execScript) + if err != nil { + return []string{name} + } + return []string{executor, name} +} + +// genHeader writes a C header file for the C-exported declarations found in .go +// source files in dir. +// +// TODO(golang.org/issue/35715): This should be simpler. +func genHeader(t *testing.T, header, dir string) { + t.Helper() + + // The 'cgo' command generates a number of additional artifacts, + // but we're only interested in the header. + // Shunt the rest of the outputs to a temporary directory. + objDir, err := ioutil.TempDir(GOPATH, "_obj") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(objDir) + + files, err := filepath.Glob(filepath.Join(dir, "*.go")) + if err != nil { + t.Fatal(err) + } + + cmd := exec.Command("go", "tool", "cgo", + "-objdir", objDir, + "-exportheader", header) + cmd.Args = append(cmd.Args, files...) + t.Log(cmd.Args) + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } +} + +func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) { + t.Helper() + cmd := exec.Command(buildcmd[0], buildcmd[1:]...) + t.Log(buildcmd) + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + if !testWork { + defer func() { + os.Remove(libgoa) + os.Remove(libgoh) + }() + } + + ccArgs := append(cc, "-o", exe, "main.c") + if GOOS == "windows" { + ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm") + } else { + ccArgs = append(ccArgs, "main_unix.c", libgoa) + } + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + t.Log(ccArgs) + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + if !testWork { + defer os.Remove(exe) + } + + binArgs := append(cmdToRun(exe), "arg1", "arg2") + cmd = exec.Command(binArgs[0], binArgs[1:]...) + if runtime.Compiler == "gccgo" { + cmd.Env = append(os.Environ(), "GCCGO=1") + } + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + checkLineComments(t, libgoh) +} + +var badLineRegexp = regexp.MustCompile(`(?m)^#line [0-9]+ "/.*$`) + +// checkLineComments checks that the export header generated by +// -buildmode=c-archive doesn't have any absolute paths in the #line +// comments. We don't want those paths because they are unhelpful for +// the user and make the files change based on details of the location +// of GOPATH. +func checkLineComments(t *testing.T, hdrname string) { + hdr, err := ioutil.ReadFile(hdrname) + if err != nil { + if !os.IsNotExist(err) { + t.Error(err) + } + return + } + if line := badLineRegexp.Find(hdr); line != nil { + t.Errorf("bad #line directive with absolute path in %s: %q", hdrname, line) + } +} + +func TestInstall(t *testing.T) { + if !testWork { + defer os.RemoveAll(filepath.Join(GOPATH, "pkg")) + } + + libgoa := "libgo.a" + if runtime.Compiler == "gccgo" { + libgoa = "liblibgo.a" + } + + // Generate the p.h header file. + // + // 'go install -i -buildmode=c-archive ./libgo' would do that too, but that + // would also attempt to install transitive standard-library dependencies to + // GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may + // be running this test in a GOROOT owned by root.) + genHeader(t, "p.h", "./p") + + testInstall(t, "./testp1"+exeSuffix, + filepath.Join(libgodir, libgoa), + filepath.Join(libgodir, "libgo.h"), + "go", "install", "-buildmode=c-archive", "./libgo") + + // Test building libgo other than installing it. + // Header files are now present. + testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h", + "go", "build", "-buildmode=c-archive", filepath.Join(".", "libgo", "libgo.go")) + + testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h", + "go", "build", "-buildmode=c-archive", "-o", "libgo.a", "./libgo") +} + +func TestEarlySignalHandler(t *testing.T) { + switch GOOS { + case "darwin", "ios": + switch GOARCH { + case "arm64": + t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) + } + case "windows": + t.Skip("skipping signal test on Windows") + } + + if !testWork { + defer func() { + os.Remove("libgo2.a") + os.Remove("libgo2.h") + os.Remove("testp") + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo2.h") + + ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + darwin := "0" + if runtime.GOOS == "darwin" { + darwin = "1" + } + cmd = exec.Command(bin[0], append(bin[1:], darwin)...) + + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } +} + +func TestSignalForwarding(t *testing.T) { + checkSignalForwardingTest(t) + + if !testWork { + defer func() { + os.Remove("libgo2.a") + os.Remove("libgo2.h") + os.Remove("testp") + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo2.h") + + ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + cmd = exec.Command(bin[0], append(bin[1:], "1")...) + + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + expectSignal(t, err, syscall.SIGSEGV) + + // SIGPIPE is never forwarded on darwin. See golang.org/issue/33384. + if runtime.GOOS != "darwin" && runtime.GOOS != "ios" { + // Test SIGPIPE forwarding + cmd = exec.Command(bin[0], append(bin[1:], "3")...) + + out, err = cmd.CombinedOutput() + t.Logf("%s", out) + expectSignal(t, err, syscall.SIGPIPE) + } +} + +func TestSignalForwardingExternal(t *testing.T) { + if GOOS == "freebsd" || GOOS == "aix" { + t.Skipf("skipping on %s/%s; signal always goes to the Go runtime", GOOS, GOARCH) + } else if GOOS == "darwin" && GOARCH == "amd64" { + t.Skipf("skipping on %s/%s: runtime does not permit SI_USER SIGSEGV", GOOS, GOARCH) + } + checkSignalForwardingTest(t) + + if !testWork { + defer func() { + os.Remove("libgo2.a") + os.Remove("libgo2.h") + os.Remove("testp") + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo2.h") + + ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + // We want to send the process a signal and see if it dies. + // Normally the signal goes to the C thread, the Go signal + // handler picks it up, sees that it is running in a C thread, + // and the program dies. Unfortunately, occasionally the + // signal is delivered to a Go thread, which winds up + // discarding it because it was sent by another program and + // there is no Go handler for it. To avoid this, run the + // program several times in the hopes that it will eventually + // fail. + const tries = 20 + for i := 0; i < tries; i++ { + cmd = exec.Command(bin[0], append(bin[1:], "2")...) + + stderr, err := cmd.StderrPipe() + if err != nil { + t.Fatal(err) + } + defer stderr.Close() + + r := bufio.NewReader(stderr) + + err = cmd.Start() + + if err != nil { + t.Fatal(err) + } + + // Wait for trigger to ensure that the process is started. + ok, err := r.ReadString('\n') + + // Verify trigger. + if err != nil || ok != "OK\n" { + t.Fatalf("Did not receive OK signal") + } + + // Give the program a chance to enter the sleep function. + time.Sleep(time.Millisecond) + + cmd.Process.Signal(syscall.SIGSEGV) + + err = cmd.Wait() + + if err == nil { + continue + } + + if expectSignal(t, err, syscall.SIGSEGV) { + return + } + } + + t.Errorf("program succeeded unexpectedly %d times", tries) +} + +// checkSignalForwardingTest calls t.Skip if the SignalForwarding test +// doesn't work on this platform. +func checkSignalForwardingTest(t *testing.T) { + switch GOOS { + case "darwin", "ios": + switch GOARCH { + case "arm64": + t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) + } + case "windows": + t.Skip("skipping signal test on Windows") + } +} + +// expectSignal checks that err, the exit status of a test program, +// shows a failure due to a specific signal. Returns whether we found +// the expected signal. +func expectSignal(t *testing.T, err error, sig syscall.Signal) bool { + if err == nil { + t.Error("test program succeeded unexpectedly") + } else if ee, ok := err.(*exec.ExitError); !ok { + t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) + } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { + t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) + } else if !ws.Signaled() || ws.Signal() != sig { + t.Errorf("got %v; expected signal %v", ee, sig) + } else { + return true + } + return false +} + +func TestOsSignal(t *testing.T) { + switch GOOS { + case "windows": + t.Skip("skipping signal test on Windows") + } + + if !testWork { + defer func() { + os.Remove("libgo3.a") + os.Remove("libgo3.h") + os.Remove("testp") + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "./libgo3") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo3.h") + + ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } +} + +func TestSigaltstack(t *testing.T) { + switch GOOS { + case "windows": + t.Skip("skipping signal test on Windows") + } + + if !testWork { + defer func() { + os.Remove("libgo4.a") + os.Remove("libgo4.h") + os.Remove("testp") + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "./libgo4") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo4.h") + + ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } +} + +const testar = `#!/usr/bin/env bash +while [[ $1 == -* ]] >/dev/null; do + shift +done +echo "testar" > $1 +echo "testar" > PWD/testar.ran +` + +func TestExtar(t *testing.T) { + switch GOOS { + case "windows": + t.Skip("skipping signal test on Windows") + } + if runtime.Compiler == "gccgo" { + t.Skip("skipping -extar test when using gccgo") + } + if runtime.GOOS == "ios" { + t.Skip("shell scripts are not executable on iOS hosts") + } + + if !testWork { + defer func() { + os.Remove("libgo4.a") + os.Remove("libgo4.h") + os.Remove("testar") + os.Remove("testar.ran") + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + os.Remove("testar") + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + s := strings.Replace(testar, "PWD", dir, 1) + if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil { + t.Fatal(err) + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "./libgo4") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo4.h") + + if _, err := os.Stat("testar.ran"); err != nil { + if os.IsNotExist(err) { + t.Error("testar does not exist after go build") + } else { + t.Errorf("error checking testar: %v", err) + } + } +} + +func TestPIE(t *testing.T) { + switch GOOS { + case "windows", "darwin", "ios", "plan9": + t.Skipf("skipping PIE test on %s", GOOS) + } + + if !testWork { + defer func() { + os.Remove("testp" + exeSuffix) + os.RemoveAll(filepath.Join(GOPATH, "pkg")) + }() + } + + // Generate the p.h header file. + // + // 'go install -i -buildmode=c-archive ./libgo' would do that too, but that + // would also attempt to install transitive standard-library dependencies to + // GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may + // be running this test in a GOROOT owned by root.) + genHeader(t, "p.h", "./p") + + cmd := exec.Command("go", "install", "-buildmode=c-archive", "./libgo") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + libgoa := "libgo.a" + if runtime.Compiler == "gccgo" { + libgoa = "liblibgo.a" + } + + ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join(libgodir, libgoa)) + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + binArgs := append(bin, "arg1", "arg2") + cmd = exec.Command(binArgs[0], binArgs[1:]...) + if runtime.Compiler == "gccgo" { + cmd.Env = append(os.Environ(), "GCCGO=1") + } + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + if GOOS != "aix" { + f, err := elf.Open("testp" + exeSuffix) + if err != nil { + t.Fatal("elf.Open failed: ", err) + } + defer f.Close() + if hasDynTag(t, f, elf.DT_TEXTREL) { + t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix) + } + } +} + +func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool { + ds := f.SectionByType(elf.SHT_DYNAMIC) + if ds == nil { + t.Error("no SHT_DYNAMIC section") + return false + } + d, err := ds.Data() + if err != nil { + t.Errorf("can't read SHT_DYNAMIC contents: %v", err) + return false + } + for len(d) > 0 { + var t elf.DynTag + switch f.Class { + case elf.ELFCLASS32: + t = elf.DynTag(f.ByteOrder.Uint32(d[:4])) + d = d[8:] + case elf.ELFCLASS64: + t = elf.DynTag(f.ByteOrder.Uint64(d[:8])) + d = d[16:] + } + if t == tag { + return true + } + } + return false +} + +func TestSIGPROF(t *testing.T) { + switch GOOS { + case "windows", "plan9": + t.Skipf("skipping SIGPROF test on %s", GOOS) + case "darwin", "ios": + t.Skipf("skipping SIGPROF test on %s; see https://golang.org/issue/19320", GOOS) + } + + t.Parallel() + + if !testWork { + defer func() { + os.Remove("testp6" + exeSuffix) + os.Remove("libgo6.a") + os.Remove("libgo6.h") + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "./libgo6") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo6.h") + + ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + argv := cmdToRun("./testp6") + cmd = exec.Command(argv[0], argv[1:]...) + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } +} + +// TestCompileWithoutShared tests that if we compile code without the +// -shared option, we can put it into an archive. When we use the go +// tool with -buildmode=c-archive, it passes -shared to the compiler, +// so we override that. The go tool doesn't work this way, but Bazel +// will likely do it in the future. And it ought to work. This test +// was added because at one time it did not work on PPC GNU/Linux. +func TestCompileWithoutShared(t *testing.T) { + // For simplicity, reuse the signal forwarding test. + checkSignalForwardingTest(t) + + if !testWork { + defer func() { + os.Remove("libgo2.a") + os.Remove("libgo2.h") + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "./libgo2") + t.Log(cmd.Args) + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + if err != nil { + t.Fatal(err) + } + checkLineComments(t, "libgo2.h") + + exe := "./testnoshared" + exeSuffix + + // In some cases, -no-pie is needed here, but not accepted everywhere. First try + // if -no-pie is accepted. See #22126. + ccArgs := append(cc, "-o", exe, "-no-pie", "main5.c", "libgo2.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + t.Log(ccArgs) + out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput() + + // If -no-pie unrecognized, try -nopie if this is possibly clang + if err != nil && bytes.Contains(out, []byte("unknown")) && !strings.Contains(cc[0], "gcc") { + ccArgs = append(cc, "-o", exe, "-nopie", "main5.c", "libgo2.a") + t.Log(ccArgs) + out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput() + } + + // Don't use either -no-pie or -nopie + if err != nil && bytes.Contains(out, []byte("unrecognized")) { + ccArgs := append(cc, "-o", exe, "main5.c", "libgo2.a") + t.Log(ccArgs) + out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput() + } + t.Logf("%s", out) + if err != nil { + t.Fatal(err) + } + if !testWork { + defer os.Remove(exe) + } + + binArgs := append(cmdToRun(exe), "1") + t.Log(binArgs) + out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput() + t.Logf("%s", out) + expectSignal(t, err, syscall.SIGSEGV) + + // SIGPIPE is never forwarded on darwin. See golang.org/issue/33384. + if runtime.GOOS != "darwin" && runtime.GOOS != "ios" { + binArgs := append(cmdToRun(exe), "3") + t.Log(binArgs) + out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput() + t.Logf("%s", out) + expectSignal(t, err, syscall.SIGPIPE) + } +} + +// Test that installing a second time recreates the header file. +func TestCachedInstall(t *testing.T) { + if !testWork { + defer os.RemoveAll(filepath.Join(GOPATH, "pkg")) + } + + h := filepath.Join(libgodir, "libgo.h") + + buildcmd := []string{"go", "install", "-buildmode=c-archive", "./libgo"} + + cmd := exec.Command(buildcmd[0], buildcmd[1:]...) + t.Log(buildcmd) + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + if _, err := os.Stat(h); err != nil { + t.Errorf("libgo.h not installed: %v", err) + } + + if err := os.Remove(h); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(buildcmd[0], buildcmd[1:]...) + t.Log(buildcmd) + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + if _, err := os.Stat(h); err != nil { + t.Errorf("libgo.h not installed in second run: %v", err) + } +} + +// Issue 35294. +func TestManyCalls(t *testing.T) { + t.Parallel() + + if !testWork { + defer func() { + os.Remove("testp7" + exeSuffix) + os.Remove("libgo7.a") + os.Remove("libgo7.h") + }() + } + + cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo7.a", "./libgo7") + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + checkLineComments(t, "libgo7.h") + + ccArgs := append(cc, "-o", "testp7"+exeSuffix, "main7.c", "libgo7.a") + if runtime.Compiler == "gccgo" { + ccArgs = append(ccArgs, "-lgo") + } + if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatal(err) + } + + argv := cmdToRun("./testp7") + cmd = exec.Command(argv[0], argv[1:]...) + var sb strings.Builder + cmd.Stdout = &sb + cmd.Stderr = &sb + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + timer := time.AfterFunc(time.Minute, + func() { + t.Error("test program timed out") + cmd.Process.Kill() + }, + ) + defer timer.Stop() + + if err := cmd.Wait(); err != nil { + t.Log(sb.String()) + t.Error(err) + } +} diff --git a/misc/cgo/testcarchive/overlaydir_test.go b/misc/cgo/testcarchive/overlaydir_test.go new file mode 100644 index 0000000..67974c5 --- /dev/null +++ b/misc/cgo/testcarchive/overlaydir_test.go @@ -0,0 +1,78 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package carchive_test + +import ( + "io" + "os" + "path/filepath" + "strings" +) + +// overlayDir makes a minimal-overhead copy of srcRoot in which new files may be added. +// +// TODO: Once we no longer need to support the misc module in GOPATH mode, +// factor this function out into a package to reduce duplication. +func overlayDir(dstRoot, srcRoot string) error { + dstRoot = filepath.Clean(dstRoot) + if err := os.MkdirAll(dstRoot, 0777); err != nil { + return err + } + + srcRoot, err := filepath.Abs(srcRoot) + if err != nil { + return err + } + + return filepath.Walk(srcRoot, func(srcPath string, info os.FileInfo, err error) error { + if err != nil || srcPath == srcRoot { + return err + } + + suffix := strings.TrimPrefix(srcPath, srcRoot) + for len(suffix) > 0 && suffix[0] == filepath.Separator { + suffix = suffix[1:] + } + dstPath := filepath.Join(dstRoot, suffix) + + perm := info.Mode() & os.ModePerm + if info.Mode()&os.ModeSymlink != 0 { + info, err = os.Stat(srcPath) + if err != nil { + return err + } + perm = info.Mode() & os.ModePerm + } + + // Always copy directories (don't symlink them). + // If we add a file in the overlay, we don't want to add it in the original. + if info.IsDir() { + return os.MkdirAll(dstPath, perm|0200) + } + + // If the OS supports symlinks, use them instead of copying bytes. + if err := os.Symlink(srcPath, dstPath); err == nil { + return nil + } + + // Otherwise, copy the bytes. + src, err := os.Open(srcPath) + if err != nil { + return err + } + defer src.Close() + + dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm) + if err != nil { + return err + } + + _, err = io.Copy(dst, src) + if closeErr := dst.Close(); err == nil { + err = closeErr + } + return err + }) +} diff --git a/misc/cgo/testcarchive/testdata/libgo/libgo.go b/misc/cgo/testcarchive/testdata/libgo/libgo.go new file mode 100644 index 0000000..37b30c1 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/libgo/libgo.go @@ -0,0 +1,53 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "syscall" + "time" + + _ "testcarchive/p" +) + +import "C" + +var initCh = make(chan int, 1) +var ranMain bool + +func init() { + // emulate an exceedingly slow package initialization function + time.Sleep(100 * time.Millisecond) + initCh <- 42 +} + +func main() { ranMain = true } + +//export DidInitRun +func DidInitRun() bool { + select { + case x := <-initCh: + if x != 42 { + // Just in case initCh was not correctly made. + println("want init value of 42, got: ", x) + syscall.Exit(2) + } + return true + default: + return false + } +} + +//export DidMainRun +func DidMainRun() bool { return ranMain } + +//export CheckArgs +func CheckArgs() { + if len(os.Args) != 3 || os.Args[1] != "arg1" || os.Args[2] != "arg2" { + fmt.Printf("CheckArgs: want [_, arg1, arg2], got: %v\n", os.Args) + os.Exit(2) + } +} diff --git a/misc/cgo/testcarchive/testdata/libgo2/libgo2.go b/misc/cgo/testcarchive/testdata/libgo2/libgo2.go new file mode 100644 index 0000000..19c8e1a --- /dev/null +++ b/misc/cgo/testcarchive/testdata/libgo2/libgo2.go @@ -0,0 +1,80 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +// Raise SIGPIPE. +static void CRaiseSIGPIPE() { + int fds[2]; + + if (pipe(fds) == -1) { + perror("pipe"); + exit(EXIT_FAILURE); + } + // Close the reader end + close(fds[0]); + // Write to the writer end to provoke a SIGPIPE + if (write(fds[1], "some data", 9) != -1) { + fprintf(stderr, "write to a closed pipe succeeded\n"); + exit(EXIT_FAILURE); + } + close(fds[1]); +} +*/ +import "C" + +import ( + "fmt" + "os" + "runtime" +) + +// RunGoroutines starts some goroutines that don't do anything. +// The idea is to get some threads going, so that a signal will be delivered +// to a thread started by Go. +//export RunGoroutines +func RunGoroutines() { + for i := 0; i < 4; i++ { + go func() { + runtime.LockOSThread() + select {} + }() + } +} + +var P *byte + +// TestSEGV makes sure that an invalid address turns into a run-time Go panic. +//export TestSEGV +func TestSEGV() { + defer func() { + if recover() == nil { + fmt.Fprintln(os.Stderr, "no panic from segv") + os.Exit(1) + } + }() + *P = 0 + fmt.Fprintln(os.Stderr, "continued after segv") + os.Exit(1) +} + +// Noop ensures that the Go runtime is initialized. +//export Noop +func Noop() { +} + +// Raise SIGPIPE. +//export GoRaiseSIGPIPE +func GoRaiseSIGPIPE() { + C.CRaiseSIGPIPE() +} + +func main() { +} diff --git a/misc/cgo/testcarchive/testdata/libgo3/libgo3.go b/misc/cgo/testcarchive/testdata/libgo3/libgo3.go new file mode 100644 index 0000000..3725f7a --- /dev/null +++ b/misc/cgo/testcarchive/testdata/libgo3/libgo3.go @@ -0,0 +1,56 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "C" + +import ( + "os" + "os/signal" + "syscall" + "time" +) + +// The channel used to read SIGIO signals. +var sigioChan chan os.Signal + +// CatchSIGIO starts catching SIGIO signals. +//export CatchSIGIO +func CatchSIGIO() { + sigioChan = make(chan os.Signal, 1) + signal.Notify(sigioChan, syscall.SIGIO) +} + +// ResetSIGIO stops catching SIGIO signals. +//export ResetSIGIO +func ResetSIGIO() { + signal.Reset(syscall.SIGIO) +} + +// SawSIGIO reports whether we saw a SIGIO. +//export SawSIGIO +func SawSIGIO() C.int { + select { + case <-sigioChan: + return 1 + case <-time.After(5 * time.Second): + return 0 + } +} + +// ProvokeSIGPIPE provokes a kernel-initiated SIGPIPE. +//export ProvokeSIGPIPE +func ProvokeSIGPIPE() { + r, w, err := os.Pipe() + if err != nil { + panic(err) + } + r.Close() + defer w.Close() + w.Write([]byte("some data")) +} + +func main() { +} diff --git a/misc/cgo/testcarchive/testdata/libgo4/libgo4.go b/misc/cgo/testcarchive/testdata/libgo4/libgo4.go new file mode 100644 index 0000000..8cc1895 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/libgo4/libgo4.go @@ -0,0 +1,52 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include <signal.h> +#include <pthread.h> + +// Raise SIGIO. +static void CRaiseSIGIO(pthread_t* p) { + pthread_kill(*p, SIGIO); +} +*/ +import "C" + +import ( + "os" + "os/signal" + "sync/atomic" + "syscall" +) + +var sigioCount int32 + +// Catch SIGIO. +//export GoCatchSIGIO +func GoCatchSIGIO() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGIO) + go func() { + for range c { + atomic.AddInt32(&sigioCount, 1) + } + }() +} + +// Raise SIGIO. +//export GoRaiseSIGIO +func GoRaiseSIGIO(p *C.pthread_t) { + C.CRaiseSIGIO(p) +} + +// Return the number of SIGIO signals seen. +//export SIGIOCount +func SIGIOCount() C.int { + return C.int(atomic.LoadInt32(&sigioCount)) +} + +func main() { +} diff --git a/misc/cgo/testcarchive/testdata/libgo6/sigprof.go b/misc/cgo/testcarchive/testdata/libgo6/sigprof.go new file mode 100644 index 0000000..4cb05dc --- /dev/null +++ b/misc/cgo/testcarchive/testdata/libgo6/sigprof.go @@ -0,0 +1,25 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "io/ioutil" + "runtime/pprof" +) + +import "C" + +//export go_start_profile +func go_start_profile() { + pprof.StartCPUProfile(ioutil.Discard) +} + +//export go_stop_profile +func go_stop_profile() { + pprof.StopCPUProfile() +} + +func main() { +} diff --git a/misc/cgo/testcarchive/testdata/libgo7/sink.go b/misc/cgo/testcarchive/testdata/libgo7/sink.go new file mode 100644 index 0000000..d61638b --- /dev/null +++ b/misc/cgo/testcarchive/testdata/libgo7/sink.go @@ -0,0 +1,17 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "C" + +var sink []byte + +//export GoFunction7 +func GoFunction7() { + sink = make([]byte, 4096) +} + +func main() { +} diff --git a/misc/cgo/testcarchive/testdata/main.c b/misc/cgo/testcarchive/testdata/main.c new file mode 100644 index 0000000..163b539 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main.c @@ -0,0 +1,48 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "p.h" +#include "libgo.h" + +extern int install_handler(); +extern int check_handler(); + +int main(void) { + int32_t res; + + int r1 = install_handler(); + if (r1!=0) { + return r1; + } + + if (!DidInitRun()) { + fprintf(stderr, "ERROR: buildmode=c-archive init should run\n"); + return 2; + } + + if (DidMainRun()) { + fprintf(stderr, "ERROR: buildmode=c-archive should not run main\n"); + return 2; + } + + int r2 = check_handler(); + if (r2!=0) { + return r2; + } + + res = FromPkg(); + if (res != 1024) { + fprintf(stderr, "ERROR: FromPkg()=%d, want 1024\n", res); + return 2; + } + + CheckArgs(); + + fprintf(stderr, "PASS\n"); + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main2.c b/misc/cgo/testcarchive/testdata/main2.c new file mode 100644 index 0000000..da35673 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main2.c @@ -0,0 +1,239 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test installing a signal handler before the Go code starts. +// This is a lot like misc/cgo/testcshared/main4.c. + +#include <setjmp.h> +#include <signal.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <sched.h> +#include <time.h> +#include <errno.h> + +#include "libgo2.h" + +static void die(const char* msg) { + perror(msg); + exit(EXIT_FAILURE); +} + +static volatile sig_atomic_t sigioSeen; +static volatile sig_atomic_t sigpipeSeen; + +// Use up some stack space. +static void recur(int i, char *p) { + char a[1024]; + + *p = '\0'; + if (i > 0) { + recur(i - 1, a); + } +} + +static void pipeHandler(int signo, siginfo_t* info, void* ctxt) { + sigpipeSeen = 1; +} + +// Signal handler that uses up more stack space than a goroutine will have. +static void ioHandler(int signo, siginfo_t* info, void* ctxt) { + char a[1024]; + + recur(4, a); + sigioSeen = 1; +} + +static jmp_buf jmp; +static char* nullPointer; + +// An arbitrary function which requires proper stack alignment; see +// http://golang.org/issue/17641. +static void callWithVarargs(void* dummy, ...) { + va_list args; + va_start(args, dummy); + va_end(args); +} + +// Signal handler for SIGSEGV on a C thread. +static void segvHandler(int signo, siginfo_t* info, void* ctxt) { + sigset_t mask; + int i; + + // Call an arbitrary function that requires the stack to be properly aligned. + callWithVarargs("dummy arg", 3.1415); + + if (sigemptyset(&mask) < 0) { + die("sigemptyset"); + } + if (sigaddset(&mask, SIGSEGV) < 0) { + die("sigaddset"); + } + i = sigprocmask(SIG_UNBLOCK, &mask, NULL); + if (i != 0) { + fprintf(stderr, "sigprocmask: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + // Don't try this at home. + longjmp(jmp, signo); + + // We should never get here. + abort(); +} + +// Set up the signal handlers in a high priority constructor, +// so that they are installed before the Go code starts. + +static void init(void) __attribute__ ((constructor (200))); + +static void init() { + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = ioHandler; + if (sigemptyset(&sa.sa_mask) < 0) { + die("sigemptyset"); + } + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGIO, &sa, NULL) < 0) { + die("sigaction"); + } + + sa.sa_sigaction = segvHandler; + if (sigaction(SIGSEGV, &sa, NULL) < 0 || sigaction(SIGBUS, &sa, NULL) < 0) { + die("sigaction"); + } + + sa.sa_sigaction = pipeHandler; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + die("sigaction"); + } +} + +int main(int argc, char** argv) { + int verbose; + sigset_t mask; + int i; + struct timespec ts; + int darwin; + + darwin = atoi(argv[1]); + + verbose = argc > 2; + + setvbuf(stdout, NULL, _IONBF, 0); + + // Call setsid so that we can use kill(0, SIGIO) below. + // Don't check the return value so that this works both from + // a job control shell and from a shell script. + setsid(); + + if (verbose) { + printf("calling RunGoroutines\n"); + } + + RunGoroutines(); + + // Block SIGIO in this thread to make it more likely that it + // will be delivered to a goroutine. + + if (verbose) { + printf("calling pthread_sigmask\n"); + } + + if (sigemptyset(&mask) < 0) { + die("sigemptyset"); + } + if (sigaddset(&mask, SIGIO) < 0) { + die("sigaddset"); + } + i = pthread_sigmask(SIG_BLOCK, &mask, NULL); + if (i != 0) { + fprintf(stderr, "pthread_sigmask: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + if (verbose) { + printf("calling kill\n"); + } + + if (kill(0, SIGIO) < 0) { + die("kill"); + } + + if (verbose) { + printf("waiting for sigioSeen\n"); + } + + // Wait until the signal has been delivered. + i = 0; + while (!sigioSeen) { + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + i++; + if (i > 5000) { + fprintf(stderr, "looping too long waiting for SIGIO\n"); + exit(EXIT_FAILURE); + } + } + + if (verbose) { + printf("provoking SIGPIPE\n"); + } + + // SIGPIPE is never forwarded on Darwin, see golang.org/issue/33384. + if (!darwin) { + GoRaiseSIGPIPE(); + + if (verbose) { + printf("waiting for sigpipeSeen\n"); + } + + // Wait until the signal has been delivered. + i = 0; + while (!sigpipeSeen) { + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + i++; + if (i > 5000) { + fprintf(stderr, "looping too long waiting for SIGPIPE\n"); + exit(EXIT_FAILURE); + } + } + } + + if (verbose) { + printf("calling setjmp\n"); + } + + // Test that a SIGSEGV on this thread is delivered to us. + if (setjmp(jmp) == 0) { + if (verbose) { + printf("triggering SIGSEGV\n"); + } + + *nullPointer = '\0'; + + fprintf(stderr, "continued after address error\n"); + exit(EXIT_FAILURE); + } + + if (verbose) { + printf("calling TestSEGV\n"); + } + + TestSEGV(); + + printf("PASS\n"); + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main3.c b/misc/cgo/testcarchive/testdata/main3.c new file mode 100644 index 0000000..4d11d9c --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main3.c @@ -0,0 +1,210 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test os/signal.Notify and os/signal.Reset. +// This is a lot like misc/cgo/testcshared/main5.c. + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sched.h> +#include <unistd.h> +#include <pthread.h> + +#include "libgo3.h" + +static void die(const char* msg) { + perror(msg); + exit(EXIT_FAILURE); +} + +static volatile sig_atomic_t sigioSeen; + +static void ioHandler(int signo, siginfo_t* info, void* ctxt) { + sigioSeen = 1; +} + +// Set up the SIGPIPE signal handler in a high priority constructor, so +// that it is installed before the Go code starts. + +static void pipeHandler(int signo, siginfo_t* info, void* ctxt) { + const char *s = "unexpected SIGPIPE\n"; + write(2, s, strlen(s)); + exit(EXIT_FAILURE); +} + +static void init(void) __attribute__ ((constructor (200))); + +static void init() { + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = pipeHandler; + if (sigemptyset(&sa.sa_mask) < 0) { + die("sigemptyset"); + } + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + die("sigaction"); + } +} + +static void *provokeSIGPIPE(void *arg) { + ProvokeSIGPIPE(); + return NULL; +} + +int main(int argc, char** argv) { + int verbose; + struct sigaction sa; + int i; + struct timespec ts; + int res; + pthread_t tid; + + verbose = argc > 2; + setvbuf(stdout, NULL, _IONBF, 0); + + if (verbose) { + printf("raising SIGPIPE\n"); + } + + // Test that the Go runtime handles SIGPIPE, even if we installed + // a non-default SIGPIPE handler before the runtime initializes. + ProvokeSIGPIPE(); + + // Test that SIGPIPE on a non-main thread is also handled by Go. + res = pthread_create(&tid, NULL, provokeSIGPIPE, NULL); + if (res != 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(res)); + exit(EXIT_FAILURE); + } + + res = pthread_join(tid, NULL); + if (res != 0) { + fprintf(stderr, "pthread_join: %s\n", strerror(res)); + exit(EXIT_FAILURE); + } + + if (verbose) { + printf("calling sigaction\n"); + } + + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = ioHandler; + if (sigemptyset(&sa.sa_mask) < 0) { + die("sigemptyset"); + } + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGIO, &sa, NULL) < 0) { + die("sigaction"); + } + + // At this point there should not be a Go signal handler + // installed for SIGIO. + + if (verbose) { + printf("raising SIGIO\n"); + } + + if (raise(SIGIO) < 0) { + die("raise"); + } + + if (verbose) { + printf("waiting for sigioSeen\n"); + } + + // Wait until the signal has been delivered. + i = 0; + while (!sigioSeen) { + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + i++; + if (i > 5000) { + fprintf(stderr, "looping too long waiting for signal\n"); + exit(EXIT_FAILURE); + } + } + + sigioSeen = 0; + + // Tell the Go code to catch SIGIO. + + if (verbose) { + printf("calling CatchSIGIO\n"); + } + + CatchSIGIO(); + + if (verbose) { + printf("raising SIGIO\n"); + } + + if (raise(SIGIO) < 0) { + die("raise"); + } + + if (verbose) { + printf("calling SawSIGIO\n"); + } + + if (!SawSIGIO()) { + fprintf(stderr, "Go handler did not see SIGIO\n"); + exit(EXIT_FAILURE); + } + + if (sigioSeen != 0) { + fprintf(stderr, "C handler saw SIGIO when only Go handler should have\n"); + exit(EXIT_FAILURE); + } + + // Tell the Go code to stop catching SIGIO. + + if (verbose) { + printf("calling ResetSIGIO\n"); + } + + ResetSIGIO(); + + if (verbose) { + printf("raising SIGIO\n"); + } + + if (raise(SIGIO) < 0) { + die("raise"); + } + + if (verbose) { + printf("calling SawSIGIO\n"); + } + + if (SawSIGIO()) { + fprintf(stderr, "Go handler saw SIGIO after Reset\n"); + exit(EXIT_FAILURE); + } + + if (verbose) { + printf("waiting for sigioSeen\n"); + } + + // Wait until the signal has been delivered. + i = 0; + while (!sigioSeen) { + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + i++; + if (i > 5000) { + fprintf(stderr, "looping too long waiting for signal\n"); + exit(EXIT_FAILURE); + } + } + + printf("PASS\n"); + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main4.c b/misc/cgo/testcarchive/testdata/main4.c new file mode 100644 index 0000000..04f7740 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main4.c @@ -0,0 +1,204 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test a C thread that calls sigaltstack and then calls Go code. + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sched.h> +#include <pthread.h> + +#include "libgo4.h" + +#ifdef _AIX +// On AIX, CSIGSTKSZ is too small to handle Go sighandler. +#define CSIGSTKSZ 0x4000 +#else +#define CSIGSTKSZ SIGSTKSZ +#endif + +static void die(const char* msg) { + perror(msg); + exit(EXIT_FAILURE); +} + +static int ok = 1; + +static void ioHandler(int signo, siginfo_t* info, void* ctxt) { +} + +// Set up the SIGIO signal handler in a high priority constructor, so +// that it is installed before the Go code starts. + +static void init(void) __attribute__ ((constructor (200))); + +static void init() { + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = ioHandler; + if (sigemptyset(&sa.sa_mask) < 0) { + die("sigemptyset"); + } + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + if (sigaction(SIGIO, &sa, NULL) < 0) { + die("sigaction"); + } +} + +// Test raising SIGIO on a C thread with an alternate signal stack +// when there is a Go signal handler for SIGIO. +static void* thread1(void* arg __attribute__ ((unused))) { + stack_t ss; + int i; + stack_t nss; + struct timespec ts; + + // Set up an alternate signal stack for this thread. + memset(&ss, 0, sizeof ss); + ss.ss_sp = malloc(CSIGSTKSZ); + if (ss.ss_sp == NULL) { + die("malloc"); + } + ss.ss_flags = 0; + ss.ss_size = CSIGSTKSZ; + if (sigaltstack(&ss, NULL) < 0) { + die("sigaltstack"); + } + + // Send ourselves a SIGIO. This will be caught by the Go + // signal handler which should forward to the C signal + // handler. + i = pthread_kill(pthread_self(), SIGIO); + if (i != 0) { + fprintf(stderr, "pthread_kill: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + // Wait until the signal has been delivered. + i = 0; + while (SIGIOCount() == 0) { + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + i++; + if (i > 5000) { + fprintf(stderr, "looping too long waiting for signal\n"); + exit(EXIT_FAILURE); + } + } + + // We should still be on the same signal stack. + if (sigaltstack(NULL, &nss) < 0) { + die("sigaltstack check"); + } + if ((nss.ss_flags & SS_DISABLE) != 0) { + fprintf(stderr, "sigaltstack disabled on return from Go\n"); + ok = 0; + } else if (nss.ss_sp != ss.ss_sp) { + fprintf(stderr, "sigaltstack changed on return from Go\n"); + ok = 0; + } + + return NULL; +} + +// Test calling a Go function to raise SIGIO on a C thread with an +// alternate signal stack when there is a Go signal handler for SIGIO. +static void* thread2(void* arg __attribute__ ((unused))) { + stack_t ss; + int i; + int oldcount; + pthread_t tid; + struct timespec ts; + stack_t nss; + + // Set up an alternate signal stack for this thread. + memset(&ss, 0, sizeof ss); + ss.ss_sp = malloc(CSIGSTKSZ); + if (ss.ss_sp == NULL) { + die("malloc"); + } + ss.ss_flags = 0; + ss.ss_size = CSIGSTKSZ; + if (sigaltstack(&ss, NULL) < 0) { + die("sigaltstack"); + } + + oldcount = SIGIOCount(); + + // Call a Go function that will call a C function to send us a + // SIGIO. + tid = pthread_self(); + GoRaiseSIGIO(&tid); + + // Wait until the signal has been delivered. + i = 0; + while (SIGIOCount() == oldcount) { + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + i++; + if (i > 5000) { + fprintf(stderr, "looping too long waiting for signal\n"); + exit(EXIT_FAILURE); + } + } + + // We should still be on the same signal stack. + if (sigaltstack(NULL, &nss) < 0) { + die("sigaltstack check"); + } + if ((nss.ss_flags & SS_DISABLE) != 0) { + fprintf(stderr, "sigaltstack disabled on return from Go\n"); + ok = 0; + } else if (nss.ss_sp != ss.ss_sp) { + fprintf(stderr, "sigaltstack changed on return from Go\n"); + ok = 0; + } + + return NULL; +} + +int main(int argc, char **argv) { + pthread_t tid; + int i; + + // Tell the Go library to start looking for SIGIO. + GoCatchSIGIO(); + + i = pthread_create(&tid, NULL, thread1, NULL); + if (i != 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + i = pthread_join(tid, NULL); + if (i != 0) { + fprintf(stderr, "pthread_join: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + i = pthread_create(&tid, NULL, thread2, NULL); + if (i != 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + i = pthread_join(tid, NULL); + if (i != 0) { + fprintf(stderr, "pthread_join: %s\n", strerror(i)); + exit(EXIT_FAILURE); + } + + if (!ok) { + exit(EXIT_FAILURE); + } + + printf("PASS\n"); + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main5.c b/misc/cgo/testcarchive/testdata/main5.c new file mode 100644 index 0000000..d431ce0 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main5.c @@ -0,0 +1,100 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test for verifying that the Go runtime properly forwards +// signals when non-Go signals are raised. + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/select.h> + +#include "libgo2.h" + +int *nilp; + +int main(int argc, char** argv) { + int verbose; + int test; + + if (argc < 2) { + printf("Missing argument\n"); + return 1; + } + + test = atoi(argv[1]); + + verbose = (argc > 2); + + if (verbose) { + printf("calling RunGoroutines\n"); + } + + Noop(); + + switch (test) { + case 1: { + if (verbose) { + printf("attempting segfault\n"); + } + + *nilp = 0; + break; + } + + case 2: { + struct timeval tv; + + if (verbose) { + printf("attempting external signal test\n"); + } + + fprintf(stderr, "OK\n"); + fflush(stderr); + + // The program should be interrupted before + // this sleep finishes. We use select rather + // than sleep because in older versions of + // glibc the sleep function does some signal + // fiddling to handle SIGCHLD. If this + // program is fiddling signals just when the + // test program sends the signal, the signal + // may be delivered to a Go thread which will + // break this test. + tv.tv_sec = 60; + tv.tv_usec = 0; + select(0, NULL, NULL, NULL, &tv); + + break; + } + case 3: { + if (verbose) { + printf("attempting SIGPIPE\n"); + } + + int fd[2]; + if (pipe(fd) != 0) { + printf("pipe(2) failed\n"); + return 0; + } + // Close the reading end. + close(fd[0]); + // Expect that write(2) fails (EPIPE) + if (write(fd[1], "some data", 9) != -1) { + printf("write(2) unexpectedly succeeded\n"); + return 0; + } + printf("did not receive SIGPIPE\n"); + return 0; + } + default: + printf("Unknown test: %d\n", test); + return 0; + } + + printf("FAIL\n"); + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main6.c b/misc/cgo/testcarchive/testdata/main6.c new file mode 100644 index 0000000..2745eb9 --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main6.c @@ -0,0 +1,34 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test that using the Go profiler in a C program does not crash. + +#include <stddef.h> +#include <sys/time.h> + +#include "libgo6.h" + +int main(int argc, char **argv) { + struct timeval tvstart, tvnow; + int diff; + + gettimeofday(&tvstart, NULL); + + go_start_profile(); + + // Busy wait so we have something to profile. + // If we just sleep the profiling signal will never fire. + while (1) { + gettimeofday(&tvnow, NULL); + diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec); + + // Profile frequency is 100Hz so we should definitely + // get a signal in 50 milliseconds. + if (diff > 50 * 1000) + break; + } + + go_stop_profile(); + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main7.c b/misc/cgo/testcarchive/testdata/main7.c new file mode 100644 index 0000000..2c6d98d --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main7.c @@ -0,0 +1,18 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test that lots of calls don't deadlock. + +#include <stdio.h> + +#include "libgo7.h" + +int main() { + int i; + + for (i = 0; i < 100000; i++) { + GoFunction7(); + } + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/main_unix.c b/misc/cgo/testcarchive/testdata/main_unix.c new file mode 100644 index 0000000..b23ac1c --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main_unix.c @@ -0,0 +1,59 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct sigaction sa; +struct sigaction osa; + +static void (*oldHandler)(int, siginfo_t*, void*); + +static void handler(int signo, siginfo_t* info, void* ctxt) { + if (oldHandler) { + oldHandler(signo, info, ctxt); + } +} + +int install_handler() { + // Install our own signal handler. + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_ONSTACK | SA_SIGINFO; + memset(&osa, 0, sizeof osa); + sigemptyset(&osa.sa_mask); + if (sigaction(SIGSEGV, &sa, &osa) < 0) { + perror("sigaction"); + return 2; + } + if (osa.sa_handler == SIG_DFL) { + fprintf(stderr, "Go runtime did not install signal handler\n"); + return 2; + } + // gccgo does not set SA_ONSTACK for SIGSEGV. + if (getenv("GCCGO") == "" && (osa.sa_flags&SA_ONSTACK) == 0) { + fprintf(stderr, "Go runtime did not install signal handler\n"); + return 2; + } + oldHandler = osa.sa_sigaction; + + return 0; +} + +int check_handler() { + if (sigaction(SIGSEGV, NULL, &sa) < 0) { + perror("sigaction check"); + return 2; + } + if (sa.sa_sigaction != handler) { + fprintf(stderr, "ERROR: wrong signal handler: %p != %p\n", sa.sa_sigaction, handler); + return 2; + } + return 0; +} + diff --git a/misc/cgo/testcarchive/testdata/main_windows.c b/misc/cgo/testcarchive/testdata/main_windows.c new file mode 100644 index 0000000..eded8af --- /dev/null +++ b/misc/cgo/testcarchive/testdata/main_windows.c @@ -0,0 +1,17 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + * Dummy implementations for Windows, because Windows doesn't + * support Unix-style signal handling. + */ + +int install_handler() { + return 0; +} + + +int check_handler() { + return 0; +} diff --git a/misc/cgo/testcarchive/testdata/p/p.go b/misc/cgo/testcarchive/testdata/p/p.go new file mode 100644 index 0000000..82b445c --- /dev/null +++ b/misc/cgo/testcarchive/testdata/p/p.go @@ -0,0 +1,10 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +import "C" + +//export FromPkg +func FromPkg() int32 { return 1024 } |