summaryrefslogtreecommitdiffstats
path: root/src/runtime/testdata/testprogcgo
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/testdata/testprogcgo')
-rw-r--r--src/runtime/testdata/testprogcgo/aprof.go56
-rw-r--r--src/runtime/testdata/testprogcgo/aprof_c.c9
-rw-r--r--src/runtime/testdata/testprogcgo/bigstack1_windows.c12
-rw-r--r--src/runtime/testdata/testprogcgo/bigstack_windows.c46
-rw-r--r--src/runtime/testdata/testprogcgo/bigstack_windows.go27
-rw-r--r--src/runtime/testdata/testprogcgo/bindm.c34
-rw-r--r--src/runtime/testdata/testprogcgo/bindm.go61
-rw-r--r--src/runtime/testdata/testprogcgo/callback.go116
-rw-r--r--src/runtime/testdata/testprogcgo/catchpanic.go47
-rw-r--r--src/runtime/testdata/testprogcgo/cgo.go108
-rw-r--r--src/runtime/testdata/testprogcgo/cgonocallback.c9
-rw-r--r--src/runtime/testdata/testprogcgo/cgonocallback.go31
-rw-r--r--src/runtime/testdata/testprogcgo/cgonoescape.go84
-rw-r--r--src/runtime/testdata/testprogcgo/crash.go45
-rw-r--r--src/runtime/testdata/testprogcgo/deadlock.go30
-rw-r--r--src/runtime/testdata/testprogcgo/destructor.c22
-rw-r--r--src/runtime/testdata/testprogcgo/destructor.go23
-rw-r--r--src/runtime/testdata/testprogcgo/dll_windows.go25
-rw-r--r--src/runtime/testdata/testprogcgo/dropm.go60
-rw-r--r--src/runtime/testdata/testprogcgo/dropm_stub.go12
-rw-r--r--src/runtime/testdata/testprogcgo/eintr.go247
-rw-r--r--src/runtime/testdata/testprogcgo/exec.go107
-rw-r--r--src/runtime/testdata/testprogcgo/gprof.go46
-rw-r--r--src/runtime/testdata/testprogcgo/gprof_c.c30
-rw-r--r--src/runtime/testdata/testprogcgo/issue29707.go60
-rw-r--r--src/runtime/testdata/testprogcgo/lockosthread.c13
-rw-r--r--src/runtime/testdata/testprogcgo/lockosthread.go110
-rw-r--r--src/runtime/testdata/testprogcgo/main.go35
-rw-r--r--src/runtime/testdata/testprogcgo/needmdeadlock.go96
-rw-r--r--src/runtime/testdata/testprogcgo/numgoroutine.go93
-rw-r--r--src/runtime/testdata/testprogcgo/panic.c9
-rw-r--r--src/runtime/testdata/testprogcgo/panic.go23
-rw-r--r--src/runtime/testdata/testprogcgo/pprof.go93
-rw-r--r--src/runtime/testdata/testprogcgo/pprof_callback.go89
-rw-r--r--src/runtime/testdata/testprogcgo/raceprof.go79
-rw-r--r--src/runtime/testdata/testprogcgo/racesig.go93
-rw-r--r--src/runtime/testdata/testprogcgo/segv.go34
-rw-r--r--src/runtime/testdata/testprogcgo/segv_linux.go32
-rw-r--r--src/runtime/testdata/testprogcgo/sigfwd.go87
-rw-r--r--src/runtime/testdata/testprogcgo/sigpanic.go28
-rw-r--r--src/runtime/testdata/testprogcgo/sigstack.go99
-rw-r--r--src/runtime/testdata/testprogcgo/sigthrow.go20
-rw-r--r--src/runtime/testdata/testprogcgo/stack_windows.go57
-rw-r--r--src/runtime/testdata/testprogcgo/stackswitch.c147
-rw-r--r--src/runtime/testdata/testprogcgo/stackswitch.go42
-rw-r--r--src/runtime/testdata/testprogcgo/threadpanic.go25
-rw-r--r--src/runtime/testdata/testprogcgo/threadpanic_unix.c26
-rw-r--r--src/runtime/testdata/testprogcgo/threadpanic_windows.c23
-rw-r--r--src/runtime/testdata/testprogcgo/threadpprof.go128
-rw-r--r--src/runtime/testdata/testprogcgo/threadprof.go105
-rw-r--r--src/runtime/testdata/testprogcgo/trace.go60
-rw-r--r--src/runtime/testdata/testprogcgo/trace_unix.c27
-rw-r--r--src/runtime/testdata/testprogcgo/trace_windows.c29
-rw-r--r--src/runtime/testdata/testprogcgo/traceback.go54
-rw-r--r--src/runtime/testdata/testprogcgo/traceback_c.c65
-rw-r--r--src/runtime/testdata/testprogcgo/tracebackctxt.go136
-rw-r--r--src/runtime/testdata/testprogcgo/tracebackctxt_c.c103
-rw-r--r--src/runtime/testdata/testprogcgo/windows/win.go14
58 files changed, 3421 insertions, 0 deletions
diff --git a/src/runtime/testdata/testprogcgo/aprof.go b/src/runtime/testdata/testprogcgo/aprof.go
new file mode 100644
index 0000000..1687014
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/aprof.go
@@ -0,0 +1,56 @@
+// 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
+
+// Test that SIGPROF received in C code does not crash the process
+// looking for the C code's func pointer.
+
+// This is a regression test for issue 14599, where profiling fails when the
+// function is the first C function. Exported functions are the first C
+// functions, so we use an exported function. Exported functions are created in
+// lexicographical order of source files, so this file is named aprof.go to
+// ensure its function is first.
+
+// extern void CallGoNop();
+import "C"
+
+import (
+ "bytes"
+ "fmt"
+ "runtime/pprof"
+ "time"
+)
+
+func init() {
+ register("CgoCCodeSIGPROF", CgoCCodeSIGPROF)
+}
+
+//export GoNop
+func GoNop() {}
+
+func CgoCCodeSIGPROF() {
+ c := make(chan bool)
+ go func() {
+ <-c
+ start := time.Now()
+ for i := 0; i < 1e7; i++ {
+ if i%1000 == 0 {
+ if time.Since(start) > time.Second {
+ break
+ }
+ }
+ C.CallGoNop()
+ }
+ c <- true
+ }()
+
+ var buf bytes.Buffer
+ pprof.StartCPUProfile(&buf)
+ c <- true
+ <-c
+ pprof.StopCPUProfile()
+
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/aprof_c.c b/src/runtime/testdata/testprogcgo/aprof_c.c
new file mode 100644
index 0000000..d588e13
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/aprof_c.c
@@ -0,0 +1,9 @@
+// Copyright 2021 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 "_cgo_export.h"
+
+void CallGoNop() {
+ GoNop();
+}
diff --git a/src/runtime/testdata/testprogcgo/bigstack1_windows.c b/src/runtime/testdata/testprogcgo/bigstack1_windows.c
new file mode 100644
index 0000000..551fb68
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/bigstack1_windows.c
@@ -0,0 +1,12 @@
+// Copyright 2021 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.
+
+// This is not in bigstack_windows.c because it needs to be part of
+// testprogcgo but is not part of the DLL built from bigstack_windows.c.
+
+#include "_cgo_export.h"
+
+void CallGoBigStack1(char* p) {
+ goBigStack1(p);
+}
diff --git a/src/runtime/testdata/testprogcgo/bigstack_windows.c b/src/runtime/testdata/testprogcgo/bigstack_windows.c
new file mode 100644
index 0000000..cd85ac8
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/bigstack_windows.c
@@ -0,0 +1,46 @@
+// 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.
+
+// This test source is used by both TestBigStackCallbackCgo (linked
+// directly into the Go binary) and TestBigStackCallbackSyscall
+// (compiled into a DLL).
+
+#include <windows.h>
+#include <stdio.h>
+
+#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
+#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
+#endif
+
+typedef void callback(char*);
+
+// Allocate a stack that's much larger than the default.
+static const int STACK_SIZE = 16<<20;
+
+static callback *bigStackCallback;
+
+static void useStack(int bytes) {
+ // Windows doesn't like huge frames, so we grow the stack 64k at a time.
+ char x[64<<10];
+ if (bytes < sizeof x) {
+ bigStackCallback(x);
+ } else {
+ useStack(bytes - sizeof x);
+ }
+}
+
+static DWORD WINAPI threadEntry(LPVOID lpParam) {
+ useStack(STACK_SIZE - (128<<10));
+ return 0;
+}
+
+void bigStack(callback *cb) {
+ bigStackCallback = cb;
+ HANDLE hThread = CreateThread(NULL, STACK_SIZE, threadEntry, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ if (hThread == NULL) {
+ fprintf(stderr, "CreateThread failed\n");
+ exit(1);
+ }
+ WaitForSingleObject(hThread, INFINITE);
+}
diff --git a/src/runtime/testdata/testprogcgo/bigstack_windows.go b/src/runtime/testdata/testprogcgo/bigstack_windows.go
new file mode 100644
index 0000000..135b5fc
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/bigstack_windows.go
@@ -0,0 +1,27 @@
+// 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
+
+/*
+typedef void callback(char*);
+extern void CallGoBigStack1(char*);
+extern void bigStack(callback*);
+*/
+import "C"
+
+func init() {
+ register("BigStack", BigStack)
+}
+
+func BigStack() {
+ // Create a large thread stack and call back into Go to test
+ // if Go correctly determines the stack bounds.
+ C.bigStack((*C.callback)(C.CallGoBigStack1))
+}
+
+//export goBigStack1
+func goBigStack1(x *C.char) {
+ println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/bindm.c b/src/runtime/testdata/testprogcgo/bindm.c
new file mode 100644
index 0000000..815d8a7
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/bindm.c
@@ -0,0 +1,34 @@
+// Copyright 2023 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.
+
+//go:build !plan9 && !windows
+
+#include <stdint.h>
+#include <pthread.h>
+#include <unistd.h>
+#include "_cgo_export.h"
+
+#define CTHREADS 2
+#define CHECKCALLS 100
+
+static void* checkBindMThread(void* thread) {
+ int i;
+ for (i = 0; i < CHECKCALLS; i++) {
+ GoCheckBindM((uintptr_t)thread);
+ usleep(1);
+ }
+ return NULL;
+}
+
+void CheckBindM() {
+ int i;
+ pthread_t s[CTHREADS];
+
+ for (i = 0; i < CTHREADS; i++) {
+ pthread_create(&s[i], NULL, checkBindMThread, &s[i]);
+ }
+ for (i = 0; i < CTHREADS; i++) {
+ pthread_join(s[i], NULL);
+ }
+}
diff --git a/src/runtime/testdata/testprogcgo/bindm.go b/src/runtime/testdata/testprogcgo/bindm.go
new file mode 100644
index 0000000..c2003c2
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/bindm.go
@@ -0,0 +1,61 @@
+// Copyright 2023 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.
+
+//go:build !plan9 && !windows
+
+// Test that callbacks from C to Go in the same C-thread always get the same m.
+// Make sure the extra M bind to the C-thread.
+
+package main
+
+/*
+extern void CheckBindM();
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "sync"
+ "sync/atomic"
+)
+
+var (
+ mutex = sync.Mutex{}
+ cThreadToM = map[uintptr]uintptr{}
+ started = atomic.Uint32{}
+)
+
+// same as CTHREADS in C, make sure all the C threads are actually started.
+const cThreadNum = 2
+
+func init() {
+ register("EnsureBindM", EnsureBindM)
+}
+
+//export GoCheckBindM
+func GoCheckBindM(thread uintptr) {
+ // Wait all threads start
+ if started.Load() != cThreadNum {
+ // Only once for each thread, since it will wait all threads start.
+ started.Add(1)
+ for started.Load() < cThreadNum {
+ runtime.Gosched()
+ }
+ }
+ m := runtime_getm_for_test()
+ mutex.Lock()
+ defer mutex.Unlock()
+ if savedM, ok := cThreadToM[thread]; ok && savedM != m {
+ fmt.Printf("m == %x want %x\n", m, savedM)
+ os.Exit(1)
+ }
+ cThreadToM[thread] = m
+}
+
+func EnsureBindM() {
+ C.CheckBindM()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/callback.go b/src/runtime/testdata/testprogcgo/callback.go
new file mode 100644
index 0000000..319572f
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/callback.go
@@ -0,0 +1,116 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+/*
+#include <pthread.h>
+
+void go_callback();
+
+static void *thr(void *arg) {
+ go_callback();
+ return 0;
+}
+
+static void foo() {
+ pthread_t th;
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setstacksize(&attr, 256 << 10);
+ pthread_create(&th, &attr, thr, 0);
+ pthread_join(th, 0);
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "sync/atomic"
+ _ "unsafe" // for go:linkname
+)
+
+func init() {
+ register("CgoCallbackGC", CgoCallbackGC)
+}
+
+//export go_callback
+func go_callback() {
+ if e := extraMInUse.Load(); e == 0 {
+ fmt.Printf("in callback extraMInUse got %d want >0\n", e)
+ os.Exit(1)
+ }
+
+ runtime.GC()
+ grow()
+ runtime.GC()
+}
+
+var cnt int
+
+func grow() {
+ x := 10000
+ sum := 0
+ if grow1(&x, &sum) == 0 {
+ panic("bad")
+ }
+}
+
+func grow1(x, sum *int) int {
+ if *x == 0 {
+ return *sum + 1
+ }
+ *x--
+ sum1 := *sum + *x
+ return grow1(x, &sum1)
+}
+
+func CgoCallbackGC() {
+ P := 100
+ if os.Getenv("RUNTIME_TEST_SHORT") != "" {
+ P = 10
+ }
+
+ if e := extraMInUse.Load(); e != 0 {
+ fmt.Printf("before testing extraMInUse got %d want 0\n", e)
+ os.Exit(1)
+ }
+
+ done := make(chan bool)
+ // allocate a bunch of stack frames and spray them with pointers
+ for i := 0; i < P; i++ {
+ go func() {
+ grow()
+ done <- true
+ }()
+ }
+ for i := 0; i < P; i++ {
+ <-done
+ }
+ // now give these stack frames to cgo callbacks
+ for i := 0; i < P; i++ {
+ go func() {
+ C.foo()
+ done <- true
+ }()
+ }
+ for i := 0; i < P; i++ {
+ <-done
+ }
+
+ if e := extraMInUse.Load(); e != 0 {
+ fmt.Printf("after testing extraMInUse got %d want 0\n", e)
+ os.Exit(1)
+ }
+
+ fmt.Printf("OK\n")
+}
+
+//go:linkname extraMInUse runtime.extraMInUse
+var extraMInUse atomic.Uint32
diff --git a/src/runtime/testdata/testprogcgo/catchpanic.go b/src/runtime/testdata/testprogcgo/catchpanic.go
new file mode 100644
index 0000000..c722d40
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/catchpanic.go
@@ -0,0 +1,47 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+/*
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+static void abrthandler(int signum) {
+ if (signum == SIGABRT) {
+ exit(0); // success
+ }
+}
+
+void registerAbortHandler() {
+ struct sigaction act;
+ memset(&act, 0, sizeof act);
+ act.sa_handler = abrthandler;
+ sigaction(SIGABRT, &act, NULL);
+}
+
+static void __attribute__ ((constructor)) sigsetup(void) {
+ if (getenv("CGOCATCHPANIC_EARLY_HANDLER") == NULL)
+ return;
+ registerAbortHandler();
+}
+*/
+import "C"
+import "os"
+
+func init() {
+ register("CgoCatchPanic", CgoCatchPanic)
+}
+
+// Test that the SIGABRT raised by panic can be caught by an early signal handler.
+func CgoCatchPanic() {
+ if _, ok := os.LookupEnv("CGOCATCHPANIC_EARLY_HANDLER"); !ok {
+ C.registerAbortHandler()
+ }
+ panic("catch me")
+}
diff --git a/src/runtime/testdata/testprogcgo/cgo.go b/src/runtime/testdata/testprogcgo/cgo.go
new file mode 100644
index 0000000..a587db3
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgo.go
@@ -0,0 +1,108 @@
+// 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
+
+/*
+void foo1(void) {}
+void foo2(void* p) {}
+*/
+import "C"
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+ "time"
+ "unsafe"
+)
+
+func init() {
+ register("CgoSignalDeadlock", CgoSignalDeadlock)
+ register("CgoTraceback", CgoTraceback)
+ register("CgoCheckBytes", CgoCheckBytes)
+}
+
+func CgoSignalDeadlock() {
+ runtime.GOMAXPROCS(100)
+ ping := make(chan bool)
+ go func() {
+ for i := 0; ; i++ {
+ runtime.Gosched()
+ select {
+ case done := <-ping:
+ if done {
+ ping <- true
+ return
+ }
+ ping <- true
+ default:
+ }
+ func() {
+ defer func() {
+ recover()
+ }()
+ var s *string
+ *s = ""
+ fmt.Printf("continued after expected panic\n")
+ }()
+ }
+ }()
+ time.Sleep(time.Millisecond)
+ start := time.Now()
+ var times []time.Duration
+ n := 64
+ if os.Getenv("RUNTIME_TEST_SHORT") != "" {
+ n = 16
+ }
+ for i := 0; i < n; i++ {
+ go func() {
+ runtime.LockOSThread()
+ select {}
+ }()
+ go func() {
+ runtime.LockOSThread()
+ select {}
+ }()
+ time.Sleep(time.Millisecond)
+ ping <- false
+ select {
+ case <-ping:
+ times = append(times, time.Since(start))
+ case <-time.After(time.Second):
+ fmt.Printf("HANG 1 %v\n", times)
+ return
+ }
+ }
+ ping <- true
+ select {
+ case <-ping:
+ case <-time.After(time.Second):
+ fmt.Printf("HANG 2 %v\n", times)
+ return
+ }
+ fmt.Printf("OK\n")
+}
+
+func CgoTraceback() {
+ C.foo1()
+ buf := make([]byte, 1)
+ runtime.Stack(buf, true)
+ fmt.Printf("OK\n")
+}
+
+func CgoCheckBytes() {
+ try, _ := strconv.Atoi(os.Getenv("GO_CGOCHECKBYTES_TRY"))
+ if try <= 0 {
+ try = 1
+ }
+ b := make([]byte, 1e6*try)
+ start := time.Now()
+ for i := 0; i < 1e3*try; i++ {
+ C.foo2(unsafe.Pointer(&b[0]))
+ if time.Since(start) > time.Second {
+ break
+ }
+ }
+}
diff --git a/src/runtime/testdata/testprogcgo/cgonocallback.c b/src/runtime/testdata/testprogcgo/cgonocallback.c
new file mode 100644
index 0000000..465a484
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgonocallback.c
@@ -0,0 +1,9 @@
+// Copyright 2023 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 "_cgo_export.h"
+
+void runCShouldNotCallback() {
+ CallbackToGo();
+}
diff --git a/src/runtime/testdata/testprogcgo/cgonocallback.go b/src/runtime/testdata/testprogcgo/cgonocallback.go
new file mode 100644
index 0000000..c13bf27
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgonocallback.go
@@ -0,0 +1,31 @@
+// Copyright 2023 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
+
+// #cgo nocallback annotations for a C function means it should not callback to Go.
+// But it do callback to go in this test, Go should crash here.
+
+/*
+// TODO(#56378): #cgo nocallback runCShouldNotCallback
+extern void runCShouldNotCallback();
+*/
+import "C"
+
+import (
+ "fmt"
+)
+
+func init() {
+ register("CgoNoCallback", CgoNoCallback)
+}
+
+//export CallbackToGo
+func CallbackToGo() {
+}
+
+func CgoNoCallback() {
+ C.runCShouldNotCallback()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/cgonoescape.go b/src/runtime/testdata/testprogcgo/cgonoescape.go
new file mode 100644
index 0000000..f5eebac
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgonoescape.go
@@ -0,0 +1,84 @@
+// Copyright 2023 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
+
+// #cgo noescape annotations for a C function means its arguments won't escape to heap.
+
+// We assume that there won't be 100 new allocated heap objects in other places,
+// i.e. runtime.ReadMemStats or other runtime background works.
+// So, the tests are:
+// 1. at least 100 new allocated heap objects after invoking withoutNoEscape 100 times.
+// 2. less than 100 new allocated heap objects after invoking withoutNoEscape 100 times.
+
+/*
+// TODO(#56378): #cgo noescape runCWithNoEscape
+
+void runCWithNoEscape(void *p) {
+}
+void runCWithoutNoEscape(void *p) {
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "runtime"
+ "runtime/debug"
+ "unsafe"
+)
+
+const num = 100
+
+func init() {
+ register("CgoNoEscape", CgoNoEscape)
+}
+
+//go:noinline
+func withNoEscape() {
+ var str string
+ C.runCWithNoEscape(unsafe.Pointer(&str))
+}
+
+//go:noinline
+func withoutNoEscape() {
+ var str string
+ C.runCWithoutNoEscape(unsafe.Pointer(&str))
+}
+
+func CgoNoEscape() {
+ // make GC stop to see the heap objects allocated
+ debug.SetGCPercent(-1)
+
+ var stats runtime.MemStats
+ runtime.ReadMemStats(&stats)
+ preHeapObjects := stats.HeapObjects
+
+ for i := 0; i < num; i++ {
+ withNoEscape()
+ }
+
+ runtime.ReadMemStats(&stats)
+ nowHeapObjects := stats.HeapObjects
+
+ if nowHeapObjects-preHeapObjects >= num {
+ fmt.Printf("too many heap objects allocated, pre: %v, now: %v\n", preHeapObjects, nowHeapObjects)
+ }
+
+ runtime.ReadMemStats(&stats)
+ preHeapObjects = stats.HeapObjects
+
+ for i := 0; i < num; i++ {
+ withoutNoEscape()
+ }
+
+ runtime.ReadMemStats(&stats)
+ nowHeapObjects = stats.HeapObjects
+
+ if nowHeapObjects-preHeapObjects < num {
+ fmt.Printf("too few heap objects allocated, pre: %v, now: %v\n", preHeapObjects, nowHeapObjects)
+ }
+
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/crash.go b/src/runtime/testdata/testprogcgo/crash.go
new file mode 100644
index 0000000..4d83132
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/crash.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 (
+ "fmt"
+ "runtime"
+)
+
+func init() {
+ register("Crash", Crash)
+}
+
+func test(name string) {
+ defer func() {
+ if x := recover(); x != nil {
+ fmt.Printf(" recovered")
+ }
+ fmt.Printf(" done\n")
+ }()
+ fmt.Printf("%s:", name)
+ var s *string
+ _ = *s
+ fmt.Print("SHOULD NOT BE HERE")
+}
+
+func testInNewThread(name string) {
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread()
+ test(name)
+ c <- true
+ }()
+ <-c
+}
+
+func Crash() {
+ runtime.LockOSThread()
+ test("main")
+ testInNewThread("new-thread")
+ testInNewThread("second-new-thread")
+ test("main-again")
+}
diff --git a/src/runtime/testdata/testprogcgo/deadlock.go b/src/runtime/testdata/testprogcgo/deadlock.go
new file mode 100644
index 0000000..2cc68a8
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/deadlock.go
@@ -0,0 +1,30 @@
+// 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
+
+/*
+char *geterror() {
+ return "cgo error";
+}
+*/
+import "C"
+import (
+ "fmt"
+)
+
+func init() {
+ register("CgoPanicDeadlock", CgoPanicDeadlock)
+}
+
+type cgoError struct{}
+
+func (cgoError) Error() string {
+ fmt.Print("") // necessary to trigger the deadlock
+ return C.GoString(C.geterror())
+}
+
+func CgoPanicDeadlock() {
+ panic(cgoError{})
+}
diff --git a/src/runtime/testdata/testprogcgo/destructor.c b/src/runtime/testdata/testprogcgo/destructor.c
new file mode 100644
index 0000000..8604d81
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/destructor.c
@@ -0,0 +1,22 @@
+// Copyright 2023 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 "_cgo_export.h"
+
+static void callDestructorCallback() {
+ GoDestructorCallback();
+}
+
+static void (*destructorFn)(void);
+
+void registerDestructor() {
+ destructorFn = callDestructorCallback;
+}
+
+__attribute__((destructor))
+static void destructor() {
+ if (destructorFn) {
+ destructorFn();
+ }
+}
diff --git a/src/runtime/testdata/testprogcgo/destructor.go b/src/runtime/testdata/testprogcgo/destructor.go
new file mode 100644
index 0000000..49529f0
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/destructor.go
@@ -0,0 +1,23 @@
+// Copyright 2023 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 void registerDestructor();
+import "C"
+
+import "fmt"
+
+func init() {
+ register("DestructorCallback", DestructorCallback)
+}
+
+//export GoDestructorCallback
+func GoDestructorCallback() {
+}
+
+func DestructorCallback() {
+ C.registerDestructor()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/dll_windows.go b/src/runtime/testdata/testprogcgo/dll_windows.go
new file mode 100644
index 0000000..25380fb
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/dll_windows.go
@@ -0,0 +1,25 @@
+// 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 <windows.h>
+
+DWORD getthread() {
+ return GetCurrentThreadId();
+}
+*/
+import "C"
+import "runtime/testdata/testprogcgo/windows"
+
+func init() {
+ register("CgoDLLImportsMain", CgoDLLImportsMain)
+}
+
+func CgoDLLImportsMain() {
+ C.getthread()
+ windows.GetThread()
+ println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/dropm.go b/src/runtime/testdata/testprogcgo/dropm.go
new file mode 100644
index 0000000..700b7fa
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/dropm.go
@@ -0,0 +1,60 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+// Test that a sequence of callbacks from C to Go get the same m.
+// This failed to be true on arm and arm64, which was the root cause
+// of issue 13881.
+
+package main
+
+/*
+#include <stddef.h>
+#include <pthread.h>
+
+extern void GoCheckM();
+
+static void* thread(void* arg __attribute__ ((unused))) {
+ GoCheckM();
+ return NULL;
+}
+
+static void CheckM() {
+ pthread_t tid;
+ pthread_create(&tid, NULL, thread, NULL);
+ pthread_join(tid, NULL);
+ pthread_create(&tid, NULL, thread, NULL);
+ pthread_join(tid, NULL);
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+)
+
+func init() {
+ register("EnsureDropM", EnsureDropM)
+}
+
+var savedM uintptr
+
+//export GoCheckM
+func GoCheckM() {
+ m := runtime_getm_for_test()
+ if savedM == 0 {
+ savedM = m
+ } else if savedM != m {
+ fmt.Printf("m == %x want %x\n", m, savedM)
+ os.Exit(1)
+ }
+}
+
+func EnsureDropM() {
+ C.CheckM()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/dropm_stub.go b/src/runtime/testdata/testprogcgo/dropm_stub.go
new file mode 100644
index 0000000..6997cfd
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/dropm_stub.go
@@ -0,0 +1,12 @@
+// 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 _ "unsafe" // for go:linkname
+
+// Defined in the runtime package.
+//
+//go:linkname runtime_getm_for_test runtime.getm
+func runtime_getm_for_test() uintptr
diff --git a/src/runtime/testdata/testprogcgo/eintr.go b/src/runtime/testdata/testprogcgo/eintr.go
new file mode 100644
index 0000000..6e9677f
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/eintr.go
@@ -0,0 +1,247 @@
+// Copyright 2020 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+/*
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+
+static int clearRestart(int sig) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof sa);
+ if (sigaction(sig, NULL, &sa) < 0) {
+ return errno;
+ }
+ sa.sa_flags &=~ SA_RESTART;
+ if (sigaction(sig, &sa, NULL) < 0) {
+ return errno;
+ }
+ return 0;
+}
+*/
+import "C"
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "os"
+ "os/exec"
+ "sync"
+ "syscall"
+ "time"
+)
+
+func init() {
+ register("EINTR", EINTR)
+ register("Block", Block)
+}
+
+// Test various operations when a signal handler is installed without
+// the SA_RESTART flag. This tests that the os and net APIs handle EINTR.
+func EINTR() {
+ if errno := C.clearRestart(C.int(syscall.SIGURG)); errno != 0 {
+ log.Fatal(syscall.Errno(errno))
+ }
+ if errno := C.clearRestart(C.int(syscall.SIGWINCH)); errno != 0 {
+ log.Fatal(syscall.Errno(errno))
+ }
+ if errno := C.clearRestart(C.int(syscall.SIGCHLD)); errno != 0 {
+ log.Fatal(syscall.Errno(errno))
+ }
+
+ var wg sync.WaitGroup
+ testPipe(&wg)
+ testNet(&wg)
+ testExec(&wg)
+ wg.Wait()
+ fmt.Println("OK")
+}
+
+// spin does CPU bound spinning and allocating for a millisecond,
+// to get a SIGURG.
+//
+//go:noinline
+func spin() (float64, []byte) {
+ stop := time.Now().Add(time.Millisecond)
+ r1 := 0.0
+ r2 := make([]byte, 200)
+ for time.Now().Before(stop) {
+ for i := 1; i < 1e6; i++ {
+ r1 += r1 / float64(i)
+ r2 = append(r2, bytes.Repeat([]byte{byte(i)}, 100)...)
+ r2 = r2[100:]
+ }
+ }
+ return r1, r2
+}
+
+// winch sends a few SIGWINCH signals to the process.
+func winch() {
+ ticker := time.NewTicker(100 * time.Microsecond)
+ defer ticker.Stop()
+ pid := syscall.Getpid()
+ for n := 10; n > 0; n-- {
+ syscall.Kill(pid, syscall.SIGWINCH)
+ <-ticker.C
+ }
+}
+
+// sendSomeSignals triggers a few SIGURG and SIGWINCH signals.
+func sendSomeSignals() {
+ done := make(chan struct{})
+ go func() {
+ spin()
+ close(done)
+ }()
+ winch()
+ <-done
+}
+
+// testPipe tests pipe operations.
+func testPipe(wg *sync.WaitGroup) {
+ r, w, err := os.Pipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := syscall.SetNonblock(int(r.Fd()), false); err != nil {
+ log.Fatal(err)
+ }
+ if err := syscall.SetNonblock(int(w.Fd()), false); err != nil {
+ log.Fatal(err)
+ }
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ defer w.Close()
+ // Spin before calling Write so that the first ReadFull
+ // in the other goroutine will likely be interrupted
+ // by a signal.
+ sendSomeSignals()
+ // This Write will likely be interrupted by a signal
+ // as the other goroutine spins in the middle of reading.
+ // We write enough data that we should always fill the
+ // pipe buffer and need multiple write system calls.
+ if _, err := w.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
+ log.Fatal(err)
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ defer r.Close()
+ b := make([]byte, 1<<20)
+ // This ReadFull will likely be interrupted by a signal,
+ // as the other goroutine spins before writing anything.
+ if _, err := io.ReadFull(r, b); err != nil {
+ log.Fatal(err)
+ }
+ // Spin after reading half the data so that the Write
+ // in the other goroutine will likely be interrupted
+ // before it completes.
+ sendSomeSignals()
+ if _, err := io.ReadFull(r, b); err != nil {
+ log.Fatal(err)
+ }
+ }()
+}
+
+// testNet tests network operations.
+func testNet(wg *sync.WaitGroup) {
+ ln, err := net.Listen("tcp4", "127.0.0.1:0")
+ if err != nil {
+ if errors.Is(err, syscall.EAFNOSUPPORT) || errors.Is(err, syscall.EPROTONOSUPPORT) {
+ return
+ }
+ log.Fatal(err)
+ }
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ defer ln.Close()
+ c, err := ln.Accept()
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer c.Close()
+ cf, err := c.(*net.TCPConn).File()
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer cf.Close()
+ if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil {
+ log.Fatal(err)
+ }
+ // See comments in testPipe.
+ sendSomeSignals()
+ if _, err := cf.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil {
+ log.Fatal(err)
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ sendSomeSignals()
+ c, err := net.Dial("tcp", ln.Addr().String())
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer c.Close()
+ cf, err := c.(*net.TCPConn).File()
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer cf.Close()
+ if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil {
+ log.Fatal(err)
+ }
+ // See comments in testPipe.
+ b := make([]byte, 1<<20)
+ if _, err := io.ReadFull(cf, b); err != nil {
+ log.Fatal(err)
+ }
+ sendSomeSignals()
+ if _, err := io.ReadFull(cf, b); err != nil {
+ log.Fatal(err)
+ }
+ }()
+}
+
+func testExec(wg *sync.WaitGroup) {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ cmd := exec.Command(os.Args[0], "Block")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ cmd.Stderr = new(bytes.Buffer)
+ cmd.Stdout = cmd.Stderr
+ if err := cmd.Start(); err != nil {
+ log.Fatal(err)
+ }
+
+ go func() {
+ sendSomeSignals()
+ stdin.Close()
+ }()
+
+ if err := cmd.Wait(); err != nil {
+ log.Fatalf("%v:\n%s", err, cmd.Stdout)
+ }
+ }()
+}
+
+// Block blocks until stdin is closed.
+func Block() {
+ io.Copy(io.Discard, os.Stdin)
+}
diff --git a/src/runtime/testdata/testprogcgo/exec.go b/src/runtime/testdata/testprogcgo/exec.go
new file mode 100644
index 0000000..c268bcd
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/exec.go
@@ -0,0 +1,107 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+/*
+#include <stddef.h>
+#include <signal.h>
+#include <pthread.h>
+
+// Save the signal mask at startup so that we see what it is before
+// the Go runtime starts setting up signals.
+
+static sigset_t mask;
+
+static void init(void) __attribute__ ((constructor));
+
+static void init() {
+ sigemptyset(&mask);
+ pthread_sigmask(SIG_SETMASK, NULL, &mask);
+}
+
+int SIGINTBlocked() {
+ return sigismember(&mask, SIGINT);
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "os/exec"
+ "os/signal"
+ "sync"
+ "syscall"
+)
+
+func init() {
+ register("CgoExecSignalMask", CgoExecSignalMask)
+}
+
+func CgoExecSignalMask() {
+ if len(os.Args) > 2 && os.Args[2] == "testsigint" {
+ if C.SIGINTBlocked() != 0 {
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGTERM)
+ go func() {
+ for range c {
+ }
+ }()
+
+ const goCount = 10
+ const execCount = 10
+ var wg sync.WaitGroup
+ wg.Add(goCount*execCount + goCount)
+ for i := 0; i < goCount; i++ {
+ go func() {
+ defer wg.Done()
+ for j := 0; j < execCount; j++ {
+ c2 := make(chan os.Signal, 1)
+ signal.Notify(c2, syscall.SIGUSR1)
+ syscall.Kill(os.Getpid(), syscall.SIGTERM)
+ go func(j int) {
+ defer wg.Done()
+ cmd := exec.Command(os.Args[0], "CgoExecSignalMask", "testsigint")
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ // An overloaded system
+ // may fail with EAGAIN.
+ // This doesn't tell us
+ // anything useful; ignore it.
+ // Issue #27731.
+ if isEAGAIN(err) {
+ return
+ }
+ fmt.Printf("iteration %d: %v\n", j, err)
+ os.Exit(1)
+ }
+ }(j)
+ signal.Stop(c2)
+ }
+ }()
+ }
+ wg.Wait()
+
+ fmt.Println("OK")
+}
+
+// isEAGAIN reports whether err is an EAGAIN error from a process execution.
+func isEAGAIN(err error) bool {
+ if p, ok := err.(*fs.PathError); ok {
+ err = p.Err
+ }
+ return err == syscall.EAGAIN
+}
diff --git a/src/runtime/testdata/testprogcgo/gprof.go b/src/runtime/testdata/testprogcgo/gprof.go
new file mode 100644
index 0000000..d453b4d
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/gprof.go
@@ -0,0 +1,46 @@
+// Copyright 2021 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
+
+// Test taking a goroutine profile with C traceback.
+
+/*
+// Defined in gprof_c.c.
+void CallGoSleep(void);
+void gprofCgoTraceback(void* parg);
+void gprofCgoContext(void* parg);
+*/
+import "C"
+
+import (
+ "fmt"
+ "io"
+ "runtime"
+ "runtime/pprof"
+ "time"
+ "unsafe"
+)
+
+func init() {
+ register("GoroutineProfile", GoroutineProfile)
+}
+
+func GoroutineProfile() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.gprofCgoTraceback), unsafe.Pointer(C.gprofCgoContext), nil)
+
+ go C.CallGoSleep()
+ go C.CallGoSleep()
+ go C.CallGoSleep()
+ time.Sleep(1 * time.Second)
+
+ prof := pprof.Lookup("goroutine")
+ prof.WriteTo(io.Discard, 1)
+ fmt.Println("OK")
+}
+
+//export GoSleep
+func GoSleep() {
+ time.Sleep(time.Hour)
+}
diff --git a/src/runtime/testdata/testprogcgo/gprof_c.c b/src/runtime/testdata/testprogcgo/gprof_c.c
new file mode 100644
index 0000000..5c7cd77
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/gprof_c.c
@@ -0,0 +1,30 @@
+// Copyright 2021 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.
+
+// The C definitions for gprof.go. That file uses //export so
+// it can't put function definitions in the "C" import comment.
+
+#include <stdint.h>
+#include <stdlib.h>
+
+// Functions exported from Go.
+extern void GoSleep();
+
+struct cgoContextArg {
+ uintptr_t context;
+};
+
+void gprofCgoContext(void *arg) {
+ ((struct cgoContextArg*)arg)->context = 1;
+}
+
+void gprofCgoTraceback(void *arg) {
+ // spend some time here so the P is more likely to be retaken.
+ volatile int i;
+ for (i = 0; i < 123456789; i++);
+}
+
+void CallGoSleep() {
+ GoSleep();
+}
diff --git a/src/runtime/testdata/testprogcgo/issue29707.go b/src/runtime/testdata/testprogcgo/issue29707.go
new file mode 100644
index 0000000..7d9299f
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/issue29707.go
@@ -0,0 +1,60 @@
+// Copyright 2011 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+// This is for issue #29707
+
+package main
+
+/*
+#include <pthread.h>
+
+extern void* callbackTraceParser(void*);
+typedef void* (*cbTraceParser)(void*);
+
+static void testCallbackTraceParser(cbTraceParser cb) {
+ pthread_t thread_id;
+ pthread_create(&thread_id, NULL, cb, NULL);
+ pthread_join(thread_id, NULL);
+}
+*/
+import "C"
+
+import (
+ "bytes"
+ "fmt"
+ traceparser "internal/trace"
+ "runtime/trace"
+ "time"
+ "unsafe"
+)
+
+func init() {
+ register("CgoTraceParser", CgoTraceParser)
+}
+
+//export callbackTraceParser
+func callbackTraceParser(unsafe.Pointer) unsafe.Pointer {
+ time.Sleep(time.Millisecond)
+ return nil
+}
+
+func CgoTraceParser() {
+ buf := new(bytes.Buffer)
+
+ trace.Start(buf)
+ C.testCallbackTraceParser(C.cbTraceParser(C.callbackTraceParser))
+ trace.Stop()
+
+ _, err := traceparser.Parse(buf, "")
+ if err == traceparser.ErrTimeOrder {
+ fmt.Println("ErrTimeOrder")
+ } else if err != nil {
+ fmt.Println("Parse error: ", err)
+ } else {
+ fmt.Println("OK")
+ }
+}
diff --git a/src/runtime/testdata/testprogcgo/lockosthread.c b/src/runtime/testdata/testprogcgo/lockosthread.c
new file mode 100644
index 0000000..b10cc4f
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/lockosthread.c
@@ -0,0 +1,13 @@
+// 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.
+
+// +build !plan9,!windows
+
+#include <stdint.h>
+
+uint32_t threadExited;
+
+void setExited(void *x) {
+ __sync_fetch_and_add(&threadExited, 1);
+}
diff --git a/src/runtime/testdata/testprogcgo/lockosthread.go b/src/runtime/testdata/testprogcgo/lockosthread.go
new file mode 100644
index 0000000..e6dce36
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/lockosthread.go
@@ -0,0 +1,110 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+import (
+ "os"
+ "runtime"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+/*
+#include <pthread.h>
+#include <stdint.h>
+
+extern uint32_t threadExited;
+
+void setExited(void *x);
+*/
+import "C"
+
+var mainThread C.pthread_t
+
+func init() {
+ registerInit("LockOSThreadMain", func() {
+ // init is guaranteed to run on the main thread.
+ mainThread = C.pthread_self()
+ })
+ register("LockOSThreadMain", LockOSThreadMain)
+
+ registerInit("LockOSThreadAlt", func() {
+ // Lock the OS thread now so main runs on the main thread.
+ runtime.LockOSThread()
+ })
+ register("LockOSThreadAlt", LockOSThreadAlt)
+}
+
+func LockOSThreadMain() {
+ // This requires GOMAXPROCS=1 from the beginning to reliably
+ // start a goroutine on the main thread.
+ if runtime.GOMAXPROCS(-1) != 1 {
+ println("requires GOMAXPROCS=1")
+ os.Exit(1)
+ }
+
+ ready := make(chan bool, 1)
+ go func() {
+ // Because GOMAXPROCS=1, this *should* be on the main
+ // thread. Stay there.
+ runtime.LockOSThread()
+ self := C.pthread_self()
+ if C.pthread_equal(mainThread, self) == 0 {
+ println("failed to start goroutine on main thread")
+ os.Exit(1)
+ }
+ // Exit with the thread locked, which should exit the
+ // main thread.
+ ready <- true
+ }()
+ <-ready
+ time.Sleep(1 * time.Millisecond)
+ // Check that this goroutine is still running on a different
+ // thread.
+ self := C.pthread_self()
+ if C.pthread_equal(mainThread, self) != 0 {
+ println("goroutine migrated to locked thread")
+ os.Exit(1)
+ }
+ println("OK")
+}
+
+func LockOSThreadAlt() {
+ // This is running locked to the main OS thread.
+
+ var subThread C.pthread_t
+ ready := make(chan bool, 1)
+ C.threadExited = 0
+ go func() {
+ // This goroutine must be running on a new thread.
+ runtime.LockOSThread()
+ subThread = C.pthread_self()
+ // Register a pthread destructor so we can tell this
+ // thread has exited.
+ var key C.pthread_key_t
+ C.pthread_key_create(&key, (*[0]byte)(unsafe.Pointer(C.setExited)))
+ C.pthread_setspecific(key, unsafe.Pointer(new(int)))
+ ready <- true
+ // Exit with the thread locked.
+ }()
+ <-ready
+ for {
+ time.Sleep(1 * time.Millisecond)
+ // Check that this goroutine is running on a different thread.
+ self := C.pthread_self()
+ if C.pthread_equal(subThread, self) != 0 {
+ println("locked thread reused")
+ os.Exit(1)
+ }
+ if atomic.LoadUint32((*uint32)(&C.threadExited)) != 0 {
+ println("OK")
+ return
+ }
+ }
+}
diff --git a/src/runtime/testdata/testprogcgo/main.go b/src/runtime/testdata/testprogcgo/main.go
new file mode 100644
index 0000000..ae491a2
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/main.go
@@ -0,0 +1,35 @@
+// 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 "os"
+
+var cmds = map[string]func(){}
+
+func register(name string, f func()) {
+ if cmds[name] != nil {
+ panic("duplicate registration: " + name)
+ }
+ cmds[name] = f
+}
+
+func registerInit(name string, f func()) {
+ if len(os.Args) >= 2 && os.Args[1] == name {
+ f()
+ }
+}
+
+func main() {
+ if len(os.Args) < 2 {
+ println("usage: " + os.Args[0] + " name-of-test")
+ return
+ }
+ f := cmds[os.Args[1]]
+ if f == nil {
+ println("unknown function: " + os.Args[1])
+ return
+ }
+ f()
+}
diff --git a/src/runtime/testdata/testprogcgo/needmdeadlock.go b/src/runtime/testdata/testprogcgo/needmdeadlock.go
new file mode 100644
index 0000000..b95ec77
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/needmdeadlock.go
@@ -0,0 +1,96 @@
+// Copyright 2020 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+// This is for issue #42207.
+// During a call to needm we could get a SIGCHLD signal
+// which would itself call needm, causing a deadlock.
+
+/*
+#include <signal.h>
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
+
+extern void GoNeedM();
+
+#define SIGNALERS 10
+
+static void* needmSignalThread(void* p) {
+ pthread_t* pt = (pthread_t*)(p);
+ int i;
+
+ for (i = 0; i < 100; i++) {
+ if (pthread_kill(*pt, SIGCHLD) < 0) {
+ return NULL;
+ }
+ usleep(1);
+ }
+ return NULL;
+}
+
+// We don't need many calls, as the deadlock is only likely
+// to occur the first couple of times that needm is called.
+// After that there will likely be an extra M available.
+#define CALLS 10
+
+static void* needmCallbackThread(void* p) {
+ int i;
+
+ for (i = 0; i < SIGNALERS; i++) {
+ sched_yield(); // Help the signal threads get started.
+ }
+ for (i = 0; i < CALLS; i++) {
+ GoNeedM();
+ }
+ return NULL;
+}
+
+static void runNeedmSignalThread() {
+ int i;
+ pthread_t caller;
+ pthread_t s[SIGNALERS];
+
+ pthread_create(&caller, NULL, needmCallbackThread, NULL);
+ for (i = 0; i < SIGNALERS; i++) {
+ pthread_create(&s[i], NULL, needmSignalThread, &caller);
+ }
+ for (i = 0; i < SIGNALERS; i++) {
+ pthread_join(s[i], NULL);
+ }
+ pthread_join(caller, NULL);
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+func init() {
+ register("NeedmDeadlock", NeedmDeadlock)
+}
+
+//export GoNeedM
+func GoNeedM() {
+}
+
+func NeedmDeadlock() {
+ // The failure symptom is that the program hangs because of a
+ // deadlock in needm, so set an alarm.
+ go func() {
+ time.Sleep(5 * time.Second)
+ fmt.Println("Hung for 5 seconds")
+ os.Exit(1)
+ }()
+
+ C.runNeedmSignalThread()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/numgoroutine.go b/src/runtime/testdata/testprogcgo/numgoroutine.go
new file mode 100644
index 0000000..9cbb4e4
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/numgoroutine.go
@@ -0,0 +1,93 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+/*
+#include <stddef.h>
+#include <pthread.h>
+
+extern void CallbackNumGoroutine();
+
+static void* thread2(void* arg __attribute__ ((unused))) {
+ CallbackNumGoroutine();
+ return NULL;
+}
+
+static void CheckNumGoroutine() {
+ pthread_t tid;
+ pthread_create(&tid, NULL, thread2, NULL);
+ pthread_join(tid, NULL);
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+)
+
+var baseGoroutines int
+
+func init() {
+ register("NumGoroutine", NumGoroutine)
+}
+
+func NumGoroutine() {
+ // Test that there are just the expected number of goroutines
+ // running. Specifically, test that the spare M's goroutine
+ // doesn't show up.
+ if _, ok := checkNumGoroutine("first", 1+baseGoroutines); !ok {
+ return
+ }
+
+ // Test that the goroutine for a callback from C appears.
+ if C.CheckNumGoroutine(); !callbackok {
+ return
+ }
+
+ // Make sure we're back to the initial goroutines.
+ if _, ok := checkNumGoroutine("third", 1+baseGoroutines); !ok {
+ return
+ }
+
+ fmt.Println("OK")
+}
+
+func checkNumGoroutine(label string, want int) (string, bool) {
+ n := runtime.NumGoroutine()
+ if n != want {
+ fmt.Printf("%s NumGoroutine: want %d; got %d\n", label, want, n)
+ return "", false
+ }
+
+ sbuf := make([]byte, 32<<10)
+ sbuf = sbuf[:runtime.Stack(sbuf, true)]
+ n = strings.Count(string(sbuf), "goroutine ")
+ if n != want {
+ fmt.Printf("%s Stack: want %d; got %d:\n%s\n", label, want, n, sbuf)
+ return "", false
+ }
+ return string(sbuf), true
+}
+
+var callbackok bool
+
+//export CallbackNumGoroutine
+func CallbackNumGoroutine() {
+ stk, ok := checkNumGoroutine("second", 2+baseGoroutines)
+ if !ok {
+ return
+ }
+ if !strings.Contains(stk, "CallbackNumGoroutine") {
+ fmt.Printf("missing CallbackNumGoroutine from stack:\n%s\n", stk)
+ return
+ }
+
+ callbackok = true
+}
diff --git a/src/runtime/testdata/testprogcgo/panic.c b/src/runtime/testdata/testprogcgo/panic.c
new file mode 100644
index 0000000..deb5ed5
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/panic.c
@@ -0,0 +1,9 @@
+// Copyright 2021 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.
+
+extern void panic_callback();
+
+void call_callback(void) {
+ panic_callback();
+}
diff --git a/src/runtime/testdata/testprogcgo/panic.go b/src/runtime/testdata/testprogcgo/panic.go
new file mode 100644
index 0000000..57ac895
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/panic.go
@@ -0,0 +1,23 @@
+package main
+
+// This program will crash.
+// We want to test unwinding from a cgo callback.
+
+/*
+void call_callback(void);
+*/
+import "C"
+
+func init() {
+ register("PanicCallback", PanicCallback)
+}
+
+//export panic_callback
+func panic_callback() {
+ var i *int
+ *i = 42
+}
+
+func PanicCallback() {
+ C.call_callback()
+}
diff --git a/src/runtime/testdata/testprogcgo/pprof.go b/src/runtime/testdata/testprogcgo/pprof.go
new file mode 100644
index 0000000..8870d0c
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/pprof.go
@@ -0,0 +1,93 @@
+// 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
+
+// Run a slow C function saving a CPU profile.
+
+/*
+#include <stdint.h>
+
+int salt1;
+int salt2;
+
+void cpuHog() {
+ int foo = salt1;
+ int i;
+
+ for (i = 0; i < 100000; i++) {
+ if (foo > 0) {
+ foo *= foo;
+ } else {
+ foo *= foo + 1;
+ }
+ }
+ salt2 = foo;
+}
+
+void cpuHog2() {
+}
+
+struct cgoTracebackArg {
+ uintptr_t context;
+ uintptr_t sigContext;
+ uintptr_t* buf;
+ uintptr_t max;
+};
+
+// pprofCgoTraceback is passed to runtime.SetCgoTraceback.
+// For testing purposes it pretends that all CPU hits in C code are in cpuHog.
+// Issue #29034: At least 2 frames are required to verify all frames are captured
+// since runtime/pprof ignores the runtime.goexit base frame if it exists.
+void pprofCgoTraceback(void* parg) {
+ struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+ arg->buf[0] = (uintptr_t)(cpuHog) + 0x10;
+ arg->buf[1] = (uintptr_t)(cpuHog2) + 0x4;
+ arg->buf[2] = 0;
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "time"
+ "unsafe"
+)
+
+func init() {
+ register("CgoPprof", CgoPprof)
+}
+
+func CgoPprof() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.pprofCgoTraceback), nil, nil)
+
+ f, err := os.CreateTemp("", "prof")
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ if err := pprof.StartCPUProfile(f); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ t0 := time.Now()
+ for time.Since(t0) < time.Second {
+ C.cpuHog()
+ }
+
+ pprof.StopCPUProfile()
+
+ name := f.Name()
+ if err := f.Close(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ fmt.Println(name)
+}
diff --git a/src/runtime/testdata/testprogcgo/pprof_callback.go b/src/runtime/testdata/testprogcgo/pprof_callback.go
new file mode 100644
index 0000000..fd87eb8
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/pprof_callback.go
@@ -0,0 +1,89 @@
+// 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.
+
+//go:build !plan9 && !windows
+
+package main
+
+// Make many C-to-Go callback while collecting a CPU profile.
+//
+// This is a regression test for issue 50936.
+
+/*
+#include <unistd.h>
+
+void goCallbackPprof();
+
+static void callGo() {
+ // Spent >20us in C so this thread is eligible for sysmon to retake its
+ // P.
+ usleep(50);
+ goCallbackPprof();
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "time"
+)
+
+func init() {
+ register("CgoPprofCallback", CgoPprofCallback)
+}
+
+//export goCallbackPprof
+func goCallbackPprof() {
+ // No-op. We want to stress the cgocall and cgocallback internals,
+ // landing as many pprof signals there as possible.
+}
+
+func CgoPprofCallback() {
+ // Issue 50936 was a crash in the SIGPROF handler when the signal
+ // arrived during the exitsyscall following a cgocall(back) in dropg or
+ // execute, when updating mp.curg.
+ //
+ // These are reachable only when exitsyscall finds no P available. Thus
+ // we make C calls from significantly more Gs than there are available
+ // Ps. Lots of runnable work combined with >20us spent in callGo makes
+ // it possible for sysmon to retake Ps, forcing C calls to go down the
+ // desired exitsyscall path.
+ //
+ // High GOMAXPROCS is used to increase opportunities for failure on
+ // high CPU machines.
+ const (
+ P = 16
+ G = 64
+ )
+ runtime.GOMAXPROCS(P)
+
+ f, err := os.CreateTemp("", "prof")
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+ defer f.Close()
+
+ if err := pprof.StartCPUProfile(f); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ for i := 0; i < G; i++ {
+ go func() {
+ for {
+ C.callGo()
+ }
+ }()
+ }
+
+ time.Sleep(time.Second)
+
+ pprof.StopCPUProfile()
+
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/raceprof.go b/src/runtime/testdata/testprogcgo/raceprof.go
new file mode 100644
index 0000000..68cabd4
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/raceprof.go
@@ -0,0 +1,79 @@
+// 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.
+
+//go:build unix
+// +build unix
+
+package main
+
+// Test that we can collect a lot of colliding profiling signals from
+// an external C thread. This used to fail when built with the race
+// detector, because a call of the predeclared function copy was
+// turned into a call to runtime.slicecopy, which is not marked nosplit.
+
+/*
+#include <signal.h>
+#include <stdint.h>
+#include <pthread.h>
+#include <sched.h>
+
+struct cgoTracebackArg {
+ uintptr_t context;
+ uintptr_t sigContext;
+ uintptr_t* buf;
+ uintptr_t max;
+};
+
+static int raceprofCount;
+
+// We want a bunch of different profile stacks that collide in the
+// hash table maintained in runtime/cpuprof.go. This code knows the
+// size of the hash table (1 << 10) and knows that the hash function
+// is simply multiplicative.
+void raceprofTraceback(void* parg) {
+ struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+ raceprofCount++;
+ arg->buf[0] = raceprofCount * (1 << 10);
+ arg->buf[1] = 0;
+}
+
+static void* raceprofThread(void* p) {
+ int i;
+
+ for (i = 0; i < 100; i++) {
+ pthread_kill(pthread_self(), SIGPROF);
+ sched_yield();
+ }
+ return 0;
+}
+
+void runRaceprofThread() {
+ pthread_t tid;
+ pthread_create(&tid, 0, raceprofThread, 0);
+ pthread_join(tid, 0);
+}
+*/
+import "C"
+
+import (
+ "bytes"
+ "fmt"
+ "runtime"
+ "runtime/pprof"
+ "unsafe"
+)
+
+func init() {
+ register("CgoRaceprof", CgoRaceprof)
+}
+
+func CgoRaceprof() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.raceprofTraceback), nil, nil)
+
+ var buf bytes.Buffer
+ pprof.StartCPUProfile(&buf)
+
+ C.runRaceprofThread()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/racesig.go b/src/runtime/testdata/testprogcgo/racesig.go
new file mode 100644
index 0000000..0667020
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/racesig.go
@@ -0,0 +1,93 @@
+// 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.
+
+//go:build unix
+// +build unix
+
+package main
+
+// Test that an external C thread that is calling malloc can be hit
+// with SIGCHLD signals. This used to fail when built with the race
+// detector, because in that case the signal handler would indirectly
+// call the C malloc function.
+
+/*
+#include <errno.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
+
+#define ALLOCERS 100
+#define SIGNALERS 10
+
+static void* signalThread(void* p) {
+ pthread_t* pt = (pthread_t*)(p);
+ int i, j;
+
+ for (i = 0; i < 100; i++) {
+ for (j = 0; j < ALLOCERS; j++) {
+ if (pthread_kill(pt[j], SIGCHLD) < 0) {
+ return NULL;
+ }
+ }
+ usleep(1);
+ }
+ return NULL;
+}
+
+#define CALLS 100
+
+static void* mallocThread(void* p) {
+ int i;
+ void *a[CALLS];
+
+ for (i = 0; i < ALLOCERS; i++) {
+ sched_yield();
+ }
+ for (i = 0; i < CALLS; i++) {
+ a[i] = malloc(i);
+ }
+ for (i = 0; i < CALLS; i++) {
+ free(a[i]);
+ }
+ return NULL;
+}
+
+void runRaceSignalThread() {
+ int i;
+ pthread_t m[ALLOCERS];
+ pthread_t s[SIGNALERS];
+
+ for (i = 0; i < ALLOCERS; i++) {
+ pthread_create(&m[i], NULL, mallocThread, NULL);
+ }
+ for (i = 0; i < SIGNALERS; i++) {
+ pthread_create(&s[i], NULL, signalThread, &m[0]);
+ }
+ for (i = 0; i < SIGNALERS; i++) {
+ pthread_join(s[i], NULL);
+ }
+ for (i = 0; i < ALLOCERS; i++) {
+ pthread_join(m[i], NULL);
+ }
+}
+*/
+import "C"
+
+import (
+ "fmt"
+)
+
+func init() {
+ register("CgoRaceSignal", CgoRaceSignal)
+}
+
+func CgoRaceSignal() {
+ C.runRaceSignalThread()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/segv.go b/src/runtime/testdata/testprogcgo/segv.go
new file mode 100644
index 0000000..c776fe6
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/segv.go
@@ -0,0 +1,34 @@
+// Copyright 2020 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.
+
+//go:build unix
+
+package main
+
+// #include <unistd.h>
+// static void nop() {}
+import "C"
+
+import "syscall"
+
+func init() {
+ register("SegvInCgo", SegvInCgo)
+}
+
+func SegvInCgo() {
+ c := make(chan bool)
+ go func() {
+ close(c)
+ for {
+ C.nop()
+ }
+ }()
+
+ <-c
+
+ syscall.Kill(syscall.Getpid(), syscall.SIGSEGV)
+
+ // Wait for the OS to deliver the signal.
+ C.pause()
+}
diff --git a/src/runtime/testdata/testprogcgo/segv_linux.go b/src/runtime/testdata/testprogcgo/segv_linux.go
new file mode 100644
index 0000000..517ce72
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/segv_linux.go
@@ -0,0 +1,32 @@
+// 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 <unistd.h>
+// static void nop() {}
+import "C"
+
+import "syscall"
+
+func init() {
+ register("TgkillSegvInCgo", TgkillSegvInCgo)
+}
+
+func TgkillSegvInCgo() {
+ c := make(chan bool)
+ go func() {
+ close(c)
+ for {
+ C.nop()
+ }
+ }()
+
+ <-c
+
+ syscall.Tgkill(syscall.Getpid(), syscall.Gettid(), syscall.SIGSEGV)
+
+ // Wait for the OS to deliver the signal.
+ C.pause()
+}
diff --git a/src/runtime/testdata/testprogcgo/sigfwd.go b/src/runtime/testdata/testprogcgo/sigfwd.go
new file mode 100644
index 0000000..f6a0c03
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/sigfwd.go
@@ -0,0 +1,87 @@
+// 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.
+
+//go:build unix
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+/*
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+sig_atomic_t expectCSigsegv;
+int *sigfwdP;
+
+static void sigsegv() {
+ expectCSigsegv = 1;
+ *sigfwdP = 1;
+ fprintf(stderr, "ERROR: C SIGSEGV not thrown on caught?.\n");
+ exit(2);
+}
+
+static void segvhandler(int signum) {
+ if (signum == SIGSEGV) {
+ if (expectCSigsegv == 0) {
+ fprintf(stderr, "SIGSEGV caught in C unexpectedly\n");
+ exit(1);
+ }
+ fprintf(stdout, "OK\n");
+ exit(0); // success
+ }
+}
+
+static void __attribute__ ((constructor)) sigsetup(void) {
+ if (getenv("GO_TEST_CGOSIGFWD") == NULL) {
+ return;
+ }
+
+ struct sigaction act;
+
+ memset(&act, 0, sizeof act);
+ act.sa_handler = segvhandler;
+ sigaction(SIGSEGV, &act, NULL);
+}
+*/
+import "C"
+
+func init() {
+ register("CgoSigfwd", CgoSigfwd)
+}
+
+var nilPtr *byte
+
+func f() (ret bool) {
+ defer func() {
+ if recover() == nil {
+ fmt.Fprintf(os.Stderr, "ERROR: couldn't raise SIGSEGV in Go\n")
+ C.exit(2)
+ }
+ ret = true
+ }()
+ *nilPtr = 1
+ return false
+}
+
+func CgoSigfwd() {
+ if os.Getenv("GO_TEST_CGOSIGFWD") == "" {
+ fmt.Fprintf(os.Stderr, "test must be run with GO_TEST_CGOSIGFWD set\n")
+ os.Exit(1)
+ }
+
+ // Test that the signal originating in Go is handled (and recovered) by Go.
+ if !f() {
+ fmt.Fprintf(os.Stderr, "couldn't recover from SIGSEGV in Go.\n")
+ C.exit(2)
+ }
+
+ // Test that the signal originating in C is handled by C.
+ C.sigsegv()
+}
diff --git a/src/runtime/testdata/testprogcgo/sigpanic.go b/src/runtime/testdata/testprogcgo/sigpanic.go
new file mode 100644
index 0000000..cb46030
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/sigpanic.go
@@ -0,0 +1,28 @@
+// 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
+
+// This program will crash.
+// We want to test unwinding from sigpanic into C code (without a C symbolizer).
+
+/*
+#cgo CFLAGS: -O0
+
+char *pnil;
+
+static int f1(void) {
+ *pnil = 0;
+ return 0;
+}
+*/
+import "C"
+
+func init() {
+ register("TracebackSigpanic", TracebackSigpanic)
+}
+
+func TracebackSigpanic() {
+ C.f1()
+}
diff --git a/src/runtime/testdata/testprogcgo/sigstack.go b/src/runtime/testdata/testprogcgo/sigstack.go
new file mode 100644
index 0000000..12ca661
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/sigstack.go
@@ -0,0 +1,99 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+// Test handling of Go-allocated signal stacks when calling from
+// C-created threads with and without signal stacks. (See issue
+// #22930.)
+
+package main
+
+/*
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#ifdef _AIX
+// On AIX, SIGSTKSZ is too small to handle Go sighandler.
+#define CSIGSTKSZ 0x4000
+#else
+#define CSIGSTKSZ SIGSTKSZ
+#endif
+
+extern void SigStackCallback();
+
+static void* WithSigStack(void* arg __attribute__((unused))) {
+ // Set up an alternate system stack.
+ void* base = mmap(0, CSIGSTKSZ, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+ if (base == MAP_FAILED) {
+ perror("mmap failed");
+ abort();
+ }
+ stack_t st = {}, ost = {};
+ st.ss_sp = (char*)base;
+ st.ss_flags = 0;
+ st.ss_size = CSIGSTKSZ;
+ if (sigaltstack(&st, &ost) < 0) {
+ perror("sigaltstack failed");
+ abort();
+ }
+
+ // Call Go.
+ SigStackCallback();
+
+ // Disable signal stack and protect it so we can detect reuse.
+ if (ost.ss_flags & SS_DISABLE) {
+ // Darwin libsystem has a bug where it checks ss_size
+ // even if SS_DISABLE is set. (The kernel gets it right.)
+ ost.ss_size = CSIGSTKSZ;
+ }
+ if (sigaltstack(&ost, NULL) < 0) {
+ perror("sigaltstack restore failed");
+ abort();
+ }
+ mprotect(base, CSIGSTKSZ, PROT_NONE);
+ return NULL;
+}
+
+static void* WithoutSigStack(void* arg __attribute__((unused))) {
+ SigStackCallback();
+ return NULL;
+}
+
+static void DoThread(int sigstack) {
+ pthread_t tid;
+ if (sigstack) {
+ pthread_create(&tid, NULL, WithSigStack, NULL);
+ } else {
+ pthread_create(&tid, NULL, WithoutSigStack, NULL);
+ }
+ pthread_join(tid, NULL);
+}
+*/
+import "C"
+
+func init() {
+ register("SigStack", SigStack)
+}
+
+func SigStack() {
+ C.DoThread(0)
+ C.DoThread(1)
+ C.DoThread(0)
+ C.DoThread(1)
+ println("OK")
+}
+
+var BadPtr *int
+
+//export SigStackCallback
+func SigStackCallback() {
+ // Cause the Go signal handler to run.
+ defer func() { recover() }()
+ *BadPtr = 42
+}
diff --git a/src/runtime/testdata/testprogcgo/sigthrow.go b/src/runtime/testdata/testprogcgo/sigthrow.go
new file mode 100644
index 0000000..665e3b0
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/sigthrow.go
@@ -0,0 +1,20 @@
+// Copyright 2021 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
+
+// This program will abort.
+
+/*
+#include <stdlib.h>
+*/
+import "C"
+
+func init() {
+ register("Abort", Abort)
+}
+
+func Abort() {
+ C.abort()
+}
diff --git a/src/runtime/testdata/testprogcgo/stack_windows.go b/src/runtime/testdata/testprogcgo/stack_windows.go
new file mode 100644
index 0000000..d095093
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/stack_windows.go
@@ -0,0 +1,57 @@
+// 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 (
+ "internal/syscall/windows"
+ "runtime"
+ "sync"
+ "syscall"
+ "unsafe"
+)
+
+func init() {
+ register("StackMemory", StackMemory)
+}
+
+func getPagefileUsage() (uintptr, error) {
+ p, err := syscall.GetCurrentProcess()
+ if err != nil {
+ return 0, err
+ }
+ var m windows.PROCESS_MEMORY_COUNTERS
+ err = windows.GetProcessMemoryInfo(p, &m, uint32(unsafe.Sizeof(m)))
+ if err != nil {
+ return 0, err
+ }
+ return m.PagefileUsage, nil
+}
+
+func StackMemory() {
+ mem1, err := getPagefileUsage()
+ if err != nil {
+ panic(err)
+ }
+ const threadCount = 100
+ var wg sync.WaitGroup
+ for i := 0; i < threadCount; i++ {
+ wg.Add(1)
+ go func() {
+ runtime.LockOSThread()
+ wg.Done()
+ select {}
+ }()
+ }
+ wg.Wait()
+ mem2, err := getPagefileUsage()
+ if err != nil {
+ panic(err)
+ }
+ // assumes that this process creates 1 thread for each
+ // thread locked goroutine plus extra 10 threads
+ // like sysmon and others
+ print((mem2 - mem1) / (threadCount + 10))
+}
diff --git a/src/runtime/testdata/testprogcgo/stackswitch.c b/src/runtime/testdata/testprogcgo/stackswitch.c
new file mode 100644
index 0000000..3473d5b
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/stackswitch.c
@@ -0,0 +1,147 @@
+// Copyright 2023 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.
+
+//go:build unix && !android && !openbsd
+
+// Required for darwin ucontext.
+#define _XOPEN_SOURCE
+// Required for netbsd stack_t if _XOPEN_SOURCE is set.
+#define _XOPEN_SOURCE_EXTENDED 1
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+#include <assert.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ucontext.h>
+
+// musl libc does not provide getcontext, etc. Skip the test there.
+//
+// musl libc doesn't provide any direct detection mechanism. So assume any
+// non-glibc linux is using musl.
+//
+// Note that bionic does not provide getcontext either, but that is skipped via
+// the android build tag.
+#if defined(__linux__) && !defined(__GLIBC__)
+#define MUSL 1
+#endif
+#if defined(MUSL)
+void callStackSwitchCallbackFromThread(void) {
+ printf("SKIP\n");
+ exit(0);
+}
+#else
+
+// Use a stack size larger than the 32kb estimate in
+// runtime.callbackUpdateSystemStack. This ensures that a second stack
+// allocation won't accidentally count as in bounds of the first stack
+#define STACK_SIZE (64ull << 10)
+
+static ucontext_t uctx_save, uctx_switch;
+
+extern void stackSwitchCallback(void);
+
+char *stack2;
+
+static void *stackSwitchThread(void *arg) {
+ // Simple test: callback works from the normal system stack.
+ stackSwitchCallback();
+
+ // Next, verify that switching stacks doesn't break callbacks.
+
+ char *stack1 = malloc(STACK_SIZE);
+ if (stack1 == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+
+ // Allocate the second stack before freeing the first to ensure we don't get
+ // the same address from malloc.
+ //
+ // Will be freed in stackSwitchThread2.
+ stack2 = malloc(STACK_SIZE);
+ if (stack1 == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+
+ if (getcontext(&uctx_switch) == -1) {
+ perror("getcontext");
+ exit(1);
+ }
+ uctx_switch.uc_stack.ss_sp = stack1;
+ uctx_switch.uc_stack.ss_size = STACK_SIZE;
+ uctx_switch.uc_link = &uctx_save;
+ makecontext(&uctx_switch, stackSwitchCallback, 0);
+
+ if (swapcontext(&uctx_save, &uctx_switch) == -1) {
+ perror("swapcontext");
+ exit(1);
+ }
+
+ if (getcontext(&uctx_switch) == -1) {
+ perror("getcontext");
+ exit(1);
+ }
+ uctx_switch.uc_stack.ss_sp = stack2;
+ uctx_switch.uc_stack.ss_size = STACK_SIZE;
+ uctx_switch.uc_link = &uctx_save;
+ makecontext(&uctx_switch, stackSwitchCallback, 0);
+
+ if (swapcontext(&uctx_save, &uctx_switch) == -1) {
+ perror("swapcontext");
+ exit(1);
+ }
+
+ free(stack1);
+
+ return NULL;
+}
+
+static void *stackSwitchThread2(void *arg) {
+ // New thread. Use stack bounds that partially overlap the previous
+ // bounds. needm should refresh the stack bounds anyway since this is a
+ // new thread.
+
+ // N.B. since we used a custom stack with makecontext,
+ // callbackUpdateSystemStack had to guess the bounds. Its guess assumes
+ // a 32KiB stack.
+ char *prev_stack_lo = stack2 + STACK_SIZE - (32*1024);
+
+ // New SP is just barely in bounds, but if we don't update the bounds
+ // we'll almost certainly overflow. The SP that
+ // callbackUpdateSystemStack sees already has some data pushed, so it
+ // will be a bit below what we set here. Thus we include some slack.
+ char *new_stack_hi = prev_stack_lo + 128;
+
+ if (getcontext(&uctx_switch) == -1) {
+ perror("getcontext");
+ exit(1);
+ }
+ uctx_switch.uc_stack.ss_sp = new_stack_hi - (STACK_SIZE / 2);
+ uctx_switch.uc_stack.ss_size = STACK_SIZE / 2;
+ uctx_switch.uc_link = &uctx_save;
+ makecontext(&uctx_switch, stackSwitchCallback, 0);
+
+ if (swapcontext(&uctx_save, &uctx_switch) == -1) {
+ perror("swapcontext");
+ exit(1);
+ }
+
+ free(stack2);
+
+ return NULL;
+}
+
+void callStackSwitchCallbackFromThread(void) {
+ pthread_t thread;
+ assert(pthread_create(&thread, NULL, stackSwitchThread, NULL) == 0);
+ assert(pthread_join(thread, NULL) == 0);
+
+ assert(pthread_create(&thread, NULL, stackSwitchThread2, NULL) == 0);
+ assert(pthread_join(thread, NULL) == 0);
+}
+
+#endif
diff --git a/src/runtime/testdata/testprogcgo/stackswitch.go b/src/runtime/testdata/testprogcgo/stackswitch.go
new file mode 100644
index 0000000..70e630e
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/stackswitch.go
@@ -0,0 +1,42 @@
+// Copyright 2023 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.
+
+//go:build unix && !android && !openbsd
+
+package main
+
+/*
+void callStackSwitchCallbackFromThread(void);
+*/
+import "C"
+
+import (
+ "fmt"
+ "runtime/debug"
+)
+
+func init() {
+ register("StackSwitchCallback", StackSwitchCallback)
+}
+
+//export stackSwitchCallback
+func stackSwitchCallback() {
+ // We want to trigger a bounds check on the g0 stack. To do this, we
+ // need to call a splittable function through systemstack().
+ // SetGCPercent contains such a systemstack call.
+ gogc := debug.SetGCPercent(100)
+ debug.SetGCPercent(gogc)
+}
+
+// Regression test for https://go.dev/issue/62440. It should be possible for C
+// threads to call into Go from different stacks without crashing due to g0
+// stack bounds checks.
+//
+// N.B. This is only OK for threads created in C. Threads with Go frames up the
+// stack must not change the stack out from under us.
+func StackSwitchCallback() {
+ C.callStackSwitchCallbackFromThread()
+
+ fmt.Printf("OK\n")
+}
diff --git a/src/runtime/testdata/testprogcgo/threadpanic.go b/src/runtime/testdata/testprogcgo/threadpanic.go
new file mode 100644
index 0000000..2d24fe6
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/threadpanic.go
@@ -0,0 +1,25 @@
+// 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.
+
+//go:build !plan9
+// +build !plan9
+
+package main
+
+// void start(void);
+import "C"
+
+func init() {
+ register("CgoExternalThreadPanic", CgoExternalThreadPanic)
+}
+
+func CgoExternalThreadPanic() {
+ C.start()
+ select {}
+}
+
+//export gopanic
+func gopanic() {
+ panic("BOOM")
+}
diff --git a/src/runtime/testdata/testprogcgo/threadpanic_unix.c b/src/runtime/testdata/testprogcgo/threadpanic_unix.c
new file mode 100644
index 0000000..c426452
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/threadpanic_unix.c
@@ -0,0 +1,26 @@
+// 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 !plan9,!windows
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <pthread.h>
+
+void gopanic(void);
+
+static void*
+die(void* x)
+{
+ gopanic();
+ return 0;
+}
+
+void
+start(void)
+{
+ pthread_t t;
+ if(pthread_create(&t, 0, die, 0) != 0)
+ printf("pthread_create failed\n");
+}
diff --git a/src/runtime/testdata/testprogcgo/threadpanic_windows.c b/src/runtime/testdata/testprogcgo/threadpanic_windows.c
new file mode 100644
index 0000000..ba66d0f
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/threadpanic_windows.c
@@ -0,0 +1,23 @@
+// 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 <process.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+void gopanic(void);
+
+static unsigned int __attribute__((__stdcall__))
+die(void* x)
+{
+ gopanic();
+ return 0;
+}
+
+void
+start(void)
+{
+ if(_beginthreadex(0, 0, die, 0, 0, 0) != 0)
+ printf("_beginthreadex failed\n");
+}
diff --git a/src/runtime/testdata/testprogcgo/threadpprof.go b/src/runtime/testdata/testprogcgo/threadpprof.go
new file mode 100644
index 0000000..70717e0
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/threadpprof.go
@@ -0,0 +1,128 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+// Run a slow C function saving a CPU profile.
+
+/*
+#include <stdint.h>
+#include <time.h>
+#include <pthread.h>
+
+int threadSalt1;
+int threadSalt2;
+
+static pthread_t tid;
+
+void cpuHogThread() {
+ int foo = threadSalt1;
+ int i;
+
+ for (i = 0; i < 100000; i++) {
+ if (foo > 0) {
+ foo *= foo;
+ } else {
+ foo *= foo + 1;
+ }
+ }
+ threadSalt2 = foo;
+}
+
+void cpuHogThread2() {
+}
+
+struct cgoTracebackArg {
+ uintptr_t context;
+ uintptr_t sigContext;
+ uintptr_t* buf;
+ uintptr_t max;
+};
+
+// pprofCgoThreadTraceback is passed to runtime.SetCgoTraceback.
+// For testing purposes it pretends that all CPU hits on the cpuHog
+// C thread are in cpuHog.
+void pprofCgoThreadTraceback(void* parg) {
+ struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+ if (pthread_self() == tid) {
+ arg->buf[0] = (uintptr_t)(cpuHogThread) + 0x10;
+ arg->buf[1] = (uintptr_t)(cpuHogThread2) + 0x4;
+ arg->buf[2] = 0;
+ } else
+ arg->buf[0] = 0;
+}
+
+static void* cpuHogDriver(void* arg __attribute__ ((unused))) {
+ while (1) {
+ cpuHogThread();
+ }
+ return 0;
+}
+
+void runCPUHogThread(void) {
+ pthread_create(&tid, 0, cpuHogDriver, 0);
+}
+*/
+import "C"
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "time"
+ "unsafe"
+)
+
+func init() {
+ register("CgoPprofThread", CgoPprofThread)
+ register("CgoPprofThreadNoTraceback", CgoPprofThreadNoTraceback)
+}
+
+func CgoPprofThread() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.pprofCgoThreadTraceback), nil, nil)
+ pprofThread()
+}
+
+func CgoPprofThreadNoTraceback() {
+ pprofThread()
+}
+
+func pprofThread() {
+ f, err := os.CreateTemp("", "prof")
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ if err := pprof.StartCPUProfile(f); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ // This goroutine may receive a profiling signal while creating the C-owned
+ // thread. If it does, the SetCgoTraceback handler will make the leaf end of
+ // the stack look almost (but not exactly) like the stacks the test case is
+ // trying to find. Attach a profiler label so the test can filter out those
+ // confusing samples.
+ pprof.Do(context.Background(), pprof.Labels("ignore", "ignore"), func(ctx context.Context) {
+ C.runCPUHogThread()
+ })
+
+ time.Sleep(1 * time.Second)
+
+ pprof.StopCPUProfile()
+
+ name := f.Name()
+ if err := f.Close(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ fmt.Println(name)
+}
diff --git a/src/runtime/testdata/testprogcgo/threadprof.go b/src/runtime/testdata/testprogcgo/threadprof.go
new file mode 100644
index 0000000..00b511d
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/threadprof.go
@@ -0,0 +1,105 @@
+// 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.
+
+//go:build !plan9 && !windows
+// +build !plan9,!windows
+
+package main
+
+/*
+#include <stdint.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <pthread.h>
+
+volatile int32_t spinlock;
+
+// Note that this thread is only started if GO_START_SIGPROF_THREAD
+// is set in the environment, which is only done when running the
+// CgoExternalThreadSIGPROF test.
+static void *thread1(void *p) {
+ (void)p;
+ while (spinlock == 0)
+ ;
+ pthread_kill(pthread_self(), SIGPROF);
+ spinlock = 0;
+ return NULL;
+}
+
+// This constructor function is run when the program starts.
+// It is used for the CgoExternalThreadSIGPROF test.
+__attribute__((constructor)) void issue9456() {
+ if (getenv("GO_START_SIGPROF_THREAD") != NULL) {
+ pthread_t tid;
+ pthread_create(&tid, 0, thread1, NULL);
+ }
+}
+
+void **nullptr;
+
+void *crash(void *p) {
+ *nullptr = p;
+ return 0;
+}
+
+int start_crashing_thread(void) {
+ pthread_t tid;
+ return pthread_create(&tid, 0, crash, 0);
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "runtime"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+func init() {
+ register("CgoExternalThreadSIGPROF", CgoExternalThreadSIGPROF)
+ register("CgoExternalThreadSignal", CgoExternalThreadSignal)
+}
+
+func CgoExternalThreadSIGPROF() {
+ // This test intends to test that sending SIGPROF to foreign threads
+ // before we make any cgo call will not abort the whole process, so
+ // we cannot make any cgo call here. See https://golang.org/issue/9456.
+ atomic.StoreInt32((*int32)(unsafe.Pointer(&C.spinlock)), 1)
+ for atomic.LoadInt32((*int32)(unsafe.Pointer(&C.spinlock))) == 1 {
+ runtime.Gosched()
+ }
+ println("OK")
+}
+
+func CgoExternalThreadSignal() {
+ if len(os.Args) > 2 && os.Args[2] == "crash" {
+ i := C.start_crashing_thread()
+ if i != 0 {
+ fmt.Println("pthread_create failed:", i)
+ // Exit with 0 because parent expects us to crash.
+ return
+ }
+
+ // We should crash immediately, but give it plenty of
+ // time before failing (by exiting 0) in case we are
+ // running on a slow system.
+ time.Sleep(5 * time.Second)
+ return
+ }
+
+ cmd := exec.Command(os.Args[0], "CgoExternalThreadSignal", "crash")
+ cmd.Dir = os.TempDir() // put any core file in tempdir
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ fmt.Println("C signal did not crash as expected")
+ fmt.Printf("\n%s\n", out)
+ os.Exit(1)
+ }
+
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/trace.go b/src/runtime/testdata/testprogcgo/trace.go
new file mode 100644
index 0000000..875434b
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/trace.go
@@ -0,0 +1,60 @@
+// Copyright 2023 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
+
+/*
+// Defined in trace_*.c.
+void cCalledFromGo(void);
+*/
+import "C"
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "runtime/trace"
+)
+
+func init() {
+ register("Trace", Trace)
+}
+
+// Trace is used by TestTraceUnwindCGO.
+func Trace() {
+ file, err := os.CreateTemp("", "testprogcgo_trace")
+ if err != nil {
+ log.Fatalf("failed to create temp file: %s", err)
+ }
+ defer file.Close()
+
+ if err := trace.Start(file); err != nil {
+ log.Fatal(err)
+ }
+ defer trace.Stop()
+
+ goCalledFromGo()
+ <-goCalledFromCThreadChan
+
+ fmt.Printf("trace path:%s", file.Name())
+}
+
+// goCalledFromGo calls cCalledFromGo which calls back into goCalledFromC and
+// goCalledFromCThread.
+func goCalledFromGo() {
+ C.cCalledFromGo()
+}
+
+//export goCalledFromC
+func goCalledFromC() {
+ trace.Log(context.Background(), "goCalledFromC", "")
+}
+
+var goCalledFromCThreadChan = make(chan struct{})
+
+//export goCalledFromCThread
+func goCalledFromCThread() {
+ trace.Log(context.Background(), "goCalledFromCThread", "")
+ close(goCalledFromCThreadChan)
+}
diff --git a/src/runtime/testdata/testprogcgo/trace_unix.c b/src/runtime/testdata/testprogcgo/trace_unix.c
new file mode 100644
index 0000000..0fa55c7
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/trace_unix.c
@@ -0,0 +1,27 @@
+// Copyright 2023 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.
+
+//go:build unix
+
+// The unix C definitions for trace.go. That file uses //export so
+// it can't put function definitions in the "C" import comment.
+
+#include <pthread.h>
+#include <assert.h>
+
+extern void goCalledFromC(void);
+extern void goCalledFromCThread(void);
+
+static void* cCalledFromCThread(void *p) {
+ goCalledFromCThread();
+ return NULL;
+}
+
+void cCalledFromGo(void) {
+ goCalledFromC();
+
+ pthread_t thread;
+ assert(pthread_create(&thread, NULL, cCalledFromCThread, NULL) == 0);
+ assert(pthread_join(thread, NULL) == 0);
+}
diff --git a/src/runtime/testdata/testprogcgo/trace_windows.c b/src/runtime/testdata/testprogcgo/trace_windows.c
new file mode 100644
index 0000000..7758054
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/trace_windows.c
@@ -0,0 +1,29 @@
+// Copyright 2023 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.
+
+// The windows C definitions for trace.go. That file uses //export so
+// it can't put function definitions in the "C" import comment.
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <process.h>
+#include "_cgo_export.h"
+
+extern void goCalledFromC(void);
+extern void goCalledFromCThread(void);
+
+__stdcall
+static unsigned int cCalledFromCThread(void *p) {
+ goCalledFromCThread();
+ return 0;
+}
+
+void cCalledFromGo(void) {
+ goCalledFromC();
+
+ uintptr_t thread;
+ thread = _beginthreadex(NULL, 0, cCalledFromCThread, NULL, 0, NULL);
+ WaitForSingleObject((HANDLE)thread, INFINITE);
+ CloseHandle((HANDLE)thread);
+}
diff --git a/src/runtime/testdata/testprogcgo/traceback.go b/src/runtime/testdata/testprogcgo/traceback.go
new file mode 100644
index 0000000..e2d7599
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/traceback.go
@@ -0,0 +1,54 @@
+// 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
+
+// This program will crash.
+// We want the stack trace to include the C functions.
+// We use a fake traceback, and a symbolizer that dumps a string we recognize.
+
+/*
+#cgo CFLAGS: -g -O0
+
+// Defined in traceback_c.c.
+extern int crashInGo;
+int tracebackF1(void);
+void cgoTraceback(void* parg);
+void cgoSymbolizer(void* parg);
+*/
+import "C"
+
+import (
+ "runtime"
+ "unsafe"
+)
+
+func init() {
+ register("CrashTraceback", CrashTraceback)
+ register("CrashTracebackGo", CrashTracebackGo)
+}
+
+func CrashTraceback() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.cgoTraceback), nil, unsafe.Pointer(C.cgoSymbolizer))
+ C.tracebackF1()
+}
+
+func CrashTracebackGo() {
+ C.crashInGo = 1
+ CrashTraceback()
+}
+
+//export h1
+func h1() {
+ h2()
+}
+
+func h2() {
+ h3()
+}
+
+func h3() {
+ var x *int
+ *x = 0
+}
diff --git a/src/runtime/testdata/testprogcgo/traceback_c.c b/src/runtime/testdata/testprogcgo/traceback_c.c
new file mode 100644
index 0000000..56eda8f
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/traceback_c.c
@@ -0,0 +1,65 @@
+// Copyright 2020 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.
+
+// The C definitions for traceback.go. That file uses //export so
+// it can't put function definitions in the "C" import comment.
+
+#include <stdint.h>
+
+char *p;
+
+int crashInGo;
+extern void h1(void);
+
+int tracebackF3(void) {
+ if (crashInGo)
+ h1();
+ else
+ *p = 0;
+ return 0;
+}
+
+int tracebackF2(void) {
+ return tracebackF3();
+}
+
+int tracebackF1(void) {
+ return tracebackF2();
+}
+
+struct cgoTracebackArg {
+ uintptr_t context;
+ uintptr_t sigContext;
+ uintptr_t* buf;
+ uintptr_t max;
+};
+
+struct cgoSymbolizerArg {
+ uintptr_t pc;
+ const char* file;
+ uintptr_t lineno;
+ const char* func;
+ uintptr_t entry;
+ uintptr_t more;
+ uintptr_t data;
+};
+
+void cgoTraceback(void* parg) {
+ struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+ arg->buf[0] = 1;
+ arg->buf[1] = 2;
+ arg->buf[2] = 3;
+ arg->buf[3] = 0;
+}
+
+void cgoSymbolizer(void* parg) {
+ struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(parg);
+ if (arg->pc != arg->data + 1) {
+ arg->file = "unexpected data";
+ } else {
+ arg->file = "cgo symbolizer";
+ }
+ arg->lineno = arg->data + 1;
+ arg->data++;
+}
diff --git a/src/runtime/testdata/testprogcgo/tracebackctxt.go b/src/runtime/testdata/testprogcgo/tracebackctxt.go
new file mode 100644
index 0000000..62ff8ec
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/tracebackctxt.go
@@ -0,0 +1,136 @@
+// 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
+
+// Test the context argument to SetCgoTraceback.
+// Use fake context, traceback, and symbolizer functions.
+
+/*
+// Defined in tracebackctxt_c.c.
+extern void C1(void);
+extern void C2(void);
+extern void tcContext(void*);
+extern void tcContextSimple(void*);
+extern void tcTraceback(void*);
+extern void tcSymbolizer(void*);
+extern int getContextCount(void);
+extern void TracebackContextPreemptionCallGo(int);
+*/
+import "C"
+
+import (
+ "fmt"
+ "runtime"
+ "sync"
+ "unsafe"
+)
+
+func init() {
+ register("TracebackContext", TracebackContext)
+ register("TracebackContextPreemption", TracebackContextPreemption)
+}
+
+var tracebackOK bool
+
+func TracebackContext() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.tcTraceback), unsafe.Pointer(C.tcContext), unsafe.Pointer(C.tcSymbolizer))
+ C.C1()
+ if got := C.getContextCount(); got != 0 {
+ fmt.Printf("at end contextCount == %d, expected 0\n", got)
+ tracebackOK = false
+ }
+ if tracebackOK {
+ fmt.Println("OK")
+ }
+}
+
+//export G1
+func G1() {
+ C.C2()
+}
+
+//export G2
+func G2() {
+ pc := make([]uintptr, 32)
+ n := runtime.Callers(0, pc)
+ cf := runtime.CallersFrames(pc[:n])
+ var frames []runtime.Frame
+ for {
+ frame, more := cf.Next()
+ frames = append(frames, frame)
+ if !more {
+ break
+ }
+ }
+
+ want := []struct {
+ function string
+ line int
+ }{
+ {"main.G2", 0},
+ {"cFunction", 0x10200},
+ {"cFunction", 0x200},
+ {"cFunction", 0x10201},
+ {"cFunction", 0x201},
+ {"main.G1", 0},
+ {"cFunction", 0x10100},
+ {"cFunction", 0x100},
+ {"main.TracebackContext", 0},
+ }
+
+ ok := true
+ i := 0
+wantLoop:
+ for _, w := range want {
+ for ; i < len(frames); i++ {
+ if w.function == frames[i].Function {
+ if w.line != 0 && w.line != frames[i].Line {
+ fmt.Printf("found function %s at wrong line %#x (expected %#x)\n", w.function, frames[i].Line, w.line)
+ ok = false
+ }
+ i++
+ continue wantLoop
+ }
+ }
+ fmt.Printf("did not find function %s in\n", w.function)
+ for _, f := range frames {
+ fmt.Println(f)
+ }
+ ok = false
+ break
+ }
+ tracebackOK = ok
+ if got := C.getContextCount(); got != 2 {
+ fmt.Printf("at bottom contextCount == %d, expected 2\n", got)
+ tracebackOK = false
+ }
+}
+
+// Issue 47441.
+func TracebackContextPreemption() {
+ runtime.SetCgoTraceback(0, unsafe.Pointer(C.tcTraceback), unsafe.Pointer(C.tcContextSimple), unsafe.Pointer(C.tcSymbolizer))
+
+ const funcs = 10
+ const calls = 1e5
+ var wg sync.WaitGroup
+ for i := 0; i < funcs; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ for j := 0; j < calls; j++ {
+ C.TracebackContextPreemptionCallGo(C.int(i*calls + j))
+ }
+ }(i)
+ }
+ wg.Wait()
+
+ fmt.Println("OK")
+}
+
+//export TracebackContextPreemptionGoFunction
+func TracebackContextPreemptionGoFunction(i C.int) {
+ // Do some busy work.
+ fmt.Sprintf("%d\n", i)
+}
diff --git a/src/runtime/testdata/testprogcgo/tracebackctxt_c.c b/src/runtime/testdata/testprogcgo/tracebackctxt_c.c
new file mode 100644
index 0000000..910cb7b
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/tracebackctxt_c.c
@@ -0,0 +1,103 @@
+// 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.
+
+// The C definitions for tracebackctxt.go. That file uses //export so
+// it can't put function definitions in the "C" import comment.
+
+#include <stdlib.h>
+#include <stdint.h>
+
+// Functions exported from Go.
+extern void G1(void);
+extern void G2(void);
+extern void TracebackContextPreemptionGoFunction(int);
+
+void C1() {
+ G1();
+}
+
+void C2() {
+ G2();
+}
+
+struct cgoContextArg {
+ uintptr_t context;
+};
+
+struct cgoTracebackArg {
+ uintptr_t context;
+ uintptr_t sigContext;
+ uintptr_t* buf;
+ uintptr_t max;
+};
+
+struct cgoSymbolizerArg {
+ uintptr_t pc;
+ const char* file;
+ uintptr_t lineno;
+ const char* func;
+ uintptr_t entry;
+ uintptr_t more;
+ uintptr_t data;
+};
+
+// Uses atomic adds and subtracts to catch the possibility of
+// erroneous calls from multiple threads; that should be impossible in
+// this test case, but we check just in case.
+static int contextCount;
+
+int getContextCount() {
+ return __sync_add_and_fetch(&contextCount, 0);
+}
+
+void tcContext(void* parg) {
+ struct cgoContextArg* arg = (struct cgoContextArg*)(parg);
+ if (arg->context == 0) {
+ arg->context = __sync_add_and_fetch(&contextCount, 1);
+ } else {
+ if (arg->context != __sync_add_and_fetch(&contextCount, 0)) {
+ abort();
+ }
+ __sync_sub_and_fetch(&contextCount, 1);
+ }
+}
+
+void tcContextSimple(void* parg) {
+ struct cgoContextArg* arg = (struct cgoContextArg*)(parg);
+ if (arg->context == 0) {
+ arg->context = 1;
+ }
+}
+
+void tcTraceback(void* parg) {
+ int base, i;
+ struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+ if (arg->context == 0 && arg->sigContext == 0) {
+ // This shouldn't happen in this program.
+ abort();
+ }
+ // Return a variable number of PC values.
+ base = arg->context << 8;
+ for (i = 0; i < arg->context; i++) {
+ if (i < arg->max) {
+ arg->buf[i] = base + i;
+ }
+ }
+}
+
+void tcSymbolizer(void *parg) {
+ struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(parg);
+ if (arg->pc == 0) {
+ return;
+ }
+ // Report two lines per PC returned by traceback, to test more handling.
+ arg->more = arg->file == NULL;
+ arg->file = "tracebackctxt.go";
+ arg->func = "cFunction";
+ arg->lineno = arg->pc + (arg->more << 16);
+}
+
+void TracebackContextPreemptionCallGo(int i) {
+ TracebackContextPreemptionGoFunction(i);
+}
diff --git a/src/runtime/testdata/testprogcgo/windows/win.go b/src/runtime/testdata/testprogcgo/windows/win.go
new file mode 100644
index 0000000..9d9f86c
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/windows/win.go
@@ -0,0 +1,14 @@
+package windows
+
+/*
+#include <windows.h>
+
+DWORD agetthread() {
+ return GetCurrentThreadId();
+}
+*/
+import "C"
+
+func GetThread() uint32 {
+ return uint32(C.agetthread())
+}