PNG  IHDRxsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<,tEXtComment File Manager

File Manager

Path: /opt/golang/1.22.0/src/runtime/

Viewing File: runtime-gdb_test.go

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime_test

import (
	"bytes"
	"flag"
	"fmt"
	"internal/abi"
	"internal/testenv"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"testing"
	"time"
)

// NOTE: In some configurations, GDB will segfault when sent a SIGWINCH signal.
// Some runtime tests send SIGWINCH to the entire process group, so those tests
// must never run in parallel with GDB tests.
//
// See issue 39021 and https://sourceware.org/bugzilla/show_bug.cgi?id=26056.

func checkGdbEnvironment(t *testing.T) {
	testenv.MustHaveGoBuild(t)
	switch runtime.GOOS {
	case "darwin":
		t.Skip("gdb does not work on darwin")
	case "netbsd":
		t.Skip("gdb does not work with threads on NetBSD; see https://golang.org/issue/22893 and https://gnats.netbsd.org/52548")
	case "linux":
		if runtime.GOARCH == "ppc64" {
			t.Skip("skipping gdb tests on linux/ppc64; see https://golang.org/issue/17366")
		}
		if runtime.GOARCH == "mips" {
			t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939")
		}
		// Disable GDB tests on alpine until issue #54352 resolved.
		if strings.HasSuffix(testenv.Builder(), "-alpine") {
			t.Skip("skipping gdb tests on alpine; see https://golang.org/issue/54352")
		}
	case "freebsd":
		t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508")
	case "aix":
		if testing.Short() {
			t.Skip("skipping gdb tests on AIX; see https://golang.org/issue/35710")
		}
	case "plan9":
		t.Skip("there is no gdb on Plan 9")
	}
	if final := os.Getenv("GOROOT_FINAL"); final != "" && testenv.GOROOT(t) != final {
		t.Skip("gdb test can fail with GOROOT_FINAL pending")
	}
}

func checkGdbVersion(t *testing.T) {
	// Issue 11214 reports various failures with older versions of gdb.
	out, err := exec.Command("gdb", "--version").CombinedOutput()
	if err != nil {
		t.Skipf("skipping: error executing gdb: %v", err)
	}
	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
	matches := re.FindSubmatch(out)
	if len(matches) < 3 {
		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
	}
	major, err1 := strconv.Atoi(string(matches[1]))
	minor, err2 := strconv.Atoi(string(matches[2]))
	if err1 != nil || err2 != nil {
		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
	}
	if major < 7 || (major == 7 && minor < 7) {
		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
	}
	t.Logf("gdb version %d.%d", major, minor)
}

func checkGdbPython(t *testing.T) {
	if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
		t.Skip("skipping gdb python tests on illumos and solaris; see golang.org/issue/20821")
	}
	args := []string{"-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')"}
	gdbArgsFixup(args)
	cmd := exec.Command("gdb", args...)
	out, err := cmd.CombinedOutput()

	if err != nil {
		t.Skipf("skipping due to issue running gdb: %v", err)
	}
	if strings.TrimSpace(string(out)) != "go gdb python support" {
		t.Skipf("skipping due to lack of python gdb support: %s", out)
	}
}

// checkCleanBacktrace checks that the given backtrace is well formed and does
// not contain any error messages from GDB.
func checkCleanBacktrace(t *testing.T, backtrace string) {
	backtrace = strings.TrimSpace(backtrace)
	lines := strings.Split(backtrace, "\n")
	if len(lines) == 0 {
		t.Fatalf("empty backtrace")
	}
	for i, l := range lines {
		if !strings.HasPrefix(l, fmt.Sprintf("#%v  ", i)) {
			t.Fatalf("malformed backtrace at line %v: %v", i, l)
		}
	}
	// TODO(mundaym): check for unknown frames (e.g. "??").
}

// NOTE: the maps below are allocated larger than abi.MapBucketCount
// to ensure that they are not "optimized out".

