github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/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  		// Disable GDB tests on alpine until issue #54352 resolved.
    44  		if strings.HasSuffix(testenv.Builder(), "-alpine") {
    45  			t.Skip("skipping gdb tests on alpine; see https://golang.org/issue/54352")
    46  		}
    47  	case "freebsd":
    48  		t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508")
    49  	case "aix":
    50  		if testing.Short() {
    51  			t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710")
    52  		}
    53  	case "plan9":
    54  		t.Skip("there is no gdb on Plan 9")
    55  	}
    56  	if final := os.Getenv("GOROOT_FINAL"); final != "" && testenv.GOROOT(t) != final {
    57  		t.Skip("gdb test can fail with GOROOT_FINAL pending")
    58  	}
    59  }
    60  
    61  func checkGdbVersion(t *testing.T) {
    62  	// Issue 11214 reports various failures with older versions of gdb.
    63  	out, err := exec.Command("gdb", "--version").CombinedOutput()
    64  	if err != nil {
    65  		t.Skipf("skipping: error executing gdb: %v", err)
    66  	}
    67  	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
    68  	matches := re.FindSubmatch(out)
    69  	if len(matches) < 3 {
    70  		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
    71  	}
    72  	major, err1 := strconv.Atoi(string(matches[1]))
    73  	minor, err2 := strconv.Atoi(string(matches[2]))
    74  	if err1 != nil || err2 != nil {
    75  		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
    76  	}
    77  	if major < 7 || (major == 7 && minor < 7) {
    78  		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
    79  	}
    80  	t.Logf("gdb version %d.%d", major, minor)
    81  }
    82  
    83  func checkGdbPython(t *testing.T) {
    84  	if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
    85  		t.Skip("skipping gdb python tests on illumos and solaris; see golang.org/issue/20821")
    86  	}
    87  
    88  	cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
    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  // checkCleanBacktrace checks that the given backtrace is well formed and does
   100  // not contain any error messages from GDB.
   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  	// TODO(mundaym): check for unknown frames (e.g. "??").
   113  }
   114  
   115  const helloSource = `
   116  import "fmt"
   117  import "runtime"
   118  var gslice []string
   119  func main() {
   120  	mapvar := make(map[string]string, 13)
   121  	slicemap := make(map[string][]string,11)
   122      chanint := make(chan int, 10)
   123      chanstr := make(chan string, 10)
   124      chanint <- 99
   125  	chanint <- 11
   126      chanstr <- "spongepants"
   127      chanstr <- "squarebob"
   128  	mapvar["abc"] = "def"
   129  	mapvar["ghi"] = "jkl"
   130  	slicemap["a"] = []string{"b","c","d"}
   131      slicemap["e"] = []string{"f","g","h"}
   132  	strvar := "abc"
   133  	ptrvar := &strvar
   134  	slicevar := make([]string, 0, 16)
   135  	slicevar = append(slicevar, mapvar["abc"])
   136  	fmt.Println("hi")
   137  	runtime.KeepAlive(ptrvar)
   138  	_ = ptrvar // set breakpoint here
   139  	gslice = slicevar
   140  	fmt.Printf("%v, %v, %v\n", slicemap, <-chanint, <-chanstr)
   141  	runtime.KeepAlive(mapvar)
   142  }  // END_OF_PROGRAM
   143  `
   144  
   145  func lastLine(src []byte) int {
   146  	eop := []byte("END_OF_PROGRAM")
   147  	for i, l := range bytes.Split(src, []byte("\n")) {
   148  		if bytes.Contains(l, eop) {
   149  			return i
   150  		}
   151  	}
   152  	return 0
   153  }
   154  
   155  func TestGdbPython(t *testing.T) {
   156  	testGdbPython(t, false)
   157  }
   158  
   159  func TestGdbPythonCgo(t *testing.T) {
   160  	if strings.HasPrefix(runtime.GOARCH, "mips") {
   161  		testenv.SkipFlaky(t, 37794)
   162  	}
   163  	testGdbPython(t, true)
   164  }
   165  
   166  func testGdbPython(t *testing.T, cgo bool) {
   167  	if cgo {
   168  		testenv.MustHaveCGO(t)
   169  	}
   170  
   171  	checkGdbEnvironment(t)
   172  	t.Parallel()
   173  	checkGdbVersion(t)
   174  	checkGdbPython(t)
   175  
   176  	dir := t.TempDir()
   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(testenv.GOROOT(t), "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(testenv.GOROOT(t), "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.Cut(got, []byte("\n"))
   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", testenv.GOROOT(t))
   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 := t.TempDir()
   407  
   408  	// Build the source code.
   409  	src := filepath.Join(dir, "main.go")
   410  	err := os.WriteFile(src, []byte(backtraceSource), 0644)
   411  	if err != nil {
   412  		t.Fatalf("failed to create file: %v", err)
   413  	}
   414  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   415  	cmd.Dir = dir
   416  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   417  	if err != nil {
   418  		t.Fatalf("building source %v\n%s", err, out)
   419  	}
   420  
   421  	// Execute gdb commands.
   422  	args := []string{"-nx", "-batch",
   423  		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
   424  		"-ex", "set startup-with-shell off",
   425  		"-ex", "break main.eee",
   426  		"-ex", "run",
   427  		"-ex", "backtrace",
   428  		"-ex", "continue",
   429  		filepath.Join(dir, "a.exe"),
   430  	}
   431  	got, err := testenv.RunWithTimeout(t, exec.Command("gdb", args...))
   432  	t.Logf("gdb output:\n%s", got)
   433  	if err != nil {
   434  		if bytes.Contains(got, []byte("internal-error: wait returned unexpected status 0x0")) {
   435  			// GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28551
   436  			testenv.SkipFlaky(t, 43068)
   437  		}
   438  		if bytes.Contains(got, []byte("Couldn't get registers: No such process.")) {
   439  			// GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=9086
   440  			testenv.SkipFlaky(t, 50838)
   441  		}
   442  		if bytes.Contains(got, []byte(" exited normally]\n")) {
   443  			// GDB bug: Sometimes the inferior exits fine,
   444  			// but then GDB hangs.
   445  			testenv.SkipFlaky(t, 37405)
   446  		}
   447  		t.Fatalf("gdb exited with error: %v", err)
   448  	}
   449  
   450  	// Check that the backtrace matches the source code.
   451  	bt := []string{
   452  		"eee",
   453  		"ddd",
   454  		"ccc",
   455  		"bbb",
   456  		"aaa",
   457  		"main",
   458  	}
   459  	for i, name := range bt {
   460  		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
   461  		re := regexp.MustCompile(s)
   462  		if found := re.Find(got) != nil; !found {
   463  			t.Fatalf("could not find '%v' in backtrace", s)
   464  		}
   465  	}
   466  }
   467  
   468  const autotmpTypeSource = `
   469  package main
   470  
   471  type astruct struct {
   472  	a, b int
   473  }
   474  
   475  func main() {
   476  	var iface interface{} = map[string]astruct{}
   477  	var iface2 interface{} = []astruct{}
   478  	println(iface, iface2)
   479  }
   480  `
   481  
   482  // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
   483  // See bug #17830.
   484  func TestGdbAutotmpTypes(t *testing.T) {
   485  	checkGdbEnvironment(t)
   486  	t.Parallel()
   487  	checkGdbVersion(t)
   488  
   489  	if runtime.GOOS == "aix" && testing.Short() {
   490  		t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64")
   491  	}
   492  
   493  	dir := t.TempDir()
   494  
   495  	// Build the source code.
   496  	src := filepath.Join(dir, "main.go")
   497  	err := os.WriteFile(src, []byte(autotmpTypeSource), 0644)
   498  	if err != nil {
   499  		t.Fatalf("failed to create file: %v", err)
   500  	}
   501  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
   502  	cmd.Dir = dir
   503  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   504  	if err != nil {
   505  		t.Fatalf("building source %v\n%s", err, out)
   506  	}
   507  
   508  	// Execute gdb commands.
   509  	args := []string{"-nx", "-batch",
   510  		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
   511  		"-ex", "set startup-with-shell off",
   512  		// Some gdb may set scheduling-locking as "step" by default. This prevents background tasks
   513  		// (e.g GC) from completing which may result in a hang when executing the step command.
   514  		// See #49852.
   515  		"-ex", "set scheduler-locking off",
   516  		"-ex", "break main.main",
   517  		"-ex", "run",
   518  		"-ex", "step",
   519  		"-ex", "info types astruct",
   520  		filepath.Join(dir, "a.exe"),
   521  	}
   522  	got, err := exec.Command("gdb", args...).CombinedOutput()
   523  	t.Logf("gdb output:\n%s", got)
   524  	if err != nil {
   525  		t.Fatalf("gdb exited with error: %v", err)
   526  	}
   527  
   528  	sgot := string(got)
   529  
   530  	// Check that the backtrace matches the source code.
   531  	types := []string{
   532  		"[]main.astruct;",
   533  		"bucket<string,main.astruct>;",
   534  		"hash<string,main.astruct>;",
   535  		"main.astruct;",
   536  		"hash<string,main.astruct> * map[string]main.astruct;",
   537  	}
   538  	for _, name := range types {
   539  		if !strings.Contains(sgot, name) {
   540  			t.Fatalf("could not find %s in 'info typrs astruct' output", name)
   541  		}
   542  	}
   543  }
   544  
   545  const constsSource = `
   546  package main
   547  
   548  const aConstant int = 42
   549  const largeConstant uint64 = ^uint64(0)
   550  const minusOne int64 = -1
   551  
   552  func main() {
   553  	println("hello world")
   554  }
   555  `
   556  
   557  func TestGdbConst(t *testing.T) {
   558  	checkGdbEnvironment(t)
   559  	t.Parallel()
   560  	checkGdbVersion(t)
   561  
   562  	dir := t.TempDir()
   563  
   564  	// Build the source code.
   565  	src := filepath.Join(dir, "main.go")
   566  	err := os.WriteFile(src, []byte(constsSource), 0644)
   567  	if err != nil {
   568  		t.Fatalf("failed to create file: %v", err)
   569  	}
   570  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
   571  	cmd.Dir = dir
   572  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   573  	if err != nil {
   574  		t.Fatalf("building source %v\n%s", err, out)
   575  	}
   576  
   577  	// Execute gdb commands.
   578  	args := []string{"-nx", "-batch",
   579  		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
   580  		"-ex", "set startup-with-shell off",
   581  		"-ex", "break main.main",
   582  		"-ex", "run",
   583  		"-ex", "print main.aConstant",
   584  		"-ex", "print main.largeConstant",
   585  		"-ex", "print main.minusOne",
   586  		"-ex", "print 'runtime.mSpanInUse'",
   587  		"-ex", "print 'runtime._PageSize'",
   588  		filepath.Join(dir, "a.exe"),
   589  	}
   590  	got, err := exec.Command("gdb", args...).CombinedOutput()
   591  	t.Logf("gdb output:\n%s", got)
   592  	if err != nil {
   593  		t.Fatalf("gdb exited with error: %v", err)
   594  	}
   595  
   596  	sgot := strings.ReplaceAll(string(got), "\r\n", "\n")
   597  
   598  	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
   599  		t.Fatalf("output mismatch")
   600  	}
   601  }
   602  
   603  const panicSource = `
   604  package main
   605  
   606  import "runtime/debug"
   607  
   608  func main() {
   609  	debug.SetTraceback("crash")
   610  	crash()
   611  }
   612  
   613  func crash() {
   614  	panic("panic!")
   615  }
   616  `
   617  
   618  // TestGdbPanic tests that gdb can unwind the stack correctly
   619  // from SIGABRTs from Go panics.
   620  func TestGdbPanic(t *testing.T) {
   621  	checkGdbEnvironment(t)
   622  	t.Parallel()
   623  	checkGdbVersion(t)
   624  
   625  	dir := t.TempDir()
   626  
   627  	// Build the source code.
   628  	src := filepath.Join(dir, "main.go")
   629  	err := os.WriteFile(src, []byte(panicSource), 0644)
   630  	if err != nil {
   631  		t.Fatalf("failed to create file: %v", err)
   632  	}
   633  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   634  	cmd.Dir = dir
   635  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   636  	if err != nil {
   637  		t.Fatalf("building source %v\n%s", err, out)
   638  	}
   639  
   640  	// Execute gdb commands.
   641  	args := []string{"-nx", "-batch",
   642  		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
   643  		"-ex", "set startup-with-shell off",
   644  		"-ex", "run",
   645  		"-ex", "backtrace",
   646  		filepath.Join(dir, "a.exe"),
   647  	}
   648  	got, err := exec.Command("gdb", args...).CombinedOutput()
   649  	t.Logf("gdb output:\n%s", got)
   650  	if err != nil {
   651  		t.Fatalf("gdb exited with error: %v", err)
   652  	}
   653  
   654  	// Check that the backtrace matches the source code.
   655  	bt := []string{
   656  		`crash`,
   657  		`main`,
   658  	}
   659  	for _, name := range bt {
   660  		s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
   661  		re := regexp.MustCompile(s)
   662  		if found := re.Find(got) != nil; !found {
   663  			t.Fatalf("could not find '%v' in backtrace", s)
   664  		}
   665  	}
   666  }
   667  
   668  const InfCallstackSource = `
   669  package main
   670  import "C"
   671  import "time"
   672  
   673  func loop() {
   674          for i := 0; i < 1000; i++ {
   675                  time.Sleep(time.Millisecond*5)
   676          }
   677  }
   678  
   679  func main() {
   680          go loop()
   681          time.Sleep(time.Second * 1)
   682  }
   683  `
   684  
   685  // TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs
   686  // on arm64 platforms without endless frames of function 'crossfunc1'.
   687  // https://golang.org/issue/37238
   688  func TestGdbInfCallstack(t *testing.T) {
   689  	checkGdbEnvironment(t)
   690  
   691  	testenv.MustHaveCGO(t)
   692  	if runtime.GOARCH != "arm64" {
   693  		t.Skip("skipping infinite callstack test on non-arm64 arches")
   694  	}
   695  
   696  	t.Parallel()
   697  	checkGdbVersion(t)
   698  
   699  	dir := t.TempDir()
   700  
   701  	// Build the source code.
   702  	src := filepath.Join(dir, "main.go")
   703  	err := os.WriteFile(src, []byte(InfCallstackSource), 0644)
   704  	if err != nil {
   705  		t.Fatalf("failed to create file: %v", err)
   706  	}
   707  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
   708  	cmd.Dir = dir
   709  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   710  	if err != nil {
   711  		t.Fatalf("building source %v\n%s", err, out)
   712  	}
   713  
   714  	// Execute gdb commands.
   715  	// 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command.
   716  	args := []string{"-nx", "-batch",
   717  		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
   718  		"-ex", "set startup-with-shell off",
   719  		"-ex", "break setg_gcc",
   720  		"-ex", "run",
   721  		"-ex", "backtrace 3",
   722  		"-ex", "disable 1",
   723  		"-ex", "continue",
   724  		filepath.Join(dir, "a.exe"),
   725  	}
   726  	got, err := exec.Command("gdb", args...).CombinedOutput()
   727  	t.Logf("gdb output:\n%s", got)
   728  	if err != nil {
   729  		t.Fatalf("gdb exited with error: %v", err)
   730  	}
   731  
   732  	// Check that the backtrace matches
   733  	// We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c
   734  	bt := []string{
   735  		`setg_gcc`,
   736  		`crosscall1`,
   737  		`threadentry`,
   738  	}
   739  	for i, name := range bt {
   740  		s := fmt.Sprintf("#%v.*%v", i, name)
   741  		re := regexp.MustCompile(s)
   742  		if found := re.Find(got) != nil; !found {
   743  			t.Fatalf("could not find '%v' in backtrace", s)
   744  		}
   745  	}
   746  }