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