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