Source file
src/runtime/debug_test.go
Documentation: runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package runtime_test
16
17 import (
18 "fmt"
19 "internal/abi"
20 "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
33
34
35 skipUnderDebugger(t)
36
37
38
39
40
41
42 ogomaxprocs := runtime.GOMAXPROCS(8)
43 ogcpercent := debug.SetGCPercent(-1)
44
45
46
47
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
79
80
81
82 func debugCallWorker2(stop *uint32, x *int) {
83 for atomic.LoadUint32(stop) == 0 {
84
85
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
96
97
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
129
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
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
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
211
212 if _, err := runtime.InjectDebugCall(g, func() { growStack(nil) }, nil, nil, debugCallTKill, false); err != nil {
213 t.Fatal(err)
214 }
215 }
216
217
218 func debugCallUnsafePointWorker(gpp **runtime.G, ready, stop *uint32) {
219
220
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
235
236 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
237 defer debug.SetGCPercent(debug.SetGCPercent(-1))
238
239
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
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 }
280
View as plain text