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