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/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  
    30  func 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  
    59  func 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  
    81  func 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.
   100  func 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  
   117  var helloSource = `
   118  import "fmt"
   119  import "runtime"
   120  var gslice []string
   121  func 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  
   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 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  
   176  func TestGdbPython(t *testing.T) {
   177  	testGdbPython(t, false)
   178  }
   179  
   180  func TestGdbPythonCgo(t *testing.T) {
   181  	if strings.HasPrefix(runtime.GOARCH, "mips") {
   182  		testenv.SkipFlaky(t, 37794)
   183  	}
   184  	testGdbPython(t, true)
   185  }
   186  
   187  func 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  
   375  const backtraceSource = `
   376  package main
   377  
   378  //go:noinline
   379  func aaa() bool { return bbb() }
   380  
   381  //go:noinline
   382  func bbb() bool { return ccc() }
   383  
   384  //go:noinline
   385  func ccc() bool { return ddd() }
   386  
   387  //go:noinline
   388  func ddd() bool { return f() }
   389  
   390  //go:noinline
   391  func eee() bool { return true }
   392  
   393  var f = eee
   394  
   395  func main() {
   396  	_ = aaa()
   397  }
   398  `
   399  
   400  // TestGdbBacktrace tests that gdb can unwind the stack correctly
   401  // using only the DWARF debug info.
   402  func 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  
   513  const autotmpTypeSource = `
   514  package main
   515  
   516  type astruct struct {
   517  	a, b int
   518  }
   519  
   520  func 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.
   529  func 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 %q in 'info typrs astruct' output", name)
   587  		}
   588  	}
   589  }
   590  
   591  const constsSource = `
   592  package main
   593  
   594  const aConstant int = 42
   595  const largeConstant uint64 = ^uint64(0)
   596  const minusOne int64 = -1
   597  
   598  func main() {
   599  	println("hello world")
   600  }
   601  `
   602  
   603  func 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  
   650  const panicSource = `
   651  package main
   652  
   653  import "runtime/debug"
   654  
   655  func main() {
   656  	debug.SetTraceback("crash")
   657  	crash()
   658  }
   659  
   660  func crash() {
   661  	panic("panic!")
   662  }
   663  `
   664  
   665  // TestGdbPanic tests that gdb can unwind the stack correctly
   666  // from SIGABRTs from Go panics.
   667  func 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  
   720  const InfCallstackSource = `
   721  package main
   722  import "C"
   723  import "time"
   724  
   725  func loop() {
   726          for i := 0; i < 1000; i++ {
   727                  time.Sleep(time.Millisecond*5)
   728          }
   729  }
   730  
   731  func 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
   740  func 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  

View as plain text