Source file
src/runtime/debug_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14 package runtime_test
15
16 import (
17 "fmt"
18 "internal/abi"
19 "internal/goexperiment"
20 "math"
21 "os"
22 "regexp"
23 "runtime"
24 "runtime/debug"
25 "sync/atomic"
26 "syscall"
27 "testing"
28 )
29
30 func startDebugCallWorker(t *testing.T) (g *runtime.G, after func()) {
31
32
33
34 skipUnderDebugger(t)
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 ogomaxprocs := runtime.GOMAXPROCS(8)
51 ogcpercent := debug.SetGCPercent(-1)
52 runtime.GC()
53
54
55
56
57 ready := make(chan *runtime.G, 1)
58 var stop uint32
59 done := make(chan error)
60 go debugCallWorker(ready, &stop, done)
61 g = <-ready
62 return g, func() {
63 atomic.StoreUint32(&stop, 1)
64 err := <-done
65 if err != nil {
66 t.Fatal(err)
67 }
68 runtime.GOMAXPROCS(ogomaxprocs)
69 debug.SetGCPercent(ogcpercent)
70 }
71 }
72
73 func debugCallWorker(ready chan<- *runtime.G, stop *uint32, done chan<- error) {
74 runtime.LockOSThread()
75 defer runtime.UnlockOSThread()
76
77 ready <- runtime.Getg()
78
79 x := 2
80 debugCallWorker2(stop, &x)
81 if x != 1 {
82 done <- fmt.Errorf("want x = 2, got %d; register pointer not adjusted?", x)
83 }
84 close(done)
85 }
86
87
88
89
90
91 func debugCallWorker2(stop *uint32, x *int) {
92 for atomic.LoadUint32(stop) == 0 {
93
94
95 *x++
96 }
97 *x = 1
98 }
99
100 func debugCallTKill(tid int) error {
101 return syscall.Tgkill(syscall.Getpid(), tid, syscall.SIGTRAP)
102 }
103
104
105
106
107 func skipUnderDebugger(t *testing.T) {
108 pid := syscall.Getpid()
109 status, err := os.ReadFile(fmt.Sprintf("/proc/%d/status", pid))
110 if err != nil {
111 t.Logf("couldn't get proc tracer: %s", err)
112 return
113 }
114 re := regexp.MustCompile(`TracerPid:\s+([0-9]+)`)
115 sub := re.FindSubmatch(status)
116 if sub == nil {
117 t.Logf("couldn't find proc tracer PID")
118 return
119 }
120 if string(sub[1]) == "0" {
121 return
122 }
123 t.Skip("test will deadlock under a debugger")
124 }
125
126 func TestDebugCall(t *testing.T) {
127 g, after := startDebugCallWorker(t)
128 defer after()
129
130 type stackArgs struct {
131 x0 int
132 x1 float64
133 y0Ret int
134 y1Ret float64
135 }
136
137
138
139 fn := func(x int, y float64) (y0Ret int, y1Ret float64) {
140 return x + 1, y + 1.0
141 }
142 var args *stackArgs
143 var regs abi.RegArgs
144 intRegs := regs.Ints[:]
145 floatRegs := regs.Floats[:]
146 fval := float64(42.0)
147 if goexperiment.RegabiArgs {
148 intRegs[0] = 42
149 floatRegs[0] = math.Float64bits(fval)
150 } else {
151 args = &stackArgs{
152 x0: 42,
153 x1: 42.0,
154 }
155 }
156
157 if _, err := runtime.InjectDebugCall(g, fn, ®s, args, debugCallTKill, false); err != nil {
158 t.Fatal(err)
159 }
160 var result0 int
161 var result1 float64
162 if goexperiment.RegabiArgs {
163 result0 = int(intRegs[0])
164 result1 = math.Float64frombits(floatRegs[0])
165 } else {
166 result0 = args.y0Ret
167 result1 = args.y1Ret
168 }
169 if result0 != 43 {
170 t.Errorf("want 43, got %d", result0)
171 }
172 if result1 != fval+1 {
173 t.Errorf("want 43, got %f", result1)
174 }
175 }
176
177 func TestDebugCallLarge(t *testing.T) {
178 g, after := startDebugCallWorker(t)
179 defer after()
180
181
182 const N = 128
183 var args struct {
184 in [N]int
185 out [N]int
186 }
187 fn := func(in [N]int) (out [N]int) {
188 for i := range in {
189 out[i] = in[i] + 1
190 }
191 return
192 }
193 var want [N]int
194 for i := range args.in {
195 args.in[i] = i
196 want[i] = i + 1
197 }
198 if _, err := runtime.InjectDebugCall(g, fn, nil, &args, debugCallTKill, false); err != nil {
199 t.Fatal(err)
200 }
201 if want != args.out {
202 t.Fatalf("want %v, got %v", want, args.out)
203 }
204 }
205
206 func TestDebugCallGC(t *testing.T) {
207 g, after := startDebugCallWorker(t)
208 defer after()
209
210
211 if _, err := runtime.InjectDebugCall(g, runtime.GC, nil, nil, debugCallTKill, false); err != nil {
212 t.Fatal(err)
213 }
214 }
215
216 func TestDebugCallGrowStack(t *testing.T) {
217 g, after := startDebugCallWorker(t)
218 defer after()
219
220
221
222 if _, err := runtime.InjectDebugCall(g, func() { growStack(nil) }, nil, nil, debugCallTKill, false); err != nil {
223 t.Fatal(err)
224 }
225 }
226
227
228 func debugCallUnsafePointWorker(gpp **runtime.G, ready, stop *uint32) {
229
230
231 runtime.LockOSThread()
232 defer runtime.UnlockOSThread()
233
234 *gpp = runtime.Getg()
235
236 for atomic.LoadUint32(stop) == 0 {
237 atomic.StoreUint32(ready, 1)
238 }
239 }
240
241 func TestDebugCallUnsafePoint(t *testing.T) {
242 skipUnderDebugger(t)
243
244
245
246 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
247
248
249
250
251
252 runtime.GC()
253 defer debug.SetGCPercent(debug.SetGCPercent(-1))
254
255
256 var g *runtime.G
257 var ready, stop uint32
258 defer atomic.StoreUint32(&stop, 1)
259 go debugCallUnsafePointWorker(&g, &ready, &stop)
260 for atomic.LoadUint32(&ready) == 0 {
261 runtime.Gosched()
262 }
263
264 _, err := runtime.InjectDebugCall(g, func() {}, nil, nil, debugCallTKill, true)
265 if msg := "call not at safe point"; err == nil || err.Error() != msg {
266 t.Fatalf("want %q, got %s", msg, err)
267 }
268 }
269
270 func TestDebugCallPanic(t *testing.T) {
271 skipUnderDebugger(t)
272
273
274 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
275
276
277
278
279
280 defer debug.SetGCPercent(debug.SetGCPercent(-1))
281
282
283
284
285
286
287 runtime.GC()
288
289 ready := make(chan *runtime.G)
290 var stop uint32
291 defer atomic.StoreUint32(&stop, 1)
292 go func() {
293 runtime.LockOSThread()
294 defer runtime.UnlockOSThread()
295 ready <- runtime.Getg()
296 for atomic.LoadUint32(&stop) == 0 {
297 }
298 }()
299 g := <-ready
300
301 p, err := runtime.InjectDebugCall(g, func() { panic("test") }, nil, nil, debugCallTKill, false)
302 if err != nil {
303 t.Fatal(err)
304 }
305 if ps, ok := p.(string); !ok || ps != "test" {
306 t.Fatalf("wanted panic %v, got %v", "test", p)
307 }
308 }
309
View as plain text