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