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