Source file src/runtime/runtime-gdb_test.go

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

View as plain text