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