mirror of
https://github.com/golang/go
synced 2024-11-08 11:16:19 -07:00
cd682f3832
This ensures that gdb tests run on Windows by ignoring any line ending. Works with gdb 7.7, however with gdb 7.9 and 7.12 gets an error error: internal-error: buildsym_init: Assertion `free_pendings == NULL' failed. Updates #21380 Change-Id: I6a6e5b2a1b5efdca4dfce009fcb9c134c87497d6 Reviewed-on: https://go-review.googlesource.com/102419 TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
481 lines
13 KiB
Go
481 lines
13 KiB
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"
|
|
"fmt"
|
|
"go/build"
|
|
"internal/testenv"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
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 golang.org/issue/22893 and gnats.netbsd.org/52548")
|
|
case "linux":
|
|
if runtime.GOARCH == "ppc64" {
|
|
t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366")
|
|
}
|
|
}
|
|
if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != 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" && testenv.Builder() != "solaris-amd64-smartosbuildlet" {
|
|
t.Skip("skipping gdb python tests on solaris; see golang.org/issue/20821")
|
|
}
|
|
|
|
cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
|
|
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)
|
|
}
|
|
}
|
|
|
|
const helloSource = `
|
|
import "fmt"
|
|
import "runtime"
|
|
var gslice []string
|
|
func main() {
|
|
mapvar := make(map[string]string, 13)
|
|
mapvar["abc"] = "def"
|
|
mapvar["ghi"] = "jkl"
|
|
strvar := "abc"
|
|
ptrvar := &strvar
|
|
slicevar := make([]string, 0, 16)
|
|
slicevar = append(slicevar, mapvar["abc"])
|
|
fmt.Println("hi")
|
|
runtime.KeepAlive(ptrvar)
|
|
_ = ptrvar
|
|
gslice = slicevar
|
|
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 TestGdbPython(t *testing.T) {
|
|
testGdbPython(t, false)
|
|
}
|
|
|
|
func TestGdbPythonCgo(t *testing.T) {
|
|
if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" {
|
|
testenv.SkipFlaky(t, 18784)
|
|
}
|
|
testGdbPython(t, true)
|
|
}
|
|
|
|
func testGdbPython(t *testing.T, cgo bool) {
|
|
if cgo && !build.Default.CgoEnabled {
|
|
t.Skip("skipping because cgo is not enabled")
|
|
}
|
|
|
|
checkGdbEnvironment(t)
|
|
t.Parallel()
|
|
checkGdbVersion(t)
|
|
checkGdbPython(t)
|
|
|
|
dir, err := ioutil.TempDir("", "go-build")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
var buf bytes.Buffer
|
|
buf.WriteString("package main\n")
|
|
if cgo {
|
|
buf.WriteString(`import "C"` + "\n")
|
|
}
|
|
buf.WriteString(helloSource)
|
|
|
|
src := buf.Bytes()
|
|
|
|
err = ioutil.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")
|
|
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(runtime.GOROOT(), "src", "runtime"),
|
|
"-ex", "set startup-with-shell off",
|
|
"-ex", "info auto-load python-scripts",
|
|
"-ex", "set python print-stack full",
|
|
"-ex", "br fmt.Println",
|
|
"-ex", "run",
|
|
"-ex", "echo BEGIN info goroutines\n",
|
|
"-ex", "info goroutines",
|
|
"-ex", "echo END\n",
|
|
"-ex", "up", // up from fmt.Println to main
|
|
"-ex", "echo BEGIN print mapvar\n",
|
|
"-ex", "print mapvar",
|
|
"-ex", "echo END\n",
|
|
"-ex", "echo BEGIN print strvar\n",
|
|
"-ex", "print strvar",
|
|
"-ex", "echo END\n",
|
|
"-ex", "echo BEGIN info locals\n",
|
|
"-ex", "info locals",
|
|
"-ex", "echo END\n",
|
|
"-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack. TODO: fix that)
|
|
"-ex", "echo BEGIN goroutine 1 bt\n",
|
|
"-ex", "goroutine 1 bt",
|
|
"-ex", "echo END\n",
|
|
"-ex", "echo BEGIN goroutine 2 bt\n",
|
|
"-ex", "goroutine 2 bt",
|
|
"-ex", "echo END\n",
|
|
"-ex", "clear fmt.Println", // 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"),
|
|
}
|
|
got, _ := exec.Command("gdb", args...).CombinedOutput()
|
|
t.Logf("gdb output: %s\n", got)
|
|
|
|
firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
|
|
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", runtime.GOROOT())
|
|
}
|
|
|
|
_, 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(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
|
|
printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
|
|
if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
|
|
!printMapvarRe2.MatchString(bl) {
|
|
t.Fatalf("print mapvar failed: %s", bl)
|
|
}
|
|
|
|
strVarRe := regexp.MustCompile(`\Q = "abc"\E$`)
|
|
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.
|
|
|
|
infoLocalsRe := regexp.MustCompile(`slicevar *= *\[\]string *= *{"def"}`)
|
|
if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
|
|
t.Fatalf("info locals failed: %s", bl)
|
|
}
|
|
|
|
btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`)
|
|
if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
|
|
t.Fatalf("goroutine 1 bt failed: %s", bl)
|
|
}
|
|
|
|
btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`)
|
|
if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
|
|
t.Fatalf("goroutine 2 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)
|
|
}
|
|
|
|
checkGdbEnvironment(t)
|
|
t.Parallel()
|
|
checkGdbVersion(t)
|
|
|
|
dir, err := ioutil.TempDir("", "go-build")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
// Build the source code.
|
|
src := filepath.Join(dir, "main.go")
|
|
err = ioutil.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")
|
|
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(runtime.GOROOT(), "src", "runtime"),
|
|
"-ex", "set startup-with-shell off",
|
|
"-ex", "break main.eee",
|
|
"-ex", "run",
|
|
"-ex", "backtrace",
|
|
"-ex", "continue",
|
|
filepath.Join(dir, "a.exe"),
|
|
}
|
|
got, _ := exec.Command("gdb", args...).CombinedOutput()
|
|
|
|
// 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.Errorf("could not find '%v' in backtrace", s)
|
|
t.Fatalf("gdb output:\n%v", string(got))
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
dir, err := ioutil.TempDir("", "go-build")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
// Build the source code.
|
|
src := filepath.Join(dir, "main.go")
|
|
err = ioutil.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")
|
|
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(runtime.GOROOT(), "src", "runtime"),
|
|
"-ex", "set startup-with-shell off",
|
|
"-ex", "break main.main",
|
|
"-ex", "run",
|
|
"-ex", "step",
|
|
"-ex", "info types astruct",
|
|
filepath.Join(dir, "a.exe"),
|
|
}
|
|
got, _ := exec.Command("gdb", args...).CombinedOutput()
|
|
|
|
sgot := string(got)
|
|
|
|
// Check that the backtrace matches the source code.
|
|
types := []string{
|
|
"struct []main.astruct;",
|
|
"struct bucket<string,main.astruct>;",
|
|
"struct hash<string,main.astruct>;",
|
|
"struct main.astruct;",
|
|
"typedef struct hash<string,main.astruct> * map[string]main.astruct;",
|
|
}
|
|
for _, name := range types {
|
|
if !strings.Contains(sgot, name) {
|
|
t.Errorf("could not find %s in 'info typrs astruct' output", name)
|
|
t.Fatalf("gdb output:\n%v", sgot)
|
|
}
|
|
}
|
|
}
|
|
|
|
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, err := ioutil.TempDir("", "go-build")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
// Build the source code.
|
|
src := filepath.Join(dir, "main.go")
|
|
err = ioutil.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")
|
|
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(runtime.GOROOT(), "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"),
|
|
}
|
|
got, _ := exec.Command("gdb", args...).CombinedOutput()
|
|
|
|
sgot := strings.Replace(string(got), "\r\n", "\n", -1)
|
|
|
|
t.Logf("output %q", sgot)
|
|
|
|
if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
|
|
t.Fatalf("output mismatch")
|
|
}
|
|
}
|