Home | History | Annotate | Download | only in 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 	"go/build"
     11 	"internal/testenv"
     12 	"io/ioutil"
     13 	"os"
     14 	"os/exec"
     15 	"path/filepath"
     16 	"regexp"
     17 	"runtime"
     18 	"strconv"
     19 	"strings"
     20 	"testing"
     21 )
     22 
     23 func checkGdbEnvironment(t *testing.T) {
     24 	testenv.MustHaveGoBuild(t)
     25 	switch runtime.GOOS {
     26 	case "darwin":
     27 		t.Skip("gdb does not work on darwin")
     28 	case "netbsd":
     29 		t.Skip("gdb does not work with threads on NetBSD; see golang.org/issue/22893 and gnats.netbsd.org/52548")
     30 	case "linux":
     31 		if runtime.GOARCH == "ppc64" {
     32 			t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366")
     33 		}
     34 	}
     35 	if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
     36 		t.Skip("gdb test can fail with GOROOT_FINAL pending")
     37 	}
     38 }
     39 
     40 func checkGdbVersion(t *testing.T) {
     41 	// Issue 11214 reports various failures with older versions of gdb.
     42 	out, err := exec.Command("gdb", "--version").CombinedOutput()
     43 	if err != nil {
     44 		t.Skipf("skipping: error executing gdb: %v", err)
     45 	}
     46 	re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
     47 	matches := re.FindSubmatch(out)
     48 	if len(matches) < 3 {
     49 		t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
     50 	}
     51 	major, err1 := strconv.Atoi(string(matches[1]))
     52 	minor, err2 := strconv.Atoi(string(matches[2]))
     53 	if err1 != nil || err2 != nil {
     54 		t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
     55 	}
     56 	if major < 7 || (major == 7 && minor < 7) {
     57 		t.Skipf("skipping: gdb version %d.%d too old", major, minor)
     58 	}
     59 	t.Logf("gdb version %d.%d", major, minor)
     60 }
     61 
     62 func checkGdbPython(t *testing.T) {
     63 	if runtime.GOOS == "solaris" && testenv.Builder() != "solaris-amd64-smartosbuildlet" {
     64 		t.Skip("skipping gdb python tests on solaris; see golang.org/issue/20821")
     65 	}
     66 
     67 	cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
     68 	out, err := cmd.CombinedOutput()
     69 
     70 	if err != nil {
     71 		t.Skipf("skipping due to issue running gdb: %v", err)
     72 	}
     73 	if string(out) != "go gdb python support\n" {
     74 		t.Skipf("skipping due to lack of python gdb support: %s", out)
     75 	}
     76 }
     77 
     78 const helloSource = `
     79 import "fmt"
     80 import "runtime"
     81 var gslice []string
     82 func main() {
     83 	mapvar := make(map[string]string, 13)
     84 	mapvar["abc"] = "def"
     85 	mapvar["ghi"] = "jkl"
     86 	strvar := "abc"
     87 	ptrvar := &strvar
     88 	slicevar := make([]string, 0, 16)
     89 	slicevar = append(slicevar, mapvar["abc"])
     90 	fmt.Println("hi") // line 13
     91 	runtime.KeepAlive(ptrvar)
     92 	gslice = slicevar
     93 	runtime.KeepAlive(mapvar)
     94 }
     95 `
     96 
     97 func TestGdbPython(t *testing.T) {
     98 	testGdbPython(t, false)
     99 }
    100 
    101 func TestGdbPythonCgo(t *testing.T) {
    102 	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" {
    103 		testenv.SkipFlaky(t, 18784)
    104 	}
    105 	testGdbPython(t, true)
    106 }
    107 
    108 func testGdbPython(t *testing.T, cgo bool) {
    109 	if cgo && !build.Default.CgoEnabled {
    110 		t.Skip("skipping because cgo is not enabled")
    111 	}
    112 
    113 	checkGdbEnvironment(t)
    114 	t.Parallel()
    115 	checkGdbVersion(t)
    116 	checkGdbPython(t)
    117 
    118 	dir, err := ioutil.TempDir("", "go-build")
    119 	if err != nil {
    120 		t.Fatalf("failed to create temp directory: %v", err)
    121 	}
    122 	defer os.RemoveAll(dir)
    123 
    124 	var buf bytes.Buffer
    125 	buf.WriteString("package main\n")
    126 	if cgo {
    127 		buf.WriteString(`import "C"` + "\n")
    128 	}
    129 	buf.WriteString(helloSource)
    130 
    131 	src := filepath.Join(dir, "main.go")
    132 	err = ioutil.WriteFile(src, buf.Bytes(), 0644)
    133 	if err != nil {
    134 		t.Fatalf("failed to create file: %v", err)
    135 	}
    136 
    137 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
    138 	cmd.Dir = dir
    139 	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    140 	if err != nil {
    141 		t.Fatalf("building source %v\n%s", err, out)
    142 	}
    143 
    144 	args := []string{"-nx", "-q", "--batch", "-iex",
    145 		fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()),
    146 		"-ex", "set startup-with-shell off",
    147 		"-ex", "info auto-load python-scripts",
    148 		"-ex", "set python print-stack full",
    149 		"-ex", "br fmt.Println",
    150 		"-ex", "run",
    151 		"-ex", "echo BEGIN info goroutines\n",
    152 		"-ex", "info goroutines",
    153 		"-ex", "echo END\n",
    154 		"-ex", "up", // up from fmt.Println to main
    155 		"-ex", "echo BEGIN print mapvar\n",
    156 		"-ex", "print mapvar",
    157 		"-ex", "echo END\n",
    158 		"-ex", "echo BEGIN print strvar\n",
    159 		"-ex", "print strvar",
    160 		"-ex", "echo END\n",
    161 		"-ex", "echo BEGIN info locals\n",
    162 		"-ex", "info locals",
    163 		"-ex", "echo END\n",
    164 		"-ex", "down", // back to fmt.Println (goroutine 2 below only works at bottom of stack.  TODO: fix that)
    165 		"-ex", "echo BEGIN goroutine 1 bt\n",
    166 		"-ex", "goroutine 1 bt",
    167 		"-ex", "echo END\n",
    168 		"-ex", "echo BEGIN goroutine 2 bt\n",
    169 		"-ex", "goroutine 2 bt",
    170 		"-ex", "echo END\n",
    171 		filepath.Join(dir, "a.exe"),
    172 	}
    173 	got, _ := exec.Command("gdb", args...).CombinedOutput()
    174 
    175 	firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
    176 	if string(firstLine) != "Loading Go Runtime support." {
    177 		// This can happen when using all.bash with
    178 		// GOROOT_FINAL set, because the tests are run before
    179 		// the final installation of the files.
    180 		cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
    181 		cmd.Env = []string{}
    182 		out, err := cmd.CombinedOutput()
    183 		if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
    184 			t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT())
    185 		}
    186 
    187 		_, file, _, _ := runtime.Caller(1)
    188 
    189 		t.Logf("package testing source file: %s", file)
    190 		t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
    191 	}
    192 
    193 	// Extract named BEGIN...END blocks from output
    194 	partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
    195 	blocks := map[string]string{}
    196 	for _, subs := range partRe.FindAllSubmatch(got, -1) {
    197 		blocks[string(subs[1])] = string(subs[2])
    198 	}
    199 
    200 	infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
    201 	if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
    202 		t.Fatalf("info goroutines failed: %s", bl)
    203 	}
    204 
    205 	printMapvarRe1 := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
    206 	printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
    207 	if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
    208 		!printMapvarRe2.MatchString(bl) {
    209 		t.Fatalf("print mapvar failed: %s", bl)
    210 	}
    211 
    212 	strVarRe := regexp.MustCompile(`\Q = "abc"\E$`)
    213 	if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
    214 		t.Fatalf("print strvar failed: %s", bl)
    215 	}
    216 
    217 	// Issue 16338: ssa decompose phase can split a structure into
    218 	// a collection of scalar vars holding the fields. In such cases
    219 	// the DWARF variable location expression should be of the
    220 	// form "var.field" and not just "field".
    221 	infoLocalsRe := regexp.MustCompile(`.*\sslicevar.cap = `)
    222 	if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
    223 		t.Fatalf("info locals failed: %s", bl)
    224 	}
    225 
    226 	btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`)
    227 	if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
    228 		t.Fatalf("goroutine 1 bt failed: %s", bl)
    229 	}
    230 
    231 	btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`)
    232 	if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
    233 		t.Fatalf("goroutine 2 bt failed: %s", bl)
    234 	}
    235 }
    236 
    237 const backtraceSource = `
    238 package main
    239 
    240 //go:noinline
    241 func aaa() bool { return bbb() }
    242 
    243 //go:noinline
    244 func bbb() bool { return ccc() }
    245 
    246 //go:noinline
    247 func ccc() bool { return ddd() }
    248 
    249 //go:noinline
    250 func ddd() bool { return f() }
    251 
    252 //go:noinline
    253 func eee() bool { return true }
    254 
    255 var f = eee
    256 
    257 func main() {
    258 	_ = aaa()
    259 }
    260 `
    261 
    262 // TestGdbBacktrace tests that gdb can unwind the stack correctly
    263 // using only the DWARF debug info.
    264 func TestGdbBacktrace(t *testing.T) {
    265 	if runtime.GOOS == "netbsd" {
    266 		testenv.SkipFlaky(t, 15603)
    267 	}
    268 
    269 	checkGdbEnvironment(t)
    270 	t.Parallel()
    271 	checkGdbVersion(t)
    272 
    273 	dir, err := ioutil.TempDir("", "go-build")
    274 	if err != nil {
    275 		t.Fatalf("failed to create temp directory: %v", err)
    276 	}
    277 	defer os.RemoveAll(dir)
    278 
    279 	// Build the source code.
    280 	src := filepath.Join(dir, "main.go")
    281 	err = ioutil.WriteFile(src, []byte(backtraceSource), 0644)
    282 	if err != nil {
    283 		t.Fatalf("failed to create file: %v", err)
    284 	}
    285 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
    286 	cmd.Dir = dir
    287 	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    288 	if err != nil {
    289 		t.Fatalf("building source %v\n%s", err, out)
    290 	}
    291 
    292 	// Execute gdb commands.
    293 	args := []string{"-nx", "-batch",
    294 		"-ex", "set startup-with-shell off",
    295 		"-ex", "break main.eee",
    296 		"-ex", "run",
    297 		"-ex", "backtrace",
    298 		"-ex", "continue",
    299 		filepath.Join(dir, "a.exe"),
    300 	}
    301 	got, _ := exec.Command("gdb", args...).CombinedOutput()
    302 
    303 	// Check that the backtrace matches the source code.
    304 	bt := []string{
    305 		"eee",
    306 		"ddd",
    307 		"ccc",
    308 		"bbb",
    309 		"aaa",
    310 		"main",
    311 	}
    312 	for i, name := range bt {
    313 		s := fmt.Sprintf("#%v.*main\\.%v", i, name)
    314 		re := regexp.MustCompile(s)
    315 		if found := re.Find(got) != nil; !found {
    316 			t.Errorf("could not find '%v' in backtrace", s)
    317 			t.Fatalf("gdb output:\n%v", string(got))
    318 		}
    319 	}
    320 }
    321 
    322 const autotmpTypeSource = `
    323 package main
    324 
    325 type astruct struct {
    326 	a, b int
    327 }
    328 
    329 func main() {
    330 	var iface interface{} = map[string]astruct{}
    331 	var iface2 interface{} = []astruct{}
    332 	println(iface, iface2)
    333 }
    334 `
    335 
    336 // TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
    337 // See bug #17830.
    338 func TestGdbAutotmpTypes(t *testing.T) {
    339 	checkGdbEnvironment(t)
    340 	t.Parallel()
    341 	checkGdbVersion(t)
    342 
    343 	dir, err := ioutil.TempDir("", "go-build")
    344 	if err != nil {
    345 		t.Fatalf("failed to create temp directory: %v", err)
    346 	}
    347 	defer os.RemoveAll(dir)
    348 
    349 	// Build the source code.
    350 	src := filepath.Join(dir, "main.go")
    351 	err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644)
    352 	if err != nil {
    353 		t.Fatalf("failed to create file: %v", err)
    354 	}
    355 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
    356 	cmd.Dir = dir
    357 	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    358 	if err != nil {
    359 		t.Fatalf("building source %v\n%s", err, out)
    360 	}
    361 
    362 	// Execute gdb commands.
    363 	args := []string{"-nx", "-batch",
    364 		"-ex", "set startup-with-shell off",
    365 		"-ex", "break main.main",
    366 		"-ex", "run",
    367 		"-ex", "step",
    368 		"-ex", "info types astruct",
    369 		filepath.Join(dir, "a.exe"),
    370 	}
    371 	got, _ := exec.Command("gdb", args...).CombinedOutput()
    372 
    373 	sgot := string(got)
    374 
    375 	// Check that the backtrace matches the source code.
    376 	types := []string{
    377 		"struct []main.astruct;",
    378 		"struct bucket<string,main.astruct>;",
    379 		"struct hash<string,main.astruct>;",
    380 		"struct main.astruct;",
    381 		"typedef struct hash<string,main.astruct> * map[string]main.astruct;",
    382 	}
    383 	for _, name := range types {
    384 		if !strings.Contains(sgot, name) {
    385 			t.Errorf("could not find %s in 'info typrs astruct' output", name)
    386 			t.Fatalf("gdb output:\n%v", sgot)
    387 		}
    388 	}
    389 }
    390 
    391 const constsSource = `
    392 package main
    393 
    394 const aConstant int = 42
    395 const largeConstant uint64 = ^uint64(0)
    396 const minusOne int64 = -1
    397 
    398 func main() {
    399 	println("hello world")
    400 }
    401 `
    402 
    403 func TestGdbConst(t *testing.T) {
    404 	checkGdbEnvironment(t)
    405 	t.Parallel()
    406 	checkGdbVersion(t)
    407 
    408 	dir, err := ioutil.TempDir("", "go-build")
    409 	if err != nil {
    410 		t.Fatalf("failed to create temp directory: %v", err)
    411 	}
    412 	defer os.RemoveAll(dir)
    413 
    414 	// Build the source code.
    415 	src := filepath.Join(dir, "main.go")
    416 	err = ioutil.WriteFile(src, []byte(constsSource), 0644)
    417 	if err != nil {
    418 		t.Fatalf("failed to create file: %v", err)
    419 	}
    420 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
    421 	cmd.Dir = dir
    422 	out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
    423 	if err != nil {
    424 		t.Fatalf("building source %v\n%s", err, out)
    425 	}
    426 
    427 	// Execute gdb commands.
    428 	args := []string{"-nx", "-batch",
    429 		"-ex", "set startup-with-shell off",
    430 		"-ex", "break main.main",
    431 		"-ex", "run",
    432 		"-ex", "print main.aConstant",
    433 		"-ex", "print main.largeConstant",
    434 		"-ex", "print main.minusOne",
    435 		"-ex", "print 'runtime._MSpanInUse'",
    436 		"-ex", "print 'runtime._PageSize'",
    437 		filepath.Join(dir, "a.exe"),
    438 	}
    439 	got, _ := exec.Command("gdb", args...).CombinedOutput()
    440 
    441 	sgot := strings.Replace(string(got), "\r\n", "\n", -1)
    442 
    443 	t.Logf("output %q", sgot)
    444 
    445 	if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
    446 		t.Fatalf("output mismatch")
    447 	}
    448 }
    449