1
0
mirror of https://github.com/golang/go synced 2024-11-19 13:04:45 -07:00
go/src/runtime/runtime-gdb_test.go
David Chase 1c24ffbf93 cmd/compile: turn on DWARF locations lists for ssa vars
This changes the default setting for -dwarflocationlists
from false to true, removes the flag from ssa/debug_test.go,
and updates runtime/runtime-gdb_test.go to match a change
in debugging output for composite variables.

Current benchmarks (perflock, -count 10)

benchstat -geomean before.log after.log
name        old time/op     new time/op     delta
Template        175ms ± 0%      182ms ± 1%   +3.68%  (p=0.000 n=8+9)
Unicode        82.0ms ± 2%     82.8ms ± 1%   +0.96%  (p=0.019 n=9+9)
GoTypes         590ms ± 1%      611ms ± 1%   +3.42%  (p=0.000 n=9+10)
Compiler        2.85s ± 0%      2.95s ± 1%   +3.60%  (p=0.000 n=9+10)
SSA             6.42s ± 1%      6.70s ± 1%   +4.31%  (p=0.000 n=10+9)
Flate           113ms ± 2%      117ms ± 1%   +3.11%  (p=0.000 n=10+9)
GoParser        140ms ± 1%      145ms ± 1%   +3.47%  (p=0.000 n=10+9)
Reflect         384ms ± 0%      398ms ± 1%   +3.56%  (p=0.000 n=8+9)
Tar             165ms ± 1%      171ms ± 1%   +3.33%  (p=0.000 n=9+9)
XML             207ms ± 2%      214ms ± 1%   +3.41%  (p=0.000 n=9+9)
StdCmd          11.8s ± 2%      12.4s ± 2%   +4.41%  (p=0.000 n=10+9)
[Geo mean]      489ms           506ms        +3.38%

name        old user-ns/op  new user-ns/op  delta
Template         247M ± 4%       254M ± 4%   +2.76%  (p=0.040 n=10+10)
Unicode          118M ±16%       121M ±11%     ~     (p=0.364 n=10+10)
GoTypes          805M ± 2%       824M ± 2%   +2.37%  (p=0.003 n=9+8)
Compiler        3.92G ± 2%      4.01G ± 2%   +2.20%  (p=0.001 n=9+9)
SSA             9.63G ± 4%     10.00G ± 2%   +3.81%  (p=0.000 n=10+9)
Flate            155M ±10%       154M ± 7%     ~     (p=0.718 n=9+10)
GoParser         184M ±11%       190M ± 7%     ~     (p=0.220 n=10+9)
Reflect          506M ± 4%       528M ± 2%   +4.27%  (p=0.000 n=10+10)
Tar              224M ± 4%       227M ± 5%     ~     (p=0.207 n=10+9)
XML              272M ± 7%       286M ± 4%   +5.23%  (p=0.010 n=10+9)
[Geo mean]       489M            502M        +2.76%

name        old text-bytes  new text-bytes  delta
HelloSize        672k ± 0%       672k ± 0%     ~     (all equal)
CmdGoSize       7.21M ± 0%      7.21M ± 0%     ~     (all equal)
[Geo mean]      2.20M           2.20M        +0.00%

name        old data-bytes  new data-bytes  delta
HelloSize       9.88k ± 0%      9.88k ± 0%     ~     (all equal)
CmdGoSize        248k ± 0%       248k ± 0%     ~     (all equal)
[Geo mean]      49.5k           49.5k        +0.00%

name        old bss-bytes   new bss-bytes   delta
HelloSize        125k ± 0%       125k ± 0%     ~     (all equal)
CmdGoSize        144k ± 0%       144k ± 0%     ~     (all equal)
[Geo mean]       135k            135k        +0.00%

name        old exe-bytes   new exe-bytes   delta
HelloSize       1.10M ± 0%      1.30M ± 0%  +17.82%  (p=0.000 n=10+10)
CmdGoSize       11.6M ± 0%      13.5M ± 0%  +16.90%  (p=0.000 n=10+10)
[Geo mean]      3.57M           4.19M       +17.36%

Change-Id: I250055813cadd25cebee8da1f9a7f995a6eae432
Reviewed-on: https://go-review.googlesource.com/100738
Reviewed-by: Heschi Kreinick <heschi@google.com>
2018-03-15 21:34:17 +00:00

478 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 string(out) != "go gdb python support\n" {
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",
fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()),
"-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",
"-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",
"-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",
"-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")
}
}