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