var helloSource = `
import "fmt"
import "runtime"
var gslice []string
func main() {
	mapvar := make(map[string]string, ` + strconv.FormatInt(abi.MapBucketCount+9, 10) + `)
	slicemap := make(map[string][]string,` + strconv.FormatInt(abi.MapBucketCount+3, 10) + `)
    chanint := make(chan int, 10)
    chanstr := make(chan string, 10)
    chanint <- 99
	chanint <- 11
    chanstr <- "spongepants"
    chanstr <- "squarebob"
	mapvar["abc"] = "def"
	mapvar["ghi"] = "jkl"
	slicemap["a"] = []string{"b","c","d"}
    slicemap["e"] = []string{"f","g","h"}
	strvar := "abc"
	ptrvar := &strvar
	slicevar := make([]string, 0, 16)
	slicevar = append(slicevar, mapvar["abc"])
	fmt.Println("hi")
	runtime.KeepAlive(ptrvar)
	_ = ptrvar // set breakpoint here
	gslice = slicevar
	fmt.Printf("%v, %v, %v\n", slicemap, <-chanint, <-chanstr)
	runtime.KeepAlive(mapvar)
}  // END_OF_PROGRAM
`

func lastLine(src []byte) int {
	eop := []byte("END_OF_PROGRAM")
	for i, l := range bytes.Split(src, []byte("\n")) {
		if bytes.Contains(l, eop) {
			return i
		}
	}
	return 0
}

func gdbArgsFixup(args []string) {
	if runtime.GOOS != "windows" {
		return
	}
	// On Windows, some gdb flavors expect -ex and -iex arguments
	// containing spaces to be double quoted.
	var quote bool
	for i, arg := range args {
		if arg == "-iex" || arg == "-ex" {
			quote = true
		} else if quote {
			if strings.ContainsRune(arg, ' ') {
				args[i] = `"` + arg + `"`
			}
			quote = false
		}
	}
}

func TestGdbPython(t *testing.T) {
	testGdbPython(t, false)
}

func TestGdbPythonCgo(t *testing.T) {
	if strings.HasPrefix(runtime.GOARCH, "mips") {
		testenv.SkipFlaky(t, 37794)
	}
	testGdbPython(t, true)
}

