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

View as plain text