Source file
src/runtime/runtime-gdb_test.go
1
2
3
4
5 package runtime_test
6
7 import (
8 "bytes"
9 "flag"
10 "fmt"
11 "internal/testenv"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "regexp"
16 "runtime"
17 "strconv"
18 "strings"
19 "testing"
20 "time"
21 )
22
23
24
25
26
27
28
29 func checkGdbEnvironment(t *testing.T) {
30 testenv.MustHaveGoBuild(t)
31 switch runtime.GOOS {
32 case "darwin":
33 t.Skip("gdb does not work on darwin")
34 case "netbsd":
35 t.Skip("gdb does not work with threads on NetBSD; see https://golang.org/issue/22893 and https://gnats.netbsd.org/52548")
36 case "linux":
37 if runtime.GOARCH == "ppc64" {
38 t.Skip("skipping gdb tests on linux/ppc64; see https://golang.org/issue/17366")
39 }
40 if runtime.GOARCH == "mips" {
41 t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939")
42 }
43
44 if strings.HasSuffix(testenv.Builder(), "-alpine") {
45 t.Skip("skipping gdb tests on alpine; see https://golang.org/issue/54352")
46 }
47 case "freebsd":
48 t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508")
49 case "aix":
50 if testing.Short() {
51 t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710")
52 }
53 case "plan9":
54 t.Skip("there is no gdb on Plan 9")
55 }
56 }
57
58 func checkGdbVersion(t *testing.T) {
59
60 out, err := exec.Command("gdb", "--version").CombinedOutput()
61 if err != nil {
62 t.Skipf("skipping: error executing gdb: %v", err)
63 }
64 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
65 matches := re.FindSubmatch(out)
66 if len(matches) < 3 {
67 t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
68 }
69 major, err1 := strconv.Atoi(string(matches[1]))
70 minor, err2 := strconv.Atoi(string(matches[2]))
71 if err1 != nil || err2 != nil {
72 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
73 }
74
75
76 if major < 10 {
77 t.Skipf("skipping: gdb version %d.%d too old", major, minor)
78 }
79 if major < 12 || (major == 12 && minor < 1) {
80 t.Logf("gdb version <12.1 is known to crash due to a SIGWINCH recieved in non-interactive mode; if you see a crash, some test may be sending SIGWINCH to the whole process group. See go.dev/issue/58932.")
81 }
82 t.Logf("gdb version %d.%d", major, minor)
83 }
84
85 func checkGdbPython(t *testing.T) {
86 if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
87 t.Skip("skipping gdb python tests on illumos and solaris; see golang.org/issue/20821")
88 }
89 args := []string{"-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')"}
90 gdbArgsFixup(args)
91 cmd := exec.Command("gdb", args...)
92 out, err := cmd.CombinedOutput()
93
94 if err != nil {
95 t.Skipf("skipping due to issue running gdb: %v", err)
96 }
97 if strings.TrimSpace(string(out)) != "go gdb python support" {
98 t.Skipf("skipping due to lack of python gdb support: %s", out)
99 }
100 }
101
102
103
104 func checkCleanBacktrace(t *testing.T, backtrace string) {
105 backtrace = strings.TrimSpace(backtrace)
106 lines := strings.Split(backtrace, "\n")
107 if len(lines) == 0 {
108 t.Fatalf("empty backtrace")
109 }
110 for i, l := range lines {
111 if !strings.HasPrefix(l, fmt.Sprintf("#%v ", i)) {
112 t.Fatalf("malformed backtrace at line %v: %v", i, l)
113 }
114 }
115
116 }
117
118
119
120
121
122
123
124
125
126 func checkPtraceScope(t *testing.T) {
127 if runtime.GOOS != "linux" {
128 return
129 }
130
131
132
133 path := "/proc/sys/kernel/yama/ptrace_scope"
134 if _, err := os.Stat(path); os.IsNotExist(err) {
135 return
136 }
137
138 data, err := os.ReadFile(path)
139 if err != nil {
140 t.Fatalf("failed to read file: %v", err)
141 }
142 value, err := strconv.Atoi(strings.TrimSpace(string(data)))
143 if err != nil {
144 t.Fatalf("failed converting value to int: %v", err)
145 }
146 switch value {
147 case 3:
148 t.Skip("skipping ptrace: Operation not permitted")
149 case 2:
150 if os.Geteuid() != 0 {
151 t.Skip("skipping ptrace: Operation not permitted with non-root user")
152 }
153 }
154 }
155
156 var helloSource = `
157 import "fmt"
158 import "runtime"
159 var gslice []string
160 // TODO(prattmic): Stack allocated maps initialized inline appear "optimized out" in GDB.
161 var smallmapvar map[string]string
162 func main() {
163 smallmapvar = make(map[string]string)
164 // NOTE: the maps below are allocated large to ensure that they are not
165 // "optimized out".
166 mapvar := make(map[string]string, 10)
167 slicemap := make(map[string][]string, 10)
168 chanint := make(chan int, 10)
169 chanstr := make(chan string, 10)
170 chanint <- 99
171 chanint <- 11
172 chanstr <- "spongepants"
173 chanstr <- "squarebob"
174 smallmapvar["abc"] = "def"
175 mapvar["abc"] = "def"
176 mapvar["ghi"] = "jkl"
177 slicemap["a"] = []string{"b","c","d"}
178 slicemap["e"] = []string{"f","g","h"}
179 strvar := "abc"
180 ptrvar := &strvar
181 slicevar := make([]string, 0, 16)
182 slicevar = append(slicevar, mapvar["abc"])
183 fmt.Println("hi")
184 runtime.KeepAlive(ptrvar)
185 _ = ptrvar // set breakpoint here
186 gslice = slicevar
187 fmt.Printf("%v, %v, %v\n", slicemap, <-chanint, <-chanstr)
188 runtime.KeepAlive(smallmapvar)
189 runtime.KeepAlive(mapvar)
190 } // END_OF_PROGRAM
191 `
192
193 func lastLine(src []byte) int {
194 eop := []byte("END_OF_PROGRAM")
195 for i, l := range bytes.Split(src, []byte("\n")) {
196 if bytes.Contains(l, eop) {
197 return i
198 }
199 }
200 return 0
201 }
202
203 func gdbArgsFixup(args []string) {
204 if runtime.GOOS != "windows" {
205 return
206 }
207
208
209 var quote bool
210 for i, arg := range args {
211 if arg == "-iex" || arg == "-ex" {
212 quote = true
213 } else if quote {
214 if strings.ContainsRune(arg, ' ') {
215 args[i] = `"` + arg + `"`
216 }
217 quote = false
218 }
219 }
220 }
221
222 func TestGdbPython(t *testing.T) {
223 testGdbPython(t, false)
224 }
225
226 func TestGdbPythonCgo(t *testing.T) {
227 if strings.HasPrefix(runtime.GOARCH, "mips") {
228 testenv.SkipFlaky(t, 37794)
229 }
230 testGdbPython(t, true)
231 }
232
233 func testGdbPython(t *testing.T, cgo bool) {
234 if cgo {
235 testenv.MustHaveCGO(t)
236 }
237
238 checkGdbEnvironment(t)
239 t.Parallel()
240 checkGdbVersion(t)
241 checkGdbPython(t)
242 checkPtraceScope(t)
243
244 dir := t.TempDir()
245
246 var buf bytes.Buffer
247 buf.WriteString("package main\n")
248 if cgo {
249 buf.WriteString(`import "C"` + "\n")
250 }
251 buf.WriteString(helloSource)
252
253 src := buf.Bytes()
254
255
256 var bp int
257 lines := bytes.Split(src, []byte("\n"))
258 for i, line := range lines {
259 if bytes.Contains(line, []byte("breakpoint")) {
260 bp = i
261 break
262 }
263 }
264
265 err := os.WriteFile(filepath.Join(dir, "main.go"), src, 0644)
266 if err != nil {
267 t.Fatalf("failed to create file: %v", err)
268 }
269 nLines := lastLine(src)
270
271 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
272 cmd.Dir = dir
273 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
274 if err != nil {
275 t.Fatalf("building source %v\n%s", err, out)
276 }
277
278 args := []string{"-nx", "-q", "--batch",
279 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
280 "-ex", "set startup-with-shell off",
281 "-ex", "set print thread-events off",
282 }
283 if cgo {
284
285
286
287
288
289 args = append(args,
290 "-ex", "source "+filepath.Join(testenv.GOROOT(t), "src", "runtime", "runtime-gdb.py"),
291 )
292 } else {
293 args = append(args,
294 "-ex", "info auto-load python-scripts",
295 )
296 }
297 args = append(args,
298 "-ex", "set python print-stack full",
299 "-ex", fmt.Sprintf("br main.go:%d", bp),
300 "-ex", "run",
301 "-ex", "echo BEGIN info goroutines\n",
302 "-ex", "info goroutines",
303 "-ex", "echo END\n",
304 "-ex", "echo BEGIN print smallmapvar\n",
305 "-ex", "print smallmapvar",
306 "-ex", "echo END\n",
307 "-ex", "echo BEGIN print mapvar\n",
308 "-ex", "print mapvar",
309 "-ex", "echo END\n",
310 "-ex", "echo BEGIN print slicemap\n",
311 "-ex", "print slicemap",
312 "-ex", "echo END\n",
313 "-ex", "echo BEGIN print strvar\n",
314 "-ex", "print strvar",
315 "-ex", "echo END\n",
316 "-ex", "echo BEGIN print chanint\n",
317 "-ex", "print chanint",
318 "-ex", "echo END\n",
319 "-ex", "echo BEGIN print chanstr\n",
320 "-ex", "print chanstr",
321 "-ex", "echo END\n",
322 "-ex", "echo BEGIN info locals\n",
323 "-ex", "info locals",
324 "-ex", "echo END\n",
325 "-ex", "echo BEGIN goroutine 1 bt\n",
326 "-ex", "goroutine 1 bt",
327 "-ex", "echo END\n",
328 "-ex", "echo BEGIN goroutine all bt\n",
329 "-ex", "goroutine all bt",
330 "-ex", "echo END\n",
331 "-ex", "clear main.go:15",
332 "-ex", fmt.Sprintf("br main.go:%d", nLines),
333 "-ex", "c",
334 "-ex", "echo BEGIN goroutine 1 bt at the end\n",
335 "-ex", "goroutine 1 bt",
336 "-ex", "echo END\n",
337 filepath.Join(dir, "a.exe"),
338 )
339 gdbArgsFixup(args)
340 got, err := exec.Command("gdb", args...).CombinedOutput()
341 t.Logf("gdb output:\n%s", got)
342 if err != nil {
343 t.Fatalf("gdb exited with error: %v", err)
344 }
345
346 got = bytes.ReplaceAll(got, []byte("\r\n"), []byte("\n"))
347
348 partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
349 blocks := map[string]string{}
350 for _, subs := range partRe.FindAllSubmatch(got, -1) {
351 blocks[string(subs[1])] = string(subs[2])
352 }
353
354 infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
355 if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
356 t.Fatalf("info goroutines failed: %s", bl)
357 }
358
359 printSmallMapvarRe := regexp.MustCompile(`^\$[0-9]+ = map\[string\]string = {\[(0x[0-9a-f]+\s+)?"abc"\] = (0x[0-9a-f]+\s+)?"def"}$`)
360 if bl := blocks["print smallmapvar"]; !printSmallMapvarRe.MatchString(bl) {
361 t.Fatalf("print smallmapvar failed: %s", bl)
362 }
363
364 printMapvarRe1 := regexp.MustCompile(`^\$[0-9]+ = map\[string\]string = {\[(0x[0-9a-f]+\s+)?"abc"\] = (0x[0-9a-f]+\s+)?"def", \[(0x[0-9a-f]+\s+)?"ghi"\] = (0x[0-9a-f]+\s+)?"jkl"}$`)
365 printMapvarRe2 := regexp.MustCompile(`^\$[0-9]+ = map\[string\]string = {\[(0x[0-9a-f]+\s+)?"ghi"\] = (0x[0-9a-f]+\s+)?"jkl", \[(0x[0-9a-f]+\s+)?"abc"\] = (0x[0-9a-f]+\s+)?"def"}$`)
366 if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
367 !printMapvarRe2.MatchString(bl) {
368 t.Fatalf("print mapvar failed: %s", bl)
369 }
370
371
372 sliceMapSfx1 := `map[string][]string = {["e"] = []string = {"f", "g", "h"}, ["a"] = []string = {"b", "c", "d"}}`
373 sliceMapSfx2 := `map[string][]string = {["a"] = []string = {"b", "c", "d"}, ["e"] = []string = {"f", "g", "h"}}`
374 if bl := strings.ReplaceAll(blocks["print slicemap"], " ", " "); !strings.HasSuffix(bl, sliceMapSfx1) && !strings.HasSuffix(bl, sliceMapSfx2) {
375 t.Fatalf("print slicemap failed: %s", bl)
376 }
377
378 chanIntSfx := `chan int = {99, 11}`
379 if bl := strings.ReplaceAll(blocks["print chanint"], " ", " "); !strings.HasSuffix(bl, chanIntSfx) {
380 t.Fatalf("print chanint failed: %s", bl)
381 }
382
383 chanStrSfx := `chan string = {"spongepants", "squarebob"}`
384 if bl := strings.ReplaceAll(blocks["print chanstr"], " ", " "); !strings.HasSuffix(bl, chanStrSfx) {
385 t.Fatalf("print chanstr failed: %s", bl)
386 }
387
388 strVarRe := regexp.MustCompile(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`)
389 if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
390 t.Fatalf("print strvar failed: %s", bl)
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404
405 if bl := blocks["info locals"]; !strings.Contains(bl, "slicevar") ||
406 !strings.Contains(bl, "mapvar") ||
407 !strings.Contains(bl, "strvar") {
408 t.Fatalf("info locals failed: %s", bl)
409 }
410
411
412 checkCleanBacktrace(t, blocks["goroutine 1 bt"])
413 checkCleanBacktrace(t, blocks["goroutine 1 bt at the end"])
414
415 btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
416 if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
417 t.Fatalf("goroutine 1 bt failed: %s", bl)
418 }
419
420 if bl := blocks["goroutine all bt"]; !btGoroutine1Re.MatchString(bl) {
421 t.Fatalf("goroutine all bt failed: %s", bl)
422 }
423
424 btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
425 if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
426 t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
427 }
428 }
429
430 const backtraceSource = `
431 package main
432
433 //go:noinline
434 func aaa() bool { return bbb() }
435
436 //go:noinline
437 func bbb() bool { return ccc() }
438
439 //go:noinline
440 func ccc() bool { return ddd() }
441
442 //go:noinline
443 func ddd() bool { return f() }
444
445 //go:noinline
446 func eee() bool { return true }
447
448 var f = eee
449
450 func main() {
451 _ = aaa()
452 }
453 `
454
455
456
457 func TestGdbBacktrace(t *testing.T) {
458 if runtime.GOOS == "netbsd" {
459 testenv.SkipFlaky(t, 15603)
460 }
461 if flag.Lookup("test.parallel").Value.(flag.Getter).Get().(int) < 2 {
462
463
464
465
466
467
468 testenv.SkipFlaky(t, 37405)
469 }
470
471 checkGdbEnvironment(t)
472 t.Parallel()
473 checkGdbVersion(t)
474 checkPtraceScope(t)
475
476 dir := t.TempDir()
477
478
479 src := filepath.Join(dir, "main.go")
480 err := os.WriteFile(src, []byte(backtraceSource), 0644)
481 if err != nil {
482 t.Fatalf("failed to create file: %v", err)
483 }
484 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
485 cmd.Dir = dir
486 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
487 if err != nil {
488 t.Fatalf("building source %v\n%s", err, out)
489 }
490
491
492 start := time.Now()
493 args := []string{"-nx", "-batch",
494 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
495 "-ex", "set startup-with-shell off",
496 "-ex", "break main.eee",
497 "-ex", "run",
498 "-ex", "backtrace",
499 "-ex", "continue",
500 filepath.Join(dir, "a.exe"),
501 }
502 gdbArgsFixup(args)
503 cmd = testenv.Command(t, "gdb", args...)
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523 cmd.Cancel = func() error {
524 t.Logf("GDB command timed out after %v: %v", time.Since(start), cmd)
525 return cmd.Process.Kill()
526 }
527
528 got, err := cmd.CombinedOutput()
529 t.Logf("gdb output:\n%s", got)
530 if err != nil {
531 noProcessRE := regexp.MustCompile(`Couldn't get [a-zA-Z_ -]* ?registers: No such process\.`)
532 switch {
533 case bytes.Contains(got, []byte("internal-error: wait returned unexpected status 0x0")):
534
535 testenv.SkipFlaky(t, 43068)
536 case noProcessRE.Match(got),
537 bytes.Contains(got, []byte("Unable to fetch general registers.: No such process.")),
538 bytes.Contains(got, []byte("reading register pc (#64): No such process.")):
539
540 testenv.SkipFlaky(t, 50838)
541 case bytes.Contains(got, []byte("waiting for new child: No child processes.")):
542
543 testenv.SkipFlaky(t, 60553)
544 case bytes.Contains(got, []byte(" exited normally]\n")):
545
546
547 testenv.SkipFlaky(t, 37405)
548 }
549 t.Fatalf("gdb exited with error: %v", err)
550 }
551
552
553 bt := []string{
554 "eee",
555 "ddd",
556 "ccc",
557 "bbb",
558 "aaa",
559 "main",
560 }
561 for i, name := range bt {
562 s := fmt.Sprintf("#%v.*main\\.%v", i, name)
563 re := regexp.MustCompile(s)
564 if found := re.Find(got) != nil; !found {
565 t.Fatalf("could not find '%v' in backtrace", s)
566 }
567 }
568 }
569
570 const autotmpTypeSource = `
571 package main
572
573 type astruct struct {
574 a, b int
575 }
576
577 func main() {
578 var iface interface{} = map[string]astruct{}
579 var iface2 interface{} = []astruct{}
580 println(iface, iface2)
581 }
582 `
583
584
585
586 func TestGdbAutotmpTypes(t *testing.T) {
587 checkGdbEnvironment(t)
588 t.Parallel()
589 checkGdbVersion(t)
590 checkPtraceScope(t)
591
592 if runtime.GOOS == "aix" && testing.Short() {
593 t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64")
594 }
595
596 dir := t.TempDir()
597
598
599 src := filepath.Join(dir, "main.go")
600 err := os.WriteFile(src, []byte(autotmpTypeSource), 0644)
601 if err != nil {
602 t.Fatalf("failed to create file: %v", err)
603 }
604 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
605 cmd.Dir = dir
606 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
607 if err != nil {
608 t.Fatalf("building source %v\n%s", err, out)
609 }
610
611
612 args := []string{"-nx", "-batch",
613 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
614 "-ex", "set startup-with-shell off",
615
616
617
618 "-ex", "set scheduler-locking off",
619 "-ex", "break main.main",
620 "-ex", "run",
621 "-ex", "step",
622 "-ex", "info types astruct",
623 filepath.Join(dir, "a.exe"),
624 }
625 gdbArgsFixup(args)
626 got, err := exec.Command("gdb", args...).CombinedOutput()
627 t.Logf("gdb output:\n%s", got)
628 if err != nil {
629 t.Fatalf("gdb exited with error: %v", err)
630 }
631
632 sgot := string(got)
633
634
635 types := []string{
636 "[]main.astruct",
637 "main.astruct",
638 "groupReference<string,main.astruct>",
639 "table<string,main.astruct>",
640 "map<string,main.astruct>",
641 "map<string,main.astruct> * map[string]main.astruct",
642 }
643 for _, name := range types {
644 if !strings.Contains(sgot, name) {
645 t.Fatalf("could not find %q in 'info typrs astruct' output", name)
646 }
647 }
648 }
649
650 const constsSource = `
651 package main
652
653 const aConstant int = 42
654 const largeConstant uint64 = ^uint64(0)
655 const minusOne int64 = -1
656
657 func main() {
658 println("hello world")
659 }
660 `
661
662 func TestGdbConst(t *testing.T) {
663 checkGdbEnvironment(t)
664 t.Parallel()
665 checkGdbVersion(t)
666 checkPtraceScope(t)
667
668 dir := t.TempDir()
669
670
671 src := filepath.Join(dir, "main.go")
672 err := os.WriteFile(src, []byte(constsSource), 0644)
673 if err != nil {
674 t.Fatalf("failed to create file: %v", err)
675 }
676 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
677 cmd.Dir = dir
678 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
679 if err != nil {
680 t.Fatalf("building source %v\n%s", err, out)
681 }
682
683
684 args := []string{"-nx", "-batch",
685 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
686 "-ex", "set startup-with-shell off",
687 "-ex", "break main.main",
688 "-ex", "run",
689 "-ex", "print main.aConstant",
690 "-ex", "print main.largeConstant",
691 "-ex", "print main.minusOne",
692 "-ex", "print 'runtime.mSpanInUse'",
693 "-ex", "print 'runtime._PageSize'",
694 filepath.Join(dir, "a.exe"),
695 }
696 gdbArgsFixup(args)
697 got, err := exec.Command("gdb", args...).CombinedOutput()
698 t.Logf("gdb output:\n%s", got)
699 if err != nil {
700 t.Fatalf("gdb exited with error: %v", err)
701 }
702
703 sgot := strings.ReplaceAll(string(got), "\r\n", "\n")
704
705 if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
706 t.Fatalf("output mismatch")
707 }
708 }
709
710 const panicSource = `
711 package main
712
713 import "runtime/debug"
714
715 func main() {
716 debug.SetTraceback("crash")
717 crash()
718 }
719
720 func crash() {
721 panic("panic!")
722 }
723 `
724
725
726
727 func TestGdbPanic(t *testing.T) {
728 checkGdbEnvironment(t)
729 t.Parallel()
730 checkGdbVersion(t)
731 checkPtraceScope(t)
732
733 if runtime.GOOS == "windows" {
734 t.Skip("no signals on windows")
735 }
736
737 dir := t.TempDir()
738
739
740 src := filepath.Join(dir, "main.go")
741 err := os.WriteFile(src, []byte(panicSource), 0644)
742 if err != nil {
743 t.Fatalf("failed to create file: %v", err)
744 }
745 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
746 cmd.Dir = dir
747 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
748 if err != nil {
749 t.Fatalf("building source %v\n%s", err, out)
750 }
751
752
753 args := []string{"-nx", "-batch",
754 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
755 "-ex", "set startup-with-shell off",
756 "-ex", "run",
757 "-ex", "backtrace",
758 filepath.Join(dir, "a.exe"),
759 }
760 gdbArgsFixup(args)
761 got, err := exec.Command("gdb", args...).CombinedOutput()
762 t.Logf("gdb output:\n%s", got)
763 if err != nil {
764 t.Fatalf("gdb exited with error: %v", err)
765 }
766
767
768 bt := []string{
769 `crash`,
770 `main`,
771 }
772 for _, name := range bt {
773 s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
774 re := regexp.MustCompile(s)
775 if found := re.Find(got) != nil; !found {
776 t.Fatalf("could not find '%v' in backtrace", s)
777 }
778 }
779 }
780
781 const InfCallstackSource = `
782 package main
783 import "C"
784 import "time"
785
786 func loop() {
787 for i := 0; i < 1000; i++ {
788 time.Sleep(time.Millisecond*5)
789 }
790 }
791
792 func main() {
793 go loop()
794 time.Sleep(time.Second * 1)
795 }
796 `
797
798
799
800
801 func TestGdbInfCallstack(t *testing.T) {
802 checkGdbEnvironment(t)
803
804 testenv.MustHaveCGO(t)
805 if runtime.GOARCH != "arm64" {
806 t.Skip("skipping infinite callstack test on non-arm64 arches")
807 }
808
809 t.Parallel()
810 checkGdbVersion(t)
811 checkPtraceScope(t)
812
813 dir := t.TempDir()
814
815
816 src := filepath.Join(dir, "main.go")
817 err := os.WriteFile(src, []byte(InfCallstackSource), 0644)
818 if err != nil {
819 t.Fatalf("failed to create file: %v", err)
820 }
821 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
822 cmd.Dir = dir
823 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
824 if err != nil {
825 t.Fatalf("building source %v\n%s", err, out)
826 }
827
828
829
830 args := []string{"-nx", "-batch",
831 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
832 "-ex", "set startup-with-shell off",
833 "-ex", "break setg_gcc",
834 "-ex", "run",
835 "-ex", "backtrace 3",
836 "-ex", "disable 1",
837 "-ex", "continue",
838 filepath.Join(dir, "a.exe"),
839 }
840 gdbArgsFixup(args)
841 got, err := exec.Command("gdb", args...).CombinedOutput()
842 t.Logf("gdb output:\n%s", got)
843 if err != nil {
844 t.Fatalf("gdb exited with error: %v", err)
845 }
846
847
848
849 bt := []string{
850 `setg_gcc`,
851 `crosscall1`,
852 `threadentry`,
853 }
854 for i, name := range bt {
855 s := fmt.Sprintf("#%v.*%v", i, name)
856 re := regexp.MustCompile(s)
857 if found := re.Find(got) != nil; !found {
858 t.Fatalf("could not find '%v' in backtrace", s)
859 }
860 }
861 }
862
View as plain text