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