github.com/s1s1ty/go@v0.0.0-20180207192209-104445e3140f/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 "linux":
    31  		if runtime.GOARCH == "ppc64" {
    32  			t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366")
    33  		}
    34  	}
    35  	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
    36  		t.Skip("gdb test can fail with GOROOT_FINAL pending")
    37  	}
    38  }
    39  
    40  func checkGdbVersion(t *testing.T) {
    41  	// Issue 11214 reports various failures with older versions of gdb.
    42  	out, err := exec.Command("gdb", "--version").CombinedOutput()
    43  	if err != nil {
    44  		t.Skipf("skipping: error executing gdb: %v", err)
    45  	}
    46  	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
    47  	matches := re.FindSubmatch(out)
    48  	if len(matches) < 3 {
    49  		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
    50  	}
    51  	major, err1 := strconv.Atoi(string(matches[1]))
    52  	minor, err2 := strconv.Atoi(string(matches[2]))
    53  	if err1 != nil || err2 != nil {
    54  		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
    55  	}
    56  	if major < 7 || (major == 7 && minor < 7) {
    57  		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
    58  	}
    59  	t.Logf("gdb version %d.%d", major, minor)
    60  }
    61  
    62  func checkGdbPython(t *testing.T) {
    63  	if runtime.GOOS == "solaris" && testenv.Builder() != "solaris-amd64-smartosbuildlet" {
    64  		t.Skip("skipping gdb python tests on solaris; see golang.org/issue/20821")
    65  	}
    66  
    67  	cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
    68  	out, err := cmd.CombinedOutput()
    69  
    70  	if err != nil {
    71  		t.Skipf("skipping due to issue running gdb: %v", err)
    72  	}
    73  	if string(out) != "go gdb python support\n" {
    74  		t.Skipf("skipping due to lack of python gdb support: %s", out)
    75  	}
    76  }
    77  
    78  const helloSource = `
    79  import "fmt"
    80  import "runtime"
    81  var gslice []string
    82  func main() {
    83  	mapvar := make(map[string]string, 13)
    84  	mapvar["abc"] = "def"
    85  	mapvar["ghi"] = "jkl"
    86  	strvar := "abc"
    87  	ptrvar := &strvar
    88  	slicevar := make([]string, 0, 16)
    89  	slicevar = append(slicevar, mapvar["abc"])
    90  	fmt.Println("hi") // line 13
    91  	runtime.KeepAlive(ptrvar)
    92  	gslice = slicevar
    93  	runtime.KeepAlive(mapvar)
    94  }
    95  `
    96  
    97  func TestGdbPython(t *testing.T) {
    98  	testGdbPython(t, false)
    99  }
   100  
   101  func TestGdbPythonCgo(t *testing.T) {
   102  	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" {
   103  		testenv.SkipFlaky(t, 18784)
   104  	}
   105  	testGdbPython(t, true)
   106  }
   107  
   108  func testGdbPython(t *testing.T, cgo bool) {
   109  	if cgo && !build.Default.CgoEnabled {
   110  		t.Skip("skipping because cgo is not enabled")
   111  	}
   112  
   113  	checkGdbEnvironment(t)
   114  	t.Parallel()
   115  	checkGdbVersion(t)
   116  	checkGdbPython(t)
   117  
   118  	dir, err := ioutil.TempDir("", "go-build")
   119  	if err != nil {
   120  		t.Fatalf("failed to create temp directory: %v", err)
   121  	}
   122  	defer os.RemoveAll(dir)
   123  
   124  	var buf bytes.Buffer
   125  	buf.WriteString("package main\n")
   126  	if cgo {
   127  		buf.WriteString(`import "C"` + "\n")
   128  	}
   129  	buf.WriteString(helloSource)
   130  
   131  	src := filepath.Join(dir, "main.go")
   132  	err = ioutil.WriteFile(src, buf.Bytes(), 0644)
   133  	if err != nil {
   134  		t.Fatalf("failed to create file: %v", err)
   135  	}
   136  
   137  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   138  	cmd.Dir = dir
   139  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   140  	if err != nil {
   141  		t.Fatalf("building source %v\n%s", err, out)
   142  	}
   143  
   144  	args := []string{"-nx", "-q", "--batch", "-iex",
   145  		fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()),
   146  		"-ex", "set startup-with-shell off",
   147  		"-ex", "info auto-load python-scripts",
   148  		"-ex", "set python print-stack full",
   149  		"-ex", "br fmt.Println",
   150  		"-ex", "run",
   151  		"-ex", "echo BEGIN info goroutines\n",
   152  		"-ex", "info goroutines",
   153  		"-ex", "echo END\n",
   154  		"-ex", "up", // up from fmt.Println to main
   155  		"-ex", "echo BEGIN print mapvar\n",
   156  		"-ex", "print mapvar",
   157  		"-ex", "echo END\n",
   158  		"-ex", "echo BEGIN print strvar\n",
   159  		"-ex", "print strvar",
   160  		"-ex", "echo END\n",
   161  		"-ex", "echo BEGIN info locals\n",
   162  		"-ex", "info locals",
   163  		"-ex", "echo END\n",
   164  		"-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack.  TODO: fix that)
   165  		"-ex", "echo BEGIN goroutine 1 bt\n",
   166  		"-ex", "goroutine 1 bt",
   167  		"-ex", "echo END\n",
   168  		"-ex", "echo BEGIN goroutine 2 bt\n",
   169  		"-ex", "goroutine 2 bt",
   170  		"-ex", "echo END\n",
   171  		filepath.Join(dir, "a.exe"),
   172  	}
   173  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   174  
   175  	firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
   176  	if string(firstLine) != "Loading Go Runtime support." {
   177  		// This can happen when using all.bash with
   178  		// GOROOT_FINAL set, because the tests are run before
   179  		// the final installation of the files.
   180  		cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
   181  		cmd.Env = []string{}
   182  		out, err := cmd.CombinedOutput()
   183  		if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
   184  			t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT())
   185  		}
   186  
   187  		_, file, _, _ := runtime.Caller(1)
   188  
   189  		t.Logf("package testing source file: %s", file)
   190  		t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
   191  	}
   192  
   193  	// Extract named BEGIN...END blocks from output
   194  	partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
   195  	blocks := map[string]string{}
   196  	for _, subs := range partRe.FindAllSubmatch(got, -1) {
   197  		blocks[string(subs[1])] = string(subs[2])
   198  	}
   199  
   200  	infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
   201  	if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
   202  		t.Fatalf("info goroutines failed: %s", bl)
   203  	}
   204  
   205  	printMapvarRe1 := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
   206  	printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
   207  	if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
   208  		!printMapvarRe2.MatchString(bl) {
   209  		t.Fatalf("print mapvar failed: %s", bl)
   210  	}
   211  
   212  	strVarRe := regexp.MustCompile(`\Q = "abc"\E$`)
   213  	if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
   214  		t.Fatalf("print strvar failed: %s", bl)
   215  	}
   216  
   217  	// Issue 16338: ssa decompose phase can split a structure into
   218  	// a collection of scalar vars holding the fields. In such cases
   219  	// the DWARF variable location expression should be of the
   220  	// form "var.field" and not just "field".
   221  	infoLocalsRe := regexp.MustCompile(`.*\sslicevar.cap = `)
   222  	if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
   223  		t.Fatalf("info locals failed: %s", bl)
   224  	}
   225  
   226  	btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`)
   227  	if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
   228  		t.Fatalf("goroutine 1 bt failed: %s", bl)
   229  	}
   230  
   231  	btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`)
   232  	if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
   233  		t.Fatalf("goroutine 2 bt failed: %s", bl)
   234  	}
   235  }
   236  
   237  const backtraceSource = `
   238  package main
   239  
   240  //go:noinline
   241  func aaa() bool { return bbb() }
   242  
   243  //go:noinline
   244  func bbb() bool { return ccc() }
   245  
   246  //go:noinline
   247  func ccc() bool { return ddd() }
   248  
   249  //go:noinline
   250  func ddd() bool { return f() }
   251  
   252  //go:noinline
   253  func eee() bool { return true }
   254  
   255  var f = eee
   256  
   257  func main() {
   258  	_ = aaa()
   259  }
   260  `
   261  
   262  // TestGdbBacktrace tests that gdb can unwind the stack correctly
   263  // using only the DWARF debug info.
   264  func TestGdbBacktrace(t *testing.T) {
   265  	if runtime.GOOS == "netbsd" {
   266  		testenv.SkipFlaky(t, 15603)
   267  	}
   268  
   269  	checkGdbEnvironment(t)
   270  	t.Parallel()
   271  	checkGdbVersion(t)
   272  
   273  	dir, err := ioutil.TempDir("", "go-build")
   274  	if err != nil {
   275  		t.Fatalf("failed to create temp directory: %v", err)
   276  	}
   277  	defer os.RemoveAll(dir)
   278  
   279  	// Build the source code.
   280  	src := filepath.Join(dir, "main.go")
   281  	err = ioutil.WriteFile(src, []byte(backtraceSource), 0644)
   282  	if err != nil {
   283  		t.Fatalf("failed to create file: %v", err)
   284  	}
   285  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   286  	cmd.Dir = dir
   287  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   288  	if err != nil {
   289  		t.Fatalf("building source %v\n%s", err, out)
   290  	}
   291  
   292  	// Execute gdb commands.
   293  	args := []string{"-nx", "-batch",
   294  		"-ex", "set startup-with-shell off",
   295  		"-ex", "break main.eee",
   296  		"-ex", "run",
   297  		"-ex", "backtrace",
   298  		"-ex", "continue",
   299  		filepath.Join(dir, "a.exe"),
   300  	}
   301  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   302  
   303  	// Check that the backtrace matches the source code.
   304  	bt := []string{
   305  		"eee",
   306  		"ddd",
   307  		"ccc",
   308  		"bbb",
   309  		"aaa",
   310  		"main",
   311  	}
   312  	for i, name := range bt {
   313  		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
   314  		re := regexp.MustCompile(s)
   315  		if found := re.Find(got) != nil; !found {
   316  			t.Errorf("could not find '%v' in backtrace", s)
   317  			t.Fatalf("gdb output:\n%v", string(got))
   318  		}
   319  	}
   320  }
   321  
   322  const autotmpTypeSource = `
   323  package main
   324  
   325  type astruct struct {
   326  	a, b int
   327  }
   328  
   329  func main() {
   330  	var iface interface{} = map[string]astruct{}
   331  	var iface2 interface{} = []astruct{}
   332  	println(iface, iface2)
   333  }
   334  `
   335  
   336  // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
   337  // See bug #17830.
   338  func TestGdbAutotmpTypes(t *testing.T) {
   339  	checkGdbEnvironment(t)
   340  	t.Parallel()
   341  	checkGdbVersion(t)
   342  
   343  	dir, err := ioutil.TempDir("", "go-build")
   344  	if err != nil {
   345  		t.Fatalf("failed to create temp directory: %v", err)
   346  	}
   347  	defer os.RemoveAll(dir)
   348  
   349  	// Build the source code.
   350  	src := filepath.Join(dir, "main.go")
   351  	err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644)
   352  	if err != nil {
   353  		t.Fatalf("failed to create file: %v", err)
   354  	}
   355  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
   356  	cmd.Dir = dir
   357  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   358  	if err != nil {
   359  		t.Fatalf("building source %v\n%s", err, out)
   360  	}
   361  
   362  	// Execute gdb commands.
   363  	args := []string{"-nx", "-batch",
   364  		"-ex", "set startup-with-shell off",
   365  		"-ex", "break main.main",
   366  		"-ex", "run",
   367  		"-ex", "step",
   368  		"-ex", "info types astruct",
   369  		filepath.Join(dir, "a.exe"),
   370  	}
   371  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   372  
   373  	sgot := string(got)
   374  
   375  	// Check that the backtrace matches the source code.
   376  	types := []string{
   377  		"struct []main.astruct;",
   378  		"struct bucket<string,main.astruct>;",
   379  		"struct hash<string,main.astruct>;",
   380  		"struct main.astruct;",
   381  		"typedef struct hash<string,main.astruct> * map[string]main.astruct;",
   382  	}
   383  	for _, name := range types {
   384  		if !strings.Contains(sgot, name) {
   385  			t.Errorf("could not find %s in 'info typrs astruct' output", name)
   386  			t.Fatalf("gdb output:\n%v", sgot)
   387  		}
   388  	}
   389  }
   390  
   391  const constsSource = `
   392  package main
   393  
   394  const aConstant int = 42
   395  const largeConstant uint64 = ^uint64(0)
   396  const minusOne int64 = -1
   397  
   398  func main() {
   399  	println("hello world")
   400  }
   401  `
   402  
   403  func TestGdbConst(t *testing.T) {
   404  	checkGdbEnvironment(t)
   405  	t.Parallel()
   406  	checkGdbVersion(t)
   407  
   408  	dir, err := ioutil.TempDir("", "go-build")
   409  	if err != nil {
   410  		t.Fatalf("failed to create temp directory: %v", err)
   411  	}
   412  	defer os.RemoveAll(dir)
   413  
   414  	// Build the source code.
   415  	src := filepath.Join(dir, "main.go")
   416  	err = ioutil.WriteFile(src, []byte(constsSource), 0644)
   417  	if err != nil {
   418  		t.Fatalf("failed to create file: %v", err)
   419  	}
   420  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
   421  	cmd.Dir = dir
   422  	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
   423  	if err != nil {
   424  		t.Fatalf("building source %v\n%s", err, out)
   425  	}
   426  
   427  	// Execute gdb commands.
   428  	args := []string{"-nx", "-batch",
   429  		"-ex", "set startup-with-shell off",
   430  		"-ex", "break main.main",
   431  		"-ex", "run",
   432  		"-ex", "print main.aConstant",
   433  		"-ex", "print main.largeConstant",
   434  		"-ex", "print main.minusOne",
   435  		"-ex", "print 'runtime._MSpanInUse'",
   436  		"-ex", "print 'runtime._PageSize'",
   437  		filepath.Join(dir, "a.exe"),
   438  	}
   439  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   440  
   441  	sgot := strings.Replace(string(got), "\r\n", "\n", -1)
   442  
   443  	t.Logf("output %q", sgot)
   444  
   445  	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
   446  		t.Fatalf("output mismatch")
   447  	}
   448  }