github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/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 "internal/testenv" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 "strconv" 17 "strings" 18 "testing" 19 ) 20 21 // NOTE: In some configurations, GDB will segfault when sent a SIGWINCH signal. 22 // Some runtime tests send SIGWINCH to the entire process group, so those tests 23 // must never run in parallel with GDB tests. 24 // 25 // See issue 39021 and https://sourceware.org/bugzilla/show_bug.cgi?id=26056. 26 27 func checkGdbEnvironment(t *testing.T) { 28 testenv.MustHaveGoBuild(t) 29 switch runtime.GOOS { 30 case "darwin": 31 t.Skip("gdb does not work on darwin") 32 case "netbsd": 33 t.Skip("gdb does not work with threads on NetBSD; see https://golang.org/issue/22893 and https://gnats.netbsd.org/52548") 34 case "windows": 35 t.Skip("gdb tests fail on Windows: https://golang.org/issue/22687") 36 case "linux": 37 if runtime.GOARCH == "ppc64" { 38 t.Skip("skipping gdb tests on linux/ppc64; see https://golang.org/issue/17366") 39 } 40 if runtime.GOARCH == "mips" { 41 t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939") 42 } 43 // Disable GDB tests on alpine until issue #54352 resolved. 44 if strings.HasSuffix(testenv.Builder(), "-alpine") { 45 t.Skip("skipping gdb tests on alpine; see https://golang.org/issue/54352") 46 } 47 case "freebsd": 48 t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508") 49 case "aix": 50 if testing.Short() { 51 t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710") 52 } 53 case "plan9": 54 t.Skip("there is no gdb on Plan 9") 55 } 56 if final := os.Getenv("GOROOT_FINAL"); final != "" && testenv.GOROOT(t) != final { 57 t.Skip("gdb test can fail with GOROOT_FINAL pending") 58 } 59 } 60 61 func checkGdbVersion(t *testing.T) { 62 // Issue 11214 reports various failures with older versions of gdb. 63 out, err := exec.Command("gdb", "--version").CombinedOutput() 64 if err != nil { 65 t.Skipf("skipping: error executing gdb: %v", err) 66 } 67 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`) 68 matches := re.FindSubmatch(out) 69 if len(matches) < 3 { 70 t.Skipf("skipping: can't determine gdb version from\n%s\n", out) 71 } 72 major, err1 := strconv.Atoi(string(matches[1])) 73 minor, err2 := strconv.Atoi(string(matches[2])) 74 if err1 != nil || err2 != nil { 75 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2) 76 } 77 if major < 7 || (major == 7 && minor < 7) { 78 t.Skipf("skipping: gdb version %d.%d too old", major, minor) 79 } 80 t.Logf("gdb version %d.%d", major, minor) 81 } 82 83 func checkGdbPython(t *testing.T) { 84 if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { 85 t.Skip("skipping gdb python tests on illumos and solaris; see golang.org/issue/20821") 86 } 87 88 cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')") 89 out, err := cmd.CombinedOutput() 90 91 if err != nil { 92 t.Skipf("skipping due to issue running gdb: %v", err) 93 } 94 if strings.TrimSpace(string(out)) != "go gdb python support" { 95 t.Skipf("skipping due to lack of python gdb support: %s", out) 96 } 97 } 98 99 // checkCleanBacktrace checks that the given backtrace is well formed and does 100 // not contain any error messages from GDB. 101 func checkCleanBacktrace(t *testing.T, backtrace string) { 102 backtrace = strings.TrimSpace(backtrace) 103 lines := strings.Split(backtrace, "\n") 104 if len(lines) == 0 { 105 t.Fatalf("empty backtrace") 106 } 107 for i, l := range lines { 108 if !strings.HasPrefix(l, fmt.Sprintf("#%v ", i)) { 109 t.Fatalf("malformed backtrace at line %v: %v", i, l) 110 } 111 } 112 // TODO(mundaym): check for unknown frames (e.g. "??"). 113 } 114 115 const helloSource = ` 116 import "fmt" 117 import "runtime" 118 var gslice []string 119 func main() { 120 mapvar := make(map[string]string, 13) 121 slicemap := make(map[string][]string,11) 122 chanint := make(chan int, 10) 123 chanstr := make(chan string, 10) 124 chanint <- 99 125 chanint <- 11 126 chanstr <- "spongepants" 127 chanstr <- "squarebob" 128 mapvar["abc"] = "def" 129 mapvar["ghi"] = "jkl" 130 slicemap["a"] = []string{"b","c","d"} 131 slicemap["e"] = []string{"f","g","h"} 132 strvar := "abc" 133 ptrvar := &strvar 134 slicevar := make([]string, 0, 16) 135 slicevar = append(slicevar, mapvar["abc"]) 136 fmt.Println("hi") 137 runtime.KeepAlive(ptrvar) 138 _ = ptrvar // set breakpoint here 139 gslice = slicevar 140 fmt.Printf("%v, %v, %v\n", slicemap, <-chanint, <-chanstr) 141 runtime.KeepAlive(mapvar) 142 } // END_OF_PROGRAM 143 ` 144 145 func lastLine(src []byte) int { 146 eop := []byte("END_OF_PROGRAM") 147 for i, l := range bytes.Split(src, []byte("\n")) { 148 if bytes.Contains(l, eop) { 149 return i 150 } 151 } 152 return 0 153 } 154 155 func TestGdbPython(t *testing.T) { 156 testGdbPython(t, false) 157 } 158 159 func TestGdbPythonCgo(t *testing.T) { 160 if strings.HasPrefix(runtime.GOARCH, "mips") { 161 testenv.SkipFlaky(t, 37794) 162 } 163 testGdbPython(t, true) 164 } 165 166 func testGdbPython(t *testing.T, cgo bool) { 167 if cgo { 168 testenv.MustHaveCGO(t) 169 } 170 171 checkGdbEnvironment(t) 172 t.Parallel() 173 checkGdbVersion(t) 174 checkGdbPython(t) 175 176 dir := t.TempDir() 177 178 var buf bytes.Buffer 179 buf.WriteString("package main\n") 180 if cgo { 181 buf.WriteString(`import "C"` + "\n") 182 } 183 buf.WriteString(helloSource) 184 185 src := buf.Bytes() 186 187 // Locate breakpoint line 188 var bp int 189 lines := bytes.Split(src, []byte("\n")) 190 for i, line := range lines { 191 if bytes.Contains(line, []byte("breakpoint")) { 192 bp = i 193 break 194 } 195 } 196 197 err := os.WriteFile(filepath.Join(dir, "main.go"), src, 0644) 198 if err != nil { 199 t.Fatalf("failed to create file: %v", err) 200 } 201 nLines := lastLine(src) 202 203 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 204 cmd.Dir = dir 205 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 206 if err != nil { 207 t.Fatalf("building source %v\n%s", err, out) 208 } 209 210 args := []string{"-nx", "-q", "--batch", 211 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 212 "-ex", "set startup-with-shell off", 213 "-ex", "set print thread-events off", 214 } 215 if cgo { 216 // When we build the cgo version of the program, the system's 217 // linker is used. Some external linkers, like GNU gold, 218 // compress the .debug_gdb_scripts into .zdebug_gdb_scripts. 219 // Until gold and gdb can work together, temporarily load the 220 // python script directly. 221 args = append(args, 222 "-ex", "source "+filepath.Join(testenv.GOROOT(t), "src", "runtime", "runtime-gdb.py"), 223 ) 224 } else { 225 args = append(args, 226 "-ex", "info auto-load python-scripts", 227 ) 228 } 229 args = append(args, 230 "-ex", "set python print-stack full", 231 "-ex", fmt.Sprintf("br main.go:%d", bp), 232 "-ex", "run", 233 "-ex", "echo BEGIN info goroutines\n", 234 "-ex", "info goroutines", 235 "-ex", "echo END\n", 236 "-ex", "echo BEGIN print mapvar\n", 237 "-ex", "print mapvar", 238 "-ex", "echo END\n", 239 "-ex", "echo BEGIN print slicemap\n", 240 "-ex", "print slicemap", 241 "-ex", "echo END\n", 242 "-ex", "echo BEGIN print strvar\n", 243 "-ex", "print strvar", 244 "-ex", "echo END\n", 245 "-ex", "echo BEGIN print chanint\n", 246 "-ex", "print chanint", 247 "-ex", "echo END\n", 248 "-ex", "echo BEGIN print chanstr\n", 249 "-ex", "print chanstr", 250 "-ex", "echo END\n", 251 "-ex", "echo BEGIN info locals\n", 252 "-ex", "info locals", 253 "-ex", "echo END\n", 254 "-ex", "echo BEGIN goroutine 1 bt\n", 255 "-ex", "goroutine 1 bt", 256 "-ex", "echo END\n", 257 "-ex", "echo BEGIN goroutine all bt\n", 258 "-ex", "goroutine all bt", 259 "-ex", "echo END\n", 260 "-ex", "clear main.go:15", // clear the previous break point 261 "-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main 262 "-ex", "c", 263 "-ex", "echo BEGIN goroutine 1 bt at the end\n", 264 "-ex", "goroutine 1 bt", 265 "-ex", "echo END\n", 266 filepath.Join(dir, "a.exe"), 267 ) 268 got, err := exec.Command("gdb", args...).CombinedOutput() 269 t.Logf("gdb output:\n%s", got) 270 if err != nil { 271 t.Fatalf("gdb exited with error: %v", err) 272 } 273 274 firstLine, _, _ := bytes.Cut(got, []byte("\n")) 275 if string(firstLine) != "Loading Go Runtime support." { 276 // This can happen when using all.bash with 277 // GOROOT_FINAL set, because the tests are run before 278 // the final installation of the files. 279 cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT") 280 cmd.Env = []string{} 281 out, err := cmd.CombinedOutput() 282 if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) { 283 t.Skipf("skipping because GOROOT=%s does not exist", testenv.GOROOT(t)) 284 } 285 286 _, file, _, _ := runtime.Caller(1) 287 288 t.Logf("package testing source file: %s", file) 289 t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got) 290 } 291 292 // Extract named BEGIN...END blocks from output 293 partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`) 294 blocks := map[string]string{} 295 for _, subs := range partRe.FindAllSubmatch(got, -1) { 296 blocks[string(subs[1])] = string(subs[2]) 297 } 298 299 infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`) 300 if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) { 301 t.Fatalf("info goroutines failed: %s", bl) 302 } 303 304 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"}$`) 305 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"}$`) 306 if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) && 307 !printMapvarRe2.MatchString(bl) { 308 t.Fatalf("print mapvar failed: %s", bl) 309 } 310 311 // 2 orders, and possible differences in spacing. 312 sliceMapSfx1 := `map[string][]string = {["e"] = []string = {"f", "g", "h"}, ["a"] = []string = {"b", "c", "d"}}` 313 sliceMapSfx2 := `map[string][]string = {["a"] = []string = {"b", "c", "d"}, ["e"] = []string = {"f", "g", "h"}}` 314 if bl := strings.ReplaceAll(blocks["print slicemap"], " ", " "); !strings.HasSuffix(bl, sliceMapSfx1) && !strings.HasSuffix(bl, sliceMapSfx2) { 315 t.Fatalf("print slicemap failed: %s", bl) 316 } 317 318 chanIntSfx := `chan int = {99, 11}` 319 if bl := strings.ReplaceAll(blocks["print chanint"], " ", " "); !strings.HasSuffix(bl, chanIntSfx) { 320 t.Fatalf("print chanint failed: %s", bl) 321 } 322 323 chanStrSfx := `chan string = {"spongepants", "squarebob"}` 324 if bl := strings.ReplaceAll(blocks["print chanstr"], " ", " "); !strings.HasSuffix(bl, chanStrSfx) { 325 t.Fatalf("print chanstr failed: %s", bl) 326 } 327 328 strVarRe := regexp.MustCompile(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`) 329 if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) { 330 t.Fatalf("print strvar failed: %s", bl) 331 } 332 333 // The exact format of composite values has changed over time. 334 // For issue 16338: ssa decompose phase split a slice into 335 // a collection of scalar vars holding its fields. In such cases 336 // the DWARF variable location expression should be of the 337 // form "var.field" and not just "field". 338 // However, the newer dwarf location list code reconstituted 339 // aggregates from their fields and reverted their printing 340 // back to its original form. 341 // Only test that all variables are listed in 'info locals' since 342 // different versions of gdb print variables in different 343 // order and with differing amount of information and formats. 344 345 if bl := blocks["info locals"]; !strings.Contains(bl, "slicevar") || 346 !strings.Contains(bl, "mapvar") || 347 !strings.Contains(bl, "strvar") { 348 t.Fatalf("info locals failed: %s", bl) 349 } 350 351 // Check that the backtraces are well formed. 352 checkCleanBacktrace(t, blocks["goroutine 1 bt"]) 353 checkCleanBacktrace(t, blocks["goroutine 1 bt at the end"]) 354 355 btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`) 356 if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) { 357 t.Fatalf("goroutine 1 bt failed: %s", bl) 358 } 359 360 if bl := blocks["goroutine all bt"]; !btGoroutine1Re.MatchString(bl) { 361 t.Fatalf("goroutine all bt failed: %s", bl) 362 } 363 364 btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`) 365 if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) { 366 t.Fatalf("goroutine 1 bt at the end failed: %s", bl) 367 } 368 } 369 370 const backtraceSource = ` 371 package main 372 373 //go:noinline 374 func aaa() bool { return bbb() } 375 376 //go:noinline 377 func bbb() bool { return ccc() } 378 379 //go:noinline 380 func ccc() bool { return ddd() } 381 382 //go:noinline 383 func ddd() bool { return f() } 384 385 //go:noinline 386 func eee() bool { return true } 387 388 var f = eee 389 390 func main() { 391 _ = aaa() 392 } 393 ` 394 395 // TestGdbBacktrace tests that gdb can unwind the stack correctly 396 // using only the DWARF debug info. 397 func TestGdbBacktrace(t *testing.T) { 398 if runtime.GOOS == "netbsd" { 399 testenv.SkipFlaky(t, 15603) 400 } 401 402 checkGdbEnvironment(t) 403 t.Parallel() 404 checkGdbVersion(t) 405 406 dir := t.TempDir() 407 408 // Build the source code. 409 src := filepath.Join(dir, "main.go") 410 err := os.WriteFile(src, []byte(backtraceSource), 0644) 411 if err != nil { 412 t.Fatalf("failed to create file: %v", err) 413 } 414 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 415 cmd.Dir = dir 416 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 417 if err != nil { 418 t.Fatalf("building source %v\n%s", err, out) 419 } 420 421 // Execute gdb commands. 422 args := []string{"-nx", "-batch", 423 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 424 "-ex", "set startup-with-shell off", 425 "-ex", "break main.eee", 426 "-ex", "run", 427 "-ex", "backtrace", 428 "-ex", "continue", 429 filepath.Join(dir, "a.exe"), 430 } 431 got, err := testenv.RunWithTimeout(t, exec.Command("gdb", args...)) 432 t.Logf("gdb output:\n%s", got) 433 if err != nil { 434 if bytes.Contains(got, []byte("internal-error: wait returned unexpected status 0x0")) { 435 // GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28551 436 testenv.SkipFlaky(t, 43068) 437 } 438 if bytes.Contains(got, []byte("Couldn't get registers: No such process.")) { 439 // GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=9086 440 testenv.SkipFlaky(t, 50838) 441 } 442 if bytes.Contains(got, []byte(" exited normally]\n")) { 443 // GDB bug: Sometimes the inferior exits fine, 444 // but then GDB hangs. 445 testenv.SkipFlaky(t, 37405) 446 } 447 t.Fatalf("gdb exited with error: %v", err) 448 } 449 450 // Check that the backtrace matches the source code. 451 bt := []string{ 452 "eee", 453 "ddd", 454 "ccc", 455 "bbb", 456 "aaa", 457 "main", 458 } 459 for i, name := range bt { 460 s := fmt.Sprintf("#%v.*main\\.%v", i, name) 461 re := regexp.MustCompile(s) 462 if found := re.Find(got) != nil; !found { 463 t.Fatalf("could not find '%v' in backtrace", s) 464 } 465 } 466 } 467 468 const autotmpTypeSource = ` 469 package main 470 471 type astruct struct { 472 a, b int 473 } 474 475 func main() { 476 var iface interface{} = map[string]astruct{} 477 var iface2 interface{} = []astruct{} 478 println(iface, iface2) 479 } 480 ` 481 482 // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info 483 // See bug #17830. 484 func TestGdbAutotmpTypes(t *testing.T) { 485 checkGdbEnvironment(t) 486 t.Parallel() 487 checkGdbVersion(t) 488 489 if runtime.GOOS == "aix" && testing.Short() { 490 t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64") 491 } 492 493 dir := t.TempDir() 494 495 // Build the source code. 496 src := filepath.Join(dir, "main.go") 497 err := os.WriteFile(src, []byte(autotmpTypeSource), 0644) 498 if err != nil { 499 t.Fatalf("failed to create file: %v", err) 500 } 501 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go") 502 cmd.Dir = dir 503 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 504 if err != nil { 505 t.Fatalf("building source %v\n%s", err, out) 506 } 507 508 // Execute gdb commands. 509 args := []string{"-nx", "-batch", 510 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 511 "-ex", "set startup-with-shell off", 512 // Some gdb may set scheduling-locking as "step" by default. This prevents background tasks 513 // (e.g GC) from completing which may result in a hang when executing the step command. 514 // See #49852. 515 "-ex", "set scheduler-locking off", 516 "-ex", "break main.main", 517 "-ex", "run", 518 "-ex", "step", 519 "-ex", "info types astruct", 520 filepath.Join(dir, "a.exe"), 521 } 522 got, err := exec.Command("gdb", args...).CombinedOutput() 523 t.Logf("gdb output:\n%s", got) 524 if err != nil { 525 t.Fatalf("gdb exited with error: %v", err) 526 } 527 528 sgot := string(got) 529 530 // Check that the backtrace matches the source code. 531 types := []string{ 532 "[]main.astruct;", 533 "bucket<string,main.astruct>;", 534 "hash<string,main.astruct>;", 535 "main.astruct;", 536 "hash<string,main.astruct> * map[string]main.astruct;", 537 } 538 for _, name := range types { 539 if !strings.Contains(sgot, name) { 540 t.Fatalf("could not find %s in 'info typrs astruct' output", name) 541 } 542 } 543 } 544 545 const constsSource = ` 546 package main 547 548 const aConstant int = 42 549 const largeConstant uint64 = ^uint64(0) 550 const minusOne int64 = -1 551 552 func main() { 553 println("hello world") 554 } 555 ` 556 557 func TestGdbConst(t *testing.T) { 558 checkGdbEnvironment(t) 559 t.Parallel() 560 checkGdbVersion(t) 561 562 dir := t.TempDir() 563 564 // Build the source code. 565 src := filepath.Join(dir, "main.go") 566 err := os.WriteFile(src, []byte(constsSource), 0644) 567 if err != nil { 568 t.Fatalf("failed to create file: %v", err) 569 } 570 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go") 571 cmd.Dir = dir 572 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 573 if err != nil { 574 t.Fatalf("building source %v\n%s", err, out) 575 } 576 577 // Execute gdb commands. 578 args := []string{"-nx", "-batch", 579 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 580 "-ex", "set startup-with-shell off", 581 "-ex", "break main.main", 582 "-ex", "run", 583 "-ex", "print main.aConstant", 584 "-ex", "print main.largeConstant", 585 "-ex", "print main.minusOne", 586 "-ex", "print 'runtime.mSpanInUse'", 587 "-ex", "print 'runtime._PageSize'", 588 filepath.Join(dir, "a.exe"), 589 } 590 got, err := exec.Command("gdb", args...).CombinedOutput() 591 t.Logf("gdb output:\n%s", got) 592 if err != nil { 593 t.Fatalf("gdb exited with error: %v", err) 594 } 595 596 sgot := strings.ReplaceAll(string(got), "\r\n", "\n") 597 598 if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") { 599 t.Fatalf("output mismatch") 600 } 601 } 602 603 const panicSource = ` 604 package main 605 606 import "runtime/debug" 607 608 func main() { 609 debug.SetTraceback("crash") 610 crash() 611 } 612 613 func crash() { 614 panic("panic!") 615 } 616 ` 617 618 // TestGdbPanic tests that gdb can unwind the stack correctly 619 // from SIGABRTs from Go panics. 620 func TestGdbPanic(t *testing.T) { 621 checkGdbEnvironment(t) 622 t.Parallel() 623 checkGdbVersion(t) 624 625 dir := t.TempDir() 626 627 // Build the source code. 628 src := filepath.Join(dir, "main.go") 629 err := os.WriteFile(src, []byte(panicSource), 0644) 630 if err != nil { 631 t.Fatalf("failed to create file: %v", err) 632 } 633 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 634 cmd.Dir = dir 635 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 636 if err != nil { 637 t.Fatalf("building source %v\n%s", err, out) 638 } 639 640 // Execute gdb commands. 641 args := []string{"-nx", "-batch", 642 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 643 "-ex", "set startup-with-shell off", 644 "-ex", "run", 645 "-ex", "backtrace", 646 filepath.Join(dir, "a.exe"), 647 } 648 got, err := exec.Command("gdb", args...).CombinedOutput() 649 t.Logf("gdb output:\n%s", got) 650 if err != nil { 651 t.Fatalf("gdb exited with error: %v", err) 652 } 653 654 // Check that the backtrace matches the source code. 655 bt := []string{ 656 `crash`, 657 `main`, 658 } 659 for _, name := range bt { 660 s := fmt.Sprintf("(#.* .* in )?main\\.%v", name) 661 re := regexp.MustCompile(s) 662 if found := re.Find(got) != nil; !found { 663 t.Fatalf("could not find '%v' in backtrace", s) 664 } 665 } 666 } 667 668 const InfCallstackSource = ` 669 package main 670 import "C" 671 import "time" 672 673 func loop() { 674 for i := 0; i < 1000; i++ { 675 time.Sleep(time.Millisecond*5) 676 } 677 } 678 679 func main() { 680 go loop() 681 time.Sleep(time.Second * 1) 682 } 683 ` 684 685 // TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs 686 // on arm64 platforms without endless frames of function 'crossfunc1'. 687 // https://golang.org/issue/37238 688 func TestGdbInfCallstack(t *testing.T) { 689 checkGdbEnvironment(t) 690 691 testenv.MustHaveCGO(t) 692 if runtime.GOARCH != "arm64" { 693 t.Skip("skipping infinite callstack test on non-arm64 arches") 694 } 695 696 t.Parallel() 697 checkGdbVersion(t) 698 699 dir := t.TempDir() 700 701 // Build the source code. 702 src := filepath.Join(dir, "main.go") 703 err := os.WriteFile(src, []byte(InfCallstackSource), 0644) 704 if err != nil { 705 t.Fatalf("failed to create file: %v", err) 706 } 707 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 708 cmd.Dir = dir 709 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 710 if err != nil { 711 t.Fatalf("building source %v\n%s", err, out) 712 } 713 714 // Execute gdb commands. 715 // 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command. 716 args := []string{"-nx", "-batch", 717 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 718 "-ex", "set startup-with-shell off", 719 "-ex", "break setg_gcc", 720 "-ex", "run", 721 "-ex", "backtrace 3", 722 "-ex", "disable 1", 723 "-ex", "continue", 724 filepath.Join(dir, "a.exe"), 725 } 726 got, err := exec.Command("gdb", args...).CombinedOutput() 727 t.Logf("gdb output:\n%s", got) 728 if err != nil { 729 t.Fatalf("gdb exited with error: %v", err) 730 } 731 732 // Check that the backtrace matches 733 // We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c 734 bt := []string{ 735 `setg_gcc`, 736 `crosscall1`, 737 `threadentry`, 738 } 739 for i, name := range bt { 740 s := fmt.Sprintf("#%v.*%v", i, name) 741 re := regexp.MustCompile(s) 742 if found := re.Find(got) != nil; !found { 743 t.Fatalf("could not find '%v' in backtrace", s) 744 } 745 } 746 }