func testGdbPython(t *testing.T, cgo bool) {
	if cgo {
		testenv.MustHaveCGO(t)
	}

	checkGdbEnvironment(t)
	t.Parallel()
	checkGdbVersion(t)
	checkGdbPython(t)

	dir := t.TempDir()

	var buf bytes.Buffer
	buf.WriteString("package main\n")
	if cgo {
		buf.WriteString(`import "C"` + "\n")
	}
	buf.WriteString(helloSource)

	src := buf.Bytes()

	// Locate breakpoint line
	var bp int
	lines := bytes.Split(src, []byte("\n"))
	for i, line := range lines {
		if bytes.Contains(line, []byte("breakpoint")) {
			bp = i
			break
		}
	}

	err := os.WriteFile(filepath.Join(dir, "main.go"), src, 0644)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}
	nLines := lastLine(src)

	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
	cmd.Dir = dir
	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
	if err != nil {
		t.Fatalf("building source %v\n%s", err, out)
	}

	args := []string{"-nx", "-q", "--batch",
		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
		"-ex", "set startup-with-shell off",
		"-ex", "set print thread-events off",
	}
	if cgo {
		// When we build the cgo version of the program, the system's
		// linker is used. Some external linkers, like GNU gold,
		// compress the .debug_gdb_scripts into .zdebug_gdb_scripts.
		// Until gold and gdb can work together, temporarily load the
		// python script directly.
		args = append(args,
			"-ex", "source "+filepath.Join(testenv.GOROOT(t), "src", "runtime", "runtime-gdb.py"),
		)
	} else {
		args = append(args,
			"-ex", "info auto-load python-scripts",
		)
	}
	args = append(args,
		"-ex", "set python print-stack full",
		"-ex", fmt.Sprintf("br main.go:%d", bp),
		"-ex", "run",
		"-ex", "echo BEGIN info goroutines\n",
		"-ex", "info goroutines",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN print mapvar\n",
		"-ex", "print mapvar",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN print slicemap\n",
		"-ex", "print slicemap",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN print strvar\n",
		"-ex", "print strvar",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN print chanint\n",
		"-ex", "print chanint",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN print chanstr\n",
		"-ex", "print chanstr",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN info locals\n",
		"-ex", "info locals",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN goroutine 1 bt\n",
		"-ex", "goroutine 1 bt",
		"-ex", "echo END\n",
		"-ex", "echo BEGIN goroutine all bt\n",
		"-ex", "goroutine all bt",
		"-ex", "echo END\n",
		"-ex", "clear main.go:15", // clear the previous break point
		"-ex", fmt.Sprintf("br main.go:%d", nLines), // new break point at the end of main
		"-ex", "c",
		"-ex", "echo BEGIN goroutine 1 bt at the end\n",
		"-ex", "goroutine 1 bt",
		"-ex", "echo END\n",
		filepath.Join(dir, "a.exe"),
	)
	gdbArgsFixup(args)
	got, err := exec.Command("gdb", args...).CombinedOutput()
	t.Logf("gdb output:\n%s", got)
	if err != nil {
		t.Fatalf("gdb exited with error: %v", err)
	}

	got = bytes.ReplaceAll(got, []byte("\r\n"), []byte("\n")) // normalize line endings
	firstLine, _, _ := bytes.Cut(got, []byte("\n"))
	if string(firstLine) != "Loading Go Runtime support." {
		// This can happen when using all.bash with
		// GOROOT_FINAL set, because the tests are run before
		// the final installation of the files.
		cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
		cmd.Env = []string{}
		out, err := cmd.CombinedOutput()
		if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
			t.Skipf("skipping because GOROOT=%s does not exist", testenv.GOROOT(t))
		}

		_, file, _, _ := runtime.Caller(1)

		t.Logf("package testing source file: %s", file)
		t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
	}

	// Extract named BEGIN...END blocks from output
	partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
	blocks := map[string]string{}
	for _, subs := range partRe.FindAllSubmatch(got, -1) {
		blocks[string(subs[1])] = string(subs[2])
	}

	infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
	if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
		t.Fatalf("info goroutines failed: %s", bl)
	}

	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"}$`)
	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"}$`)
	if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
		!printMapvarRe2.MatchString(bl) {
		t.Fatalf("print mapvar failed: %s", bl)
	}

	// 2 orders, and possible differences in spacing.
	sliceMapSfx1 := `map[string][]string = {["e"] = []string = {"f", "g", "h"}, ["a"] = []string = {"b", "c", "d"}}`
	sliceMapSfx2 := `map[string][]string = {["a"] = []string = {"b", "c", "d"}, ["e"] = []string = {"f", "g", "h"}}`
	if bl := strings.ReplaceAll(blocks["print slicemap"], "  ", " "); !strings.HasSuffix(bl, sliceMapSfx1) && !strings.HasSuffix(bl, sliceMapSfx2) {
		t.Fatalf("print slicemap failed: %s", bl)
	}

	chanIntSfx := `chan int = {99, 11}`
	if bl := strings.ReplaceAll(blocks["print chanint"], "  ", " "); !strings.HasSuffix(bl, chanIntSfx) {
		t.Fatalf("print chanint failed: %s", bl)
	}

	chanStrSfx := `chan string = {"spongepants", "squarebob"}`
	if bl := strings.ReplaceAll(blocks["print chanstr"], "  ", " "); !strings.HasSuffix(bl, chanStrSfx) {
		t.Fatalf("print chanstr failed: %s", bl)
	}

	strVarRe := regexp.MustCompile(`^\$[0-9]+ = (0x[0-9a-f]+\s+)?"abc"$`)
	if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
		t.Fatalf("print strvar failed: %s", bl)
	}

	// The exact format of composite values has changed over time.
	// For issue 16338: ssa decompose phase split a slice into
	// a collection of scalar vars holding its fields. In such cases
	// the DWARF variable location expression should be of the
	// form "var.field" and not just "field".
	// However, the newer dwarf location list code reconstituted
	// aggregates from their fields and reverted their printing
	// back to its original form.
	// Only test that all variables are listed in 'info locals' since
	// different versions of gdb print variables in different
	// order and with differing amount of information and formats.

	if bl := blocks["info locals"]; !strings.Contains(bl, "slicevar") ||
		!strings.Contains(bl, "mapvar") ||
		!strings.Contains(bl, "strvar") {
		t.Fatalf("info locals failed: %s", bl)
	}

	// Check that the backtraces are well formed.
	checkCleanBacktrace(t, blocks["goroutine 1 bt"])
	checkCleanBacktrace(t, blocks["goroutine 1 bt at the end"])

	btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
	if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
		t.Fatalf("goroutine 1 bt failed: %s", bl)
	}

	if bl := blocks["goroutine all bt"]; !btGoroutine1Re.MatchString(bl) {
		t.Fatalf("goroutine all bt failed: %s", bl)
	}

	btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
	if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
		t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
	}
}

const backtraceSource = `
package main

//go:noinline
func aaa() bool { return bbb() }

//go:noinline
func bbb() bool { return ccc() }

//go:noinline
func ccc() bool { return ddd() }

//go:noinline
func ddd() bool { return f() }

//go:noinline
func eee() bool { return true }

var f = eee

func main() {
	_ = aaa()
}
`

