| // 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" | |
| "os" | |
| "regexp" | |
| "runtime" | |
| "runtime/debug" | |
| "sync/atomic" | |
| "syscall" | |
| "testing" | |
| ) | |
| func startDebugCallWorker(t *testing.T) (g *runtime.G, after func()) { | |
| // This can deadlock if run under a debugger because it | |
| // depends on catching SIGTRAP, which is usually swallowed by | |
| // a debugger. | |
| skipUnderDebugger(t) | |
| // This can deadlock if there aren't enough threads or if a GC | |
| // tries to interrupt an atomic loop (see issue #10958). We | |
| // use 8 Ps so there's room for the debug call worker, | |
| // something that's trying to preempt the call worker, and the | |
| // goroutine that's trying to stop the call worker. | |
| ogomaxprocs := runtime.GOMAXPROCS(8) | |
| ogcpercent := debug.SetGCPercent(-1) | |
| // ready is a buffered channel so debugCallWorker won't block | |
| // on sending to it. This makes it less likely we'll catch | |
| // debugCallWorker while it's in the runtime. | |
| ready := make(chan *runtime.G, 1) | |
| 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) | |
| } | |
| // Don't inline this function, since we want to test adjusting | |
| // pointers in the arguments. | |
| // | |
| //go:noinline | |
| 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) | |
| } | |
| // skipUnderDebugger skips the current test when running under a | |
| // debugger (specifically if this process has a tracer). This is | |
| // Linux-specific. | |
| func skipUnderDebugger(t *testing.T) { | |
| pid := syscall.Getpid() | |
| status, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)) | |
| if err != nil { | |
| t.Logf("couldn't get proc tracer: %s", err) | |
| return | |
| } | |
| re := regexp.MustCompile(`TracerPid:\s+([0-9]+)`) | |
| sub := re.FindSubmatch(status) | |
| if sub == nil { | |
| t.Logf("couldn't find proc tracer PID") | |
| return | |
| } | |
| if string(sub[1]) == "0" { | |
| return | |
| } | |
| t.Skip("test will deadlock under a debugger") | |
| } | |
| 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, false); 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, false); 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, false); 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, false); 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) { | |
| skipUnderDebugger(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(8)) | |
| 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, true) | |
| 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) { | |
| skipUnderDebugger(t) | |
| // This can deadlock if there aren't enough threads. | |
| defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) | |
| 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, false) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if ps, ok := p.(string); !ok || ps != "test" { | |
| t.Fatalf("wanted panic %v, got %v", "test", p) | |
| } | |
| } |