github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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  	"internal/testenv"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"strconv"
    18  	"testing"
    19  )
    20  
    21  func checkGdbEnvironment(t *testing.T) {
    22  	testenv.MustHaveGoBuild(t)
    23  	if runtime.GOOS == "darwin" {
    24  		t.Skip("gdb does not work on darwin")
    25  	}
    26  	if runtime.GOOS == "linux" && runtime.GOARCH == "ppc64" {
    27  		t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366")
    28  	}
    29  	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
    30  		t.Skip("gdb test can fail with GOROOT_FINAL pending")
    31  	}
    32  }
    33  
    34  func checkGdbVersion(t *testing.T) {
    35  	// Issue 11214 reports various failures with older versions of gdb.
    36  	out, err := exec.Command("gdb", "--version").CombinedOutput()
    37  	if err != nil {
    38  		t.Skipf("skipping: error executing gdb: %v", err)
    39  	}
    40  	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
    41  	matches := re.FindSubmatch(out)
    42  	if len(matches) < 3 {
    43  		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
    44  	}
    45  	major, err1 := strconv.Atoi(string(matches[1]))
    46  	minor, err2 := strconv.Atoi(string(matches[2]))
    47  	if err1 != nil || err2 != nil {
    48  		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
    49  	}
    50  	if major < 7 || (major == 7 && minor < 7) {
    51  		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
    52  	}
    53  	t.Logf("gdb version %d.%d", major, minor)
    54  }
    55  
    56  func checkGdbPython(t *testing.T) {
    57  	cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
    58  	out, err := cmd.CombinedOutput()
    59  
    60  	if err != nil {
    61  		t.Skipf("skipping due to issue running gdb: %v", err)
    62  	}
    63  	if string(out) != "go gdb python support\n" {
    64  		t.Skipf("skipping due to lack of python gdb support: %s", out)
    65  	}
    66  }
    67  
    68  const helloSource = `
    69  package main
    70  import "fmt"
    71  var gslice []string
    72  func main() {
    73  	mapvar := make(map[string]string,5)
    74  	mapvar["abc"] = "def"
    75  	mapvar["ghi"] = "jkl"
    76  	strvar := "abc"
    77  	ptrvar := &strvar
    78  	slicevar := make([]string, 0, 16)
    79  	slicevar = append(slicevar, mapvar["abc"])
    80  	fmt.Println("hi") // line 12
    81  	_ = ptrvar
    82  	gslice = slicevar
    83  }
    84  `
    85  
    86  func TestGdbPython(t *testing.T) {
    87  	checkGdbEnvironment(t)
    88  	checkGdbVersion(t)
    89  	checkGdbPython(t)
    90  
    91  	dir, err := ioutil.TempDir("", "go-build")
    92  	if err != nil {
    93  		t.Fatalf("failed to create temp directory: %v", err)
    94  	}
    95  	defer os.RemoveAll(dir)
    96  
    97  	src := filepath.Join(dir, "main.go")
    98  	err = ioutil.WriteFile(src, []byte(helloSource), 0644)
    99  	if err != nil {
   100  		t.Fatalf("failed to create file: %v", err)
   101  	}
   102  
   103  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   104  	cmd.Dir = dir
   105  	out, err := testEnv(cmd).CombinedOutput()
   106  	if err != nil {
   107  		t.Fatalf("building source %v\n%s", err, out)
   108  	}
   109  
   110  	args := []string{"-nx", "-q", "--batch", "-iex",
   111  		fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()),
   112  		"-ex", "set startup-with-shell off",
   113  		"-ex", "info auto-load python-scripts",
   114  		"-ex", "set python print-stack full",
   115  		"-ex", "br fmt.Println",
   116  		"-ex", "run",
   117  		"-ex", "echo BEGIN info goroutines\n",
   118  		"-ex", "info goroutines",
   119  		"-ex", "echo END\n",
   120  		"-ex", "up", // up from fmt.Println to main
   121  		"-ex", "echo BEGIN print mapvar\n",
   122  		"-ex", "print mapvar",
   123  		"-ex", "echo END\n",
   124  		"-ex", "echo BEGIN print strvar\n",
   125  		"-ex", "print strvar",
   126  		"-ex", "echo END\n",
   127  		"-ex", "echo BEGIN info locals\n",
   128  		"-ex", "info locals",
   129  		"-ex", "echo END\n",
   130  		"-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack.  TODO: fix that)
   131  		"-ex", "echo BEGIN goroutine 2 bt\n",
   132  		"-ex", "goroutine 2 bt",
   133  		"-ex", "echo END\n",
   134  		filepath.Join(dir, "a.exe"),
   135  	}
   136  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   137  
   138  	firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
   139  	if string(firstLine) != "Loading Go Runtime support." {
   140  		// This can happen when using all.bash with
   141  		// GOROOT_FINAL set, because the tests are run before
   142  		// the final installation of the files.
   143  		cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
   144  		cmd.Env = []string{}
   145  		out, err := cmd.CombinedOutput()
   146  		if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
   147  			t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT())
   148  		}
   149  
   150  		_, file, _, _ := runtime.Caller(1)
   151  
   152  		t.Logf("package testing source file: %s", file)
   153  		t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
   154  	}
   155  
   156  	// Extract named BEGIN...END blocks from output
   157  	partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
   158  	blocks := map[string]string{}
   159  	for _, subs := range partRe.FindAllSubmatch(got, -1) {
   160  		blocks[string(subs[1])] = string(subs[2])
   161  	}
   162  
   163  	infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
   164  	if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
   165  		t.Fatalf("info goroutines failed: %s", bl)
   166  	}
   167  
   168  	printMapvarRe := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
   169  	if bl := blocks["print mapvar"]; !printMapvarRe.MatchString(bl) {
   170  		t.Fatalf("print mapvar failed: %s", bl)
   171  	}
   172  
   173  	strVarRe := regexp.MustCompile(`\Q = "abc"\E$`)
   174  	if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
   175  		t.Fatalf("print strvar failed: %s", bl)
   176  	}
   177  
   178  	// Issue 16338: ssa decompose phase can split a structure into
   179  	// a collection of scalar vars holding the fields. In such cases
   180  	// the DWARF variable location expression should be of the
   181  	// form "var.field" and not just "field".
   182  	infoLocalsRe := regexp.MustCompile(`^slicevar.len = `)
   183  	if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
   184  		t.Fatalf("info locals failed: %s", bl)
   185  	}
   186  
   187  	btGoroutineRe := regexp.MustCompile(`^#0\s+runtime.+at`)
   188  	if bl := blocks["goroutine 2 bt"]; !btGoroutineRe.MatchString(bl) {
   189  		t.Fatalf("goroutine 2 bt failed: %s", bl)
   190  	}
   191  }
   192  
   193  const backtraceSource = `
   194  package main
   195  
   196  //go:noinline
   197  func aaa() bool { return bbb() }
   198  
   199  //go:noinline
   200  func bbb() bool { return ccc() }
   201  
   202  //go:noinline
   203  func ccc() bool { return ddd() }
   204  
   205  //go:noinline
   206  func ddd() bool { return f() }
   207  
   208  //go:noinline
   209  func eee() bool { return true }
   210  
   211  var f = eee
   212  
   213  func main() {
   214  	_ = aaa()
   215  }
   216  `
   217  
   218  // TestGdbBacktrace tests that gdb can unwind the stack correctly
   219  // using only the DWARF debug info.
   220  func TestGdbBacktrace(t *testing.T) {
   221  	checkGdbEnvironment(t)
   222  	checkGdbVersion(t)
   223  
   224  	if runtime.GOOS == "netbsd" {
   225  		testenv.SkipFlaky(t, 15603)
   226  	}
   227  
   228  	dir, err := ioutil.TempDir("", "go-build")
   229  	if err != nil {
   230  		t.Fatalf("failed to create temp directory: %v", err)
   231  	}
   232  	defer os.RemoveAll(dir)
   233  
   234  	// Build the source code.
   235  	src := filepath.Join(dir, "main.go")
   236  	err = ioutil.WriteFile(src, []byte(backtraceSource), 0644)
   237  	if err != nil {
   238  		t.Fatalf("failed to create file: %v", err)
   239  	}
   240  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
   241  	cmd.Dir = dir
   242  	out, err := testEnv(cmd).CombinedOutput()
   243  	if err != nil {
   244  		t.Fatalf("building source %v\n%s", err, out)
   245  	}
   246  
   247  	// Execute gdb commands.
   248  	args := []string{"-nx", "-batch",
   249  		"-ex", "set startup-with-shell off",
   250  		"-ex", "break main.eee",
   251  		"-ex", "run",
   252  		"-ex", "backtrace",
   253  		"-ex", "continue",
   254  		filepath.Join(dir, "a.exe"),
   255  	}
   256  	got, _ := exec.Command("gdb", args...).CombinedOutput()
   257  
   258  	// Check that the backtrace matches the source code.
   259  	bt := []string{
   260  		"eee",
   261  		"ddd",
   262  		"ccc",
   263  		"bbb",
   264  		"aaa",
   265  		"main",
   266  	}
   267  	for i, name := range bt {
   268  		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
   269  		re := regexp.MustCompile(s)
   270  		if found := re.Find(got) != nil; !found {
   271  			t.Errorf("could not find '%v' in backtrace", s)
   272  			t.Fatalf("gdb output:\n%v", string(got))
   273  		}
   274  	}
   275  }