// TestGdbBacktrace tests that gdb can unwind the stack correctly
// using only the DWARF debug info.
func TestGdbBacktrace(t *testing.T) {
	if runtime.GOOS == "netbsd" {
		testenv.SkipFlaky(t, 15603)
	}
	if flag.Lookup("test.parallel").Value.(flag.Getter).Get().(int) < 2 {
		// It is possible that this test will hang for a long time due to an
		// apparent GDB bug reported in https://go.dev/issue/37405.
		// If test parallelism is high enough, that might be ok: the other parallel
		// tests will finish, and then this test will finish right before it would
		// time out. However, if test are running sequentially, a hang in this test
		// would likely cause the remaining tests to run out of time.
		testenv.SkipFlaky(t, 37405)
	}

	checkGdbEnvironment(t)
	t.Parallel()
	checkGdbVersion(t)

	dir := t.TempDir()

	// Build the source code.
	src := filepath.Join(dir, "main.go")
	err := os.WriteFile(src, []byte(backtraceSource), 0644)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
	cmd.Dir = dir
	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
	if err != nil {
		t.Fatalf("building source %v\n%s", err, out)
	}

	// Execute gdb commands.
	start := time.Now()
	args := []string{"-nx", "-batch",
		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
		"-ex", "set startup-with-shell off",
		"-ex", "break main.eee",
		"-ex", "run",
		"-ex", "backtrace",
		"-ex", "continue",
		filepath.Join(dir, "a.exe"),
	}
	gdbArgsFixup(args)
	cmd = testenv.Command(t, "gdb", args...)

	// Work around the GDB hang reported in https://go.dev/issue/37405.
	// Sometimes (rarely), the GDB process hangs completely when the Go program
	// exits, and we suspect that the bug is on the GDB side.
	//
	// The default Cancel function added by testenv.Command will mark the test as
	// failed if it is in danger of timing out, but we want to instead mark it as
	// skipped. Change the Cancel function to kill the process and merely log
	// instead of failing the test.
	//
	// (This approach does not scale: if the test parallelism is less than or
	// equal to the number of tests that run right up to the deadline, then the
	// remaining parallel tests are likely to time out. But as long as it's just
	// this one flaky test, it's probably fine..?)
	//
	// If there is no deadline set on the test at all, relying on the timeout set
	// by testenv.Command will cause the test to hang indefinitely, but that's
	// what “no deadline” means, after all — and it's probably the right behavior
	// anyway if someone is trying to investigate and fix the GDB bug.
	cmd.Cancel = func() error {
		t.Logf("GDB command timed out after %v: %v", time.Since(start), cmd)
		return cmd.Process.Kill()
	}

	got, err := cmd.CombinedOutput()
	t.Logf("gdb output:\n%s", got)
	if err != nil {
		switch {
		case bytes.Contains(got, []byte("internal-error: wait returned unexpected status 0x0")):
			// GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28551
			testenv.SkipFlaky(t, 43068)
		case bytes.Contains(got, []byte("Couldn't get registers: No such process.")),
			bytes.Contains(got, []byte("Unable to fetch general registers.: No such process.")),
			bytes.Contains(got, []byte("reading register pc (#64): No such process.")):
			// GDB bug: https://sourceware.org/bugzilla/show_bug.cgi?id=9086
			testenv.SkipFlaky(t, 50838)
		case bytes.Contains(got, []byte("waiting for new child: No child processes.")):
			// GDB bug: Sometimes it fails to wait for a clone child.
			testenv.SkipFlaky(t, 60553)
		case bytes.Contains(got, []byte(" exited normally]\n")):
			// GDB bug: Sometimes the inferior exits fine,
			// but then GDB hangs.
			testenv.SkipFlaky(t, 37405)
		}
		t.Fatalf("gdb exited with error: %v", err)
	}

	// Check that the backtrace matches the source code.
	bt := []string{
		"eee",
		"ddd",
		"ccc",
		"bbb",
		"aaa",
		"main",
	}
	for i, name := range bt {
		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
		re := regexp.MustCompile(s)
		if found := re.Find(got) != nil; !found {
			t.Fatalf("could not find '%v' in backtrace", s)
		}
	}
}

const autotmpTypeSource = `
package main

type astruct struct {
	a, b int
}

func main() {
	var iface interface{} = map[string]astruct{}
	var iface2 interface{} = []astruct{}
	println(iface, iface2)
}
`

// TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
// See bug #17830.
func TestGdbAutotmpTypes(t *testing.T) {
	checkGdbEnvironment(t)
	t.Parallel()
	checkGdbVersion(t)

	if runtime.GOOS == "aix" && testing.Short() {
		t.Skip("TestGdbAutotmpTypes is too slow on aix/ppc64")
	}

	dir := t.TempDir()

	// Build the source code.
	src := filepath.Join(dir, "main.go")
	err := os.WriteFile(src, []byte(autotmpTypeSource), 0644)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
	cmd.Dir = dir
	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
	if err != nil {
		t.Fatalf("building source %v\n%s", err, out)
	}

	// Execute gdb commands.
	args := []string{"-nx", "-batch",
		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
		"-ex", "set startup-with-shell off",
		// Some gdb may set scheduling-locking as "step" by default. This prevents background tasks
		// (e.g GC) from completing which may result in a hang when executing the step command.
		// See #49852.
		"-ex", "set scheduler-locking off",
		"-ex", "break main.main",
		"-ex", "run",
		"-ex", "step",
		"-ex", "info types astruct",
		filepath.Join(dir, "a.exe"),
	}
	gdbArgsFixup(args)
	got, err := exec.Command("gdb", args...).CombinedOutput()
	t.Logf("gdb output:\n%s", got)
	if err != nil {
		t.Fatalf("gdb exited with error: %v", err)
	}

	sgot := string(got)

	// Check that the backtrace matches the source code.
	types := []string{
		"[]main.astruct;",
		"bucket<string,main.astruct>;",
		"hash<string,main.astruct>;",
		"main.astruct;",
		"hash<string,main.astruct> * map[string]main.astruct;",
	}
	for _, name := range types {
		if !strings.Contains(sgot, name) {
			t.Fatalf("could not find %s in 'info typrs astruct' output", name)
		}
	}
}

const constsSource = `
package main

const aConstant int = 42
const largeConstant uint64 = ^uint64(0)
const minusOne int64 = -1

func main() {
	println("hello world")
}
`

func TestGdbConst(t *testing.T) {
	checkGdbEnvironment(t)
	t.Parallel()
	checkGdbVersion(t)

	dir := t.TempDir()

	// Build the source code.
	src := filepath.Join(dir, "main.go")
	err := os.WriteFile(src, []byte(constsSource), 0644)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe", "main.go")
	cmd.Dir = dir
	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
	if err != nil {
		t.Fatalf("building source %v\n%s", err, out)
	}

	// Execute gdb commands.
	args := []string{"-nx", "-batch",
		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
		"-ex", "set startup-with-shell off",
		"-ex", "break main.main",
		"-ex", "run",
		"-ex", "print main.aConstant",
		"-ex", "print main.largeConstant",
		"-ex", "print main.minusOne",
		"-ex", "print 'runtime.mSpanInUse'",
		"-ex", "print 'runtime._PageSize'",
		filepath.Join(dir, "a.exe"),
	}
	gdbArgsFixup(args)
	got, err := exec.Command("gdb", args...).CombinedOutput()
	t.Logf("gdb output:\n%s", got)
	if err != nil {
		t.Fatalf("gdb exited with error: %v", err)
	}

	sgot := strings.ReplaceAll(string(got), "\r\n", "\n")

	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
		t.Fatalf("output mismatch")
	}
}

const panicSource = `
package main

import "runtime/debug"

func main() {
	debug.SetTraceback("crash")
	crash()
}

func crash() {
	panic("panic!")
}
`

// TestGdbPanic tests that gdb can unwind the stack correctly
// from SIGABRTs from Go panics.
func TestGdbPanic(t *testing.T) {
	checkGdbEnvironment(t)
	t.Parallel()
	checkGdbVersion(t)

	if runtime.GOOS == "windows" {
		t.Skip("no signals on windows")
	}

	dir := t.TempDir()

	// Build the source code.
	src := filepath.Join(dir, "main.go")
	err := os.WriteFile(src, []byte(panicSource), 0644)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
	cmd.Dir = dir
	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
	if err != nil {
		t.Fatalf("building source %v\n%s", err, out)
	}

	// Execute gdb commands.
	args := []string{"-nx", "-batch",
		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
		"-ex", "set startup-with-shell off",
		"-ex", "run",
		"-ex", "backtrace",
		filepath.Join(dir, "a.exe"),
	}
	gdbArgsFixup(args)
	got, err := exec.Command("gdb", args...).CombinedOutput()
	t.Logf("gdb output:\n%s", got)
	if err != nil {
		t.Fatalf("gdb exited with error: %v", err)
	}

	// Check that the backtrace matches the source code.
	bt := []string{
		`crash`,
		`main`,
	}
	for _, name := range bt {
		s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
		re := regexp.MustCompile(s)
		if found := re.Find(got) != nil; !found {
			t.Fatalf("could not find '%v' in backtrace", s)
		}
	}
}

