1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime_test
6
7import (
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// NOTE: In some configurations, GDB will segfault when sent a SIGWINCH signal.
25// Some runtime tests send SIGWINCH to the entire process group, so those tests
26// must never run in parallel with GDB tests.
27//
28// See issue 39021 and https://sourceware.org/bugzilla/show_bug.cgi?id=26056.
29
30func 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		// Disable GDB tests on alpine until issue #54352 resolved.
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
59func checkGdbVersion(t *testing.T) {
60	// Issue 11214 reports various failures with older versions of gdb.
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
81func 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// checkCleanBacktrace checks that the given backtrace is well formed and does
99// not contain any error messages from GDB.
100func 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	// TODO(mundaym): check for unknown frames (e.g. "??").
112}
113
114// NOTE: the maps below are allocated larger than abi.MapBucketCount
115// to ensure that they are not "optimized out".
116
117var helloSource = `
118import "fmt"
119import "runtime"
120var gslice []string
121func 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
147func 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
157func gdbArgsFixup(args []string) {
158	if runtime.GOOS != "windows" {
159		return
160	}
161	// On Windows, some gdb flavors expect -ex and -iex arguments
162	// containing spaces to be double quoted.
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
176func TestGdbPython(t *testing.T) {
177	testGdbPython(t, false)
178}
179
180func TestGdbPythonCgo(t *testing.T) {
181	if strings.HasPrefix(runtime.GOARCH, "mips") {
182		testenv.SkipFlaky(t, 37794)
183	}
184	testGdbPython(t, true)
185}
186
187func 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	// Locate breakpoint line
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		// When we build the cgo version of the program, the system's
238		// linker is used. Some external linkers, like GNU gold,
239		// compress the .debug_gdb_scripts into .zdebug_gdb_scripts.
240		// Until gold and gdb can work together, temporarily load the
241		// python script directly.
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", // clear the previous break point
282		"-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main
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")) // normalize line endings
297	// Extract named BEGIN...END blocks from output
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	// 2 orders, and possible differences in spacing.
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	// The exact format of composite values has changed over time.
339	// For issue 16338: ssa decompose phase split a slice into
340	// a collection of scalar vars holding its fields. In such cases
341	// the DWARF variable location expression should be of the
342	// form "var.field" and not just "field".
343	// However, the newer dwarf location list code reconstituted
344	// aggregates from their fields and reverted their printing
345	// back to its original form.
346	// Only test that all variables are listed in 'info locals' since
347	// different versions of gdb print variables in different
348	// order and with differing amount of information and formats.
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	// Check that the backtraces are well formed.
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
375const backtraceSource = `
376package main
377
378//go:noinline
379func aaa() bool { return bbb() }
380
381//go:noinline
382func bbb() bool { return ccc() }
383
384//go:noinline
385func ccc() bool { return ddd() }
386
387//go:noinline
388func ddd() bool { return f() }
389
390//go:noinline
391func eee() bool { return true }
392
393var f = eee
394
395func main() {
396	_ = aaa()
397}
398`
399
400// TestGdbBacktrace tests that gdb can unwind the stack correctly
401// using only the DWARF debug info.
402func 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		// It is possible that this test will hang for a long time due to an
408		// apparent GDB bug reported in https://go.dev/issue/37405.
409		// If test parallelism is high enough, that might be ok: the other parallel
410		// tests will finish, and then this test will finish right before it would
411		// time out. However, if test are running sequentially, a hang in this test
412		// would likely cause the remaining tests to run out of time.
413		testenv.SkipFlaky(t, 37405)
414	}
415
416	checkGdbEnvironment(t)
417	t.Parallel()
418	checkGdbVersion(t)
419
420	dir := t.TempDir()
421
422	// Build the source code.
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	// Execute gdb commands.
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	// Work around the GDB hang reported in https://go.dev/issue/37405.
450	// Sometimes (rarely), the GDB process hangs completely when the Go program
451	// exits, and we suspect that the bug is on the GDB side.
452	//
453	// The default Cancel function added by testenv.Command will mark the test as
454	// failed if it is in danger of timing out, but we want to instead mark it as
455	// skipped. Change the Cancel function to kill the process and merely log
456	// instead of failing the test.
457	//
458	// (This approach does not scale: if the test parallelism is less than or
459	// equal to the number of tests that run right up to the deadline, then the
460	// remaining parallel tests are likely to time out. But as long as it's just
461	// this one flaky test, it's probably fine..?)
462	//
463	// If there is no deadline set on the test at all, relying on the timeout set
464	// by testenv.Command will cause the test to hang indefinitely, but that's
465	// what “no deadline” means, after all — and it's probably the right behavior
466	// anyway if someone is trying to investigate and fix the GDB bug.
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			// GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28551
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			// GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=9086
483			testenv.SkipFlaky(t, 50838)
484		case bytes.Contains(got, []byte("waiting for new child: No child processes.")):
485			// GDB bug: Sometimes it fails to wait for a clone child.
486			testenv.SkipFlaky(t, 60553)
487		case bytes.Contains(got, []byte(" exited normally]\n")):
488			// GDB bug: Sometimes the inferior exits fine,
489			// but then GDB hangs.
490			testenv.SkipFlaky(t, 37405)
491		}
492		t.Fatalf("gdb exited with error: %v", err)
493	}
494
495	// Check that the backtrace matches the source code.
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
513const autotmpTypeSource = `
514package main
515
516type astruct struct {
517	a, b int
518}
519
520func main() {
521	var iface interface{} = map[string]astruct{}
522	var iface2 interface{} = []astruct{}
523	println(iface, iface2)
524}
525`
526
527// TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
528// See bug #17830.
529func 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	// Build the source code.
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	// Execute gdb commands.
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		// Some gdb may set scheduling-locking as "step" by default. This prevents background tasks
558		// (e.g GC) from completing which may result in a hang when executing the step command.
559		// See #49852.
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	// Check that the backtrace matches the source code.
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 %s in 'info typrs astruct' output", name)
587		}
588	}
589}
590
591const constsSource = `
592package main
593
594const aConstant int = 42
595const largeConstant uint64 = ^uint64(0)
596const minusOne int64 = -1
597
598func main() {
599	println("hello world")
600}
601`
602
603func TestGdbConst(t *testing.T) {
604	checkGdbEnvironment(t)
605	t.Parallel()
606	checkGdbVersion(t)
607
608	dir := t.TempDir()
609
610	// Build the source code.
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	// Execute gdb commands.
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
650const panicSource = `
651package main
652
653import "runtime/debug"
654
655func main() {
656	debug.SetTraceback("crash")
657	crash()
658}
659
660func crash() {
661	panic("panic!")
662}
663`
664
665// TestGdbPanic tests that gdb can unwind the stack correctly
666// from SIGABRTs from Go panics.
667func 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	// Build the source code.
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	// Execute gdb commands.
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	// Check that the backtrace matches the source code.
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
720const InfCallstackSource = `
721package main
722import "C"
723import "time"
724
725func loop() {
726        for i := 0; i < 1000; i++ {
727                time.Sleep(time.Millisecond*5)
728        }
729}
730
731func main() {
732        go loop()
733        time.Sleep(time.Second * 1)
734}
735`
736
737// TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs
738// on arm64 platforms without endless frames of function 'crossfunc1'.
739// https://golang.org/issue/37238
740func 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	// Build the source code.
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	// Execute gdb commands.
767	// 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command.
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	// Check that the backtrace matches
786	// We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c
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