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