summaryrefslogtreecommitdiff
path: root/libgo/go/runtime/debug_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/runtime/debug_test.go')
-rw-r--r--libgo/go/runtime/debug_test.go207
1 files changed, 207 insertions, 0 deletions
diff --git a/libgo/go/runtime/debug_test.go b/libgo/go/runtime/debug_test.go
new file mode 100644
index 00000000000..38c764fadb3
--- /dev/null
+++ b/libgo/go/runtime/debug_test.go
@@ -0,0 +1,207 @@
+// 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.
+
+// TODO: This test could be implemented on all (most?) UNIXes if we
+// added syscall.Tgkill more widely.
+
+// We skip all of these tests under race mode because our test thread
+// spends all of its time in the race runtime, which isn't a safe
+// point.
+
+// +build ignore_for_gccgo
+// +build amd64
+// +build linux
+// +build !race
+
+package runtime_test
+
+import (
+ "fmt"
+ "runtime"
+ "runtime/debug"
+ "sync/atomic"
+ "syscall"
+ "testing"
+)
+
+func startDebugCallWorker(t *testing.T) (g *runtime.G, after func()) {
+ // This can deadlock if there aren't enough threads or if a GC
+ // tries to interrupt an atomic loop (see issue #10958).
+ ogomaxprocs := runtime.GOMAXPROCS(2)
+ ogcpercent := debug.SetGCPercent(-1)
+
+ ready := make(chan *runtime.G)
+ var stop uint32
+ done := make(chan error)
+ go debugCallWorker(ready, &stop, done)
+ g = <-ready
+ return g, func() {
+ atomic.StoreUint32(&stop, 1)
+ err := <-done
+ if err != nil {
+ t.Fatal(err)
+ }
+ runtime.GOMAXPROCS(ogomaxprocs)
+ debug.SetGCPercent(ogcpercent)
+ }
+}
+
+func debugCallWorker(ready chan<- *runtime.G, stop *uint32, done chan<- error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ready <- runtime.Getg()
+
+ x := 2
+ debugCallWorker2(stop, &x)
+ if x != 1 {
+ done <- fmt.Errorf("want x = 2, got %d; register pointer not adjusted?", x)
+ }
+ close(done)
+}
+
+func debugCallWorker2(stop *uint32, x *int) {
+ for atomic.LoadUint32(stop) == 0 {
+ // Strongly encourage x to live in a register so we
+ // can test pointer register adjustment.
+ *x++
+ }
+ *x = 1
+}
+
+func debugCallTKill(tid int) error {
+ return syscall.Tgkill(syscall.Getpid(), tid, syscall.SIGTRAP)
+}
+
+func TestDebugCall(t *testing.T) {
+ g, after := startDebugCallWorker(t)
+ defer after()
+
+ // Inject a call into the debugCallWorker goroutine and test
+ // basic argument and result passing.
+ var args struct {
+ x int
+ yRet int
+ }
+ fn := func(x int) (yRet int) {
+ return x + 1
+ }
+ args.x = 42
+ if _, err := runtime.InjectDebugCall(g, fn, &args, debugCallTKill); err != nil {
+ t.Fatal(err)
+ }
+ if args.yRet != 43 {
+ t.Fatalf("want 43, got %d", args.yRet)
+ }
+}
+
+func TestDebugCallLarge(t *testing.T) {
+ g, after := startDebugCallWorker(t)
+ defer after()
+
+ // Inject a call with a large call frame.
+ const N = 128
+ var args struct {
+ in [N]int
+ out [N]int
+ }
+ fn := func(in [N]int) (out [N]int) {
+ for i := range in {
+ out[i] = in[i] + 1
+ }
+ return
+ }
+ var want [N]int
+ for i := range args.in {
+ args.in[i] = i
+ want[i] = i + 1
+ }
+ if _, err := runtime.InjectDebugCall(g, fn, &args, debugCallTKill); err != nil {
+ t.Fatal(err)
+ }
+ if want != args.out {
+ t.Fatalf("want %v, got %v", want, args.out)
+ }
+}
+
+func TestDebugCallGC(t *testing.T) {
+ g, after := startDebugCallWorker(t)
+ defer after()
+
+ // Inject a call that performs a GC.
+ if _, err := runtime.InjectDebugCall(g, runtime.GC, nil, debugCallTKill); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDebugCallGrowStack(t *testing.T) {
+ g, after := startDebugCallWorker(t)
+ defer after()
+
+ // Inject a call that grows the stack. debugCallWorker checks
+ // for stack pointer breakage.
+ if _, err := runtime.InjectDebugCall(g, func() { growStack(nil) }, nil, debugCallTKill); err != nil {
+ t.Fatal(err)
+ }
+}
+
+//go:nosplit
+func debugCallUnsafePointWorker(gpp **runtime.G, ready, stop *uint32) {
+ // The nosplit causes this function to not contain safe-points
+ // except at calls.
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ *gpp = runtime.Getg()
+
+ for atomic.LoadUint32(stop) == 0 {
+ atomic.StoreUint32(ready, 1)
+ }
+}
+
+func TestDebugCallUnsafePoint(t *testing.T) {
+ // This can deadlock if there aren't enough threads or if a GC
+ // tries to interrupt an atomic loop (see issue #10958).
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+ defer debug.SetGCPercent(debug.SetGCPercent(-1))
+
+ // Test that the runtime refuses call injection at unsafe points.
+ var g *runtime.G
+ var ready, stop uint32
+ defer atomic.StoreUint32(&stop, 1)
+ go debugCallUnsafePointWorker(&g, &ready, &stop)
+ for atomic.LoadUint32(&ready) == 0 {
+ runtime.Gosched()
+ }
+
+ _, err := runtime.InjectDebugCall(g, func() {}, nil, debugCallTKill)
+ if msg := "call not at safe point"; err == nil || err.Error() != msg {
+ t.Fatalf("want %q, got %s", msg, err)
+ }
+}
+
+func TestDebugCallPanic(t *testing.T) {
+ // This can deadlock if there aren't enough threads.
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+
+ ready := make(chan *runtime.G)
+ var stop uint32
+ defer atomic.StoreUint32(&stop, 1)
+ go func() {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+ ready <- runtime.Getg()
+ for atomic.LoadUint32(&stop) == 0 {
+ }
+ }()
+ g := <-ready
+
+ p, err := runtime.InjectDebugCall(g, func() { panic("test") }, nil, debugCallTKill)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ps, ok := p.(string); !ok || ps != "test" {
+ t.Fatalf("wanted panic %v, got %v", "test", p)
+ }
+}