github.com/lzhfromustc/gofuzz@v0.0.0-20211116160056-151b3108bbd1/runtime/runtime-gdb_test.go (about)

     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  
     5  package runtime_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  // NOTE: In some configurations, GDB will segfault when sent a SIGWINCH signal.
    22  // Some runtime tests send SIGWINCH to the entire process group, so those tests
    23  // must never run in parallel with GDB tests.
    24  //
    25  // See issue 39021 and https://sourceware.org/bugzilla/show_bug.cgi?id=26056.
    26  
    27  func checkGdbEnvironment(t *testing.T) {
    28  	testenv.MustHaveGoBuild(t)
    29  	switch runtime.GOOS {
    30  	case "darwin":
    31  		t.Skip("gdb does not work on darwin")
    32  	case "netbsd":
    33  		t.Skip("gdb does not work with threads on NetBSD; see https://golang.org/issue/22893 and https://gnats.netbsd.org/52548")
    34  	case "windows":
    35  		t.Skip("gdb tests fail on Windows: https://golang.org/issue/22687")
    36  	case "linux":
    37  		if runtime.GOARCH == "ppc64" {
    38  			t.Skip("skipping gdb tests on linux/ppc64; see https://golang.org/issue/17366")
    39  		}
    40  		if runtime.GOARCH == "mips" {
    41  			t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939")
    42  		}
    43  	case "freebsd":
    44  		t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508")
    45  	case "aix":
    46  		if testing.Short() {
    47  			t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710")
    48  		}
    49  	case "plan9":
    50  		t.Skip("there is no gdb on Plan 9")
    51  	}
    52  	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
    53  		t.Skip("gdb test can fail with GOROOT_FINAL pending")
    54  	}
    55  }
    56  
    57  func checkGdbVersion(t *testing.T) {
    58  	// Issue 11214 reports various failures with older versions of gdb.
    59  	out, err := exec.Command("gdb", "--version").CombinedOutput()
    60  	if err != nil {
    61  		t.Skipf("skipping: error executing gdb: %v", err)
    62  	}
    63  	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
    64  	matches := re.FindSubmatch(out)
    65  	if len(matches) < 3 {
    66  		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
    67  	}
    68  	major, err1 := strconv.Atoi(string(matches[1]))
    69  	minor, err2 := strconv.Atoi(string(matches[2]))
    70  	if err1 != nil || err2 != nil {
    71  		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
    72  	}
    73  	if major < 7 || (major == 7 && minor < 7) {
    74  		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
    75  	}
    76  	t.Logf("gdb version %d.%d", major, minor)
    77  }
    78  
    79  func checkGdbPython(t *testing.T) {
    80  	if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
    81  		t.Skip("skipping gdb python tests on illumos and solaris; see golang.org/issue/20821")
    82  	}
    83  
    84  	cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
    85  	out, err := cmd.CombinedOutput()
    86  
    87  	if err != nil {
    88  		t.Skipf("skipping due to issue running gdb: %v", err)
    89  	}
    90  	if strings.TrimSpace(string(out)) != "go gdb python support" {
    91  		t.Skipf("skipping due to lack of python gdb support: %s", out)
    92  	}
    93  }
    94  
    95  // checkCleanBacktrace checks that the given backtrace is well formed and does
    96  // not contain any error messages from GDB.
    97  func checkCleanBacktrace(t *testing.T, backtrace string) {
    98  	backtrace = strings.TrimSpace(backtrace)
    99  	lines := strings.Split(backtrace, "\n")
   100  	if len(lines) == 0 {
   101  		t.Fatalf("empty backtrace")
   102  	}
   103  	for i, l := range lines {
   104  		if !strings.HasPrefix(l, fmt.Sprintf("#%v  ", i)) {
   105  			t.Fatalf("malformed backtrace at line %v: %v", i, l)
   106  		}
   107  	}
   108  	// TODO(mundaym): check for unknown frames (e.g. "??").
   109  }
   110  
   111  const helloSource = `
   112  import "fmt"
   113  import "runtime"
   114  var gslice []string
   115  func main() {
   116  	mapvar := make(map[string]string, 13)
   117  	slicemap := make(map[string][]string,11)
   118      chanint := make(chan int, 10)
   119      chanstr := make(chan string, 10)
   120      chanint <- 99
   121  	chanint <- 11
   122      chanstr <- "spongepants"
   123      chanstr <- "squarebob"
   124  	mapvar["abc"] = "def"
   125  	mapvar["ghi"] = "jkl"
   126  	slicemap["a"] = []string{"b","c","d"}
   127      slicemap["e"] = []string{"f","g","h"}
   128  	strvar := "abc"
   129  	ptrvar := &strvar
   130  	slicevar := make([]string, 0, 16)
   131  	slicevar = append(slicevar, mapvar["abc"])
   132  	fmt.Println("hi")
   133  	runtime.KeepAlive(ptrvar)
   134  	_ = ptrvar // set breakpoint here
   135  	gslice = slicevar
   136  	fmt.Printf("%v, %v, %v\n", slicemap, <-chanint, <-chanstr)
   137  	runtime.KeepAlive(mapvar)
   138  }  // END_OF_PROGRAM
   139  `
   140  
   141  func lastLine(src []byte) int {
   142  	eop := []byte("END_OF_PROGRAM")
   143  	for i, l := range bytes.Split(src, []byte("\n")) {
   144  		if bytes.Contains(l, eop) {
   145  			return i
   146  		}
   147  	}
   148  	return 0
   149  }
   150  
   151  func TestGdbPython(t *testing.T) {
   152  	testGdbPython(t, false)
   153  }
   154  
   155  func TestGdbPythonCgo(t *testing.T) {
   156  	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" {
   157  		testenv.SkipFlaky(t, 18784)
   158  	}
   159  	testGdbPython(t, true)
   160  }
   161  
   162  func testGdbPython(t *testing.T, cgo bool) {
   163  	if cgo {
   164  		testenv.MustHaveCGO(t)
   165  	}
   166  
   167  	checkGdbEnvironment(t)
   168  	t.Parallel()
   169  	checkGdbVersion(t)
   170  	checkGdbPython(t)
   171  
   172  	dir, err := os.MkdirTemp("", "go-build")
   173  	if err != nil {
   174  		t.Fatalf("failed to create temp directory: %v", err)
   175  	}
   176  	defer os.RemoveAll(dir)
   177  
   178  	var buf bytes.Buffer
   179  	buf.WriteString("package main\n")
   180  	if cgo {
   181  		buf.WriteString(`import "C"` + "\n")
   182  	}
   183  	buf.WriteString(helloSource)
   184  
   185  	src := buf.Bytes()
   186  
   187  	// Locate breakpoint line
   188  	var bp int
   189  	lines := bytes.Split(src, []byte("\n"))
   190  	for i, line := range lines {
   191  		if bytes.Contains(line, []byte("breakpoint")) {
   192  			bp = i
   193  			break
   194  		}
   195  	}
   196  
   197  	err = os.WriteFile(filepath.Join(dir, "main.go"), src, 0644)
   198  	if err != nil {
   199  		t.Fatalf("failed to create file: %v", err)
   200  	}
   201  	nLines := lastLine(src)
   202  
   203  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   204  	cmd.Dir = dir
   205  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   206  	if err != nil {
   207  		t.Fatalf("building source %v\n%s", err, out)
   208  	}
   209  
   210  	args := []string{"-nx", "-q", "--batch",
   211  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   212  		"-ex", "set startup-with-shell off",
   213  		"-ex", "set print thread-events off",
   214  	}
   215  	if cgo {
   216  		// When we build the cgo version of the program, the system's
   217  		// linker is used. Some external linkers, like GNU gold,
   218  		// compress the .debug_gdb_scripts into .zdebug_gdb_scripts.
   219  		// Until gold and gdb can work together, temporarily load the
   220  		// python script directly.
   221  		args = append(args,
   222  			"-ex", "source "+filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime-gdb.py"),
   223  		)
   224  	} else {
   225  		args = append(args,
   226  			"-ex", "info auto-load python-scripts",
   227  		)
   228  	}
   229  	args = append(args,
   230  		"-ex", "set python print-stack full",
   231  		"-ex", fmt.Sprintf("br main.go:%d", bp),
   232  		"-ex", "run",
   233  		"-ex", "echo BEGIN info goroutines\n",
   234  		"-ex", "info goroutines",
   235  		"-ex", "echo END\n",
   236  		"-ex", "echo BEGIN print mapvar\n",
   237  		"-ex", "print mapvar",
   238  		"-ex", "echo END\n",
   239  		"-ex", "echo BEGIN print slicemap\n",
   240  		"-ex", "print slicemap",
   241  		"-ex", "echo END\n",
   242  		"-ex", "echo BEGIN print strvar\n",
   243  		"-ex", "print strvar",
   244  		"-ex", "echo END\n",
   245  		"-ex", "echo BEGIN print chanint\n",
   246  		"-ex", "print chanint",
   247  		"-ex", "echo END\n",
   248  		"-ex", "echo BEGIN print chanstr\n",
   249  		"-ex", "print chanstr",
   250  		"-ex", "echo END\n",
   251  		"-ex", "echo BEGIN info locals\n",
   252  		"-ex", "info locals",
   253  		"-ex", "echo END\n",
   254  		"-ex", "echo BEGIN goroutine 1 bt\n",
   255  		"-ex", "goroutine 1 bt",
   256  		"-ex", "echo END\n",
   257  		"-ex", "echo BEGIN goroutine all bt\n",
   258  		"-ex", "goroutine all bt",
   259  		"-ex", "echo END\n",
   260  		"-ex", "clear main.go:15", // clear the previous break point
   261  		"-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main
   262  		"-ex", "c",
   263  		"-ex", "echo BEGIN goroutine 1 bt at the end\n",
   264  		"-ex", "goroutine 1 bt",
   265  		"-ex", "echo END\n",
   266  		filepath.Join(dir, "a.exe"),
   267  	)
   268  	got, err := exec.Command("gdb", args...).CombinedOutput()
   269  	t.Logf("gdb output:\n%s", got)
   270  	if err != nil {
   271  		t.Fatalf("gdb exited with error: %v", err)
   272  	}
   273  
   274  	firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
   275  	if string(firstLine) != "Loading Go Runtime support." {
   276  		// This can happen when using all.bash with
   277  		// GOROOT_FINAL set, because the tests are run before
   278  		// the final installation of the files.
   279  		cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
   280  		cmd.Env = []string{}
   281  		out, err := cmd.CombinedOutput()
   282  		if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
   283  			t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT())
   284  		}
   285  
   286  		_, file, _, _ := runtime.Caller(1)
   287  
   288  		t.Logf("package testing source file: %s", file)
   289  		t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
   290  	}
   291  
   292  	// Extract named BEGIN...END blocks from output
   293  	partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
   294  	blocks := map[string]string{}
   295  	for _, subs := range partRe.FindAllSubmatch(got, -1) {
   296  		blocks[string(subs[1])] = string(subs[2])
   297  	}
   298  
   299  	infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
   300  	if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
   301  		t.Fatalf("info goroutines failed: %s", bl)
   302  	}
   303  
   304  	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"}$`)
   305  	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"}$`)
   306  	if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
   307  		!printMapvarRe2.MatchString(bl) {
   308  		t.Fatalf("print mapvar failed: %s", bl)
   309  	}
   310  
   311  	// 2 orders, and possible differences in spacing.
   312  	sliceMapSfx1 := `map[string][]string = {["e"] = []string = {"f", "g", "h"}, ["a"] = []string = {"b", "c", "d"}}`
   313  	sliceMapSfx2 := `map[string][]string = {["a"] = []string = {"b", "c", "d"}, ["e"] = []string = {"f", "g", "h"}}`
   314  	if bl := strings.ReplaceAll(blocks["print slicemap"], "  ", " "); !strings.HasSuffix(bl, sliceMapSfx1) && !strings.HasSuffix(bl, sliceMapSfx2) {
   315  		t.Fatalf("print slicemap failed: %s", bl)
   316  	}
   317  
   318  	chanIntSfx := `chan int = {99, 11}`
   319  	if bl := strings.ReplaceAll(blocks["print chanint"], "  ", " "); !strings.HasSuffix(bl, chanIntSfx) {
   320  		t.Fatalf("print chanint failed: %s", bl)
   321  	}
   322  
   323  	chanStrSfx := `chan string = {"spongepants", "squarebob"}`
   324  	if bl := strings.ReplaceAll(blocks["print chanstr"], "  ", " "); !strings.HasSuffix(bl, chanStrSfx) {
   325  		t.Fatalf("print chanstr failed: %s", bl)
   326  	}
   327  
   328  	strVarRe := regexp.MustCompile(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`)
   329  	if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
   330  		t.Fatalf("print strvar failed: %s", bl)
   331  	}
   332  
   333  	// The exact format of composite values has changed over time.
   334  	// For issue 16338: ssa decompose phase split a slice into
   335  	// a collection of scalar vars holding its fields. In such cases
   336  	// the DWARF variable location expression should be of the
   337  	// form "var.field" and not just "field".
   338  	// However, the newer dwarf location list code reconstituted
   339  	// aggregates from their fields and reverted their printing
   340  	// back to its original form.
   341  	// Only test that all variables are listed in 'info locals' since
   342  	// different versions of gdb print variables in different
   343  	// order and with differing amount of information and formats.
   344  
   345  	if bl := blocks["info locals"]; !strings.Contains(bl, "slicevar") ||
   346  		!strings.Contains(bl, "mapvar") ||
   347  		!strings.Contains(bl, "strvar") {
   348  		t.Fatalf("info locals failed: %s", bl)
   349  	}
   350  
   351  	// Check that the backtraces are well formed.
   352  	checkCleanBacktrace(t, blocks["goroutine 1 bt"])
   353  	checkCleanBacktrace(t, blocks["goroutine 1 bt at the end"])
   354  
   355  	btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
   356  	if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
   357  		t.Fatalf("goroutine 1 bt failed: %s", bl)
   358  	}
   359  
   360  	if bl := blocks["goroutine all bt"]; !btGoroutine1Re.MatchString(bl) {
   361  		t.Fatalf("goroutine all bt failed: %s", bl)
   362  	}
   363  
   364  	btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
   365  	if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
   366  		t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
   367  	}
   368  }
   369  
   370  const backtraceSource = `
   371  package main
   372  
   373  //go:noinline
   374  func aaa() bool { return bbb() }
   375  
   376  //go:noinline
   377  func bbb() bool { return ccc() }
   378  
   379  //go:noinline
   380  func ccc() bool { return ddd() }
   381  
   382  //go:noinline
   383  func ddd() bool { return f() }
   384  
   385  //go:noinline
   386  func eee() bool { return true }
   387  
   388  var f = eee
   389  
   390  func main() {
   391  	_ = aaa()
   392  }
   393  `
   394  
   395  // TestGdbBacktrace tests that gdb can unwind the stack correctly
   396  // using only the DWARF debug info.
   397  func TestGdbBacktrace(t *testing.T) {
   398  	if runtime.GOOS == "netbsd" {
   399  		testenv.SkipFlaky(t, 15603)
   400  	}
   401  
   402  	checkGdbEnvironment(t)
   403  	t.Parallel()
   404  	checkGdbVersion(t)
   405  
   406  	dir, err := os.MkdirTemp("", "go-build")
   407  	if err != nil {
   408  		t.Fatalf("failed to create temp directory: %v", err)
   409  	}
   410  	defer os.RemoveAll(dir)
   411  
   412  	// Build the source code.
   413  	src := filepath.Join(dir, "main.go")
   414  	err = os.WriteFile(src, []byte(backtraceSource), 0644)
   415  	if err != nil {
   416  		t.Fatalf("failed to create file: %v", err)
   417  	}
   418  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   419  	cmd.Dir = dir
   420  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   421  	if err != nil {
   422  		t.Fatalf("building source %v\n%s", err, out)
   423  	}
   424  
   425  	// Execute gdb commands.
   426  	args := []string{"-nx", "-batch",
   427  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   428  		"-ex", "set startup-with-shell off",
   429  		"-ex", "break main.eee",
   430  		"-ex", "run",
   431  		"-ex", "backtrace",
   432  		"-ex", "continue",
   433  		filepath.Join(dir, "a.exe"),
   434  	}
   435  	got, err := exec.Command("gdb", args...).CombinedOutput()
   436  	t.Logf("gdb output:\n%s", got)
   437  	if err != nil {
   438  		t.Fatalf("gdb exited with error: %v", err)
   439  	}
   440  
   441  	// Check that the backtrace matches the source code.
   442  	bt := []string{
   443  		"eee",
   444  		"ddd",
   445  		"ccc",
   446  		"bbb",
   447  		"aaa",
   448  		"main",
   449  	}
   450  	for i, name := range bt {
   451  		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
   452  		re := regexp.MustCompile(s)
   453  		if found := re.Find(got) != nil; !found {
   454  			t.Fatalf("could not find '%v' in backtrace", s)
   455  		}
   456  	}
   457  }
   458  
   459  const autotmpTypeSource = `
   460  package main
   461  
   462  type astruct struct {
   463  	a, b int
   464  }
   465  
   466  func main() {
   467  	var iface interface{} = map[string]astruct{}
   468  	var iface2 interface{} = []astruct{}
   469  	println(iface, iface2)
   470  }
   471  `
   472  
   473  // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
   474  // See bug #17830.
   475  func TestGdbAutotmpTypes(t *testing.T) {
   476  	checkGdbEnvironment(t)
   477  	t.Parallel()
   478  	checkGdbVersion(t)
   479  
   480  	if runtime.GOOS == "aix" && testing.Short() {
   481  		t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64")
   482  	}
   483  
   484  	dir, err := os.MkdirTemp("", "go-build")
   485  	if err != nil {
   486  		t.Fatalf("failed to create temp directory: %v", err)
   487  	}
   488  	defer os.RemoveAll(dir)
   489  
   490  	// Build the source code.
   491  	src := filepath.Join(dir, "main.go")
   492  	err = os.WriteFile(src, []byte(autotmpTypeSource), 0644)
   493  	if err != nil {
   494  		t.Fatalf("failed to create file: %v", err)
   495  	}
   496  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
   497  	cmd.Dir = dir
   498  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   499  	if err != nil {
   500  		t.Fatalf("building source %v\n%s", err, out)
   501  	}
   502  
   503  	// Execute gdb commands.
   504  	args := []string{"-nx", "-batch",
   505  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   506  		"-ex", "set startup-with-shell off",
   507  		"-ex", "break main.main",
   508  		"-ex", "run",
   509  		"-ex", "step",
   510  		"-ex", "info types astruct",
   511  		filepath.Join(dir, "a.exe"),
   512  	}
   513  	got, err := exec.Command("gdb", args...).CombinedOutput()
   514  	t.Logf("gdb output:\n%s", got)
   515  	if err != nil {
   516  		t.Fatalf("gdb exited with error: %v", err)
   517  	}
   518  
   519  	sgot := string(got)
   520  
   521  	// Check that the backtrace matches the source code.
   522  	types := []string{
   523  		"[]main.astruct;",
   524  		"bucket<string,main.astruct>;",
   525  		"hash<string,main.astruct>;",
   526  		"main.astruct;",
   527  		"hash<string,main.astruct> * map[string]main.astruct;",
   528  	}
   529  	for _, name := range types {
   530  		if !strings.Contains(sgot, name) {
   531  			t.Fatalf("could not find %s in 'info typrs astruct' output", name)
   532  		}
   533  	}
   534  }
   535  
   536  const constsSource = `
   537  package main
   538  
   539  const aConstant int = 42
   540  const largeConstant uint64 = ^uint64(0)
   541  const minusOne int64 = -1
   542  
   543  func main() {
   544  	println("hello world")
   545  }
   546  `
   547  
   548  func TestGdbConst(t *testing.T) {
   549  	checkGdbEnvironment(t)
   550  	t.Parallel()
   551  	checkGdbVersion(t)
   552  
   553  	dir, err := os.MkdirTemp("", "go-build")
   554  	if err != nil {
   555  		t.Fatalf("failed to create temp directory: %v", err)
   556  	}
   557  	defer os.RemoveAll(dir)
   558  
   559  	// Build the source code.
   560  	src := filepath.Join(dir, "main.go")
   561  	err = os.WriteFile(src, []byte(constsSource), 0644)
   562  	if err != nil {
   563  		t.Fatalf("failed to create file: %v", err)
   564  	}
   565  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
   566  	cmd.Dir = dir
   567  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   568  	if err != nil {
   569  		t.Fatalf("building source %v\n%s", err, out)
   570  	}
   571  
   572  	// Execute gdb commands.
   573  	args := []string{"-nx", "-batch",
   574  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   575  		"-ex", "set startup-with-shell off",
   576  		"-ex", "break main.main",
   577  		"-ex", "run",
   578  		"-ex", "print main.aConstant",
   579  		"-ex", "print main.largeConstant",
   580  		"-ex", "print main.minusOne",
   581  		"-ex", "print 'runtime.mSpanInUse'",
   582  		"-ex", "print 'runtime._PageSize'",
   583  		filepath.Join(dir, "a.exe"),
   584  	}
   585  	got, err := exec.Command("gdb", args...).CombinedOutput()
   586  	t.Logf("gdb output:\n%s", got)
   587  	if err != nil {
   588  		t.Fatalf("gdb exited with error: %v", err)
   589  	}
   590  
   591  	sgot := strings.ReplaceAll(string(got), "\r\n", "\n")
   592  
   593  	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
   594  		t.Fatalf("output mismatch")
   595  	}
   596  }
   597  
   598  const panicSource = `
   599  package main
   600  
   601  import "runtime/debug"
   602  
   603  func main() {
   604  	debug.SetTraceback("crash")
   605  	crash()
   606  }
   607  
   608  func crash() {
   609  	panic("panic!")
   610  }
   611  `
   612  
   613  // TestGdbPanic tests that gdb can unwind the stack correctly
   614  // from SIGABRTs from Go panics.
   615  func TestGdbPanic(t *testing.T) {
   616  	checkGdbEnvironment(t)
   617  	t.Parallel()
   618  	checkGdbVersion(t)
   619  
   620  	dir, err := os.MkdirTemp("", "go-build")
   621  	if err != nil {
   622  		t.Fatalf("failed to create temp directory: %v", err)
   623  	}
   624  	defer os.RemoveAll(dir)
   625  
   626  	// Build the source code.
   627  	src := filepath.Join(dir, "main.go")
   628  	err = os.WriteFile(src, []byte(panicSource), 0644)
   629  	if err != nil {
   630  		t.Fatalf("failed to create file: %v", err)
   631  	}
   632  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   633  	cmd.Dir = dir
   634  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   635  	if err != nil {
   636  		t.Fatalf("building source %v\n%s", err, out)
   637  	}
   638  
   639  	// Execute gdb commands.
   640  	args := []string{"-nx", "-batch",
   641  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   642  		"-ex", "set startup-with-shell off",
   643  		"-ex", "run",
   644  		"-ex", "backtrace",
   645  		filepath.Join(dir, "a.exe"),
   646  	}
   647  	got, err := exec.Command("gdb", args...).CombinedOutput()
   648  	t.Logf("gdb output:\n%s", got)
   649  	if err != nil {
   650  		t.Fatalf("gdb exited with error: %v", err)
   651  	}
   652  
   653  	// Check that the backtrace matches the source code.
   654  	bt := []string{
   655  		`crash`,
   656  		`main`,
   657  	}
   658  	for _, name := range bt {
   659  		s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
   660  		re := regexp.MustCompile(s)
   661  		if found := re.Find(got) != nil; !found {
   662  			t.Fatalf("could not find '%v' in backtrace", s)
   663  		}
   664  	}
   665  }
   666  
   667  const InfCallstackSource = `
   668  package main
   669  import "C"
   670  import "time"
   671  
   672  func loop() {
   673          for i := 0; i < 1000; i++ {
   674                  time.Sleep(time.Millisecond*5)
   675          }
   676  }
   677  
   678  func main() {
   679          go loop()
   680          time.Sleep(time.Second * 1)
   681  }
   682  `
   683  
   684  // TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs
   685  // on arm64 platforms without endless frames of function 'crossfunc1'.
   686  // https://golang.org/issue/37238
   687  func TestGdbInfCallstack(t *testing.T) {
   688  	checkGdbEnvironment(t)
   689  
   690  	testenv.MustHaveCGO(t)
   691  	if runtime.GOARCH != "arm64" {
   692  		t.Skip("skipping infinite callstack test on non-arm64 arches")
   693  	}
   694  
   695  	t.Parallel()
   696  	checkGdbVersion(t)
   697  
   698  	dir, err := os.MkdirTemp("", "go-build")
   699  	if err != nil {
   700  		t.Fatalf("failed to create temp directory: %v", err)
   701  	}
   702  	defer os.RemoveAll(dir)
   703  
   704  	// Build the source code.
   705  	src := filepath.Join(dir, "main.go")
   706  	err = os.WriteFile(src, []byte(InfCallstackSource), 0644)
   707  	if err != nil {
   708  		t.Fatalf("failed to create file: %v", err)
   709  	}
   710  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   711  	cmd.Dir = dir
   712  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   713  	if err != nil {
   714  		t.Fatalf("building source %v\n%s", err, out)
   715  	}
   716  
   717  	// Execute gdb commands.
   718  	// 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command.
   719  	args := []string{"-nx", "-batch",
   720  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   721  		"-ex", "set startup-with-shell off",
   722  		"-ex", "break setg_gcc",
   723  		"-ex", "run",
   724  		"-ex", "backtrace 3",
   725  		"-ex", "disable 1",
   726  		"-ex", "continue",
   727  		filepath.Join(dir, "a.exe"),
   728  	}
   729  	got, err := exec.Command("gdb", args...).CombinedOutput()
   730  	t.Logf("gdb output:\n%s", got)
   731  	if err != nil {
   732  		t.Fatalf("gdb exited with error: %v", err)
   733  	}
   734  
   735  	// Check that the backtrace matches
   736  	// We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c
   737  	bt := []string{
   738  		`setg_gcc`,
   739  		`crosscall1`,
   740  		`threadentry`,
   741  	}
   742  	for i, name := range bt {
   743  		s := fmt.Sprintf("#%v.*%v", i, name)
   744  		re := regexp.MustCompile(s)
   745  		if found := re.Find(got) != nil; !found {
   746  			t.Fatalf("could not find '%v' in backtrace", s)
   747  		}
   748  	}
   749  }