1 package runtime_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "regexp" 11 "runtime" 12 "strconv" 13 "testing" 14 ) 15 16 func checkGdbPython(t *testing.T) { 17 cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") 18 out, err := cmd.CombinedOutput() 19 20 if err != nil { 21 t.Skipf("skipping due to issue running gdb: %v", err) 22 } 23 if string(out) != "go gdb python support\n" { 24 t.Skipf("skipping due to lack of python gdb support: %s", out) 25 } 26 27 // Issue 11214 reports various failures with older versions of gdb. 28 out, err = exec.Command("gdb", "--version").CombinedOutput() 29 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`) 30 matches := re.FindSubmatch(out) 31 if len(matches) < 3 { 32 t.Skipf("skipping: can't determine gdb version from\n%s\n", out) 33 } 34 major, err1 := strconv.Atoi(string(matches[1])) 35 minor, err2 := strconv.Atoi(string(matches[2])) 36 if err1 != nil || err2 != nil { 37 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2) 38 } 39 if major < 7 || (major == 7 && minor < 7) { 40 t.Skipf("skipping: gdb version %d.%d too old", major, minor) 41 } 42 t.Logf("gdb version %d.%d", major, minor) 43 } 44 45 const helloSource = ` 46 package main 47 import "fmt" 48 func main() { 49 mapvar := make(map[string]string,5) 50 mapvar["abc"] = "def" 51 mapvar["ghi"] = "jkl" 52 strvar := "abc" 53 ptrvar := &strvar 54 fmt.Println("hi") // line 10 55 _ = ptrvar 56 } 57 ` 58 59 func TestGdbPython(t *testing.T) { 60 if runtime.GOOS == "darwin" { 61 t.Skip("gdb does not work on darwin") 62 } 63 64 checkGdbPython(t) 65 66 dir, err := ioutil.TempDir("", "go-build") 67 if err != nil { 68 t.Fatalf("failed to create temp directory: %v", err) 69 } 70 defer os.RemoveAll(dir) 71 72 src := filepath.Join(dir, "main.go") 73 err = ioutil.WriteFile(src, []byte(helloSource), 0644) 74 if err != nil { 75 t.Fatalf("failed to create file: %v", err) 76 } 77 78 cmd := exec.Command("go", "build", "-o", "a.exe") 79 cmd.Dir = dir 80 out, err := testEnv(cmd).CombinedOutput() 81 if err != nil { 82 t.Fatalf("building source %v\n%s", err, out) 83 } 84 85 args := []string{"-nx", "-q", "--batch", "-iex", 86 fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()), 87 "-ex", "br main.go:10", 88 "-ex", "run", 89 "-ex", "echo BEGIN info goroutines\n", 90 "-ex", "info goroutines", 91 "-ex", "echo END\n", 92 "-ex", "echo BEGIN print mapvar\n", 93 "-ex", "print mapvar", 94 "-ex", "echo END\n", 95 "-ex", "echo BEGIN print strvar\n", 96 "-ex", "print strvar", 97 "-ex", "echo END\n", 98 "-ex", "echo BEGIN print ptrvar\n", 99 "-ex", "print ptrvar", 100 "-ex", "echo END\n"} 101 102 // without framepointer, gdb cannot backtrace our non-standard 103 // stack frames on RISC architectures. 104 canBackTrace := false 105 switch runtime.GOARCH { 106 case "amd64", "386", "ppc64", "ppc64le", "arm", "arm64": 107 canBackTrace = true 108 args = append(args, 109 "-ex", "echo BEGIN goroutine 2 bt\n", 110 "-ex", "goroutine 2 bt", 111 "-ex", "echo END\n") 112 } 113 114 args = append(args, filepath.Join(dir, "a.exe")) 115 got, _ := exec.Command("gdb", args...).CombinedOutput() 116 117 firstLine := bytes.SplitN(got, []byte("\n"), 2)[0] 118 if string(firstLine) != "Loading Go Runtime support." { 119 // This can happen when using all.bash with 120 // GOROOT_FINAL set, because the tests are run before 121 // the final installation of the files. 122 cmd := exec.Command("go", "env", "GOROOT") 123 cmd.Env = []string{} 124 out, err := cmd.CombinedOutput() 125 if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) { 126 t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT()) 127 } 128 129 t.Fatalf("failed to load Go runtime support: %s", firstLine) 130 } 131 132 // Extract named BEGIN...END blocks from output 133 partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`) 134 blocks := map[string]string{} 135 for _, subs := range partRe.FindAllSubmatch(got, -1) { 136 blocks[string(subs[1])] = string(subs[2]) 137 } 138 139 infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`) 140 if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) { 141 t.Fatalf("info goroutines failed: %s", bl) 142 } 143 144 printMapvarRe := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`) 145 if bl := blocks["print mapvar"]; !printMapvarRe.MatchString(bl) { 146 t.Fatalf("print mapvar failed: %s", bl) 147 } 148 149 strVarRe := regexp.MustCompile(`\Q = "abc"\E$`) 150 if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) { 151 t.Fatalf("print strvar failed: %s", bl) 152 } 153 154 if bl := blocks["print ptrvar"]; !strVarRe.MatchString(bl) { 155 t.Fatalf("print ptrvar failed: %s", bl) 156 } 157 158 btGoroutineRe := regexp.MustCompile(`^#0\s+runtime.+at`) 159 if bl := blocks["goroutine 2 bt"]; canBackTrace && !btGoroutineRe.MatchString(bl) { 160 t.Fatalf("goroutine 2 bt failed: %s", bl) 161 } else if !canBackTrace { 162 t.Logf("gdb cannot backtrace for GOARCH=%s, skipped goroutine backtrace test", runtime.GOARCH) 163 } 164 } 165