...

Source file src/runtime/runtime-gdb_test.go

Documentation: runtime

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

View as plain text