mirror of
https://github.com/golang/go
synced 2024-11-19 13:04:45 -07:00
b1d1ec9183
CL 93658 moved stack trace printing inside a systemstack call to sidestep complexity in case the runtime is in a inconsistent state. Unfortunately, debuggers generating backtraces for a Go panic will be confused and come up with a technical correct but useless stack. This CL moves just the crash performing - typically a SIGABRT signal - outside the systemstack call to improve backtraces. Unfortunately, the crash function now needs to be marked nosplit and that triggers the no split stackoverflow check. To work around that, split fatalpanic in two: fatalthrow for runtime.throw and fatalpanic for runtime.gopanic. Only Go panics really needs crashes on the right stack and there is enough stack for gopanic. Example program: package main import "runtime/debug" func main() { debug.SetTraceback("crash") crash() } func crash() { panic("panic!") } Before: (lldb) bt * thread #1, name = 'simple', stop reason = signal SIGABRT * frame #0: 0x000000000044ffe4 simple`runtime.raise at <autogenerated>:1 frame #1: 0x0000000000438cfb simple`runtime.dieFromSignal(sig=<unavailable>) at signal_unix.go:424 frame #2: 0x0000000000438ec9 simple`runtime.crash at signal_unix.go:525 frame #3: 0x00000000004268f5 simple`runtime.dopanic_m(gp=<unavailable>, pc=<unavailable>, sp=<unavailable>) at panic.go:758 frame #4: 0x000000000044bead simple`runtime.fatalpanic.func1 at panic.go:657 frame #5: 0x000000000044d066 simple`runtime.systemstack at <autogenerated>:1 frame #6: 0x000000000042a980 simple at proc.go:1094 frame #7: 0x0000000000438ec9 simple`runtime.crash at signal_unix.go:525 frame #8: 0x00000000004268f5 simple`runtime.dopanic_m(gp=<unavailable>, pc=<unavailable>, sp=<unavailable>) at panic.go:758 frame #9: 0x000000000044bead simple`runtime.fatalpanic.func1 at panic.go:657 frame #10: 0x000000000044d066 simple`runtime.systemstack at <autogenerated>:1 frame #11: 0x000000000042a980 simple at proc.go:1094 frame #12: 0x00000000004268f5 simple`runtime.dopanic_m(gp=<unavailable>, pc=<unavailable>, sp=<unavailable>) at panic.go:758 frame #13: 0x000000000044bead simple`runtime.fatalpanic.func1 at panic.go:657 frame #14: 0x000000000044d066 simple`runtime.systemstack at <autogenerated>:1 frame #15: 0x000000000042a980 simple at proc.go:1094 frame #16: 0x000000000044bead simple`runtime.fatalpanic.func1 at panic.go:657 frame #17: 0x000000000044d066 simple`runtime.systemstack at <autogenerated>:1 After: (lldb) bt * thread #7, stop reason = signal SIGABRT * frame #0: 0x0000000000450024 simple`runtime.raise at <autogenerated>:1 frame #1: 0x0000000000438d1b simple`runtime.dieFromSignal(sig=<unavailable>) at signal_unix.go:424 frame #2: 0x0000000000438ee9 simple`runtime.crash at signal_unix.go:525 frame #3: 0x00000000004264e3 simple`runtime.fatalpanic(msgs=<unavailable>) at panic.go:664 frame #4: 0x0000000000425f1b simple`runtime.gopanic(e=<unavailable>) at panic.go:537 frame #5: 0x0000000000470c62 simple`main.crash at simple.go:11 frame #6: 0x0000000000470c00 simple`main.main at simple.go:6 frame #7: 0x0000000000427be7 simple`runtime.main at proc.go:198 frame #8: 0x000000000044ef91 simple`runtime.goexit at <autogenerated>:1 Updates #22716 Change-Id: Ib5fa35c13662c1dac2f1eac8b59c4a5824b98d92 Reviewed-on: https://go-review.googlesource.com/110065 Run-TryBot: Elias Naur <elias.naur@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
547 lines
15 KiB
Go
547 lines
15 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")
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
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(panicSource), 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", "run",
|
|
"-ex", "backtrace",
|
|
filepath.Join(dir, "a.exe"),
|
|
}
|
|
got, _ := exec.Command("gdb", args...).CombinedOutput()
|
|
|
|
// 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.Errorf("could not find '%v' in backtrace", s)
|
|
t.Fatalf("gdb output:\n%v", string(got))
|
|
}
|
|
}
|
|
}
|