|  | // Copyright 2015 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package runtime_test | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "internal/testenv" | 
|  | "io/ioutil" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path/filepath" | 
|  | "regexp" | 
|  | "runtime" | 
|  | "strconv" | 
|  | "strings" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | func checkGdbEnvironment(t *testing.T) { | 
|  | testenv.MustHaveGoBuild(t) | 
|  | switch runtime.GOOS { | 
|  | case "darwin": | 
|  | t.Skip("gdb does not work on darwin") | 
|  | case "netbsd": | 
|  | t.Skip("gdb does not work with threads on NetBSD; see https://golang.org/issue/22893 and https://gnats.netbsd.org/52548") | 
|  | case "windows": | 
|  | t.Skip("gdb tests fail on Windows: https://golang.org/issue/22687") | 
|  | case "linux": | 
|  | if runtime.GOARCH == "ppc64" { | 
|  | t.Skip("skipping gdb tests on linux/ppc64; see https://golang.org/issue/17366") | 
|  | } | 
|  | if runtime.GOARCH == "mips" { | 
|  | t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939") | 
|  | } | 
|  | case "freebsd": | 
|  | t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508") | 
|  | case "aix": | 
|  | if testing.Short() { | 
|  | t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710") | 
|  | } | 
|  | } | 
|  | if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { | 
|  | t.Skip("gdb test can fail with GOROOT_FINAL pending") | 
|  | } | 
|  | } | 
|  |  | 
|  | func checkGdbVersion(t *testing.T) { | 
|  | // Issue 11214 reports various failures with older versions of gdb. | 
|  | out, err := exec.Command("gdb", "--version").CombinedOutput() | 
|  | if err != nil { | 
|  | t.Skipf("skipping: error executing gdb: %v", err) | 
|  | } | 
|  | re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`) | 
|  | matches := re.FindSubmatch(out) | 
|  | if len(matches) < 3 { | 
|  | t.Skipf("skipping: can't determine gdb version from\n%s\n", out) | 
|  | } | 
|  | major, err1 := strconv.Atoi(string(matches[1])) | 
|  | minor, err2 := strconv.Atoi(string(matches[2])) | 
|  | if err1 != nil || err2 != nil { | 
|  | t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2) | 
|  | } | 
|  | if major < 7 || (major == 7 && minor < 7) { | 
|  | t.Skipf("skipping: gdb version %d.%d too old", major, minor) | 
|  | } | 
|  | t.Logf("gdb version %d.%d", major, minor) | 
|  | } | 
|  |  | 
|  | func checkGdbPython(t *testing.T) { | 
|  | if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { | 
|  | t.Skip("skipping gdb python tests on illumos and solaris; see golang.org/issue/20821") | 
|  | } | 
|  |  | 
|  | cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") | 
|  | out, err := cmd.CombinedOutput() | 
|  |  | 
|  | if err != nil { | 
|  | t.Skipf("skipping due to issue running gdb: %v", err) | 
|  | } | 
|  | if strings.TrimSpace(string(out)) != "go gdb python support" { | 
|  | t.Skipf("skipping due to lack of python gdb support: %s", out) | 
|  | } | 
|  | } | 
|  |  | 
|  | // checkCleanBacktrace checks that the given backtrace is well formed and does | 
|  | // not contain any error messages from GDB. | 
|  | func checkCleanBacktrace(t *testing.T, backtrace string) { | 
|  | backtrace = strings.TrimSpace(backtrace) | 
|  | lines := strings.Split(backtrace, "\n") | 
|  | if len(lines) == 0 { | 
|  | t.Fatalf("empty backtrace") | 
|  | } | 
|  | for i, l := range lines { | 
|  | if !strings.HasPrefix(l, fmt.Sprintf("#%v  ", i)) { | 
|  | t.Fatalf("malformed backtrace at line %v: %v", i, l) | 
|  | } | 
|  | } | 
|  | // TODO(mundaym): check for unknown frames (e.g. "??"). | 
|  | } | 
|  |  | 
|  | const helloSource = ` | 
|  | import "fmt" | 
|  | import "runtime" | 
|  | var gslice []string | 
|  | func main() { | 
|  | mapvar := make(map[string]string, 13) | 
|  | mapvar["abc"] = "def" | 
|  | mapvar["ghi"] = "jkl" | 
|  | strvar := "abc" | 
|  | ptrvar := &strvar | 
|  | slicevar := make([]string, 0, 16) | 
|  | slicevar = append(slicevar, mapvar["abc"]) | 
|  | fmt.Println("hi") | 
|  | runtime.KeepAlive(ptrvar) | 
|  | _ = ptrvar | 
|  | gslice = slicevar | 
|  | runtime.KeepAlive(mapvar) | 
|  | }  // END_OF_PROGRAM | 
|  | ` | 
|  |  | 
|  | func lastLine(src []byte) int { | 
|  | eop := []byte("END_OF_PROGRAM") | 
|  | for i, l := range bytes.Split(src, []byte("\n")) { | 
|  | if bytes.Contains(l, eop) { | 
|  | return i | 
|  | } | 
|  | } | 
|  | return 0 | 
|  | } | 
|  |  | 
|  | func TestGdbPython(t *testing.T) { | 
|  | testGdbPython(t, false) | 
|  | } | 
|  |  | 
|  | func TestGdbPythonCgo(t *testing.T) { | 
|  | if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" { | 
|  | testenv.SkipFlaky(t, 18784) | 
|  | } | 
|  | testGdbPython(t, true) | 
|  | } | 
|  |  | 
|  | func testGdbPython(t *testing.T, cgo bool) { | 
|  | if cgo { | 
|  | testenv.MustHaveCGO(t) | 
|  | } | 
|  |  | 
|  | checkGdbEnvironment(t) | 
|  | t.Parallel() | 
|  | checkGdbVersion(t) | 
|  | checkGdbPython(t) | 
|  |  | 
|  | dir, err := ioutil.TempDir("", "go-build") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create temp directory: %v", err) | 
|  | } | 
|  | defer os.RemoveAll(dir) | 
|  |  | 
|  | var buf bytes.Buffer | 
|  | buf.WriteString("package main\n") | 
|  | if cgo { | 
|  | buf.WriteString(`import "C"` + "\n") | 
|  | } | 
|  | buf.WriteString(helloSource) | 
|  |  | 
|  | src := buf.Bytes() | 
|  |  | 
|  | err = ioutil.WriteFile(filepath.Join(dir, "main.go"), src, 0644) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create file: %v", err) | 
|  | } | 
|  | nLines := lastLine(src) | 
|  |  | 
|  | cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") | 
|  | cmd.Dir = dir | 
|  | out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Fatalf("building source %v\n%s", err, out) | 
|  | } | 
|  |  | 
|  | args := []string{"-nx", "-q", "--batch", | 
|  | "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), | 
|  | "-ex", "set startup-with-shell off", | 
|  | "-ex", "set print thread-events off", | 
|  | } | 
|  | if cgo { | 
|  | // When we build the cgo version of the program, the system's | 
|  | // linker is used. Some external linkers, like GNU gold, | 
|  | // compress the .debug_gdb_scripts into .zdebug_gdb_scripts. | 
|  | // Until gold and gdb can work together, temporarily load the | 
|  | // python script directly. | 
|  | args = append(args, | 
|  | "-ex", "source "+filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime-gdb.py"), | 
|  | ) | 
|  | } else { | 
|  | args = append(args, | 
|  | "-ex", "info auto-load python-scripts", | 
|  | ) | 
|  | } | 
|  | args = append(args, | 
|  | "-ex", "set python print-stack full", | 
|  | "-ex", "br main.go:15", | 
|  | "-ex", "run", | 
|  | "-ex", "echo BEGIN info goroutines\n", | 
|  | "-ex", "info goroutines", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "echo BEGIN print mapvar\n", | 
|  | "-ex", "print mapvar", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "echo BEGIN print strvar\n", | 
|  | "-ex", "print strvar", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "echo BEGIN info locals\n", | 
|  | "-ex", "info locals", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "echo BEGIN goroutine 1 bt\n", | 
|  | "-ex", "goroutine 1 bt", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "echo BEGIN goroutine 2 bt\n", | 
|  | "-ex", "goroutine 2 bt", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "echo BEGIN goroutine all bt\n", | 
|  | "-ex", "goroutine all bt", | 
|  | "-ex", "echo END\n", | 
|  | "-ex", "clear main.go:15", // clear the previous break point | 
|  | "-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main | 
|  | "-ex", "c", | 
|  | "-ex", "echo BEGIN goroutine 1 bt at the end\n", | 
|  | "-ex", "goroutine 1 bt", | 
|  | "-ex", "echo END\n", | 
|  | filepath.Join(dir, "a.exe"), | 
|  | ) | 
|  | got, _ := exec.Command("gdb", args...).CombinedOutput() | 
|  | t.Logf("gdb output: %s\n", got) | 
|  |  | 
|  | firstLine := bytes.SplitN(got, []byte("\n"), 2)[0] | 
|  | if string(firstLine) != "Loading Go Runtime support." { | 
|  | // This can happen when using all.bash with | 
|  | // GOROOT_FINAL set, because the tests are run before | 
|  | // the final installation of the files. | 
|  | cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT") | 
|  | cmd.Env = []string{} | 
|  | out, err := cmd.CombinedOutput() | 
|  | if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) { | 
|  | t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT()) | 
|  | } | 
|  |  | 
|  | _, file, _, _ := runtime.Caller(1) | 
|  |  | 
|  | t.Logf("package testing source file: %s", file) | 
|  | t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got) | 
|  | } | 
|  |  | 
|  | // Extract named BEGIN...END blocks from output | 
|  | partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`) | 
|  | blocks := map[string]string{} | 
|  | for _, subs := range partRe.FindAllSubmatch(got, -1) { | 
|  | blocks[string(subs[1])] = string(subs[2]) | 
|  | } | 
|  |  | 
|  | infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`) | 
|  | if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) { | 
|  | t.Fatalf("info goroutines failed: %s", bl) | 
|  | } | 
|  |  | 
|  | 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"}$`) | 
|  | 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"}$`) | 
|  | if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) && | 
|  | !printMapvarRe2.MatchString(bl) { | 
|  | t.Fatalf("print mapvar failed: %s", bl) | 
|  | } | 
|  |  | 
|  | strVarRe := regexp.MustCompile(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`) | 
|  | if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) { | 
|  | t.Fatalf("print strvar failed: %s", bl) | 
|  | } | 
|  |  | 
|  | // The exact format of composite values has changed over time. | 
|  | // For issue 16338: ssa decompose phase split a slice into | 
|  | // a collection of scalar vars holding its fields. In such cases | 
|  | // the DWARF variable location expression should be of the | 
|  | // form "var.field" and not just "field". | 
|  | // However, the newer dwarf location list code reconstituted | 
|  | // aggregates from their fields and reverted their printing | 
|  | // back to its original form. | 
|  | // Only test that all variables are listed in 'info locals' since | 
|  | // different versions of gdb print variables in different | 
|  | // order and with differing amount of information and formats. | 
|  |  | 
|  | if bl := blocks["info locals"]; !strings.Contains(bl, "slicevar") || | 
|  | !strings.Contains(bl, "mapvar") || | 
|  | !strings.Contains(bl, "strvar") { | 
|  | t.Fatalf("info locals failed: %s", bl) | 
|  | } | 
|  |  | 
|  | // Check that the backtraces are well formed. | 
|  | checkCleanBacktrace(t, blocks["goroutine 1 bt"]) | 
|  | checkCleanBacktrace(t, blocks["goroutine 2 bt"]) | 
|  | checkCleanBacktrace(t, blocks["goroutine 1 bt at the end"]) | 
|  |  | 
|  | btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`) | 
|  | if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) { | 
|  | t.Fatalf("goroutine 1 bt failed: %s", bl) | 
|  | } | 
|  |  | 
|  | btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`) | 
|  | if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) { | 
|  | t.Fatalf("goroutine 2 bt failed: %s", bl) | 
|  | } | 
|  |  | 
|  | if bl := blocks["goroutine all bt"]; !btGoroutine1Re.MatchString(bl) || !btGoroutine2Re.MatchString(bl) { | 
|  | t.Fatalf("goroutine all bt failed: %s", bl) | 
|  | } | 
|  |  | 
|  | btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`) | 
|  | if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) { | 
|  | t.Fatalf("goroutine 1 bt at the end failed: %s", bl) | 
|  | } | 
|  | } | 
|  |  | 
|  | const backtraceSource = ` | 
|  | package main | 
|  |  | 
|  | //go:noinline | 
|  | func aaa() bool { return bbb() } | 
|  |  | 
|  | //go:noinline | 
|  | func bbb() bool { return ccc() } | 
|  |  | 
|  | //go:noinline | 
|  | func ccc() bool { return ddd() } | 
|  |  | 
|  | //go:noinline | 
|  | func ddd() bool { return f() } | 
|  |  | 
|  | //go:noinline | 
|  | func eee() bool { return true } | 
|  |  | 
|  | var f = eee | 
|  |  | 
|  | func main() { | 
|  | _ = aaa() | 
|  | } | 
|  | ` | 
|  |  | 
|  | // TestGdbBacktrace tests that gdb can unwind the stack correctly | 
|  | // using only the DWARF debug info. | 
|  | func TestGdbBacktrace(t *testing.T) { | 
|  | if runtime.GOOS == "netbsd" { | 
|  | testenv.SkipFlaky(t, 15603) | 
|  | } | 
|  |  | 
|  | checkGdbEnvironment(t) | 
|  | t.Parallel() | 
|  | checkGdbVersion(t) | 
|  |  | 
|  | dir, err := ioutil.TempDir("", "go-build") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create temp directory: %v", err) | 
|  | } | 
|  | defer os.RemoveAll(dir) | 
|  |  | 
|  | // Build the source code. | 
|  | src := filepath.Join(dir, "main.go") | 
|  | err = ioutil.WriteFile(src, []byte(backtraceSource), 0644) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create file: %v", err) | 
|  | } | 
|  | cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") | 
|  | cmd.Dir = dir | 
|  | out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Fatalf("building source %v\n%s", err, out) | 
|  | } | 
|  |  | 
|  | // Execute gdb commands. | 
|  | args := []string{"-nx", "-batch", | 
|  | "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), | 
|  | "-ex", "set startup-with-shell off", | 
|  | "-ex", "break main.eee", | 
|  | "-ex", "run", | 
|  | "-ex", "backtrace", | 
|  | "-ex", "continue", | 
|  | filepath.Join(dir, "a.exe"), | 
|  | } | 
|  | got, _ := exec.Command("gdb", args...).CombinedOutput() | 
|  |  | 
|  | // Check that the backtrace matches the source code. | 
|  | bt := []string{ | 
|  | "eee", | 
|  | "ddd", | 
|  | "ccc", | 
|  | "bbb", | 
|  | "aaa", | 
|  | "main", | 
|  | } | 
|  | for i, name := range bt { | 
|  | s := fmt.Sprintf("#%v.*main\\.%v", i, name) | 
|  | re := regexp.MustCompile(s) | 
|  | if found := re.Find(got) != nil; !found { | 
|  | t.Errorf("could not find '%v' in backtrace", s) | 
|  | t.Fatalf("gdb output:\n%v", string(got)) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | const autotmpTypeSource = ` | 
|  | package main | 
|  |  | 
|  | type astruct struct { | 
|  | a, b int | 
|  | } | 
|  |  | 
|  | func main() { | 
|  | var iface interface{} = map[string]astruct{} | 
|  | var iface2 interface{} = []astruct{} | 
|  | println(iface, iface2) | 
|  | } | 
|  | ` | 
|  |  | 
|  | // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info | 
|  | // See bug #17830. | 
|  | func TestGdbAutotmpTypes(t *testing.T) { | 
|  | checkGdbEnvironment(t) | 
|  | t.Parallel() | 
|  | checkGdbVersion(t) | 
|  |  | 
|  | if runtime.GOOS == "aix" && testing.Short() { | 
|  | t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64") | 
|  | } | 
|  |  | 
|  | dir, err := ioutil.TempDir("", "go-build") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create temp directory: %v", err) | 
|  | } | 
|  | defer os.RemoveAll(dir) | 
|  |  | 
|  | // Build the source code. | 
|  | src := filepath.Join(dir, "main.go") | 
|  | err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create file: %v", err) | 
|  | } | 
|  | cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go") | 
|  | cmd.Dir = dir | 
|  | out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Fatalf("building source %v\n%s", err, out) | 
|  | } | 
|  |  | 
|  | // Execute gdb commands. | 
|  | args := []string{"-nx", "-batch", | 
|  | "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), | 
|  | "-ex", "set startup-with-shell off", | 
|  | "-ex", "break main.main", | 
|  | "-ex", "run", | 
|  | "-ex", "step", | 
|  | "-ex", "info types astruct", | 
|  | filepath.Join(dir, "a.exe"), | 
|  | } | 
|  | got, _ := exec.Command("gdb", args...).CombinedOutput() | 
|  |  | 
|  | sgot := string(got) | 
|  |  | 
|  | // Check that the backtrace matches the source code. | 
|  | types := []string{ | 
|  | "[]main.astruct;", | 
|  | "bucket<string,main.astruct>;", | 
|  | "hash<string,main.astruct>;", | 
|  | "main.astruct;", | 
|  | "hash<string,main.astruct> * map[string]main.astruct;", | 
|  | } | 
|  | for _, name := range types { | 
|  | if !strings.Contains(sgot, name) { | 
|  | t.Errorf("could not find %s in 'info typrs astruct' output", name) | 
|  | t.Fatalf("gdb output:\n%v", sgot) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | const constsSource = ` | 
|  | package main | 
|  |  | 
|  | const aConstant int = 42 | 
|  | const largeConstant uint64 = ^uint64(0) | 
|  | const minusOne int64 = -1 | 
|  |  | 
|  | func main() { | 
|  | println("hello world") | 
|  | } | 
|  | ` | 
|  |  | 
|  | func TestGdbConst(t *testing.T) { | 
|  | checkGdbEnvironment(t) | 
|  | t.Parallel() | 
|  | checkGdbVersion(t) | 
|  |  | 
|  | dir, err := ioutil.TempDir("", "go-build") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create temp directory: %v", err) | 
|  | } | 
|  | defer os.RemoveAll(dir) | 
|  |  | 
|  | // Build the source code. | 
|  | src := filepath.Join(dir, "main.go") | 
|  | err = ioutil.WriteFile(src, []byte(constsSource), 0644) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create file: %v", err) | 
|  | } | 
|  | cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go") | 
|  | cmd.Dir = dir | 
|  | out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Fatalf("building source %v\n%s", err, out) | 
|  | } | 
|  |  | 
|  | // Execute gdb commands. | 
|  | args := []string{"-nx", "-batch", | 
|  | "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), | 
|  | "-ex", "set startup-with-shell off", | 
|  | "-ex", "break main.main", | 
|  | "-ex", "run", | 
|  | "-ex", "print main.aConstant", | 
|  | "-ex", "print main.largeConstant", | 
|  | "-ex", "print main.minusOne", | 
|  | "-ex", "print 'runtime.mSpanInUse'", | 
|  | "-ex", "print 'runtime._PageSize'", | 
|  | filepath.Join(dir, "a.exe"), | 
|  | } | 
|  | got, _ := exec.Command("gdb", args...).CombinedOutput() | 
|  |  | 
|  | sgot := strings.ReplaceAll(string(got), "\r\n", "\n") | 
|  |  | 
|  | t.Logf("output %q", sgot) | 
|  |  | 
|  | if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") { | 
|  | t.Fatalf("output mismatch") | 
|  | } | 
|  | } | 
|  |  | 
|  | const panicSource = ` | 
|  | package main | 
|  |  | 
|  | import "runtime/debug" | 
|  |  | 
|  | func main() { | 
|  | debug.SetTraceback("crash") | 
|  | crash() | 
|  | } | 
|  |  | 
|  | func crash() { | 
|  | panic("panic!") | 
|  | } | 
|  | ` | 
|  |  | 
|  | // TestGdbPanic tests that gdb can unwind the stack correctly | 
|  | // from SIGABRTs from Go panics. | 
|  | func TestGdbPanic(t *testing.T) { | 
|  | checkGdbEnvironment(t) | 
|  | t.Parallel() | 
|  | checkGdbVersion(t) | 
|  |  | 
|  | dir, err := ioutil.TempDir("", "go-build") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create temp directory: %v", err) | 
|  | } | 
|  | defer os.RemoveAll(dir) | 
|  |  | 
|  | // Build the source code. | 
|  | src := filepath.Join(dir, "main.go") | 
|  | err = ioutil.WriteFile(src, []byte(panicSource), 0644) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create file: %v", err) | 
|  | } | 
|  | cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") | 
|  | cmd.Dir = dir | 
|  | out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() | 
|  | if err != nil { | 
|  | t.Fatalf("building source %v\n%s", err, out) | 
|  | } | 
|  |  | 
|  | // Execute gdb commands. | 
|  | args := []string{"-nx", "-batch", | 
|  | "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), | 
|  | "-ex", "set startup-with-shell off", | 
|  | "-ex", "run", | 
|  | "-ex", "backtrace", | 
|  | filepath.Join(dir, "a.exe"), | 
|  | } | 
|  | got, _ := exec.Command("gdb", args...).CombinedOutput() | 
|  |  | 
|  | // Check that the backtrace matches the source code. | 
|  | bt := []string{ | 
|  | `crash`, | 
|  | `main`, | 
|  | } | 
|  | for _, name := range bt { | 
|  | s := fmt.Sprintf("(#.* .* in )?main\\.%v", name) | 
|  | re := regexp.MustCompile(s) | 
|  | if found := re.Find(got) != nil; !found { | 
|  | t.Errorf("could not find '%v' in backtrace", s) | 
|  | t.Fatalf("gdb output:\n%v", string(got)) | 
|  | } | 
|  | } | 
|  | } |