github.com/comwrg/go/src@v0.0.0-20220319063731-c238d0440370/runtime/debug_test.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // TODO: This test could be implemented on all (most?) UNIXes if we 6 // added syscall.Tgkill more widely. 7 8 // We skip all of these tests under race mode because our test thread 9 // spends all of its time in the race runtime, which isn't a safe 10 // point. 11 12 //go:build amd64 && linux && !race 13 // +build amd64,linux,!race 14 15 package runtime_test 16 17 import ( 18 "fmt" 19 "github.com/comwrg/go/src/internal/abi" 20 "github.com/comwrg/go/src/internal/goexperiment" 21 "math" 22 "os" 23 "regexp" 24 "runtime" 25 "runtime/debug" 26 "sync/atomic" 27 "syscall" 28 "testing" 29 ) 30 31 func startDebugCallWorker(t *testing.T) (g *runtime.G, after func()) { 32 // This can deadlock if run under a debugger because it 33 // depends on catching SIGTRAP, which is usually swallowed by 34 // a debugger. 35 skipUnderDebugger(t) 36 37 // This can deadlock if there aren't enough threads or if a GC 38 // tries to interrupt an atomic loop (see issue #10958). We 39 // use 8 Ps so there's room for the debug call worker, 40 // something that's trying to preempt the call worker, and the 41 // goroutine that's trying to stop the call worker. 42 ogomaxprocs := runtime.GOMAXPROCS(8) 43 ogcpercent := debug.SetGCPercent(-1) 44 45 // ready is a buffered channel so debugCallWorker won't block 46 // on sending to it. This makes it less likely we'll catch 47 // debugCallWorker while it's in the runtime. 48 ready := make(chan *runtime.G, 1) 49 var stop uint32 50 done := make(chan error) 51 go debugCallWorker(ready, &stop, done) 52 g = <-ready 53 return g, func() { 54 atomic.StoreUint32(&stop, 1) 55 err := <-done 56 if err != nil { 57 t.Fatal(err) 58 } 59 runtime.GOMAXPROCS(ogomaxprocs) 60 debug.SetGCPercent(ogcpercent) 61 } 62 } 63 64 func debugCallWorker(ready chan<- *runtime.G, stop *uint32, done chan<- error) { 65 runtime.LockOSThread() 66 defer runtime.UnlockOSThread() 67 68 ready <- runtime.Getg() 69 70 x := 2 71 debugCallWorker2(stop, &x) 72 if x != 1 { 73 done <- fmt.Errorf("want x = 2, got %d; register pointer not adjusted?", x) 74 } 75 close(done) 76 } 77 78 // Don't inline this function, since we want to test adjusting 79 // pointers in the arguments. 80 // 81 //go:noinline 82 func debugCallWorker2(stop *uint32, x *int) { 83 for atomic.LoadUint32(stop) == 0 { 84 // Strongly encourage x to live in a register so we 85 // can test pointer register adjustment. 86 *x++ 87 } 88 *x = 1 89 } 90 91 func debugCallTKill(tid int) error { 92 return syscall.Tgkill(syscall.Getpid(), tid, syscall.SIGTRAP) 93 } 94 95 // skipUnderDebugger skips the current test when running under a 96 // debugger (specifically if this process has a tracer). This is 97 // Linux-specific. 98 func skipUnderDebugger(t *testing.T) { 99 pid := syscall.Getpid() 100 status, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid)) 101 if err != nil { 102 t.Logf("couldn't get proc tracer: %s", err) 103 return 104 } 105 re := regexp.MustCompile(`TracerPid:\s+([0-9]+)`) 106 sub := re.FindSubmatch(status) 107 if sub == nil { 108 t.Logf("couldn't find proc tracer PID") 109 return 110 } 111 if string(sub[1]) == "0" { 112 return 113 } 114 t.Skip("test will deadlock under a debugger") 115 } 116 117 func TestDebugCall(t *testing.T) { 118 g, after := startDebugCallWorker(t) 119 defer after() 120 121 type stackArgs struct { 122 x0 int 123 x1 float64 124 y0Ret int 125 y1Ret float64 126 } 127 128 // Inject a call into the debugCallWorker goroutine and test 129 // basic argument and result passing. 130 fn := func(x int, y float64) (y0Ret int, y1Ret float64) { 131 return x + 1, y + 1.0 132 } 133 var args *stackArgs 134 var regs abi.RegArgs 135 intRegs := regs.Ints[:] 136 floatRegs := regs.Floats[:] 137 fval := float64(42.0) 138 if goexperiment.RegabiArgs { 139 intRegs[0] = 42 140 floatRegs[0] = math.Float64bits(fval) 141 } else { 142 args = &stackArgs{ 143 x0: 42, 144 x1: 42.0, 145 } 146 } 147 if _, err := runtime.InjectDebugCall(g, fn, ®s, args, debugCallTKill, false); err != nil { 148 t.Fatal(err) 149 } 150 var result0 int 151 var result1 float64 152 if goexperiment.RegabiArgs { 153 result0 = int(intRegs[0]) 154 result1 = math.Float64frombits(floatRegs[0]) 155 } else { 156 result0 = args.y0Ret 157 result1 = args.y1Ret 158 } 159 if result0 != 43 { 160 t.Errorf("want 43, got %d", result0) 161 } 162 if result1 != fval+1 { 163 t.Errorf("want 43, got %f", result1) 164 } 165 } 166 167 func TestDebugCallLarge(t *testing.T) { 168 g, after := startDebugCallWorker(t) 169 defer after() 170 171 // Inject a call with a large call frame. 172 const N = 128 173 var args struct { 174 in [N]int 175 out [N]int 176 } 177 fn := func(in [N]int) (out [N]int) { 178 for i := range in { 179 out[i] = in[i] + 1 180 } 181 return 182 } 183 var want [N]int 184 for i := range args.in { 185 args.in[i] = i 186 want[i] = i + 1 187 } 188 if _, err := runtime.InjectDebugCall(g, fn, nil, &args, debugCallTKill, false); err != nil { 189 t.Fatal(err) 190 } 191 if want != args.out { 192 t.Fatalf("want %v, got %v", want, args.out) 193 } 194 } 195 196 func TestDebugCallGC(t *testing.T) { 197 g, after := startDebugCallWorker(t) 198 defer after() 199 200 // Inject a call that performs a GC. 201 if _, err := runtime.InjectDebugCall(g, runtime.GC, nil, nil, debugCallTKill, false); err != nil { 202 t.Fatal(err) 203 } 204 } 205 206 func TestDebugCallGrowStack(t *testing.T) { 207 g, after := startDebugCallWorker(t) 208 defer after() 209 210 // Inject a call that grows the stack. debugCallWorker checks 211 // for stack pointer breakage. 212 if _, err := runtime.InjectDebugCall(g, func() { growStack(nil) }, nil, nil, debugCallTKill, false); err != nil { 213 t.Fatal(err) 214 } 215 } 216 217 //go:nosplit 218 func debugCallUnsafePointWorker(gpp **runtime.G, ready, stop *uint32) { 219 // The nosplit causes this function to not contain safe-points 220 // except at calls. 221 runtime.LockOSThread() 222 defer runtime.UnlockOSThread() 223 224 *gpp = runtime.Getg() 225 226 for atomic.LoadUint32(stop) == 0 { 227 atomic.StoreUint32(ready, 1) 228 } 229 } 230 231 func TestDebugCallUnsafePoint(t *testing.T) { 232 skipUnderDebugger(t) 233 234 // This can deadlock if there aren't enough threads or if a GC 235 // tries to interrupt an atomic loop (see issue #10958). 236 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) 237 defer debug.SetGCPercent(debug.SetGCPercent(-1)) 238 239 // Test that the runtime refuses call injection at unsafe points. 240 var g *runtime.G 241 var ready, stop uint32 242 defer atomic.StoreUint32(&stop, 1) 243 go debugCallUnsafePointWorker(&g, &ready, &stop) 244 for atomic.LoadUint32(&ready) == 0 { 245 runtime.Gosched() 246 } 247 248 _, err := runtime.InjectDebugCall(g, func() {}, nil, nil, debugCallTKill, true) 249 if msg := "call not at safe point"; err == nil || err.Error() != msg { 250 t.Fatalf("want %q, got %s", msg, err) 251 } 252 } 253 254 func TestDebugCallPanic(t *testing.T) { 255 skipUnderDebugger(t) 256 257 // This can deadlock if there aren't enough threads. 258 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) 259 260 ready := make(chan *runtime.G) 261 var stop uint32 262 defer atomic.StoreUint32(&stop, 1) 263 go func() { 264 runtime.LockOSThread() 265 defer runtime.UnlockOSThread() 266 ready <- runtime.Getg() 267 for atomic.LoadUint32(&stop) == 0 { 268 } 269 }() 270 g := <-ready 271 272 p, err := runtime.InjectDebugCall(g, func() { panic("test") }, nil, nil, debugCallTKill, false) 273 if err != nil { 274 t.Fatal(err) 275 } 276 if ps, ok := p.(string); !ok || ps != "test" { 277 t.Fatalf("wanted panic %v, got %v", "test", p) 278 } 279 }