github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/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 "windows": 31 t.Skip("gdb tests fail on Windows: https://golang.org/issue/22687") 32 case "linux": 33 if runtime.GOARCH == "ppc64" { 34 t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366") 35 } 36 if runtime.GOARCH == "mips" { 37 t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939") 38 } 39 } 40 if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final { 41 t.Skip("gdb test can fail with GOROOT_FINAL pending") 42 } 43 } 44 45 func checkGdbVersion(t *testing.T) { 46 // Issue 11214 reports various failures with older versions of gdb. 47 out, err := exec.Command("gdb", "--version").CombinedOutput() 48 if err != nil { 49 t.Skipf("skipping: error executing gdb: %v", err) 50 } 51 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`) 52 matches := re.FindSubmatch(out) 53 if len(matches) < 3 { 54 t.Skipf("skipping: can't determine gdb version from\n%s\n", out) 55 } 56 major, err1 := strconv.Atoi(string(matches[1])) 57 minor, err2 := strconv.Atoi(string(matches[2])) 58 if err1 != nil || err2 != nil { 59 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2) 60 } 61 if major < 7 || (major == 7 && minor < 7) { 62 t.Skipf("skipping: gdb version %d.%d too old", major, minor) 63 } 64 t.Logf("gdb version %d.%d", major, minor) 65 } 66 67 func checkGdbPython(t *testing.T) { 68 if runtime.GOOS == "solaris" && testenv.Builder() != "solaris-amd64-smartosbuildlet" { 69 t.Skip("skipping gdb python tests on solaris; see golang.org/issue/20821") 70 } 71 72 cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") 73 out, err := cmd.CombinedOutput() 74 75 if err != nil { 76 t.Skipf("skipping due to issue running gdb: %v", err) 77 } 78 if strings.TrimSpace(string(out)) != "go gdb python support" { 79 t.Skipf("skipping due to lack of python gdb support: %s", out) 80 } 81 } 82 83 const helloSource = ` 84 import "fmt" 85 import "runtime" 86 var gslice []string 87 func main() { 88 mapvar := make(map[string]string, 13) 89 mapvar["abc"] = "def" 90 mapvar["ghi"] = "jkl" 91 strvar := "abc" 92 ptrvar := &strvar 93 slicevar := make([]string, 0, 16) 94 slicevar = append(slicevar, mapvar["abc"]) 95 fmt.Println("hi") 96 runtime.KeepAlive(ptrvar) 97 _ = ptrvar 98 gslice = slicevar 99 runtime.KeepAlive(mapvar) 100 } // END_OF_PROGRAM 101 ` 102 103 func lastLine(src []byte) int { 104 eop := []byte("END_OF_PROGRAM") 105 for i, l := range bytes.Split(src, []byte("\n")) { 106 if bytes.Contains(l, eop) { 107 return i 108 } 109 } 110 return 0 111 } 112 113 func TestGdbPython(t *testing.T) { 114 testGdbPython(t, false) 115 } 116 117 func TestGdbPythonCgo(t *testing.T) { 118 if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" { 119 testenv.SkipFlaky(t, 18784) 120 } 121 testGdbPython(t, true) 122 } 123 124 func testGdbPython(t *testing.T, cgo bool) { 125 if cgo && !build.Default.CgoEnabled { 126 t.Skip("skipping because cgo is not enabled") 127 } 128 129 checkGdbEnvironment(t) 130 t.Parallel() 131 checkGdbVersion(t) 132 checkGdbPython(t) 133 134 dir, err := ioutil.TempDir("", "go-build") 135 if err != nil { 136 t.Fatalf("failed to create temp directory: %v", err) 137 } 138 defer os.RemoveAll(dir) 139 140 var buf bytes.Buffer 141 buf.WriteString("package main\n") 142 if cgo { 143 buf.WriteString(`import "C"` + "\n") 144 } 145 buf.WriteString(helloSource) 146 147 src := buf.Bytes() 148 149 err = ioutil.WriteFile(filepath.Join(dir, "main.go"), src, 0644) 150 if err != nil { 151 t.Fatalf("failed to create file: %v", err) 152 } 153 nLines := lastLine(src) 154 155 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") 156 cmd.Dir = dir 157 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 158 if err != nil { 159 t.Fatalf("building source %v\n%s", err, out) 160 } 161 162 args := []string{"-nx", "-q", "--batch", 163 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 164 "-ex", "set startup-with-shell off", 165 } 166 if cgo { 167 // When we build the cgo version of the program, the system's 168 // linker is used. Some external linkers, like GNU gold, 169 // compress the .debug_gdb_scripts into .zdebug_gdb_scripts. 170 // Until gold and gdb can work together, temporarily load the 171 // python script directly. 172 args = append(args, 173 "-ex", "source "+filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime-gdb.py"), 174 ) 175 } else { 176 args = append(args, 177 "-ex", "info auto-load python-scripts", 178 ) 179 } 180 args = append(args, 181 "-ex", "set python print-stack full", 182 "-ex", "br fmt.Println", 183 "-ex", "run", 184 "-ex", "echo BEGIN info goroutines\n", 185 "-ex", "info goroutines", 186 "-ex", "echo END\n", 187 "-ex", "up", // up from fmt.Println to main 188 "-ex", "echo BEGIN print mapvar\n", 189 "-ex", "print mapvar", 190 "-ex", "echo END\n", 191 "-ex", "echo BEGIN print strvar\n", 192 "-ex", "print strvar", 193 "-ex", "echo END\n", 194 "-ex", "echo BEGIN info locals\n", 195 "-ex", "info locals", 196 "-ex", "echo END\n", 197 "-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack. TODO: fix that) 198 "-ex", "echo BEGIN goroutine 1 bt\n", 199 "-ex", "goroutine 1 bt", 200 "-ex", "echo END\n", 201 "-ex", "echo BEGIN goroutine 2 bt\n", 202 "-ex", "goroutine 2 bt", 203 "-ex", "echo END\n", 204 "-ex", "clear fmt.Println", // clear the previous break point 205 "-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main 206 "-ex", "c", 207 "-ex", "echo BEGIN goroutine 1 bt at the end\n", 208 "-ex", "goroutine 1 bt", 209 "-ex", "echo END\n", 210 filepath.Join(dir, "a.exe"), 211 ) 212 got, _ := exec.Command("gdb", args...).CombinedOutput() 213 t.Logf("gdb output: %s\n", got) 214 215 firstLine := bytes.SplitN(got, []byte("\n"), 2)[0] 216 if string(firstLine) != "Loading Go Runtime support." { 217 // This can happen when using all.bash with 218 // GOROOT_FINAL set, because the tests are run before 219 // the final installation of the files. 220 cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT") 221 cmd.Env = []string{} 222 out, err := cmd.CombinedOutput() 223 if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) { 224 t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT()) 225 } 226 227 _, file, _, _ := runtime.Caller(1) 228 229 t.Logf("package testing source file: %s", file) 230 t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got) 231 } 232 233 // Extract named BEGIN...END blocks from output 234 partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`) 235 blocks := map[string]string{} 236 for _, subs := range partRe.FindAllSubmatch(got, -1) { 237 blocks[string(subs[1])] = string(subs[2]) 238 } 239 240 infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`) 241 if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) { 242 t.Fatalf("info goroutines failed: %s", bl) 243 } 244 245 printMapvarRe1 := regexp.MustCompile(`^\$[0-9]+ = map\[string\]string = {\[(0x[0-9a-f]+\s+)?"abc"\] = (0x[0-9a-f]+\s+)?"def", \[(0x[0-9a-f]+\s+)?"ghi"\] = (0x[0-9a-f]+\s+)?"jkl"}$`) 246 printMapvarRe2 := regexp.MustCompile(`^\$[0-9]+ = map\[string\]string = {\[(0x[0-9a-f]+\s+)?"ghi"\] = (0x[0-9a-f]+\s+)?"jkl", \[(0x[0-9a-f]+\s+)?"abc"\] = (0x[0-9a-f]+\s+)?"def"}$`) 247 if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) && 248 !printMapvarRe2.MatchString(bl) { 249 t.Fatalf("print mapvar failed: %s", bl) 250 } 251 252 strVarRe := regexp.MustCompile(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`) 253 if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) { 254 t.Fatalf("print strvar failed: %s", bl) 255 } 256 257 // The exact format of composite values has changed over time. 258 // For issue 16338: ssa decompose phase split a slice into 259 // a collection of scalar vars holding its fields. In such cases 260 // the DWARF variable location expression should be of the 261 // form "var.field" and not just "field". 262 // However, the newer dwarf location list code reconstituted 263 // aggregates from their fields and reverted their printing 264 // back to its original form. 265 266 infoLocalsRe1 := regexp.MustCompile(`slicevar *= *\[\]string *= *{"def"}`) 267 // Format output from gdb v8.2 268 infoLocalsRe2 := regexp.MustCompile(`^slicevar = .*\nmapvar = .*\nstrvar = 0x[0-9a-f]+ "abc"`) 269 if bl := blocks["info locals"]; !infoLocalsRe1.MatchString(bl) && 270 !infoLocalsRe2.MatchString(bl) { 271 t.Fatalf("info locals failed: %s", bl) 272 } 273 274 btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`) 275 if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) { 276 t.Fatalf("goroutine 1 bt failed: %s", bl) 277 } 278 279 btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`) 280 if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) { 281 t.Fatalf("goroutine 2 bt failed: %s", bl) 282 } 283 btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`) 284 if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) { 285 t.Fatalf("goroutine 1 bt at the end failed: %s", bl) 286 } 287 } 288 289 const backtraceSource = ` 290 package main 291 292 //go:noinline 293 func aaa() bool { return bbb() } 294 295 //go:noinline 296 func bbb() bool { return ccc() } 297 298 //go:noinline 299 func ccc() bool { return ddd() } 300 301 //go:noinline 302 func ddd() bool { return f() } 303 304 //go:noinline 305 func eee() bool { return true } 306 307 var f = eee 308 309 func main() { 310 _ = aaa() 311 } 312 ` 313 314 // TestGdbBacktrace tests that gdb can unwind the stack correctly 315 // using only the DWARF debug info. 316 func TestGdbBacktrace(t *testing.T) { 317 if runtime.GOOS == "netbsd" { 318 testenv.SkipFlaky(t, 15603) 319 } 320 321 checkGdbEnvironment(t) 322 t.Parallel() 323 checkGdbVersion(t) 324 325 dir, err := ioutil.TempDir("", "go-build") 326 if err != nil { 327 t.Fatalf("failed to create temp directory: %v", err) 328 } 329 defer os.RemoveAll(dir) 330 331 // Build the source code. 332 src := filepath.Join(dir, "main.go") 333 err = ioutil.WriteFile(src, []byte(backtraceSource), 0644) 334 if err != nil { 335 t.Fatalf("failed to create file: %v", err) 336 } 337 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") 338 cmd.Dir = dir 339 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 340 if err != nil { 341 t.Fatalf("building source %v\n%s", err, out) 342 } 343 344 // Execute gdb commands. 345 args := []string{"-nx", "-batch", 346 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 347 "-ex", "set startup-with-shell off", 348 "-ex", "break main.eee", 349 "-ex", "run", 350 "-ex", "backtrace", 351 "-ex", "continue", 352 filepath.Join(dir, "a.exe"), 353 } 354 got, _ := exec.Command("gdb", args...).CombinedOutput() 355 356 // Check that the backtrace matches the source code. 357 bt := []string{ 358 "eee", 359 "ddd", 360 "ccc", 361 "bbb", 362 "aaa", 363 "main", 364 } 365 for i, name := range bt { 366 s := fmt.Sprintf("#%v.*main\\.%v", i, name) 367 re := regexp.MustCompile(s) 368 if found := re.Find(got) != nil; !found { 369 t.Errorf("could not find '%v' in backtrace", s) 370 t.Fatalf("gdb output:\n%v", string(got)) 371 } 372 } 373 } 374 375 const autotmpTypeSource = ` 376 package main 377 378 type astruct struct { 379 a, b int 380 } 381 382 func main() { 383 var iface interface{} = map[string]astruct{} 384 var iface2 interface{} = []astruct{} 385 println(iface, iface2) 386 } 387 ` 388 389 // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info 390 // See bug #17830. 391 func TestGdbAutotmpTypes(t *testing.T) { 392 checkGdbEnvironment(t) 393 t.Parallel() 394 checkGdbVersion(t) 395 396 dir, err := ioutil.TempDir("", "go-build") 397 if err != nil { 398 t.Fatalf("failed to create temp directory: %v", err) 399 } 400 defer os.RemoveAll(dir) 401 402 // Build the source code. 403 src := filepath.Join(dir, "main.go") 404 err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644) 405 if err != nil { 406 t.Fatalf("failed to create file: %v", err) 407 } 408 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe") 409 cmd.Dir = dir 410 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 411 if err != nil { 412 t.Fatalf("building source %v\n%s", err, out) 413 } 414 415 // Execute gdb commands. 416 args := []string{"-nx", "-batch", 417 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 418 "-ex", "set startup-with-shell off", 419 "-ex", "break main.main", 420 "-ex", "run", 421 "-ex", "step", 422 "-ex", "info types astruct", 423 filepath.Join(dir, "a.exe"), 424 } 425 got, _ := exec.Command("gdb", args...).CombinedOutput() 426 427 sgot := string(got) 428 429 // Check that the backtrace matches the source code. 430 types := []string{ 431 "[]main.astruct;", 432 "bucket<string,main.astruct>;", 433 "hash<string,main.astruct>;", 434 "main.astruct;", 435 "hash<string,main.astruct> * map[string]main.astruct;", 436 } 437 for _, name := range types { 438 if !strings.Contains(sgot, name) { 439 t.Errorf("could not find %s in 'info typrs astruct' output", name) 440 t.Fatalf("gdb output:\n%v", sgot) 441 } 442 } 443 } 444 445 const constsSource = ` 446 package main 447 448 const aConstant int = 42 449 const largeConstant uint64 = ^uint64(0) 450 const minusOne int64 = -1 451 452 func main() { 453 println("hello world") 454 } 455 ` 456 457 func TestGdbConst(t *testing.T) { 458 checkGdbEnvironment(t) 459 t.Parallel() 460 checkGdbVersion(t) 461 462 dir, err := ioutil.TempDir("", "go-build") 463 if err != nil { 464 t.Fatalf("failed to create temp directory: %v", err) 465 } 466 defer os.RemoveAll(dir) 467 468 // Build the source code. 469 src := filepath.Join(dir, "main.go") 470 err = ioutil.WriteFile(src, []byte(constsSource), 0644) 471 if err != nil { 472 t.Fatalf("failed to create file: %v", err) 473 } 474 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe") 475 cmd.Dir = dir 476 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 477 if err != nil { 478 t.Fatalf("building source %v\n%s", err, out) 479 } 480 481 // Execute gdb commands. 482 args := []string{"-nx", "-batch", 483 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 484 "-ex", "set startup-with-shell off", 485 "-ex", "break main.main", 486 "-ex", "run", 487 "-ex", "print main.aConstant", 488 "-ex", "print main.largeConstant", 489 "-ex", "print main.minusOne", 490 "-ex", "print 'runtime.mSpanInUse'", 491 "-ex", "print 'runtime._PageSize'", 492 filepath.Join(dir, "a.exe"), 493 } 494 got, _ := exec.Command("gdb", args...).CombinedOutput() 495 496 sgot := strings.ReplaceAll(string(got), "\r\n", "\n") 497 498 t.Logf("output %q", sgot) 499 500 if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") { 501 t.Fatalf("output mismatch") 502 } 503 } 504 505 const panicSource = ` 506 package main 507 508 import "runtime/debug" 509 510 func main() { 511 debug.SetTraceback("crash") 512 crash() 513 } 514 515 func crash() { 516 panic("panic!") 517 } 518 ` 519 520 // TestGdbPanic tests that gdb can unwind the stack correctly 521 // from SIGABRTs from Go panics. 522 func TestGdbPanic(t *testing.T) { 523 checkGdbEnvironment(t) 524 t.Parallel() 525 checkGdbVersion(t) 526 527 dir, err := ioutil.TempDir("", "go-build") 528 if err != nil { 529 t.Fatalf("failed to create temp directory: %v", err) 530 } 531 defer os.RemoveAll(dir) 532 533 // Build the source code. 534 src := filepath.Join(dir, "main.go") 535 err = ioutil.WriteFile(src, []byte(panicSource), 0644) 536 if err != nil { 537 t.Fatalf("failed to create file: %v", err) 538 } 539 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") 540 cmd.Dir = dir 541 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 542 if err != nil { 543 t.Fatalf("building source %v\n%s", err, out) 544 } 545 546 // Execute gdb commands. 547 args := []string{"-nx", "-batch", 548 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 549 "-ex", "set startup-with-shell off", 550 "-ex", "run", 551 "-ex", "backtrace", 552 filepath.Join(dir, "a.exe"), 553 } 554 got, _ := exec.Command("gdb", args...).CombinedOutput() 555 556 // Check that the backtrace matches the source code. 557 bt := []string{ 558 `crash`, 559 `main`, 560 } 561 for _, name := range bt { 562 s := fmt.Sprintf("(#.* .* in )?main\\.%v", name) 563 re := regexp.MustCompile(s) 564 if found := re.Find(got) != nil; !found { 565 t.Errorf("could not find '%v' in backtrace", s) 566 t.Fatalf("gdb output:\n%v", string(got)) 567 } 568 } 569 }