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