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