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