diff options
Diffstat (limited to 'src/runtime/testdata/testprogcgo')
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()) +} |