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