const InfCallstackSource = `
package main
import "C"
import "time"

func loop() {
        for i := 0; i < 1000; i++ {
                time.Sleep(time.Millisecond*5)
        }
}

func main() {
        go loop()
        time.Sleep(time.Second * 1)
}
`

// TestGdbInfCallstack tests that gdb can unwind the callstack of cgo programs
// on arm64 platforms without endless frames of function 'crossfunc1'.
// https://golang.org/issue/37238
func TestGdbInfCallstack(t *testing.T) {
	checkGdbEnvironment(t)

	testenv.MustHaveCGO(t)
	if runtime.GOARCH != "arm64" {
		t.Skip("skipping infinite callstack test on non-arm64 arches")
	}

	t.Parallel()
	checkGdbVersion(t)

	dir := t.TempDir()

	// Build the source code.
	src := filepath.Join(dir, "main.go")
	err := os.WriteFile(src, []byte(InfCallstackSource), 0644)
	if err != nil {
		t.Fatalf("failed to create file: %v", err)
	}
	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
	cmd.Dir = dir
	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
	if err != nil {
		t.Fatalf("building source %v\n%s", err, out)
	}

	// Execute gdb commands.
	// 'setg_gcc' is the first point where we can reproduce the issue with just one 'run' command.
	args := []string{"-nx", "-batch",
		"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
		"-ex", "set startup-with-shell off",
		"-ex", "break setg_gcc",
		"-ex", "run",
		"-ex", "backtrace 3",
		"-ex", "disable 1",
		"-ex", "continue",
		filepath.Join(dir, "a.exe"),
	}
	gdbArgsFixup(args)
	got, err := exec.Command("gdb", args...).CombinedOutput()
	t.Logf("gdb output:\n%s", got)
	if err != nil {
		t.Fatalf("gdb exited with error: %v", err)
	}

	// Check that the backtrace matches
	// We check the 3 inner most frames only as they are present certainly, according to gcc_<OS>_arm64.c
	bt := []string{
		`setg_gcc`,
		`crosscall1`,
		`threadentry`,
	}
	for i, name := range bt {
		s := fmt.Sprintf("#%v.*%v", i, name)
		re := regexp.MustCompile(s)
		if found := re.Find(got) != nil; !found {
			t.Fatalf("could not find '%v' in backtrace", s)
		}
	}
}
b IDATxytVսϓ22 A@IR :hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-EIENT ;@xT.i%-X}SvS5.r/UHz^_$-W"w)Ɗ/@Z &IoX P$K}JzX:;` &, ŋui,e6mX ԵrKb1ԗ)DADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADA݀!I*]R;I2$eZ#ORZSrr6mteffu*((Pu'v{DIߔ4^pIm'77WEEE;vƎ4-$]'RI{\I&G :IHJ DWBB=\WR޽m o$K(V9ABB.}jѢv`^?IOȅ} ڶmG}T#FJ`56$-ھ}FI&v;0(h;Б38CӧOWf!;A i:F_m9s&|q%=#wZprrrla A &P\\СC[A#! {olF} `E2}MK/vV)i{4BffV\|ۭX`b@kɶ@%i$K z5zhmX[IXZ` 'b%$r5M4º/l ԃߖxhʔ)[@=} K6IM}^5k㏷݆z ΗÿO:gdGBmyT/@+Vɶ纽z񕏵l.y޴it뭷zV0[Y^>Wsqs}\/@$(T7f.InݺiR$푔n.~?H))\ZRW'Mo~v Ov6oԃxz! S,&xm/yɞԟ?'uaSѽb,8GלKboi&3t7Y,)JJ c[nzӳdE&KsZLӄ I?@&%ӟ۶mSMMњ0iؐSZ,|J+N ~,0A0!5%Q-YQQa3}$_vVrf9f?S8`zDADADADADADADADADAdqP,تmMmg1V?rSI꒟]u|l RCyEf٢9 jURbztѰ!m5~tGj2DhG*{H9)꒟ר3:(+3\?/;TUݭʴ~S6lڧUJ*i$d(#=Yݺd{,p|3B))q:vN0Y.jkק6;SɶVzHJJЀ-utѹսk>QUU\޲~]fFnK?&ߡ5b=z9)^|u_k-[y%ZNU6 7Mi:]ۦtk[n X(e6Bb."8cۭ|~teuuw|ήI-5"~Uk;ZicEmN/:]M> cQ^uiƞ??Ңpc#TUU3UakNwA`:Y_V-8.KKfRitv޲* 9S6ֿj,ՃNOMߤ]z^fOh|<>@Å5 _/Iu?{SY4hK/2]4%it5q]GGe2%iR| W&f*^]??vq[LgE_3f}Fxu~}qd-ږFxu~I N>\;͗O֊:̗WJ@BhW=y|GgwܷH_NY?)Tdi'?խwhlmQi !SUUsw4kӺe4rfxu-[nHtMFj}H_u~w>)oV}(T'ebʒv3_[+vn@Ȭ\S}ot}w=kHFnxg S 0eޢm~l}uqZfFoZuuEg `zt~? b;t%>WTkķh[2eG8LIWx,^\thrl^Ϊ{=dž<}qV@ ⠨Wy^LF_>0UkDuʫuCs$)Iv:IK;6ֲ4{^6եm+l3>݆uM 9u?>Zc }g~qhKwڭeFMM~pМuqǿz6Tb@8@Y|jx](^]gf}M"tG -w.@vOqh~/HII`S[l.6nØXL9vUcOoB\xoǤ'T&IǍQw_wpv[kmO{w~>#=P1Pɞa-we:iǏlHo׈꒟f9SzH?+shk%Fs:qVhqY`jvO'ρ?PyX3lх]˾uV{ݞ]1,MzYNW~̈́ joYn}ȚF߾׮mS]F z+EDxm/d{F{-W-4wY듏:??_gPf ^3ecg ҵs8R2מz@TANGj)}CNi/R~}c:5{!ZHӋӾ6}T]G]7W6^n 9*,YqOZj:P?Q DFL|?-^.Ɵ7}fFh׶xe2Pscz1&5\cn[=Vn[ĶE鎀uˌd3GII k;lNmشOuuRVfBE]ۣeӶu :X-[(er4~LHi6:Ѻ@ԅrST0trk%$Č0ez" *z"T/X9|8.C5Feg}CQ%͞ˣJvL/?j^h&9xF`њZ(&yF&Iݻfg#W;3^{Wo^4'vV[[K';+mӍִ]AC@W?1^{එyh +^]fm~iԵ]AB@WTk̏t uR?l.OIHiYyԶ]Aˀ7c:q}ힽaf6Z~қm(+sK4{^6}T*UUu]n.:kx{:2 _m=sAߤU@?Z-Vކеz왍Nэ{|5 pڶn b p-@sPg]0G7fy-M{GCF'%{4`=$-Ge\ eU:m+Zt'WjO!OAF@ik&t݆ϥ_ e}=]"Wz_.͜E3leWFih|t-wZۍ-uw=6YN{6|} |*={Ѽn.S.z1zjۻTH]흾 DuDvmvK.`V]yY~sI@t?/ϓ. m&["+P?MzovVЫG3-GRR[(!!\_,^%?v@ҵő m`Y)tem8GMx.))A]Y i`ViW`?^~!S#^+ѽGZj?Vģ0.))A꨷lzL*]OXrY`DBBLOj{-MH'ii-ϰ ok7^ )쭡b]UXSְmռY|5*cֽk0B7镹%ڽP#8nȎq}mJr23_>lE5$iwui+ H~F`IjƵ@q \ @#qG0".0" l`„.0! ,AQHN6qzkKJ#o;`Xv2>,tێJJ7Z/*A .@fفjMzkg @TvZH3Zxu6Ra'%O?/dQ5xYkU]Rֽkق@DaS^RSּ5|BeHNN͘p HvcYcC5:y #`οb;z2.!kr}gUWkyZn=f Pvsn3p~;4p˚=ē~NmI] ¾ 0lH[_L hsh_ғߤc_њec)g7VIZ5yrgk̞W#IjӪv>՞y睝M8[|]\շ8M6%|@PZڨI-m>=k='aiRo-x?>Q.}`Ȏ:Wsmu u > .@,&;+!!˱tﭧDQwRW\vF\~Q7>spYw$%A~;~}6¾ g&if_=j,v+UL1(tWake:@Ș>j$Gq2t7S?vL|]u/ .(0E6Mk6hiۺzښOrifޱxm/Gx> Lal%%~{lBsR4*}{0Z/tNIɚpV^#Lf:u@k#RSu =S^ZyuR/.@n&΃z~B=0eg뺆#,Þ[B/?H uUf7y Wy}Bwegל`Wh(||`l`.;Ws?V@"c:iɍL֯PGv6zctM̠':wuW;d=;EveD}9J@B(0iհ bvP1{\P&G7D޴Iy_$-Qjm~Yrr&]CDv%bh|Yzni_ˆR;kg}nJOIIwyuL}{ЌNj}:+3Y?:WJ/N+Rzd=hb;dj͒suݔ@NKMԄ jqzC5@y°hL m;*5ezᕏ=ep XL n?מ:r`۵tŤZ|1v`V뽧_csج'ߤ%oTuumk%%%h)uy]Nk[n 'b2 l.=͜E%gf$[c;s:V-͞WߤWh-j7]4=F-X]>ZLSi[Y*We;Zan(ӇW|e(HNNP5[= r4tP &0<pc#`vTNV GFqvTi*Tyam$ߏWyE*VJKMTfFw>'$-ؽ.Ho.8c"@DADADADADADADADADA~j*֘,N;Pi3599h=goضLgiJ5փy~}&Zd9p֚ e:|hL``b/d9p? fgg+%%hMgXosج, ΩOl0Zh=xdjLmhݻoO[g_l,8a]٭+ӧ0$I]c]:粹:Teꢢ"5a^Kgh,&= =՟^߶“ߢE ܹS J}I%:8 IDAT~,9/ʃPW'Mo}zNƍ쨓zPbNZ~^z=4mswg;5 Y~SVMRXUյڱRf?s:w ;6H:ºi5-maM&O3;1IKeamZh͛7+##v+c ~u~ca]GnF'ټL~PPPbn voC4R,ӟgg %hq}@#M4IÇ Oy^xMZx ) yOw@HkN˖-Sǎmb]X@n+i͖!++K3gd\$mt$^YfJ\8PRF)77Wא!Cl$i:@@_oG I{$# 8磌ŋ91A (Im7֭>}ߴJq7ޗt^ -[ԩSj*}%]&' -ɓ'ꫯVzzvB#;a 7@GxI{j޼ƌ.LÇWBB7`O"I$/@R @eee@۷>}0,ɒ2$53Xs|cS~rpTYYY} kHc %&k.], @ADADADADADADADADA@lT<%''*Lo^={رc5h %$+CnܸQ3fҥK}vUVVs9G R,_{xˇ3o߾;TTTd}馛]uuuG~iԩ@4bnvmvfϞ /Peeeq}}za I~,誫{UWW뮻}_~YƍSMMMYχ֝waw\ďcxꩧtEƍկ_?۷5@u?1kNׯWzz/wy>}zj3 k(ٺuq_Zvf̘:~ ABQ&r|!%KҥKgԞ={<_X-z !CyFUUz~ ABQIIIjݺW$UXXDٳZ~ ABQƍecW$<(~<RSSvZujjjԧOZQu@4 8m&&&jԩg$ď1h ͟?_{768@g =@`)))5o6m3)ѣƌJ;wҿUTT /KZR{~a=@0o<*狔iFɶ[ˎ;T]]OX@?K.ۈxN pppppppppppppppppPfl߾] ,{ァk۶mڿo5BTӦMӴiӴ|r DB2e|An!Dy'tkΝ[A $***t5' "!駟oaDnΝ:t֭[gDШQ06qD;@ x M6v(PiizmZ4ew"@̴ixf [~-Fٱc&IZ2|n!?$@{[HTɏ#@hȎI# _m(F /6Z3z'\r,r!;w2Z3j=~GY7"I$iI.p_"?pN`y DD?: _  Gÿab7J !Bx@0 Bo cG@`1C[@0G @`0C_u V1 aCX>W ` | `!<S `"<. `#c`?cAC4 ?c p#~@0?:08&_MQ1J h#?/`7;I  q 7a wQ A 1 Hp !#<8/#@1Ul7=S=K.4Z?E_$i@!1!E4?`P_  @Bă10#: "aU,xbFY1 [n|n #'vEH:`xb #vD4Y hi.i&EΖv#O H4IŶ}:Ikh @tZRF#(tXҙzZ ?I3l7q@õ|ۍ1,GpuY Ꮿ@hJv#xxk$ v#9 5 }_$c S#=+"K{F*m7`#%H:NRSp6I?sIՖ{Ap$I$I:QRv2$Z @UJ*$]<FO4IENDB`