2018-04-26 19:43:19 -06:00
|
|
|
// 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"
|
2018-05-23 12:01:21 -06:00
|
|
|
"runtime/debug"
|
2018-04-26 19:43:19 -06:00
|
|
|
"sync/atomic"
|
|
|
|
"syscall"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
func startDebugCallWorker(t *testing.T) (g *runtime.G, after func()) {
|
2018-05-23 12:01:21 -06:00
|
|
|
// This can deadlock if there aren't enough threads or if a GC
|
|
|
|
// tries to interrupt an atomic loop (see issue #10958).
|
2018-04-26 19:43:19 -06:00
|
|
|
ogomaxprocs := runtime.GOMAXPROCS(2)
|
2018-05-23 12:01:21 -06:00
|
|
|
ogcpercent := debug.SetGCPercent(-1)
|
2018-04-26 19:43:19 -06:00
|
|
|
|
|
|
|
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)
|
2018-05-23 12:01:21 -06:00
|
|
|
debug.SetGCPercent(ogcpercent)
|
2018-04-26 19:43:19 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2018-05-23 12:01:21 -06:00
|
|
|
// This can deadlock if there aren't enough threads or if a GC
|
|
|
|
// tries to interrupt an atomic loop (see issue #10958).
|
2018-04-26 19:43:19 -06:00
|
|
|
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
|
2018-05-23 12:01:21 -06:00
|
|
|
defer debug.SetGCPercent(debug.SetGCPercent(-1))
|
2018-04-26 19:43:19 -06:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|