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