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