Source file
src/runtime/runtime-gdb_unix_test.go
1
2
3
4
5
6
7 package runtime_test
8
9 import (
10 "bytes"
11 "fmt"
12 "internal/testenv"
13 "io"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "regexp"
18 "runtime"
19 "syscall"
20 "testing"
21 )
22
23 func canGenerateCore(t *testing.T) bool {
24
25 var lim syscall.Rlimit
26 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
27 if err != nil {
28 t.Fatalf("error getting rlimit: %v", err)
29 }
30
31
32 const minRlimitCore = 100 << 20
33 if lim.Max < minRlimitCore {
34 t.Skipf("RLIMIT_CORE max too low: %#+v", lim)
35 }
36
37
38 b, err := os.ReadFile("/proc/sys/kernel/core_pattern")
39 if err != nil {
40 t.Fatalf("error reading core_pattern: %v", err)
41 }
42 if string(b) != "core\n" {
43 t.Skipf("Unexpected core pattern %q", string(b))
44 }
45
46 coreUsesPID := false
47 b, err = os.ReadFile("/proc/sys/kernel/core_uses_pid")
48 if err == nil {
49 switch string(bytes.TrimSpace(b)) {
50 case "0":
51 case "1":
52 coreUsesPID = true
53 default:
54 t.Skipf("unexpected core_uses_pid value %q", string(b))
55 }
56 }
57 return coreUsesPID
58 }
59
60 const coreSignalSource = `
61 package main
62
63 import (
64 "flag"
65 "fmt"
66 "os"
67 "runtime/debug"
68 "syscall"
69 )
70
71 var pipeFD = flag.Int("pipe-fd", -1, "FD of write end of control pipe")
72
73 func enableCore() {
74 debug.SetTraceback("crash")
75
76 var lim syscall.Rlimit
77 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
78 if err != nil {
79 panic(fmt.Sprintf("error getting rlimit: %v", err))
80 }
81 lim.Cur = lim.Max
82 fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim)
83 err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim)
84 if err != nil {
85 panic(fmt.Sprintf("error setting rlimit: %v", err))
86 }
87 }
88
89 func main() {
90 flag.Parse()
91
92 enableCore()
93
94 // Ready to go. Notify parent.
95 if err := syscall.Close(*pipeFD); err != nil {
96 panic(fmt.Sprintf("error closing control pipe fd %d: %v", *pipeFD, err))
97 }
98
99 for {}
100 }
101 `
102
103
104
105 func TestGdbCoreSignalBacktrace(t *testing.T) {
106 if runtime.GOOS != "linux" {
107
108
109 t.Skip("Test only supported on Linux")
110 }
111 if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" {
112
113
114 t.Skip("Backtrace through signal handler only works on 386 and amd64")
115 }
116
117 checkGdbEnvironment(t)
118 t.Parallel()
119 checkGdbVersion(t)
120
121 coreUsesPID := canGenerateCore(t)
122
123
124 dir := t.TempDir()
125 src := filepath.Join(dir, "main.go")
126 err := os.WriteFile(src, []byte(coreSignalSource), 0644)
127 if err != nil {
128 t.Fatalf("failed to create file: %v", err)
129 }
130 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
131 cmd.Dir = dir
132 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
133 if err != nil {
134 t.Fatalf("building source %v\n%s", err, out)
135 }
136
137 r, w, err := os.Pipe()
138 if err != nil {
139 t.Fatalf("error creating control pipe: %v", err)
140 }
141 defer r.Close()
142
143
144 cmd = testenv.Command(t, "./a.exe", "-pipe-fd=3")
145 cmd.Dir = dir
146 cmd.ExtraFiles = []*os.File{w}
147 var output bytes.Buffer
148 cmd.Stdout = &output
149 cmd.Stderr = &output
150
151 if err := cmd.Start(); err != nil {
152 t.Fatalf("error starting test binary: %v", err)
153 }
154 w.Close()
155
156 pid := cmd.Process.Pid
157
158
159 var buf [1]byte
160 if _, err := r.Read(buf[:]); err != io.EOF {
161 t.Fatalf("control pipe read get err %v want io.EOF", err)
162 }
163
164
165 if err := cmd.Process.Signal(os.Signal(syscall.SIGABRT)); err != nil {
166 t.Fatalf("erroring signaling child: %v", err)
167 }
168
169 err = cmd.Wait()
170 t.Logf("child output:\n%s", output.String())
171 if err == nil {
172 t.Fatalf("Wait succeeded, want SIGABRT")
173 }
174 ee, ok := err.(*exec.ExitError)
175 if !ok {
176 t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee)
177 }
178 ws, ok := ee.Sys().(syscall.WaitStatus)
179 if !ok {
180 t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys())
181 }
182 if ws.Signal() != syscall.SIGABRT {
183 t.Fatalf("Signal got %d want SIGABRT", ws.Signal())
184 }
185 if !ws.CoreDump() {
186 t.Fatalf("CoreDump got %v want true", ws.CoreDump())
187 }
188
189 coreFile := "core"
190 if coreUsesPID {
191 coreFile += fmt.Sprintf(".%d", pid)
192 }
193
194
195 args := []string{"-nx", "-batch",
196 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
197 "-ex", "backtrace",
198 filepath.Join(dir, "a.exe"),
199 filepath.Join(dir, coreFile),
200 }
201 cmd = testenv.Command(t, "gdb", args...)
202
203 got, err := cmd.CombinedOutput()
204 t.Logf("gdb output:\n%s", got)
205 if err != nil {
206 t.Fatalf("gdb exited with error: %v", err)
207 }
208
209
210
211
212
213
214
215 re := regexp.MustCompile(`#.* runtime\.sigtramp `)
216 if found := re.Find(got) != nil; !found {
217 t.Fatalf("could not find sigtramp in backtrace")
218 }
219
220 re = regexp.MustCompile("#.* <signal handler called>")
221 loc := re.FindIndex(got)
222 if loc == nil {
223 t.Fatalf("could not find signal handler marker in backtrace")
224 }
225 rest := got[loc[1]:]
226
227
228
229
230
231
232 re = regexp.MustCompile(`#.* runtime\.`)
233 if found := re.Find(rest) != nil; !found {
234 t.Fatalf("could not find runtime symbol in backtrace after signal handler:\n%s", rest)
235 }
236 }
237
238 const coreCrashThreadSource = `
239 package main
240
241 /*
242 #cgo CFLAGS: -g -O0
243 #include <stdio.h>
244 #include <stddef.h>
245 void trigger_crash()
246 {
247 int* ptr = NULL;
248 *ptr = 1024;
249 }
250 */
251 import "C"
252 import (
253 "flag"
254 "fmt"
255 "os"
256 "runtime/debug"
257 "syscall"
258 )
259
260 func enableCore() {
261 debug.SetTraceback("crash")
262
263 var lim syscall.Rlimit
264 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
265 if err != nil {
266 panic(fmt.Sprintf("error getting rlimit: %v", err))
267 }
268 lim.Cur = lim.Max
269 fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim)
270 err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim)
271 if err != nil {
272 panic(fmt.Sprintf("error setting rlimit: %v", err))
273 }
274 }
275
276 func main() {
277 flag.Parse()
278
279 enableCore()
280
281 C.trigger_crash()
282 }
283 `
284
285
286
287 func TestGdbCoreCrashThreadBacktrace(t *testing.T) {
288 if runtime.GOOS != "linux" {
289
290
291 t.Skip("Test only supported on Linux")
292 }
293 if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" {
294
295
296 t.Skip("Backtrace through signal handler only works on 386 and amd64")
297 }
298
299 testenv.SkipFlaky(t, 65138)
300
301 testenv.MustHaveCGO(t)
302 checkGdbEnvironment(t)
303 t.Parallel()
304 checkGdbVersion(t)
305
306 coreUsesPID := canGenerateCore(t)
307
308
309 dir := t.TempDir()
310 src := filepath.Join(dir, "main.go")
311 err := os.WriteFile(src, []byte(coreCrashThreadSource), 0644)
312 if err != nil {
313 t.Fatalf("failed to create file: %v", err)
314 }
315 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
316 cmd.Dir = dir
317 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
318 if err != nil {
319 t.Fatalf("building source %v\n%s", err, out)
320 }
321
322
323 cmd = testenv.Command(t, "./a.exe")
324 cmd.Dir = dir
325 var output bytes.Buffer
326 cmd.Stdout = &output
327 cmd.Stderr = &output
328
329 if err := cmd.Start(); err != nil {
330 t.Fatalf("error starting test binary: %v", err)
331 }
332
333 pid := cmd.Process.Pid
334
335 err = cmd.Wait()
336 t.Logf("child output:\n%s", output.String())
337 if err == nil {
338 t.Fatalf("Wait succeeded, want SIGABRT")
339 }
340 ee, ok := err.(*exec.ExitError)
341 if !ok {
342 t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee)
343 }
344 ws, ok := ee.Sys().(syscall.WaitStatus)
345 if !ok {
346 t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys())
347 }
348 if ws.Signal() != syscall.SIGABRT {
349 t.Fatalf("Signal got %d want SIGABRT", ws.Signal())
350 }
351 if !ws.CoreDump() {
352 t.Fatalf("CoreDump got %v want true", ws.CoreDump())
353 }
354
355 coreFile := "core"
356 if coreUsesPID {
357 coreFile += fmt.Sprintf(".%d", pid)
358 }
359
360
361 args := []string{"-nx", "-batch",
362 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
363 "-ex", "backtrace",
364 filepath.Join(dir, "a.exe"),
365 filepath.Join(dir, coreFile),
366 }
367 cmd = testenv.Command(t, "gdb", args...)
368
369 got, err := cmd.CombinedOutput()
370 t.Logf("gdb output:\n%s", got)
371 if err != nil {
372 t.Fatalf("gdb exited with error: %v", err)
373 }
374
375 re := regexp.MustCompile(`#.* trigger_crash`)
376 if found := re.Find(got) != nil; !found {
377 t.Fatalf("could not find trigger_crash in backtrace")
378 }
379 }
380
View as plain text