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