// 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 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) { 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) } }