github.com/activestate/go@v0.0.0-20170614201249-0b81c023a722/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 "go/build" 11 "internal/testenv" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "strconv" 19 "strings" 20 "testing" 21 ) 22 23 func checkGdbEnvironment(t *testing.T) { 24 testenv.MustHaveGoBuild(t) 25 if runtime.GOOS == "darwin" { 26 t.Skip("gdb does not work on darwin") 27 } 28 if runtime.GOOS == "linux" && runtime.GOARCH == "ppc64" { 29 t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366") 30 } 31 if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { 32 t.Skip("gdb test can fail with GOROOT_FINAL pending") 33 } 34 } 35 36 func checkGdbVersion(t *testing.T) { 37 // Issue 11214 reports various failures with older versions of gdb. 38 out, err := exec.Command("gdb", "--version").CombinedOutput() 39 if err != nil { 40 t.Skipf("skipping: error executing gdb: %v", err) 41 } 42 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`) 43 matches := re.FindSubmatch(out) 44 if len(matches) < 3 { 45 t.Skipf("skipping: can't determine gdb version from\n%s\n", out) 46 } 47 major, err1 := strconv.Atoi(string(matches[1])) 48 minor, err2 := strconv.Atoi(string(matches[2])) 49 if err1 != nil || err2 != nil { 50 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2) 51 } 52 if major < 7 || (major == 7 && minor < 7) { 53 t.Skipf("skipping: gdb version %d.%d too old", major, minor) 54 } 55 t.Logf("gdb version %d.%d", major, minor) 56 } 57 58 func checkGdbPython(t *testing.T) { 59 cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") 60 out, err := cmd.CombinedOutput() 61 62 if err != nil { 63 t.Skipf("skipping due to issue running gdb: %v", err) 64 } 65 if string(out) != "go gdb python support\n" { 66 t.Skipf("skipping due to lack of python gdb support: %s", out) 67 } 68 } 69 70 const helloSource = ` 71 import "fmt" 72 import "runtime" 73 var gslice []string 74 func main() { 75 mapvar := make(map[string]string,5) 76 mapvar["abc"] = "def" 77 mapvar["ghi"] = "jkl" 78 strvar := "abc" 79 ptrvar := &strvar 80 slicevar := make([]string, 0, 16) 81 slicevar = append(slicevar, mapvar["abc"]) 82 fmt.Println("hi") // line 13 83 _ = ptrvar 84 gslice = slicevar 85 runtime.KeepAlive(mapvar) 86 } 87 ` 88 89 func TestGdbPython(t *testing.T) { 90 testGdbPython(t, false) 91 } 92 93 func TestGdbPythonCgo(t *testing.T) { 94 if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" { 95 testenv.SkipFlaky(t, 18784) 96 } 97 testGdbPython(t, true) 98 } 99 100 func testGdbPython(t *testing.T, cgo bool) { 101 if runtime.GOARCH == "mips64" { 102 testenv.SkipFlaky(t, 18173) 103 } 104 if cgo && !build.Default.CgoEnabled { 105 t.Skip("skipping because cgo is not enabled") 106 } 107 108 t.Parallel() 109 checkGdbEnvironment(t) 110 checkGdbVersion(t) 111 checkGdbPython(t) 112 113 dir, err := ioutil.TempDir("", "go-build") 114 if err != nil { 115 t.Fatalf("failed to create temp directory: %v", err) 116 } 117 defer os.RemoveAll(dir) 118 119 var buf bytes.Buffer 120 buf.WriteString("package main\n") 121 if cgo { 122 buf.WriteString(`import "C"` + "\n") 123 } 124 buf.WriteString(helloSource) 125 126 src := filepath.Join(dir, "main.go") 127 err = ioutil.WriteFile(src, buf.Bytes(), 0644) 128 if err != nil { 129 t.Fatalf("failed to create file: %v", err) 130 } 131 132 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") 133 cmd.Dir = dir 134 out, err := testEnv(cmd).CombinedOutput() 135 if err != nil { 136 t.Fatalf("building source %v\n%s", err, out) 137 } 138 139 args := []string{"-nx", "-q", "--batch", "-iex", 140 fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()), 141 "-ex", "set startup-with-shell off", 142 "-ex", "info auto-load python-scripts", 143 "-ex", "set python print-stack full", 144 "-ex", "br fmt.Println", 145 "-ex", "run", 146 "-ex", "echo BEGIN info goroutines\n", 147 "-ex", "info goroutines", 148 "-ex", "echo END\n", 149 "-ex", "up", // up from fmt.Println to main 150 "-ex", "echo BEGIN print mapvar\n", 151 "-ex", "print mapvar", 152 "-ex", "echo END\n", 153 "-ex", "echo BEGIN print strvar\n", 154 "-ex", "print strvar", 155 "-ex", "echo END\n", 156 "-ex", "echo BEGIN info locals\n", 157 "-ex", "info locals", 158 "-ex", "echo END\n", 159 "-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack. TODO: fix that) 160 "-ex", "echo BEGIN goroutine 1 bt\n", 161 "-ex", "goroutine 1 bt", 162 "-ex", "echo END\n", 163 "-ex", "echo BEGIN goroutine 2 bt\n", 164 "-ex", "goroutine 2 bt", 165 "-ex", "echo END\n", 166 filepath.Join(dir, "a.exe"), 167 } 168 got, _ := exec.Command("gdb", args...).CombinedOutput() 169 170 firstLine := bytes.SplitN(got, []byte("\n"), 2)[0] 171 if string(firstLine) != "Loading Go Runtime support." { 172 // This can happen when using all.bash with 173 // GOROOT_FINAL set, because the tests are run before 174 // the final installation of the files. 175 cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT") 176 cmd.Env = []string{} 177 out, err := cmd.CombinedOutput() 178 if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) { 179 t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT()) 180 } 181 182 _, file, _, _ := runtime.Caller(1) 183 184 t.Logf("package testing source file: %s", file) 185 t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got) 186 } 187 188 // Extract named BEGIN...END blocks from output 189 partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`) 190 blocks := map[string]string{} 191 for _, subs := range partRe.FindAllSubmatch(got, -1) { 192 blocks[string(subs[1])] = string(subs[2]) 193 } 194 195 infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`) 196 if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) { 197 t.Fatalf("info goroutines failed: %s", bl) 198 } 199 200 printMapvarRe := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`) 201 if bl := blocks["print mapvar"]; !printMapvarRe.MatchString(bl) { 202 t.Fatalf("print mapvar failed: %s", bl) 203 } 204 205 strVarRe := regexp.MustCompile(`\Q = "abc"\E$`) 206 if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) { 207 t.Fatalf("print strvar failed: %s", bl) 208 } 209 210 // Issue 16338: ssa decompose phase can split a structure into 211 // a collection of scalar vars holding the fields. In such cases 212 // the DWARF variable location expression should be of the 213 // form "var.field" and not just "field". 214 infoLocalsRe := regexp.MustCompile(`^slicevar.len = `) 215 if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) { 216 t.Fatalf("info locals failed: %s", bl) 217 } 218 219 btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`) 220 if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) { 221 t.Fatalf("goroutine 1 bt failed: %s", bl) 222 } 223 224 btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`) 225 if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) { 226 t.Fatalf("goroutine 2 bt failed: %s", bl) 227 } 228 } 229 230 const backtraceSource = ` 231 package main 232 233 //go:noinline 234 func aaa() bool { return bbb() } 235 236 //go:noinline 237 func bbb() bool { return ccc() } 238 239 //go:noinline 240 func ccc() bool { return ddd() } 241 242 //go:noinline 243 func ddd() bool { return f() } 244 245 //go:noinline 246 func eee() bool { return true } 247 248 var f = eee 249 250 func main() { 251 _ = aaa() 252 } 253 ` 254 255 // TestGdbBacktrace tests that gdb can unwind the stack correctly 256 // using only the DWARF debug info. 257 func TestGdbBacktrace(t *testing.T) { 258 if runtime.GOOS == "netbsd" { 259 testenv.SkipFlaky(t, 15603) 260 } 261 if runtime.GOARCH == "mips64" { 262 testenv.SkipFlaky(t, 18173) 263 } 264 265 t.Parallel() 266 checkGdbEnvironment(t) 267 checkGdbVersion(t) 268 269 dir, err := ioutil.TempDir("", "go-build") 270 if err != nil { 271 t.Fatalf("failed to create temp directory: %v", err) 272 } 273 defer os.RemoveAll(dir) 274 275 // Build the source code. 276 src := filepath.Join(dir, "main.go") 277 err = ioutil.WriteFile(src, []byte(backtraceSource), 0644) 278 if err != nil { 279 t.Fatalf("failed to create file: %v", err) 280 } 281 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") 282 cmd.Dir = dir 283 out, err := testEnv(cmd).CombinedOutput() 284 if err != nil { 285 t.Fatalf("building source %v\n%s", err, out) 286 } 287 288 // Execute gdb commands. 289 args := []string{"-nx", "-batch", 290 "-ex", "set startup-with-shell off", 291 "-ex", "break main.eee", 292 "-ex", "run", 293 "-ex", "backtrace", 294 "-ex", "continue", 295 filepath.Join(dir, "a.exe"), 296 } 297 got, _ := exec.Command("gdb", args...).CombinedOutput() 298 299 // Check that the backtrace matches the source code. 300 bt := []string{ 301 "eee", 302 "ddd", 303 "ccc", 304 "bbb", 305 "aaa", 306 "main", 307 } 308 for i, name := range bt { 309 s := fmt.Sprintf("#%v.*main\\.%v", i, name) 310 re := regexp.MustCompile(s) 311 if found := re.Find(got) != nil; !found { 312 t.Errorf("could not find '%v' in backtrace", s) 313 t.Fatalf("gdb output:\n%v", string(got)) 314 } 315 } 316 } 317 318 const autotmpTypeSource = ` 319 package main 320 321 type astruct struct { 322 a, b int 323 } 324 325 func main() { 326 var iface interface{} = map[string]astruct{} 327 var iface2 interface{} = []astruct{} 328 println(iface, iface2) 329 } 330 ` 331 332 // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info 333 // See bug #17830. 334 func TestGdbAutotmpTypes(t *testing.T) { 335 if runtime.GOARCH == "mips64" { 336 testenv.SkipFlaky(t, 18173) 337 } 338 339 t.Parallel() 340 checkGdbEnvironment(t) 341 checkGdbVersion(t) 342 343 dir, err := ioutil.TempDir("", "go-build") 344 if err != nil { 345 t.Fatalf("failed to create temp directory: %v", err) 346 } 347 defer os.RemoveAll(dir) 348 349 // Build the source code. 350 src := filepath.Join(dir, "main.go") 351 err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644) 352 if err != nil { 353 t.Fatalf("failed to create file: %v", err) 354 } 355 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-N -l", "-o", "a.exe") 356 cmd.Dir = dir 357 out, err := testEnv(cmd).CombinedOutput() 358 if err != nil { 359 t.Fatalf("building source %v\n%s", err, out) 360 } 361 362 // Execute gdb commands. 363 args := []string{"-nx", "-batch", 364 "-ex", "set startup-with-shell off", 365 "-ex", "break main.main", 366 "-ex", "run", 367 "-ex", "step", 368 "-ex", "info types astruct", 369 filepath.Join(dir, "a.exe"), 370 } 371 got, _ := exec.Command("gdb", args...).CombinedOutput() 372 373 sgot := string(got) 374 375 // Check that the backtrace matches the source code. 376 types := []string{ 377 "struct []main.astruct;", 378 "struct bucket<string,main.astruct>;", 379 "struct hash<string,main.astruct>;", 380 "struct main.astruct;", 381 "typedef struct hash<string,main.astruct> * map[string]main.astruct;", 382 } 383 for _, name := range types { 384 if !strings.Contains(sgot, name) { 385 t.Errorf("could not find %s in 'info typrs astruct' output", name) 386 t.Fatalf("gdb output:\n%v", sgot) 387 } 388 } 389 }