github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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 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, err := os.MkdirTemp("", "go-build") 173 if err != nil { 174 t.Fatalf("failed to create temp directory: %v", err) 175 } 176 defer os.RemoveAll(dir) 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(runtime.GOROOT(), "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(runtime.GOROOT(), "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.SplitN(got, []byte("\n"), 2)[0] 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", runtime.GOROOT()) 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, err := os.MkdirTemp("", "go-build") 407 if err != nil { 408 t.Fatalf("failed to create temp directory: %v", err) 409 } 410 defer os.RemoveAll(dir) 411 412 // Build the source code. 413 src := filepath.Join(dir, "main.go") 414 err = os.WriteFile(src, []byte(backtraceSource), 0644) 415 if err != nil { 416 t.Fatalf("failed to create file: %v", err) 417 } 418 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 419 cmd.Dir = dir 420 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 421 if err != nil { 422 t.Fatalf("building source %v\n%s", err, out) 423 } 424 425 // Execute gdb commands. 426 args := []string{"-nx", "-batch", 427 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 428 "-ex", "set startup-with-shell off", 429 "-ex", "break main.eee", 430 "-ex", "run", 431 "-ex", "backtrace", 432 "-ex", "continue", 433 filepath.Join(dir, "a.exe"), 434 } 435 got, err := exec.Command("gdb", args...).CombinedOutput() 436 t.Logf("gdb output:\n%s", got) 437 if err != nil { 438 t.Fatalf("gdb exited with error: %v", err) 439 } 440 441 // Check that the backtrace matches the source code. 442 bt := []string{ 443 "eee", 444 "ddd", 445 "ccc", 446 "bbb", 447 "aaa", 448 "main", 449 } 450 for i, name := range bt { 451 s := fmt.Sprintf("#%v.*main\\.%v", i, name) 452 re := regexp.MustCompile(s) 453 if found := re.Find(got) != nil; !found { 454 t.Fatalf("could not find '%v' in backtrace", s) 455 } 456 } 457 } 458 459 const autotmpTypeSource = ` 460 package main 461 462 type astruct struct { 463 a, b int 464 } 465 466 func main() { 467 var iface interface{} = map[string]astruct{} 468 var iface2 interface{} = []astruct{} 469 println(iface, iface2) 470 } 471 ` 472 473 // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info 474 // See bug #17830. 475 func TestGdbAutotmpTypes(t *testing.T) { 476 checkGdbEnvironment(t) 477 t.Parallel() 478 checkGdbVersion(t) 479 480 if runtime.GOOS == "aix" && testing.Short() { 481 t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64") 482 } 483 484 dir, err := os.MkdirTemp("", "go-build") 485 if err != nil { 486 t.Fatalf("failed to create temp directory: %v", err) 487 } 488 defer os.RemoveAll(dir) 489 490 // Build the source code. 491 src := filepath.Join(dir, "main.go") 492 err = os.WriteFile(src, []byte(autotmpTypeSource), 0644) 493 if err != nil { 494 t.Fatalf("failed to create file: %v", err) 495 } 496 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go") 497 cmd.Dir = dir 498 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 499 if err != nil { 500 t.Fatalf("building source %v\n%s", err, out) 501 } 502 503 // Execute gdb commands. 504 args := []string{"-nx", "-batch", 505 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 506 "-ex", "set startup-with-shell off", 507 "-ex", "break main.main", 508 "-ex", "run", 509 "-ex", "step", 510 "-ex", "info types astruct", 511 filepath.Join(dir, "a.exe"), 512 } 513 got, err := exec.Command("gdb", args...).CombinedOutput() 514 t.Logf("gdb output:\n%s", got) 515 if err != nil { 516 t.Fatalf("gdb exited with error: %v", err) 517 } 518 519 sgot := string(got) 520 521 // Check that the backtrace matches the source code. 522 types := []string{ 523 "[]main.astruct;", 524 "bucket<string,main.astruct>;", 525 "hash<string,main.astruct>;", 526 "main.astruct;", 527 "hash<string,main.astruct> * map[string]main.astruct;", 528 } 529 for _, name := range types { 530 if !strings.Contains(sgot, name) { 531 t.Fatalf("could not find %s in 'info typrs astruct' output", name) 532 } 533 } 534 } 535 536 const constsSource = ` 537 package main 538 539 const aConstant int = 42 540 const largeConstant uint64 = ^uint64(0) 541 const minusOne int64 = -1 542 543 func main() { 544 println("hello world") 545 } 546 ` 547 548 func TestGdbConst(t *testing.T) { 549 checkGdbEnvironment(t) 550 t.Parallel() 551 checkGdbVersion(t) 552 553 dir, err := os.MkdirTemp("", "go-build") 554 if err != nil { 555 t.Fatalf("failed to create temp directory: %v", err) 556 } 557 defer os.RemoveAll(dir) 558 559 // Build the source code. 560 src := filepath.Join(dir, "main.go") 561 err = os.WriteFile(src, []byte(constsSource), 0644) 562 if err != nil { 563 t.Fatalf("failed to create file: %v", err) 564 } 565 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go") 566 cmd.Dir = dir 567 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 568 if err != nil { 569 t.Fatalf("building source %v\n%s", err, out) 570 } 571 572 // Execute gdb commands. 573 args := []string{"-nx", "-batch", 574 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 575 "-ex", "set startup-with-shell off", 576 "-ex", "break main.main", 577 "-ex", "run", 578 "-ex", "print main.aConstant", 579 "-ex", "print main.largeConstant", 580 "-ex", "print main.minusOne", 581 "-ex", "print 'runtime.mSpanInUse'", 582 "-ex", "print 'runtime._PageSize'", 583 filepath.Join(dir, "a.exe"), 584 } 585 got, err := exec.Command("gdb", args...).CombinedOutput() 586 t.Logf("gdb output:\n%s", got) 587 if err != nil { 588 t.Fatalf("gdb exited with error: %v", err) 589 } 590 591 sgot := strings.ReplaceAll(string(got), "\r\n", "\n") 592 593 if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") { 594 t.Fatalf("output mismatch") 595 } 596 } 597 598 const panicSource = ` 599 package main 600 601 import "runtime/debug" 602 603 func main() { 604 debug.SetTraceback("crash") 605 crash() 606 } 607 608 func crash() { 609 panic("panic!") 610 } 611 ` 612 613 // TestGdbPanic tests that gdb can unwind the stack correctly 614 // from SIGABRTs from Go panics. 615 func TestGdbPanic(t *testing.T) { 616 checkGdbEnvironment(t) 617 t.Parallel() 618 checkGdbVersion(t) 619 620 dir, err := os.MkdirTemp("", "go-build") 621 if err != nil { 622 t.Fatalf("failed to create temp directory: %v", err) 623 } 624 defer os.RemoveAll(dir) 625 626 // Build the source code. 627 src := filepath.Join(dir, "main.go") 628 err = os.WriteFile(src, []byte(panicSource), 0644) 629 if err != nil { 630 t.Fatalf("failed to create file: %v", err) 631 } 632 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 633 cmd.Dir = dir 634 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 635 if err != nil { 636 t.Fatalf("building source %v\n%s", err, out) 637 } 638 639 // Execute gdb commands. 640 args := []string{"-nx", "-batch", 641 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 642 "-ex", "set startup-with-shell off", 643 "-ex", "run", 644 "-ex", "backtrace", 645 filepath.Join(dir, "a.exe"), 646 } 647 got, err := exec.Command("gdb", args...).CombinedOutput() 648 t.Logf("gdb output:\n%s", got) 649 if err != nil { 650 t.Fatalf("gdb exited with error: %v", err) 651 } 652 653 // Check that the backtrace matches the source code. 654 bt := []string{ 655 `crash`, 656 `main`, 657 } 658 for _, name := range bt { 659 s := fmt.Sprintf("(#.* .* in )?main\\.%v", name) 660 re := regexp.MustCompile(s) 661 if found := re.Find(got) != nil; !found { 662 t.Fatalf("could not find '%v' in backtrace", s) 663 } 664 } 665 } 666 667 const InfCallstackSource = ` 668 package main 669 import "C" 670 import "time" 671 672 func loop() { 673 for i := 0; i < 1000; i++ { 674 time.Sleep(time.Millisecond*5) 675 } 676 } 677 678 func main() { 679 go loop() 680 time.Sleep(time.Second * 1) 681 } 682 ` 683 684 // TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs 685 // on arm64 platforms without endless frames of function 'crossfunc1'. 686 // https://golang.org/issue/37238 687 func TestGdbInfCallstack(t *testing.T) { 688 checkGdbEnvironment(t) 689 690 testenv.MustHaveCGO(t) 691 if runtime.GOARCH != "arm64" { 692 t.Skip("skipping infinite callstack test on non-arm64 arches") 693 } 694 695 t.Parallel() 696 checkGdbVersion(t) 697 698 dir, err := os.MkdirTemp("", "go-build") 699 if err != nil { 700 t.Fatalf("failed to create temp directory: %v", err) 701 } 702 defer os.RemoveAll(dir) 703 704 // Build the source code. 705 src := filepath.Join(dir, "main.go") 706 err = os.WriteFile(src, []byte(InfCallstackSource), 0644) 707 if err != nil { 708 t.Fatalf("failed to create file: %v", err) 709 } 710 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 711 cmd.Dir = dir 712 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 713 if err != nil { 714 t.Fatalf("building source %v\n%s", err, out) 715 } 716 717 // Execute gdb commands. 718 // 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command. 719 args := []string{"-nx", "-batch", 720 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"), 721 "-ex", "set startup-with-shell off", 722 "-ex", "break setg_gcc", 723 "-ex", "run", 724 "-ex", "backtrace 3", 725 "-ex", "disable 1", 726 "-ex", "continue", 727 filepath.Join(dir, "a.exe"), 728 } 729 got, err := exec.Command("gdb", args...).CombinedOutput() 730 t.Logf("gdb output:\n%s", got) 731 if err != nil { 732 t.Fatalf("gdb exited with error: %v", err) 733 } 734 735 // Check that the backtrace matches 736 // We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c 737 bt := []string{ 738 `setg_gcc`, 739 `crosscall1`, 740 `threadentry`, 741 } 742 for i, name := range bt { 743 s := fmt.Sprintf("#%v.*%v", i, name) 744 re := regexp.MustCompile(s) 745 if found := re.Find(got) != nil; !found { 746 t.Fatalf("could not find '%v' in backtrace", s) 747 } 748 } 749 }