summaryrefslogtreecommitdiffstats
path: root/misc/cgo/testcshared
diff options
context:
space:
mode:
Diffstat (limited to 'misc/cgo/testcshared')
-rw-r--r--misc/cgo/testcshared/cshared_test.go914
-rw-r--r--misc/cgo/testcshared/overlaydir_test.go78
-rw-r--r--misc/cgo/testcshared/testdata/go2c2go/go/shlib.go12
-rw-r--r--misc/cgo/testcshared/testdata/go2c2go/m1/c.c9
-rw-r--r--misc/cgo/testcshared/testdata/go2c2go/m1/main.go22
-rw-r--r--misc/cgo/testcshared/testdata/go2c2go/m2/main.go22
-rw-r--r--misc/cgo/testcshared/testdata/issue36233/issue36233.go29
-rw-r--r--misc/cgo/testcshared/testdata/libgo/libgo.go46
-rw-r--r--misc/cgo/testcshared/testdata/libgo2/dup2.go13
-rw-r--r--misc/cgo/testcshared/testdata/libgo2/dup3.go13
-rw-r--r--misc/cgo/testcshared/testdata/libgo2/libgo2.go52
-rw-r--r--misc/cgo/testcshared/testdata/libgo4/libgo4.go45
-rw-r--r--misc/cgo/testcshared/testdata/libgo5/libgo5.go44
-rw-r--r--misc/cgo/testcshared/testdata/main0.c42
-rw-r--r--misc/cgo/testcshared/testdata/main1.c69
-rw-r--r--misc/cgo/testcshared/testdata/main2.c56
-rw-r--r--misc/cgo/testcshared/testdata/main3.c29
-rw-r--r--misc/cgo/testcshared/testdata/main4.c215
-rw-r--r--misc/cgo/testcshared/testdata/main5.c199
-rw-r--r--misc/cgo/testcshared/testdata/p/p.go13
20 files changed, 1922 insertions, 0 deletions
diff --git a/misc/cgo/testcshared/cshared_test.go b/misc/cgo/testcshared/cshared_test.go
new file mode 100644
index 0000000..2b57249
--- /dev/null
+++ b/misc/cgo/testcshared/cshared_test.go
@@ -0,0 +1,914 @@
+// Copyright 2017 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 cshared_test
+
+import (
+ "bufio"
+ "bytes"
+ "debug/elf"
+ "debug/pe"
+ "encoding/binary"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+ "unicode"
+)
+
+// C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
+var cc []string
+
+// ".exe" on Windows.
+var exeSuffix string
+
+var GOOS, GOARCH, GOROOT string
+var installdir, androiddir string
+var libgoname string
+
+func TestMain(m *testing.M) {
+ os.Exit(testMain(m))
+}
+
+func testMain(m *testing.M) int {
+ log.SetFlags(log.Lshortfile)
+ 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)
+ }
+ if runtime.GOOS == "linux" {
+ if _, err := os.Stat("/etc/alpine-release"); err == nil {
+ fmt.Printf("SKIP - skipping failing test on alpine - go.dev/issue/19938\n")
+ os.Exit(0)
+ }
+ }
+
+ GOOS = goEnv("GOOS")
+ GOARCH = goEnv("GOARCH")
+ GOROOT = goEnv("GOROOT")
+
+ if _, err := os.Stat(GOROOT); os.IsNotExist(err) {
+ log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT)
+ }
+
+ androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid())
+ if runtime.GOOS != GOOS && GOOS == "android" {
+ args := append(adbCmd(), "exec-out", "mkdir", "-p", androiddir)
+ cmd := exec.Command(args[0], args[1:]...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Fatalf("setupAndroid failed: %v\n%s\n", err, out)
+ }
+ defer cleanupAndroid()
+ }
+
+ cc = []string{goEnv("CC")}
+
+ 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:])
+ }
+
+ switch GOOS {
+ case "darwin", "ios":
+ // For Darwin/ARM.
+ // TODO(crawshaw): can we do better?
+ cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
+ case "android":
+ cc = append(cc, "-pie")
+ }
+ libgodir := GOOS + "_" + GOARCH
+ switch GOOS {
+ case "darwin", "ios":
+ if GOARCH == "arm64" {
+ libgodir += "_shared"
+ }
+ case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
+ libgodir += "_shared"
+ }
+ cc = append(cc, "-I", filepath.Join("pkg", libgodir))
+
+ // Force reallocation (and avoid aliasing bugs) for parallel tests that append to cc.
+ cc = cc[:len(cc):len(cc)]
+
+ if GOOS == "windows" {
+ exeSuffix = ".exe"
+ }
+
+ // Copy testdata into GOPATH/src/testcshared, along with a go.mod file
+ // declaring the same path.
+
+ GOPATH, err := os.MkdirTemp("", "cshared_test")
+ if err != nil {
+ log.Panic(err)
+ }
+ defer os.RemoveAll(GOPATH)
+ os.Setenv("GOPATH", GOPATH)
+
+ modRoot := filepath.Join(GOPATH, "src", "testcshared")
+ 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 := os.WriteFile("go.mod", []byte("module testcshared\n"), 0666); err != nil {
+ log.Panic(err)
+ }
+
+ defer func() {
+ if installdir != "" {
+ err := os.RemoveAll(installdir)
+ if err != nil {
+ log.Panic(err)
+ }
+ }
+ }()
+
+ return m.Run()
+}
+
+func goEnv(key string) string {
+ out, err := exec.Command("go", "env", key).Output()
+ if err != nil {
+ log.Printf("go env %s failed:\n%s", key, err)
+ log.Panicf("%s", err.(*exec.ExitError).Stderr)
+ }
+ return strings.TrimSpace(string(out))
+}
+
+func cmdToRun(name string) string {
+ return "./" + name + exeSuffix
+}
+
+func adbCmd() []string {
+ cmd := []string{"adb"}
+ if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
+ cmd = append(cmd, strings.Split(flags, " ")...)
+ }
+ return cmd
+}
+
+func adbPush(t *testing.T, filename string) {
+ if runtime.GOOS == GOOS || GOOS != "android" {
+ return
+ }
+ args := append(adbCmd(), "push", filename, fmt.Sprintf("%s/%s", androiddir, filename))
+ cmd := exec.Command(args[0], args[1:]...)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("adb command failed: %v\n%s\n", err, out)
+ }
+}
+
+func adbRun(t *testing.T, env []string, adbargs ...string) string {
+ if GOOS != "android" {
+ t.Fatalf("trying to run adb command when operating system is not android.")
+ }
+ args := append(adbCmd(), "exec-out")
+ // Propagate LD_LIBRARY_PATH to the adb shell invocation.
+ for _, e := range env {
+ if strings.Contains(e, "LD_LIBRARY_PATH=") {
+ adbargs = append([]string{e}, adbargs...)
+ break
+ }
+ }
+ shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " "))
+ args = append(args, shellcmd)
+ cmd := exec.Command(args[0], args[1:]...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("adb command failed: %v\n%s\n", err, out)
+ }
+ return strings.Replace(string(out), "\r", "", -1)
+}
+
+func run(t *testing.T, extraEnv []string, args ...string) string {
+ t.Helper()
+ cmd := exec.Command(args[0], args[1:]...)
+ if len(extraEnv) > 0 {
+ cmd.Env = append(os.Environ(), extraEnv...)
+ }
+
+ if GOOS != "windows" {
+ // TestUnexportedSymbols relies on file descriptor 30
+ // being closed when the program starts, so enforce
+ // that in all cases. (The first three descriptors are
+ // stdin/stdout/stderr, so we just need to make sure
+ // that cmd.ExtraFiles[27] exists and is nil.)
+ cmd.ExtraFiles = make([]*os.File, 28)
+ }
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out)
+ } else {
+ t.Logf("run: %v", args)
+ }
+ return string(out)
+}
+
+func runExe(t *testing.T, extraEnv []string, args ...string) string {
+ t.Helper()
+ if runtime.GOOS != GOOS && GOOS == "android" {
+ return adbRun(t, append(os.Environ(), extraEnv...), args...)
+ }
+ return run(t, extraEnv, args...)
+}
+
+func runCC(t *testing.T, args ...string) string {
+ t.Helper()
+ // This function is run in parallel, so append to a copy of cc
+ // rather than cc itself.
+ return run(t, nil, append(append([]string(nil), cc...), args...)...)
+}
+
+func createHeaders() error {
+ // 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 := os.MkdirTemp("", "testcshared_obj")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(objDir)
+
+ // Generate a C header file for p, which is a non-main dependency
+ // of main package libgo.
+ //
+ // TODO(golang.org/issue/35715): This should be simpler.
+ args := []string{"go", "tool", "cgo",
+ "-objdir", objDir,
+ "-exportheader", "p.h",
+ filepath.Join(".", "p", "p.go")}
+ cmd := exec.Command(args[0], args[1:]...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
+ }
+
+ // Generate a C header file for libgo itself.
+ installdir, err = os.MkdirTemp("", "testcshared")
+ if err != nil {
+ return err
+ }
+ libgoname = "libgo.a"
+
+ args = []string{"go", "build", "-buildmode=c-shared", "-o", filepath.Join(installdir, libgoname), "./libgo"}
+ cmd = exec.Command(args[0], args[1:]...)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
+ }
+
+ args = []string{"go", "build", "-buildmode=c-shared",
+ "-installsuffix", "testcshared",
+ "-o", libgoname,
+ filepath.Join(".", "libgo", "libgo.go")}
+ if GOOS == "windows" && strings.HasSuffix(args[6], ".a") {
+ args[6] = strings.TrimSuffix(args[6], ".a") + ".dll"
+ }
+ cmd = exec.Command(args[0], args[1:]...)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
+ }
+ if GOOS == "windows" {
+ // We can't simply pass -Wl,--out-implib, because this relies on having imports from multiple packages,
+ // which results in the linkers output implib getting overwritten at each step. So instead build the
+ // import library the traditional way, using a def file.
+ err = os.WriteFile("libgo.def",
+ []byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n\t_cgo_dummy_export\n"),
+ 0644)
+ if err != nil {
+ return fmt.Errorf("unable to write def file: %v", err)
+ }
+ out, err = exec.Command(cc[0], append(cc[1:], "-print-prog-name=dlltool")...).CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("unable to find dlltool path: %v\n%s\n", err, out)
+ }
+ dlltoolpath := strings.TrimSpace(string(out))
+ if filepath.Ext(dlltoolpath) == "" {
+ // Some compilers report slash-separated paths without extensions
+ // instead of ordinary Windows paths.
+ // Try to find the canonical name for the path.
+ if lp, err := exec.LookPath(dlltoolpath); err == nil {
+ dlltoolpath = lp
+ }
+ }
+
+ args := []string{dlltoolpath, "-D", args[6], "-l", libgoname, "-d", "libgo.def"}
+
+ if filepath.Ext(dlltoolpath) == "" {
+ // This is an unfortunate workaround for
+ // https://github.com/mstorsjo/llvm-mingw/issues/205 in which
+ // we basically reimplement the contents of the dlltool.sh
+ // wrapper: https://git.io/JZFlU.
+ // TODO(thanm): remove this workaround once we can upgrade
+ // the compilers on the windows-arm64 builder.
+ dlltoolContents, err := os.ReadFile(args[0])
+ if err != nil {
+ return fmt.Errorf("unable to read dlltool: %v\n", err)
+ }
+ if bytes.HasPrefix(dlltoolContents, []byte("#!/bin/sh")) && bytes.Contains(dlltoolContents, []byte("llvm-dlltool")) {
+ base, name := filepath.Split(args[0])
+ args[0] = filepath.Join(base, "llvm-dlltool")
+ var machine string
+ switch prefix, _, _ := strings.Cut(name, "-"); prefix {
+ case "i686":
+ machine = "i386"
+ case "x86_64":
+ machine = "i386:x86-64"
+ case "armv7":
+ machine = "arm"
+ case "aarch64":
+ machine = "arm64"
+ }
+ if len(machine) > 0 {
+ args = append(args, "-m", machine)
+ }
+ }
+ }
+
+ out, err = exec.Command(args[0], args[1:]...).CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("unable to run dlltool to create import library: %v\n%s\n", err, out)
+ }
+ }
+
+ if runtime.GOOS != GOOS && GOOS == "android" {
+ args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname))
+ cmd = exec.Command(args[0], args[1:]...)
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("adb command failed: %v\n%s\n", err, out)
+ }
+ }
+
+ return nil
+}
+
+var (
+ headersOnce sync.Once
+ headersErr error
+)
+
+func createHeadersOnce(t *testing.T) {
+ headersOnce.Do(func() {
+ headersErr = createHeaders()
+ })
+ if headersErr != nil {
+ t.Helper()
+ t.Fatal(headersErr)
+ }
+}
+
+func cleanupAndroid() {
+ if GOOS != "android" {
+ return
+ }
+ args := append(adbCmd(), "exec-out", "rm", "-rf", androiddir)
+ cmd := exec.Command(args[0], args[1:]...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Panicf("cleanupAndroid failed: %v\n%s\n", err, out)
+ }
+}
+
+// test0: exported symbols in shared lib are accessible.
+func TestExportedSymbols(t *testing.T) {
+ t.Parallel()
+
+ cmd := "testp0"
+ bin := cmdToRun(cmd)
+
+ createHeadersOnce(t)
+
+ runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname)
+ adbPush(t, cmd)
+
+ defer os.Remove(bin)
+
+ out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin)
+ if strings.TrimSpace(out) != "PASS" {
+ t.Error(out)
+ }
+}
+
+func checkNumberOfExportedFunctionsWindows(t *testing.T, exportAllSymbols bool) {
+ const prog = `
+package main
+
+import "C"
+
+//export GoFunc
+func GoFunc() {
+ println(42)
+}
+
+//export GoFunc2
+func GoFunc2() {
+ println(24)
+}
+
+func main() {
+}
+`
+
+ tmpdir := t.TempDir()
+
+ srcfile := filepath.Join(tmpdir, "test.go")
+ objfile := filepath.Join(tmpdir, "test.dll")
+ if err := os.WriteFile(srcfile, []byte(prog), 0666); err != nil {
+ t.Fatal(err)
+ }
+ argv := []string{"build", "-buildmode=c-shared"}
+ if exportAllSymbols {
+ argv = append(argv, "-ldflags", "-extldflags=-Wl,--export-all-symbols")
+ }
+ argv = append(argv, "-o", objfile, srcfile)
+ out, err := exec.Command("go", argv...).CombinedOutput()
+ if err != nil {
+ t.Fatalf("build failure: %s\n%s\n", err, string(out))
+ }
+
+ f, err := pe.Open(objfile)
+ if err != nil {
+ t.Fatalf("pe.Open failed: %v", err)
+ }
+ defer f.Close()
+ section := f.Section(".edata")
+ if section == nil {
+ t.Skip(".edata section is not present")
+ }
+
+ // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go
+ type IMAGE_EXPORT_DIRECTORY struct {
+ _ [2]uint32
+ _ [2]uint16
+ _ [2]uint32
+ NumberOfFunctions uint32
+ NumberOfNames uint32
+ _ [3]uint32
+ }
+ var e IMAGE_EXPORT_DIRECTORY
+ if err := binary.Read(section.Open(), binary.LittleEndian, &e); err != nil {
+ t.Fatalf("binary.Read failed: %v", err)
+ }
+
+ // Only the two exported functions and _cgo_dummy_export should be exported
+ expectedNumber := uint32(3)
+
+ if exportAllSymbols {
+ if e.NumberOfFunctions <= expectedNumber {
+ t.Fatalf("missing exported functions: %v", e.NumberOfFunctions)
+ }
+ if e.NumberOfNames <= expectedNumber {
+ t.Fatalf("missing exported names: %v", e.NumberOfNames)
+ }
+ } else {
+ if e.NumberOfFunctions != expectedNumber {
+ t.Fatalf("got %d exported functions; want %d", e.NumberOfFunctions, expectedNumber)
+ }
+ if e.NumberOfNames != expectedNumber {
+ t.Fatalf("got %d exported names; want %d", e.NumberOfNames, expectedNumber)
+ }
+ }
+}
+
+func TestNumberOfExportedFunctions(t *testing.T) {
+ if GOOS != "windows" {
+ t.Skip("skipping windows only test")
+ }
+ t.Parallel()
+
+ t.Run("OnlyExported", func(t *testing.T) {
+ checkNumberOfExportedFunctionsWindows(t, false)
+ })
+ t.Run("All", func(t *testing.T) {
+ checkNumberOfExportedFunctionsWindows(t, true)
+ })
+}
+
+// test1: shared library can be dynamically loaded and exported symbols are accessible.
+func TestExportedSymbolsWithDynamicLoad(t *testing.T) {
+ t.Parallel()
+
+ if GOOS == "windows" {
+ t.Logf("Skipping on %s", GOOS)
+ return
+ }
+
+ cmd := "testp1"
+ bin := cmdToRun(cmd)
+
+ createHeadersOnce(t)
+
+ if GOOS != "freebsd" {
+ runCC(t, "-o", cmd, "main1.c", "-ldl")
+ } else {
+ runCC(t, "-o", cmd, "main1.c")
+ }
+ adbPush(t, cmd)
+
+ defer os.Remove(bin)
+
+ out := runExe(t, nil, bin, "./"+libgoname)
+ if strings.TrimSpace(out) != "PASS" {
+ t.Error(out)
+ }
+}
+
+// test2: tests libgo2 which does not export any functions.
+func TestUnexportedSymbols(t *testing.T) {
+ t.Parallel()
+
+ if GOOS == "windows" {
+ t.Logf("Skipping on %s", GOOS)
+ return
+ }
+
+ cmd := "testp2"
+ bin := cmdToRun(cmd)
+ libname := "libgo2.a"
+
+ run(t,
+ nil,
+ "go", "build",
+ "-buildmode=c-shared",
+ "-installsuffix", "testcshared",
+ "-o", libname, "./libgo2",
+ )
+ adbPush(t, libname)
+
+ linkFlags := "-Wl,--no-as-needed"
+ if GOOS == "darwin" || GOOS == "ios" {
+ linkFlags = ""
+ }
+
+ runCC(t, "-o", cmd, "main2.c", linkFlags, libname)
+ adbPush(t, cmd)
+
+ defer os.Remove(libname)
+ defer os.Remove(bin)
+
+ out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin)
+
+ if strings.TrimSpace(out) != "PASS" {
+ t.Error(out)
+ }
+}
+
+// test3: tests main.main is exported on android.
+func TestMainExportedOnAndroid(t *testing.T) {
+ t.Parallel()
+
+ switch GOOS {
+ case "android":
+ break
+ default:
+ t.Logf("Skipping on %s", GOOS)
+ return
+ }
+
+ cmd := "testp3"
+ bin := cmdToRun(cmd)
+
+ createHeadersOnce(t)
+
+ runCC(t, "-o", cmd, "main3.c", "-ldl")
+ adbPush(t, cmd)
+
+ defer os.Remove(bin)
+
+ out := runExe(t, nil, bin, "./"+libgoname)
+ if strings.TrimSpace(out) != "PASS" {
+ t.Error(out)
+ }
+}
+
+func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) {
+ libname := pkgname + ".a"
+ run(t,
+ nil,
+ "go", "build",
+ "-buildmode=c-shared",
+ "-installsuffix", "testcshared",
+ "-o", libname, pkgname,
+ )
+ adbPush(t, libname)
+ if GOOS != "freebsd" {
+ runCC(t, "-pthread", "-o", cmd, cfile, "-ldl")
+ } else {
+ runCC(t, "-pthread", "-o", cmd, cfile)
+ }
+ adbPush(t, cmd)
+
+ bin := cmdToRun(cmd)
+
+ defer os.Remove(libname)
+ defer os.Remove(bin)
+ defer os.Remove(pkgname + ".h")
+
+ out := runExe(t, nil, bin, "./"+libname)
+ if strings.TrimSpace(out) != "PASS" {
+ t.Error(run(t, nil, bin, libname, "verbose"))
+ }
+}
+
+// test4: test signal handlers
+func TestSignalHandlers(t *testing.T) {
+ t.Parallel()
+ if GOOS == "windows" {
+ t.Logf("Skipping on %s", GOOS)
+ return
+ }
+ testSignalHandlers(t, "./libgo4", "main4.c", "testp4")
+}
+
+// test5: test signal handlers with os/signal.Notify
+func TestSignalHandlersWithNotify(t *testing.T) {
+ t.Parallel()
+ if GOOS == "windows" {
+ t.Logf("Skipping on %s", GOOS)
+ return
+ }
+ testSignalHandlers(t, "./libgo5", "main5.c", "testp5")
+}
+
+func TestPIE(t *testing.T) {
+ t.Parallel()
+
+ switch GOOS {
+ case "linux", "android":
+ break
+ default:
+ t.Logf("Skipping on %s", GOOS)
+ return
+ }
+
+ createHeadersOnce(t)
+
+ f, err := elf.Open(libgoname)
+ if err != nil {
+ t.Fatalf("elf.Open failed: %v", err)
+ }
+ defer f.Close()
+
+ ds := f.SectionByType(elf.SHT_DYNAMIC)
+ if ds == nil {
+ t.Fatalf("no SHT_DYNAMIC section")
+ }
+ d, err := ds.Data()
+ if err != nil {
+ t.Fatalf("can't read SHT_DYNAMIC contents: %v", err)
+ }
+ for len(d) > 0 {
+ var tag elf.DynTag
+ switch f.Class {
+ case elf.ELFCLASS32:
+ tag = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
+ d = d[8:]
+ case elf.ELFCLASS64:
+ tag = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
+ d = d[16:]
+ }
+ if tag == elf.DT_TEXTREL {
+ t.Fatalf("%s has DT_TEXTREL flag", libgoname)
+ }
+ }
+}
+
+// Test that installing a second time recreates the header file.
+func TestCachedInstall(t *testing.T) {
+ tmpdir, err := os.MkdirTemp("", "cshared")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "go.mod"), "go.mod")
+ copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "libgo", "libgo.go"), filepath.Join("libgo", "libgo.go"))
+ copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "p", "p.go"), filepath.Join("p", "p.go"))
+
+ buildcmd := []string{"go", "install", "-x", "-buildmode=c-shared", "-installsuffix", "testcshared", "./libgo"}
+
+ cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
+ cmd.Dir = filepath.Join(tmpdir, "src", "testcshared")
+ env := append(cmd.Environ(),
+ "GOPATH="+tmpdir,
+ "GOBIN="+filepath.Join(tmpdir, "bin"),
+ "GO111MODULE=off", // 'go install' only works in GOPATH mode
+ )
+ cmd.Env = env
+ t.Log(buildcmd)
+ out, err := cmd.CombinedOutput()
+ t.Logf("%s", out)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var libgoh, ph string
+
+ walker := func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ t.Fatal(err)
+ }
+ var ps *string
+ switch filepath.Base(path) {
+ case "libgo.h":
+ ps = &libgoh
+ case "p.h":
+ ps = &ph
+ }
+ if ps != nil {
+ if *ps != "" {
+ t.Fatalf("%s found again", *ps)
+ }
+ *ps = path
+ }
+ return nil
+ }
+
+ if err := filepath.Walk(tmpdir, walker); err != nil {
+ t.Fatal(err)
+ }
+
+ if libgoh == "" {
+ t.Fatal("libgo.h not installed")
+ }
+
+ if err := os.Remove(libgoh); err != nil {
+ t.Fatal(err)
+ }
+
+ cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
+ cmd.Dir = filepath.Join(tmpdir, "src", "testcshared")
+ cmd.Env = env
+ t.Log(buildcmd)
+ out, err = cmd.CombinedOutput()
+ t.Logf("%s", out)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := os.Stat(libgoh); err != nil {
+ t.Errorf("libgo.h not installed in second run: %v", err)
+ }
+}
+
+// copyFile copies src to dst.
+func copyFile(t *testing.T, dst, src string) {
+ t.Helper()
+ data, err := os.ReadFile(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.WriteFile(dst, data, 0666); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestGo2C2Go(t *testing.T) {
+ switch GOOS {
+ case "darwin", "ios", "windows":
+ // Non-ELF shared libraries don't support the multiple
+ // copies of the runtime package implied by this test.
+ t.Skipf("linking c-shared into Go programs not supported on %s; issue 29061, 49457", GOOS)
+ case "android":
+ t.Skip("test fails on android; issue 29087")
+ }
+
+ t.Parallel()
+
+ tmpdir, err := os.MkdirTemp("", "cshared-TestGo2C2Go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ lib := filepath.Join(tmpdir, "libtestgo2c2go.a")
+ var env []string
+ if GOOS == "windows" && strings.HasSuffix(lib, ".a") {
+ env = append(env, "CGO_LDFLAGS=-Wl,--out-implib,"+lib, "CGO_LDFLAGS_ALLOW=.*")
+ lib = strings.TrimSuffix(lib, ".a") + ".dll"
+ }
+ run(t, env, "go", "build", "-buildmode=c-shared", "-o", lib, "./go2c2go/go")
+
+ cgoCflags := os.Getenv("CGO_CFLAGS")
+ if cgoCflags != "" {
+ cgoCflags += " "
+ }
+ cgoCflags += "-I" + tmpdir
+
+ cgoLdflags := os.Getenv("CGO_LDFLAGS")
+ if cgoLdflags != "" {
+ cgoLdflags += " "
+ }
+ cgoLdflags += "-L" + tmpdir + " -ltestgo2c2go"
+
+ goenv := []string{"CGO_CFLAGS=" + cgoCflags, "CGO_LDFLAGS=" + cgoLdflags}
+
+ ldLibPath := os.Getenv("LD_LIBRARY_PATH")
+ if ldLibPath != "" {
+ ldLibPath += ":"
+ }
+ ldLibPath += tmpdir
+
+ runenv := []string{"LD_LIBRARY_PATH=" + ldLibPath}
+
+ bin := filepath.Join(tmpdir, "m1") + exeSuffix
+ run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m1")
+ runExe(t, runenv, bin)
+
+ bin = filepath.Join(tmpdir, "m2") + exeSuffix
+ run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m2")
+ runExe(t, runenv, bin)
+}
+
+func TestIssue36233(t *testing.T) {
+ t.Parallel()
+
+ // Test that the export header uses GoComplex64 and GoComplex128
+ // for complex types.
+
+ tmpdir, err := os.MkdirTemp("", "cshared-TestIssue36233")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ const exportHeader = "issue36233.h"
+
+ run(t, nil, "go", "tool", "cgo", "-exportheader", exportHeader, "-objdir", tmpdir, "./issue36233/issue36233.go")
+ data, err := os.ReadFile(exportHeader)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ funcs := []struct{ name, signature string }{
+ {"exportComplex64", "GoComplex64 exportComplex64(GoComplex64 v)"},
+ {"exportComplex128", "GoComplex128 exportComplex128(GoComplex128 v)"},
+ {"exportComplexfloat", "GoComplex64 exportComplexfloat(GoComplex64 v)"},
+ {"exportComplexdouble", "GoComplex128 exportComplexdouble(GoComplex128 v)"},
+ }
+
+ scanner := bufio.NewScanner(bytes.NewReader(data))
+ var found int
+ for scanner.Scan() {
+ b := scanner.Bytes()
+ for _, fn := range funcs {
+ if bytes.Contains(b, []byte(fn.name)) {
+ found++
+ if !bytes.Contains(b, []byte(fn.signature)) {
+ t.Errorf("function signature mismatch; got %q, want %q", b, fn.signature)
+ }
+ }
+ }
+ }
+ if err = scanner.Err(); err != nil {
+ t.Errorf("scanner encountered error: %v", err)
+ }
+ if found != len(funcs) {
+ t.Error("missing functions")
+ }
+}
diff --git a/misc/cgo/testcshared/overlaydir_test.go b/misc/cgo/testcshared/overlaydir_test.go
new file mode 100644
index 0000000..85d6b44
--- /dev/null
+++ b/misc/cgo/testcshared/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 cshared_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/testcshared/testdata/go2c2go/go/shlib.go b/misc/cgo/testcshared/testdata/go2c2go/go/shlib.go
new file mode 100644
index 0000000..76a5323
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/go2c2go/go/shlib.go
@@ -0,0 +1,12 @@
+// Copyright 2018 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"
+
+//export GoFunc
+func GoFunc() int { return 1 }
+
+func main() {}
diff --git a/misc/cgo/testcshared/testdata/go2c2go/m1/c.c b/misc/cgo/testcshared/testdata/go2c2go/m1/c.c
new file mode 100644
index 0000000..0e8fac4
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/go2c2go/m1/c.c
@@ -0,0 +1,9 @@
+// Copyright 2018 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 "libtestgo2c2go.h"
+
+int CFunc(void) {
+ return (GoFunc() << 8) + 2;
+}
diff --git a/misc/cgo/testcshared/testdata/go2c2go/m1/main.go b/misc/cgo/testcshared/testdata/go2c2go/m1/main.go
new file mode 100644
index 0000000..17ba1eb
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/go2c2go/m1/main.go
@@ -0,0 +1,22 @@
+// Copyright 2018 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
+
+// extern int CFunc(void);
+import "C"
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ got := C.CFunc()
+ const want = (1 << 8) | 2
+ if got != want {
+ fmt.Printf("got %#x, want %#x\n", got, want)
+ os.Exit(1)
+ }
+}
diff --git a/misc/cgo/testcshared/testdata/go2c2go/m2/main.go b/misc/cgo/testcshared/testdata/go2c2go/m2/main.go
new file mode 100644
index 0000000..91bf308
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/go2c2go/m2/main.go
@@ -0,0 +1,22 @@
+// Copyright 2018 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 "libtestgo2c2go.h"
+import "C"
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ got := C.GoFunc()
+ const want = 1
+ if got != want {
+ fmt.Printf("got %#x, want %#x\n", got, want)
+ os.Exit(1)
+ }
+}
diff --git a/misc/cgo/testcshared/testdata/issue36233/issue36233.go b/misc/cgo/testcshared/testdata/issue36233/issue36233.go
new file mode 100644
index 0000000..d0d1e5d
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/issue36233/issue36233.go
@@ -0,0 +1,29 @@
+// Copyright 2022 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 <complex.h>
+import "C"
+
+//export exportComplex64
+func exportComplex64(v complex64) complex64 {
+ return v
+}
+
+//export exportComplex128
+func exportComplex128(v complex128) complex128 {
+ return v
+}
+
+//export exportComplexfloat
+func exportComplexfloat(v C.complexfloat) C.complexfloat {
+ return v
+}
+
+//export exportComplexdouble
+func exportComplexdouble(v C.complexdouble) C.complexdouble {
+ return v
+}
+
+func main() {}
diff --git a/misc/cgo/testcshared/testdata/libgo/libgo.go b/misc/cgo/testcshared/testdata/libgo/libgo.go
new file mode 100644
index 0000000..0634417
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/libgo/libgo.go
@@ -0,0 +1,46 @@
+// 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 (
+ "syscall"
+ _ "testcshared/p"
+ "time"
+)
+
+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
+}
diff --git a/misc/cgo/testcshared/testdata/libgo2/dup2.go b/misc/cgo/testcshared/testdata/libgo2/dup2.go
new file mode 100644
index 0000000..d343aa5
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/libgo2/dup2.go
@@ -0,0 +1,13 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux,!arm64,!riscv64 netbsd openbsd
+
+package main
+
+import "syscall"
+
+func dup2(oldfd, newfd int) error {
+ return syscall.Dup2(oldfd, newfd)
+}
diff --git a/misc/cgo/testcshared/testdata/libgo2/dup3.go b/misc/cgo/testcshared/testdata/libgo2/dup3.go
new file mode 100644
index 0000000..459f0dc
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/libgo2/dup3.go
@@ -0,0 +1,13 @@
+// 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.
+
+// +build linux,arm64 linux,riscv64
+
+package main
+
+import "syscall"
+
+func dup2(oldfd, newfd int) error {
+ return syscall.Dup3(oldfd, newfd, 0)
+}
diff --git a/misc/cgo/testcshared/testdata/libgo2/libgo2.go b/misc/cgo/testcshared/testdata/libgo2/libgo2.go
new file mode 100644
index 0000000..e57c93b
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/libgo2/libgo2.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.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package main
+
+// Test a shared library created by -buildmode=c-shared that does not
+// export anything.
+
+import (
+ "fmt"
+ "os"
+ "syscall"
+)
+
+// To test this we want to communicate between the main program and
+// the shared library without using any exported symbols. The init
+// function creates a pipe and Dups the read end to a known number
+// that the C code can also use.
+
+const (
+ fd = 30
+)
+
+func init() {
+ var p [2]int
+ if e := syscall.Pipe(p[0:]); e != nil {
+ fmt.Fprintf(os.Stderr, "pipe: %v\n", e)
+ os.Exit(2)
+ }
+
+ if e := dup2(p[0], fd); e != nil {
+ fmt.Fprintf(os.Stderr, "dup2: %v\n", e)
+ os.Exit(2)
+ }
+
+ const str = "PASS"
+ if n, e := syscall.Write(p[1], []byte(str)); e != nil || n != len(str) {
+ fmt.Fprintf(os.Stderr, "write: %d %v\n", n, e)
+ os.Exit(2)
+ }
+
+ if e := syscall.Close(p[1]); e != nil {
+ fmt.Fprintf(os.Stderr, "close: %v\n", e)
+ os.Exit(2)
+ }
+}
+
+func main() {
+}
diff --git a/misc/cgo/testcshared/testdata/libgo4/libgo4.go b/misc/cgo/testcshared/testdata/libgo4/libgo4.go
new file mode 100644
index 0000000..ab40b75
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/libgo4/libgo4.go
@@ -0,0 +1,45 @@
+// 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 (
+ "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)
+}
+
+func main() {
+}
diff --git a/misc/cgo/testcshared/testdata/libgo5/libgo5.go b/misc/cgo/testcshared/testdata/libgo5/libgo5.go
new file mode 100644
index 0000000..94e5d21
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/libgo5/libgo5.go
@@ -0,0 +1,44 @@
+// 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 returns whether we saw a SIGIO within a brief pause.
+//export SawSIGIO
+func SawSIGIO() C.int {
+ select {
+ case <-sigioChan:
+ return 1
+ case <-time.After(100 * time.Millisecond):
+ return 0
+ }
+}
+
+func main() {
+}
diff --git a/misc/cgo/testcshared/testdata/main0.c b/misc/cgo/testcshared/testdata/main0.c
new file mode 100644
index 0000000..39ef7e3
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/main0.c
@@ -0,0 +1,42 @@
+// 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 "p.h"
+#include "libgo.h"
+
+// Tests libgo.so to export the following functions.
+// int8_t DidInitRun();
+// int8_t DidMainRun();
+// int32_t FromPkg();
+// uint32_t Divu(uint32_t, uint32_t);
+int main(void) {
+ int8_t ran_init = DidInitRun();
+ if (!ran_init) {
+ fprintf(stderr, "ERROR: DidInitRun returned unexpected results: %d\n",
+ ran_init);
+ return 1;
+ }
+ int8_t ran_main = DidMainRun();
+ if (ran_main) {
+ fprintf(stderr, "ERROR: DidMainRun returned unexpected results: %d\n",
+ ran_main);
+ return 1;
+ }
+ int32_t from_pkg = FromPkg();
+ if (from_pkg != 1024) {
+ fprintf(stderr, "ERROR: FromPkg=%d, want %d\n", from_pkg, 1024);
+ return 1;
+ }
+ uint32_t divu = Divu(2264, 31);
+ if (divu != 73) {
+ fprintf(stderr, "ERROR: Divu(2264, 31)=%d, want %d\n", divu, 73);
+ return 1;
+ }
+ // test.bash looks for "PASS" to ensure this program has reached the end.
+ printf("PASS\n");
+ return 0;
+}
diff --git a/misc/cgo/testcshared/testdata/main1.c b/misc/cgo/testcshared/testdata/main1.c
new file mode 100644
index 0000000..420dd1e
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/main1.c
@@ -0,0 +1,69 @@
+// 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 <dlfcn.h>
+
+int check_int8(void* handle, const char* fname, int8_t want) {
+ int8_t (*fn)();
+ fn = (int8_t (*)())dlsym(handle, fname);
+ if (!fn) {
+ fprintf(stderr, "ERROR: missing %s: %s\n", fname, dlerror());
+ return 1;
+ }
+ signed char ret = fn();
+ if (ret != want) {
+ fprintf(stderr, "ERROR: %s=%d, want %d\n", fname, ret, want);
+ return 1;
+ }
+ return 0;
+}
+
+int check_int32(void* handle, const char* fname, int32_t want) {
+ int32_t (*fn)();
+ fn = (int32_t (*)())dlsym(handle, fname);
+ if (!fn) {
+ fprintf(stderr, "ERROR: missing %s: %s\n", fname, dlerror());
+ return 1;
+ }
+ int32_t ret = fn();
+ if (ret != want) {
+ fprintf(stderr, "ERROR: %s=%d, want %d\n", fname, ret, want);
+ return 1;
+ }
+ return 0;
+}
+
+// Tests libgo.so to export the following functions.
+// int8_t DidInitRun() // returns true
+// int8_t DidMainRun() // returns true
+// int32_t FromPkg() // returns 1024
+int main(int argc, char** argv) {
+ void* handle = dlopen(argv[1], RTLD_LAZY | RTLD_GLOBAL);
+ if (!handle) {
+ fprintf(stderr, "ERROR: failed to open the shared library: %s\n",
+ dlerror());
+ return 2;
+ }
+
+ int ret = 0;
+ ret = check_int8(handle, "DidInitRun", 1);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = check_int8(handle, "DidMainRun", 0);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = check_int32(handle, "FromPkg", 1024);
+ if (ret != 0) {
+ return ret;
+ }
+ // test.bash looks for "PASS" to ensure this program has reached the end.
+ printf("PASS\n");
+ return 0;
+}
diff --git a/misc/cgo/testcshared/testdata/main2.c b/misc/cgo/testcshared/testdata/main2.c
new file mode 100644
index 0000000..f89bcca
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/main2.c
@@ -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.
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define fd (30)
+
+// Tests libgo2.so, which does not export any functions.
+// Read a string from the file descriptor and print it.
+int main(void) {
+ int i;
+ ssize_t n;
+ char buf[20];
+ struct timespec ts;
+
+ // The descriptor will be initialized in a thread, so we have to
+ // give a chance to get opened.
+ for (i = 0; i < 200; i++) {
+ n = read(fd, buf, sizeof buf);
+ if (n >= 0)
+ break;
+ if (errno != EBADF && errno != EINVAL) {
+ fprintf(stderr, "BUG: read: %s\n", strerror(errno));
+ return 2;
+ }
+
+ // An EBADF error means that the shared library has not opened the
+ // descriptor yet.
+ ts.tv_sec = 0;
+ ts.tv_nsec = 10000000;
+ nanosleep(&ts, NULL);
+ }
+
+ if (n < 0) {
+ fprintf(stderr, "BUG: failed to read any data from pipe\n");
+ return 2;
+ }
+
+ if (n == 0) {
+ fprintf(stderr, "BUG: unexpected EOF\n");
+ return 2;
+ }
+
+ if (n == sizeof buf) {
+ n--;
+ }
+ buf[n] = '\0';
+ printf("%s\n", buf);
+ return 0;
+}
diff --git a/misc/cgo/testcshared/testdata/main3.c b/misc/cgo/testcshared/testdata/main3.c
new file mode 100644
index 0000000..49cc055
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/main3.c
@@ -0,0 +1,29 @@
+// 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 <dlfcn.h>
+
+// Tests "main.main" is exported on android/arm,
+// which golang.org/x/mobile/app depends on.
+int main(int argc, char** argv) {
+ void* handle = dlopen(argv[1], RTLD_LAZY | RTLD_GLOBAL);
+ if (!handle) {
+ fprintf(stderr, "ERROR: failed to open the shared library: %s\n",
+ dlerror());
+ return 2;
+ }
+
+ uintptr_t main_fn = (uintptr_t)dlsym(handle, "main.main");
+ if (!main_fn) {
+ fprintf(stderr, "ERROR: missing main.main: %s\n", dlerror());
+ return 2;
+ }
+
+ // TODO(hyangah): check that main.main can run.
+
+ printf("PASS\n");
+ return 0;
+}
diff --git a/misc/cgo/testcshared/testdata/main4.c b/misc/cgo/testcshared/testdata/main4.c
new file mode 100644
index 0000000..355cdef
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/main4.c
@@ -0,0 +1,215 @@
+// 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 that a signal handler that uses up stack space does not crash
+// if the signal is delivered to a thread running a goroutine.
+// This is a lot like misc/cgo/testcarchive/main2.c.
+
+#include <setjmp.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sched.h>
+#include <time.h>
+#include <dlfcn.h>
+
+static void die(const char* msg) {
+ perror(msg);
+ exit(EXIT_FAILURE);
+}
+
+static volatile sig_atomic_t sigioSeen;
+
+// Use up some stack space.
+static void recur(int i, char *p) {
+ char a[1024];
+
+ *p = '\0';
+ if (i > 0) {
+ recur(i - 1, a);
+ }
+}
+
+// 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;
+
+// Signal handler for SIGSEGV on a C thread.
+static void segvHandler(int signo, siginfo_t* info, void* ctxt) {
+ sigset_t mask;
+ int i;
+
+ 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();
+}
+
+int main(int argc, char** argv) {
+ int verbose;
+ struct sigaction sa;
+ void* handle;
+ void (*fn)(void);
+ sigset_t mask;
+ int i;
+ struct timespec ts;
+
+ 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 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");
+ }
+
+ sa.sa_sigaction = segvHandler;
+ if (sigaction(SIGSEGV, &sa, NULL) < 0 || sigaction(SIGBUS, &sa, NULL) < 0) {
+ die("sigaction");
+ }
+
+ if (verbose) {
+ printf("calling dlopen\n");
+ }
+
+ handle = dlopen(argv[1], RTLD_NOW | RTLD_GLOBAL);
+ if (handle == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ if (verbose) {
+ printf("calling dlsym\n");
+ }
+
+ // Start some goroutines.
+ fn = (void(*)(void))dlsym(handle, "RunGoroutines");
+ if (fn == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ if (verbose) {
+ printf("calling RunGoroutines\n");
+ }
+
+ fn();
+
+ // 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 signal\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 dlsym\n");
+ }
+
+ // Make sure that a SIGSEGV in Go causes a run-time panic.
+ fn = (void (*)(void))dlsym(handle, "TestSEGV");
+ if (fn == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ if (verbose) {
+ printf("calling TestSEGV\n");
+ }
+
+ fn();
+
+ printf("PASS\n");
+ return 0;
+}
diff --git a/misc/cgo/testcshared/testdata/main5.c b/misc/cgo/testcshared/testdata/main5.c
new file mode 100644
index 0000000..1bc9910
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/main5.c
@@ -0,0 +1,199 @@
+// 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 that a signal handler works in non-Go code when using
+// os/signal.Notify.
+// This is a lot like misc/cgo/testcarchive/main3.c.
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sched.h>
+#include <dlfcn.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;
+}
+
+int main(int argc, char** argv) {
+ int verbose;
+ struct sigaction sa;
+ void* handle;
+ void (*fn1)(void);
+ int (*sawSIGIO)(void);
+ int i;
+ struct timespec ts;
+
+ verbose = argc > 2;
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ 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");
+ }
+
+ if (verbose) {
+ printf("calling dlopen\n");
+ }
+
+ handle = dlopen(argv[1], RTLD_NOW | RTLD_GLOBAL);
+ if (handle == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ // 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 dlsym\n");
+ }
+
+ fn1 = (void(*)(void))dlsym(handle, "CatchSIGIO");
+ if (fn1 == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ if (verbose) {
+ printf("calling CatchSIGIO\n");
+ }
+
+ fn1();
+
+ if (verbose) {
+ printf("raising SIGIO\n");
+ }
+
+ if (raise(SIGIO) < 0) {
+ die("raise");
+ }
+
+ if (verbose) {
+ printf("calling dlsym\n");
+ }
+
+ // Check that the Go code saw SIGIO.
+ sawSIGIO = (int (*)(void))dlsym(handle, "SawSIGIO");
+ if (sawSIGIO == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ 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 dlsym\n");
+ }
+
+ fn1 = (void(*)(void))dlsym(handle, "ResetSIGIO");
+ if (fn1 == NULL) {
+ fprintf(stderr, "%s\n", dlerror());
+ exit(EXIT_FAILURE);
+ }
+
+ if (verbose) {
+ printf("calling ResetSIGIO\n");
+ }
+
+ fn1();
+
+ 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/testcshared/testdata/p/p.go b/misc/cgo/testcshared/testdata/p/p.go
new file mode 100644
index 0000000..0f02cf3
--- /dev/null
+++ b/misc/cgo/testcshared/testdata/p/p.go
@@ -0,0 +1,13 @@
+// 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 }
+
+//export Divu
+func Divu(a, b uint32) uint32 { return a / b }