...
Run Format

Source file src/runtime/runtime-gdb_test.go

Documentation: runtime

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package runtime_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/build"
    11  	"internal/testenv"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  )
    22  
    23  func checkGdbEnvironment(t *testing.T) {
    24  	testenv.MustHaveGoBuild(t)
    25  	switch runtime.GOOS {
    26  	case "darwin":
    27  		t.Skip("gdb does not work on darwin")
    28  	case "netbsd":
    29  		t.Skip("gdb does not work with threads on NetBSD; see golang.org/issue/22893 and gnats.netbsd.org/52548")
    30  	case "windows":
    31  		t.Skip("gdb tests fail on Windows: https://golang.org/issue/22687")
    32  	case "linux":
    33  		if runtime.GOARCH == "ppc64" {
    34  			t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366")
    35  		}
    36  		if runtime.GOARCH == "mips" {
    37  			t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939")
    38  		}
    39  	}
    40  	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
    41  		t.Skip("gdb test can fail with GOROOT_FINAL pending")
    42  	}
    43  }
    44  
    45  func checkGdbVersion(t *testing.T) {
    46  	// Issue 11214 reports various failures with older versions of gdb.
    47  	out, err := exec.Command("gdb", "--version").CombinedOutput()
    48  	if err != nil {
    49  		t.Skipf("skipping: error executing gdb: %v", err)
    50  	}
    51  	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
    52  	matches := re.FindSubmatch(out)
    53  	if len(matches) < 3 {
    54  		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
    55  	}
    56  	major, err1 := strconv.Atoi(string(matches[1]))
    57  	minor, err2 := strconv.Atoi(string(matches[2]))
    58  	if err1 != nil || err2 != nil {
    59  		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
    60  	}
    61  	if major < 7 || (major == 7 && minor < 7) {
    62  		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
    63  	}
    64  	t.Logf("gdb version %d.%d", major, minor)
    65  }
    66  
    67  func checkGdbPython(t *testing.T) {
    68  	if runtime.GOOS == "solaris" && testenv.Builder() != "solaris-amd64-smartosbuildlet" {
    69  		t.Skip("skipping gdb python tests on solaris; see golang.org/issue/20821")
    70  	}
    71  
    72  	cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
    73  	out, err := cmd.CombinedOutput()
    74  
    75  	if err != nil {
    76  		t.Skipf("skipping due to issue running gdb: %v", err)
    77  	}
    78  	if strings.TrimSpace(string(out)) != "go gdb python support" {
    79  		t.Skipf("skipping due to lack of python gdb support: %s", out)
    80  	}
    81  }
    82  
    83  const helloSource = `
    84  import "fmt"
    85  import "runtime"
    86  var gslice []string
    87  func main() {
    88  	mapvar := make(map[string]string, 13)
    89  	mapvar["abc"] = "def"
    90  	mapvar["ghi"] = "jkl"
    91  	strvar := "abc"
    92  	ptrvar := &strvar
    93  	slicevar := make([]string, 0, 16)
    94  	slicevar = append(slicevar, mapvar["abc"])
    95  	fmt.Println("hi")
    96  	runtime.KeepAlive(ptrvar)
    97  	_ = ptrvar
    98  	gslice = slicevar
    99  	runtime.KeepAlive(mapvar)
   100  }  // END_OF_PROGRAM
   101  `
   102  
   103  func lastLine(src []byte) int {
   104  	eop := []byte("END_OF_PROGRAM")
   105  	for i, l := range bytes.Split(src, []byte("\n")) {
   106  		if bytes.Contains(l, eop) {
   107  			return i
   108  		}
   109  	}
   110  	return 0
   111  }
   112  
   113  func TestGdbPython(t *testing.T) {
   114  	testGdbPython(t, false)
   115  }
   116  
   117  func TestGdbPythonCgo(t *testing.T) {
   118  	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" {
   119  		testenv.SkipFlaky(t, 18784)
   120  	}
   121  	testGdbPython(t, true)
   122  }
   123  
   124  func testGdbPython(t *testing.T, cgo bool) {
   125  	if cgo && !build.Default.CgoEnabled {
   126  		t.Skip("skipping because cgo is not enabled")
   127  	}
   128  
   129  	checkGdbEnvironment(t)
   130  	t.Parallel()
   131  	checkGdbVersion(t)
   132  	checkGdbPython(t)
   133  
   134  	dir, err := ioutil.TempDir("", "go-build")
   135  	if err != nil {
   136  		t.Fatalf("failed to create temp directory: %v", err)
   137  	}
   138  	defer os.RemoveAll(dir)
   139  
   140  	var buf bytes.Buffer
   141  	buf.WriteString("package main\n")
   142  	if cgo {
   143  		buf.WriteString(`import "C"` + "\n")
   144  	}
   145  	buf.WriteString(helloSource)
   146  
   147  	src := buf.Bytes()
   148  
   149  	err = ioutil.WriteFile(filepath.Join(dir, "main.go"), src, 0644)
   150  	if err != nil {
   151  		t.Fatalf("failed to create file: %v", err)
   152  	}
   153  	nLines := lastLine(src)
   154  
   155  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   156  	cmd.Dir = dir
   157  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   158  	if err != nil {
   159  		t.Fatalf("building source %v\n%s", err, out)
   160  	}
   161  
   162  	args := []string{"-nx", "-q", "--batch",
   163  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   164  		"-ex", "set startup-with-shell off",
   165  	}
   166  	if cgo {
   167  		// When we build the cgo version of the program, the system's
   168  		// linker is used. Some external linkers, like GNU gold,
   169  		// compress the .debug_gdb_scripts into .zdebug_gdb_scripts.
   170  		// Until gold and gdb can work together, temporarily load the
   171  		// python script directly.
   172  		args = append(args,
   173  			"-ex", "source "+filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime-gdb.py"),
   174  		)
   175  	} else {
   176  		args = append(args,
   177  			"-ex", "info auto-load python-scripts",
   178  		)
   179  	}
   180  	args = append(args,
   181  		"-ex", "set python print-stack full",
   182  		"-ex", "br fmt.Println",
   183  		"-ex", "run",
   184  		"-ex", "echo BEGIN info goroutines\n",
   185  		"-ex", "info goroutines",
   186  		"-ex", "echo END\n",
   187  		"-ex", "up", // up from fmt.Println to main
   188  		"-ex", "echo BEGIN print mapvar\n",
   189  		"-ex", "print mapvar",
   190  		"-ex", "echo END\n",
   191  		"-ex", "echo BEGIN print strvar\n",
   192  		"-ex", "print strvar",
   193  		"-ex", "echo END\n",
   194  		"-ex", "echo BEGIN info locals\n",
   195  		"-ex", "info locals",
   196  		"-ex", "echo END\n",
   197  		"-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack.  TODO: fix that)
   198  		"-ex", "echo BEGIN goroutine 1 bt\n",
   199  		"-ex", "goroutine 1 bt",
   200  		"-ex", "echo END\n",
   201  		"-ex", "echo BEGIN goroutine 2 bt\n",
   202  		"-ex", "goroutine 2 bt",
   203  		"-ex", "echo END\n",
   204  		"-ex", "clear fmt.Println", // clear the previous break point
   205  		"-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main
   206  		"-ex", "c",
   207  		"-ex", "echo BEGIN goroutine 1 bt at the end\n",
   208  		"-ex", "goroutine 1 bt",
   209  		"-ex", "echo END\n",
   210  		filepath.Join(dir, "a.exe"),
   211  	)
   212  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   213  	t.Logf("gdb output: %s\n", got)
   214  
   215  	firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
   216  	if string(firstLine) != "Loading Go Runtime support." {
   217  		// This can happen when using all.bash with
   218  		// GOROOT_FINAL set, because the tests are run before
   219  		// the final installation of the files.
   220  		cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
   221  		cmd.Env = []string{}
   222  		out, err := cmd.CombinedOutput()
   223  		if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
   224  			t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT())
   225  		}
   226  
   227  		_, file, _, _ := runtime.Caller(1)
   228  
   229  		t.Logf("package testing source file: %s", file)
   230  		t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
   231  	}
   232  
   233  	// Extract named BEGIN...END blocks from output
   234  	partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
   235  	blocks := map[string]string{}
   236  	for _, subs := range partRe.FindAllSubmatch(got, -1) {
   237  		blocks[string(subs[1])] = string(subs[2])
   238  	}
   239  
   240  	infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
   241  	if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
   242  		t.Fatalf("info goroutines failed: %s", bl)
   243  	}
   244  
   245  	printMapvarRe1 := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
   246  	printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
   247  	if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
   248  		!printMapvarRe2.MatchString(bl) {
   249  		t.Fatalf("print mapvar failed: %s", bl)
   250  	}
   251  
   252  	strVarRe := regexp.MustCompile(`\Q = "abc"\E$`)
   253  	if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
   254  		t.Fatalf("print strvar failed: %s", bl)
   255  	}
   256  
   257  	// The exact format of composite values has changed over time.
   258  	// For issue 16338: ssa decompose phase split a slice into
   259  	// a collection of scalar vars holding its fields. In such cases
   260  	// the DWARF variable location expression should be of the
   261  	// form "var.field" and not just "field".
   262  	// However, the newer dwarf location list code reconstituted
   263  	// aggregates from their fields and reverted their printing
   264  	// back to its original form.
   265  
   266  	infoLocalsRe := regexp.MustCompile(`slicevar *= *\[\]string *= *{"def"}`)
   267  	if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
   268  		t.Fatalf("info locals failed: %s", bl)
   269  	}
   270  
   271  	btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`)
   272  	if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
   273  		t.Fatalf("goroutine 1 bt failed: %s", bl)
   274  	}
   275  
   276  	btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`)
   277  	if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
   278  		t.Fatalf("goroutine 2 bt failed: %s", bl)
   279  	}
   280  	btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
   281  	if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
   282  		t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
   283  	}
   284  }
   285  
   286  const backtraceSource = `
   287  package main
   288  
   289  //go:noinline
   290  func aaa() bool { return bbb() }
   291  
   292  //go:noinline
   293  func bbb() bool { return ccc() }
   294  
   295  //go:noinline
   296  func ccc() bool { return ddd() }
   297  
   298  //go:noinline
   299  func ddd() bool { return f() }
   300  
   301  //go:noinline
   302  func eee() bool { return true }
   303  
   304  var f = eee
   305  
   306  func main() {
   307  	_ = aaa()
   308  }
   309  `
   310  
   311  // TestGdbBacktrace tests that gdb can unwind the stack correctly
   312  // using only the DWARF debug info.
   313  func TestGdbBacktrace(t *testing.T) {
   314  	if runtime.GOOS == "netbsd" {
   315  		testenv.SkipFlaky(t, 15603)
   316  	}
   317  
   318  	checkGdbEnvironment(t)
   319  	t.Parallel()
   320  	checkGdbVersion(t)
   321  
   322  	dir, err := ioutil.TempDir("", "go-build")
   323  	if err != nil {
   324  		t.Fatalf("failed to create temp directory: %v", err)
   325  	}
   326  	defer os.RemoveAll(dir)
   327  
   328  	// Build the source code.
   329  	src := filepath.Join(dir, "main.go")
   330  	err = ioutil.WriteFile(src, []byte(backtraceSource), 0644)
   331  	if err != nil {
   332  		t.Fatalf("failed to create file: %v", err)
   333  	}
   334  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   335  	cmd.Dir = dir
   336  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   337  	if err != nil {
   338  		t.Fatalf("building source %v\n%s", err, out)
   339  	}
   340  
   341  	// Execute gdb commands.
   342  	args := []string{"-nx", "-batch",
   343  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   344  		"-ex", "set startup-with-shell off",
   345  		"-ex", "break main.eee",
   346  		"-ex", "run",
   347  		"-ex", "backtrace",
   348  		"-ex", "continue",
   349  		filepath.Join(dir, "a.exe"),
   350  	}
   351  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   352  
   353  	// Check that the backtrace matches the source code.
   354  	bt := []string{
   355  		"eee",
   356  		"ddd",
   357  		"ccc",
   358  		"bbb",
   359  		"aaa",
   360  		"main",
   361  	}
   362  	for i, name := range bt {
   363  		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
   364  		re := regexp.MustCompile(s)
   365  		if found := re.Find(got) != nil; !found {
   366  			t.Errorf("could not find '%v' in backtrace", s)
   367  			t.Fatalf("gdb output:\n%v", string(got))
   368  		}
   369  	}
   370  }
   371  
   372  const autotmpTypeSource = `
   373  package main
   374  
   375  type astruct struct {
   376  	a, b int
   377  }
   378  
   379  func main() {
   380  	var iface interface{} = map[string]astruct{}
   381  	var iface2 interface{} = []astruct{}
   382  	println(iface, iface2)
   383  }
   384  `
   385  
   386  // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
   387  // See bug #17830.
   388  func TestGdbAutotmpTypes(t *testing.T) {
   389  	checkGdbEnvironment(t)
   390  	t.Parallel()
   391  	checkGdbVersion(t)
   392  
   393  	dir, err := ioutil.TempDir("", "go-build")
   394  	if err != nil {
   395  		t.Fatalf("failed to create temp directory: %v", err)
   396  	}
   397  	defer os.RemoveAll(dir)
   398  
   399  	// Build the source code.
   400  	src := filepath.Join(dir, "main.go")
   401  	err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644)
   402  	if err != nil {
   403  		t.Fatalf("failed to create file: %v", err)
   404  	}
   405  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
   406  	cmd.Dir = dir
   407  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   408  	if err != nil {
   409  		t.Fatalf("building source %v\n%s", err, out)
   410  	}
   411  
   412  	// Execute gdb commands.
   413  	args := []string{"-nx", "-batch",
   414  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   415  		"-ex", "set startup-with-shell off",
   416  		"-ex", "break main.main",
   417  		"-ex", "run",
   418  		"-ex", "step",
   419  		"-ex", "info types astruct",
   420  		filepath.Join(dir, "a.exe"),
   421  	}
   422  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   423  
   424  	sgot := string(got)
   425  
   426  	// Check that the backtrace matches the source code.
   427  	types := []string{
   428  		"struct []main.astruct;",
   429  		"struct bucket<string,main.astruct>;",
   430  		"struct hash<string,main.astruct>;",
   431  		"struct main.astruct;",
   432  		"typedef struct hash<string,main.astruct> * map[string]main.astruct;",
   433  	}
   434  	for _, name := range types {
   435  		if !strings.Contains(sgot, name) {
   436  			t.Errorf("could not find %s in 'info typrs astruct' output", name)
   437  			t.Fatalf("gdb output:\n%v", sgot)
   438  		}
   439  	}
   440  }
   441  
   442  const constsSource = `
   443  package main
   444  
   445  const aConstant int = 42
   446  const largeConstant uint64 = ^uint64(0)
   447  const minusOne int64 = -1
   448  
   449  func main() {
   450  	println("hello world")
   451  }
   452  `
   453  
   454  func TestGdbConst(t *testing.T) {
   455  	checkGdbEnvironment(t)
   456  	t.Parallel()
   457  	checkGdbVersion(t)
   458  
   459  	dir, err := ioutil.TempDir("", "go-build")
   460  	if err != nil {
   461  		t.Fatalf("failed to create temp directory: %v", err)
   462  	}
   463  	defer os.RemoveAll(dir)
   464  
   465  	// Build the source code.
   466  	src := filepath.Join(dir, "main.go")
   467  	err = ioutil.WriteFile(src, []byte(constsSource), 0644)
   468  	if err != nil {
   469  		t.Fatalf("failed to create file: %v", err)
   470  	}
   471  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
   472  	cmd.Dir = dir
   473  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   474  	if err != nil {
   475  		t.Fatalf("building source %v\n%s", err, out)
   476  	}
   477  
   478  	// Execute gdb commands.
   479  	args := []string{"-nx", "-batch",
   480  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   481  		"-ex", "set startup-with-shell off",
   482  		"-ex", "break main.main",
   483  		"-ex", "run",
   484  		"-ex", "print main.aConstant",
   485  		"-ex", "print main.largeConstant",
   486  		"-ex", "print main.minusOne",
   487  		"-ex", "print 'runtime._MSpanInUse'",
   488  		"-ex", "print 'runtime._PageSize'",
   489  		filepath.Join(dir, "a.exe"),
   490  	}
   491  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   492  
   493  	sgot := strings.Replace(string(got), "\r\n", "\n", -1)
   494  
   495  	t.Logf("output %q", sgot)
   496  
   497  	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
   498  		t.Fatalf("output mismatch")
   499  	}
   500  }
   501  
   502  const panicSource = `
   503  package main
   504  
   505  import "runtime/debug"
   506  
   507  func main() {
   508  	debug.SetTraceback("crash")
   509  	crash()
   510  }
   511  
   512  func crash() {
   513  	panic("panic!")
   514  }
   515  `
   516  
   517  // TestGdbPanic tests that gdb can unwind the stack correctly
   518  // from SIGABRTs from Go panics.
   519  func TestGdbPanic(t *testing.T) {
   520  	checkGdbEnvironment(t)
   521  	t.Parallel()
   522  	checkGdbVersion(t)
   523  
   524  	dir, err := ioutil.TempDir("", "go-build")
   525  	if err != nil {
   526  		t.Fatalf("failed to create temp directory: %v", err)
   527  	}
   528  	defer os.RemoveAll(dir)
   529  
   530  	// Build the source code.
   531  	src := filepath.Join(dir, "main.go")
   532  	err = ioutil.WriteFile(src, []byte(panicSource), 0644)
   533  	if err != nil {
   534  		t.Fatalf("failed to create file: %v", err)
   535  	}
   536  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   537  	cmd.Dir = dir
   538  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   539  	if err != nil {
   540  		t.Fatalf("building source %v\n%s", err, out)
   541  	}
   542  
   543  	// Execute gdb commands.
   544  	args := []string{"-nx", "-batch",
   545  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   546  		"-ex", "set startup-with-shell off",
   547  		"-ex", "run",
   548  		"-ex", "backtrace",
   549  		filepath.Join(dir, "a.exe"),
   550  	}
   551  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   552  
   553  	// Check that the backtrace matches the source code.
   554  	bt := []string{
   555  		`crash`,
   556  		`main`,
   557  	}
   558  	for _, name := range bt {
   559  		s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
   560  		re := regexp.MustCompile(s)
   561  		if found := re.Find(got) != nil; !found {
   562  			t.Errorf("could not find '%v' in backtrace", s)
   563  			t.Fatalf("gdb output:\n%v", string(got))
   564  		}
   565  	}
   566  }
   567  

View as plain text