github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/runtime/runtime-gdb_test.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package runtime_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"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(`^\$[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"}$`)
   246  	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"}$`)
   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(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`)
   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  	infoLocalsRe1 := regexp.MustCompile(`slicevar *= *\[\]string *= *{"def"}`)
   267  	// Format output from gdb v8.2
   268  	infoLocalsRe2 := regexp.MustCompile(`^slicevar = .*\nmapvar = .*\nstrvar = 0x[0-9a-f]+ "abc"`)
   269  	if bl := blocks["info locals"]; !infoLocalsRe1.MatchString(bl) &&
   270  		!infoLocalsRe2.MatchString(bl) {
   271  		t.Fatalf("info locals failed: %s", bl)
   272  	}
   273  
   274  	btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`)
   275  	if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
   276  		t.Fatalf("goroutine 1 bt failed: %s", bl)
   277  	}
   278  
   279  	btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`)
   280  	if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
   281  		t.Fatalf("goroutine 2 bt failed: %s", bl)
   282  	}
   283  	btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
   284  	if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
   285  		t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
   286  	}
   287  }
   288  
   289  const backtraceSource = `
   290  package main
   291  
   292  //go:noinline
   293  func aaa() bool { return bbb() }
   294  
   295  //go:noinline
   296  func bbb() bool { return ccc() }
   297  
   298  //go:noinline
   299  func ccc() bool { return ddd() }
   300  
   301  //go:noinline
   302  func ddd() bool { return f() }
   303  
   304  //go:noinline
   305  func eee() bool { return true }
   306  
   307  var f = eee
   308  
   309  func main() {
   310  	_ = aaa()
   311  }
   312  `
   313  
   314  // TestGdbBacktrace tests that gdb can unwind the stack correctly
   315  // using only the DWARF debug info.
   316  func TestGdbBacktrace(t *testing.T) {
   317  	if runtime.GOOS == "netbsd" {
   318  		testenv.SkipFlaky(t, 15603)
   319  	}
   320  
   321  	checkGdbEnvironment(t)
   322  	t.Parallel()
   323  	checkGdbVersion(t)
   324  
   325  	dir, err := ioutil.TempDir("", "go-build")
   326  	if err != nil {
   327  		t.Fatalf("failed to create temp directory: %v", err)
   328  	}
   329  	defer os.RemoveAll(dir)
   330  
   331  	// Build the source code.
   332  	src := filepath.Join(dir, "main.go")
   333  	err = ioutil.WriteFile(src, []byte(backtraceSource), 0644)
   334  	if err != nil {
   335  		t.Fatalf("failed to create file: %v", err)
   336  	}
   337  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   338  	cmd.Dir = dir
   339  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   340  	if err != nil {
   341  		t.Fatalf("building source %v\n%s", err, out)
   342  	}
   343  
   344  	// Execute gdb commands.
   345  	args := []string{"-nx", "-batch",
   346  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   347  		"-ex", "set startup-with-shell off",
   348  		"-ex", "break main.eee",
   349  		"-ex", "run",
   350  		"-ex", "backtrace",
   351  		"-ex", "continue",
   352  		filepath.Join(dir, "a.exe"),
   353  	}
   354  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   355  
   356  	// Check that the backtrace matches the source code.
   357  	bt := []string{
   358  		"eee",
   359  		"ddd",
   360  		"ccc",
   361  		"bbb",
   362  		"aaa",
   363  		"main",
   364  	}
   365  	for i, name := range bt {
   366  		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
   367  		re := regexp.MustCompile(s)
   368  		if found := re.Find(got) != nil; !found {
   369  			t.Errorf("could not find '%v' in backtrace", s)
   370  			t.Fatalf("gdb output:\n%v", string(got))
   371  		}
   372  	}
   373  }
   374  
   375  const autotmpTypeSource = `
   376  package main
   377  
   378  type astruct struct {
   379  	a, b int
   380  }
   381  
   382  func main() {
   383  	var iface interface{} = map[string]astruct{}
   384  	var iface2 interface{} = []astruct{}
   385  	println(iface, iface2)
   386  }
   387  `
   388  
   389  // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
   390  // See bug #17830.
   391  func TestGdbAutotmpTypes(t *testing.T) {
   392  	checkGdbEnvironment(t)
   393  	t.Parallel()
   394  	checkGdbVersion(t)
   395  
   396  	dir, err := ioutil.TempDir("", "go-build")
   397  	if err != nil {
   398  		t.Fatalf("failed to create temp directory: %v", err)
   399  	}
   400  	defer os.RemoveAll(dir)
   401  
   402  	// Build the source code.
   403  	src := filepath.Join(dir, "main.go")
   404  	err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644)
   405  	if err != nil {
   406  		t.Fatalf("failed to create file: %v", err)
   407  	}
   408  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
   409  	cmd.Dir = dir
   410  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   411  	if err != nil {
   412  		t.Fatalf("building source %v\n%s", err, out)
   413  	}
   414  
   415  	// Execute gdb commands.
   416  	args := []string{"-nx", "-batch",
   417  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   418  		"-ex", "set startup-with-shell off",
   419  		"-ex", "break main.main",
   420  		"-ex", "run",
   421  		"-ex", "step",
   422  		"-ex", "info types astruct",
   423  		filepath.Join(dir, "a.exe"),
   424  	}
   425  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   426  
   427  	sgot := string(got)
   428  
   429  	// Check that the backtrace matches the source code.
   430  	types := []string{
   431  		"[]main.astruct;",
   432  		"bucket<string,main.astruct>;",
   433  		"hash<string,main.astruct>;",
   434  		"main.astruct;",
   435  		"hash<string,main.astruct> * map[string]main.astruct;",
   436  	}
   437  	for _, name := range types {
   438  		if !strings.Contains(sgot, name) {
   439  			t.Errorf("could not find %s in 'info typrs astruct' output", name)
   440  			t.Fatalf("gdb output:\n%v", sgot)
   441  		}
   442  	}
   443  }
   444  
   445  const constsSource = `
   446  package main
   447  
   448  const aConstant int = 42
   449  const largeConstant uint64 = ^uint64(0)
   450  const minusOne int64 = -1
   451  
   452  func main() {
   453  	println("hello world")
   454  }
   455  `
   456  
   457  func TestGdbConst(t *testing.T) {
   458  	checkGdbEnvironment(t)
   459  	t.Parallel()
   460  	checkGdbVersion(t)
   461  
   462  	dir, err := ioutil.TempDir("", "go-build")
   463  	if err != nil {
   464  		t.Fatalf("failed to create temp directory: %v", err)
   465  	}
   466  	defer os.RemoveAll(dir)
   467  
   468  	// Build the source code.
   469  	src := filepath.Join(dir, "main.go")
   470  	err = ioutil.WriteFile(src, []byte(constsSource), 0644)
   471  	if err != nil {
   472  		t.Fatalf("failed to create file: %v", err)
   473  	}
   474  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
   475  	cmd.Dir = dir
   476  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   477  	if err != nil {
   478  		t.Fatalf("building source %v\n%s", err, out)
   479  	}
   480  
   481  	// Execute gdb commands.
   482  	args := []string{"-nx", "-batch",
   483  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   484  		"-ex", "set startup-with-shell off",
   485  		"-ex", "break main.main",
   486  		"-ex", "run",
   487  		"-ex", "print main.aConstant",
   488  		"-ex", "print main.largeConstant",
   489  		"-ex", "print main.minusOne",
   490  		"-ex", "print 'runtime.mSpanInUse'",
   491  		"-ex", "print 'runtime._PageSize'",
   492  		filepath.Join(dir, "a.exe"),
   493  	}
   494  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   495  
   496  	sgot := strings.ReplaceAll(string(got), "\r\n", "\n")
   497  
   498  	t.Logf("output %q", sgot)
   499  
   500  	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
   501  		t.Fatalf("output mismatch")
   502  	}
   503  }
   504  
   505  const panicSource = `
   506  package main
   507  
   508  import "runtime/debug"
   509  
   510  func main() {
   511  	debug.SetTraceback("crash")
   512  	crash()
   513  }
   514  
   515  func crash() {
   516  	panic("panic!")
   517  }
   518  `
   519  
   520  // TestGdbPanic tests that gdb can unwind the stack correctly
   521  // from SIGABRTs from Go panics.
   522  func TestGdbPanic(t *testing.T) {
   523  	checkGdbEnvironment(t)
   524  	t.Parallel()
   525  	checkGdbVersion(t)
   526  
   527  	dir, err := ioutil.TempDir("", "go-build")
   528  	if err != nil {
   529  		t.Fatalf("failed to create temp directory: %v", err)
   530  	}
   531  	defer os.RemoveAll(dir)
   532  
   533  	// Build the source code.
   534  	src := filepath.Join(dir, "main.go")
   535  	err = ioutil.WriteFile(src, []byte(panicSource), 0644)
   536  	if err != nil {
   537  		t.Fatalf("failed to create file: %v", err)
   538  	}
   539  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   540  	cmd.Dir = dir
   541  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   542  	if err != nil {
   543  		t.Fatalf("building source %v\n%s", err, out)
   544  	}
   545  
   546  	// Execute gdb commands.
   547  	args := []string{"-nx", "-batch",
   548  		"-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
   549  		"-ex", "set startup-with-shell off",
   550  		"-ex", "run",
   551  		"-ex", "backtrace",
   552  		filepath.Join(dir, "a.exe"),
   553  	}
   554  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   555  
   556  	// Check that the backtrace matches the source code.
   557  	bt := []string{
   558  		`crash`,
   559  		`main`,
   560  	}
   561  	for _, name := range bt {
   562  		s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
   563  		re := regexp.MustCompile(s)
   564  		if found := re.Find(got) != nil; !found {
   565  			t.Errorf("could not find '%v' in backtrace", s)
   566  			t.Fatalf("gdb output:\n%v", string(got))
   567  		}
   568  	}
   569  }