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