mirror of
https://github.com/autc04/Retro68.git
synced 2024-10-19 13:26:21 +00:00
251 lines
6.1 KiB
Go
251 lines
6.1 KiB
Go
|
// 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"
|
||
|
"io/ioutil"
|
||
|
"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 := ioutil.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); 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) {
|
||
|
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)
|
||
|
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)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if ps, ok := p.(string); !ok || ps != "test" {
|
||
|
t.Fatalf("wanted panic %v, got %v", "test", p)
|
||
|
}
|
||
|
}
|