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