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/abi"
12 "internal/goexperiment"
13 "internal/testenv"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "regexp"
18 "runtime"
19 "strconv"
20 "strings"
21 "testing"
22 "time"
23 )
24
25
26
27
28
29
30
31 func checkGdbEnvironment(t *testing.T) {
32 testenv.MustHaveGoBuild(t)
33 switch runtime.GOOS {
34 case "darwin":
35 t.Skip("gdb does not work on darwin")
36 case "netbsd":
37 t.Skip("gdb does not work with threads on NetBSD; see https://golang.org/issue/22893 and https://gnats.netbsd.org/52548")
38 case "linux":
39 if runtime.GOARCH == "ppc64" {
40 t.Skip("skipping gdb tests on linux/ppc64; see https://golang.org/issue/17366")
41 }
42 if runtime.GOARCH == "mips" {
43 t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939")
44 }
45
46 if strings.HasSuffix(testenv.Builder(), "-alpine") {
47 t.Skip("skipping gdb tests on alpine; see https://golang.org/issue/54352")
48 }
49 case "freebsd":
50 t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508")
51 case "aix":
52 if testing.Short() {
53 t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710")
54 }
55 case "plan9":
56 t.Skip("there is no gdb on Plan 9")
57 }
58 }
59
60 func checkGdbVersion(t *testing.T) {
61
62 out, err := exec.Command("gdb", "--version").CombinedOutput()
63 if err != nil {
64 t.Skipf("skipping: error executing gdb: %v", err)
65 }
66 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
67 matches := re.FindSubmatch(out)
68 if len(matches) < 3 {
69 t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
70 }
71 major, err1 := strconv.Atoi(string(matches[1]))
72 minor, err2 := strconv.Atoi(string(matches[2]))
73 if err1 != nil || err2 != nil {
74 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
75 }
76
77
78 if major < 10 {
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
121
122
123
124
125 func checkPtraceScope(t *testing.T) {
126 if runtime.GOOS != "linux" {
127 return
128 }
129
130
131
132 path := "/proc/sys/kernel/yama/ptrace_scope"
133 if _, err := os.Stat(path); os.IsNotExist(err) {
134 return
135 }
136
137 data, err := os.ReadFile(path)
138 if err != nil {
139 t.Fatalf("failed to read file: %v", err)
140 }
141 value, err := strconv.Atoi(strings.TrimSpace(string(data)))
142 if err != nil {
143 t.Fatalf("failed converting value to int: %v", err)
144 }
145 switch value {
146 case 3:
147 t.Skip("skipping ptrace: Operation not permitted")
148 case 2:
149 if os.Geteuid() != 0 {
150 t.Skip("skipping ptrace: Operation not permitted with non-root user")
151 }
152 }
153 }
154
155
156
157
158 var helloSource = `
159 import "fmt"
160 import "runtime"
161 var gslice []string
162 // TODO(prattmic): Stack allocated maps initialized inline appear "optimized out" in GDB.
163 var smallmapvar map[string]string
164 func main() {
165 smallmapvar = make(map[string]string)
166 mapvar := make(map[string]string, ` + strconv.FormatInt(abi.OldMapBucketCount+9, 10) + `)
167 slicemap := make(map[string][]string,` + strconv.FormatInt(abi.OldMapBucketCount+3, 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 }
639 if goexperiment.SwissMap {
640 types = append(types, []string{
641 "groupReference<string,main.astruct>",
642 "table<string,main.astruct>",
643 "map<string,main.astruct>",
644 "map<string,main.astruct> * map[string]main.astruct",
645 }...)
646 } else {
647 types = append(types, []string{
648 "bucket<string,main.astruct>",
649 "hash<string,main.astruct>",
650 "hash<string,main.astruct> * map[string]main.astruct",
651 }...)
652 }
653 for _, name := range types {
654 if !strings.Contains(sgot, name) {
655 t.Fatalf("could not find %q in 'info typrs astruct' output", name)
656 }
657 }
658 }
659
660 const constsSource = `
661 package main
662
663 const aConstant int = 42
664 const largeConstant uint64 = ^uint64(0)
665 const minusOne int64 = -1
666
667 func main() {
668 println("hello world")
669 }
670 `
671
672 func TestGdbConst(t *testing.T) {
673 checkGdbEnvironment(t)
674 t.Parallel()
675 checkGdbVersion(t)
676 checkPtraceScope(t)
677
678 dir := t.TempDir()
679
680
681 src := filepath.Join(dir, "main.go")
682 err := os.WriteFile(src, []byte(constsSource), 0644)
683 if err != nil {
684 t.Fatalf("failed to create file: %v", err)
685 }
686 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
687 cmd.Dir = dir
688 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
689 if err != nil {
690 t.Fatalf("building source %v\n%s", err, out)
691 }
692
693
694 args := []string{"-nx", "-batch",
695 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
696 "-ex", "set startup-with-shell off",
697 "-ex", "break main.main",
698 "-ex", "run",
699 "-ex", "print main.aConstant",
700 "-ex", "print main.largeConstant",
701 "-ex", "print main.minusOne",
702 "-ex", "print 'runtime.mSpanInUse'",
703 "-ex", "print 'runtime._PageSize'",
704 filepath.Join(dir, "a.exe"),
705 }
706 gdbArgsFixup(args)
707 got, err := exec.Command("gdb", args...).CombinedOutput()
708 t.Logf("gdb output:\n%s", got)
709 if err != nil {
710 t.Fatalf("gdb exited with error: %v", err)
711 }
712
713 sgot := strings.ReplaceAll(string(got), "\r\n", "\n")
714
715 if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
716 t.Fatalf("output mismatch")
717 }
718 }
719
720 const panicSource = `
721 package main
722
723 import "runtime/debug"
724
725 func main() {
726 debug.SetTraceback("crash")
727 crash()
728 }
729
730 func crash() {
731 panic("panic!")
732 }
733 `
734
735
736
737 func TestGdbPanic(t *testing.T) {
738 checkGdbEnvironment(t)
739 t.Parallel()
740 checkGdbVersion(t)
741 checkPtraceScope(t)
742
743 if runtime.GOOS == "windows" {
744 t.Skip("no signals on windows")
745 }
746
747 dir := t.TempDir()
748
749
750 src := filepath.Join(dir, "main.go")
751 err := os.WriteFile(src, []byte(panicSource), 0644)
752 if err != nil {
753 t.Fatalf("failed to create file: %v", err)
754 }
755 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
756 cmd.Dir = dir
757 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
758 if err != nil {
759 t.Fatalf("building source %v\n%s", err, out)
760 }
761
762
763 args := []string{"-nx", "-batch",
764 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
765 "-ex", "set startup-with-shell off",
766 "-ex", "run",
767 "-ex", "backtrace",
768 filepath.Join(dir, "a.exe"),
769 }
770 gdbArgsFixup(args)
771 got, err := exec.Command("gdb", args...).CombinedOutput()
772 t.Logf("gdb output:\n%s", got)
773 if err != nil {
774 t.Fatalf("gdb exited with error: %v", err)
775 }
776
777
778 bt := []string{
779 `crash`,
780 `main`,
781 }
782 for _, name := range bt {
783 s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
784 re := regexp.MustCompile(s)
785 if found := re.Find(got) != nil; !found {
786 t.Fatalf("could not find '%v' in backtrace", s)
787 }
788 }
789 }
790
791 const InfCallstackSource = `
792 package main
793 import "C"
794 import "time"
795
796 func loop() {
797 for i := 0; i < 1000; i++ {
798 time.Sleep(time.Millisecond*5)
799 }
800 }
801
802 func main() {
803 go loop()
804 time.Sleep(time.Second * 1)
805 }
806 `
807
808
809
810
811 func TestGdbInfCallstack(t *testing.T) {
812 checkGdbEnvironment(t)
813
814 testenv.MustHaveCGO(t)
815 if runtime.GOARCH != "arm64" {
816 t.Skip("skipping infinite callstack test on non-arm64 arches")
817 }
818
819 t.Parallel()
820 checkGdbVersion(t)
821 checkPtraceScope(t)
822
823 dir := t.TempDir()
824
825
826 src := filepath.Join(dir, "main.go")
827 err := os.WriteFile(src, []byte(InfCallstackSource), 0644)
828 if err != nil {
829 t.Fatalf("failed to create file: %v", err)
830 }
831 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
832 cmd.Dir = dir
833 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
834 if err != nil {
835 t.Fatalf("building source %v\n%s", err, out)
836 }
837
838
839
840 args := []string{"-nx", "-batch",
841 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
842 "-ex", "set startup-with-shell off",
843 "-ex", "break setg_gcc",
844 "-ex", "run",
845 "-ex", "backtrace 3",
846 "-ex", "disable 1",
847 "-ex", "continue",
848 filepath.Join(dir, "a.exe"),
849 }
850 gdbArgsFixup(args)
851 got, err := exec.Command("gdb", args...).CombinedOutput()
852 t.Logf("gdb output:\n%s", got)
853 if err != nil {
854 t.Fatalf("gdb exited with error: %v", err)
855 }
856
857
858
859 bt := []string{
860 `setg_gcc`,
861 `crosscall1`,
862 `threadentry`,
863 }
864 for i, name := range bt {
865 s := fmt.Sprintf("#%v.*%v", i, name)
866 re := regexp.MustCompile(s)
867 if found := re.Find(got) != nil; !found {
868 t.Fatalf("could not find '%v' in backtrace", s)
869 }
870 }
871 }
872
View as plain text