Source file
src/runtime/runtime-gdb_test.go
Documentation: runtime
1
2
3
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 "windows":
31 t.Skip("gdb tests fail on Windows: https://golang.org/issue/22687")
32 case "linux":
33 if runtime.GOARCH == "ppc64" {
34 t.Skip("skipping gdb tests on linux/ppc64; see golang.org/issue/17366")
35 }
36 if runtime.GOARCH == "mips" {
37 t.Skip("skipping gdb tests on linux/mips; see https://golang.org/issue/25939")
38 }
39 case "freebsd":
40 t.Skip("skipping gdb tests on FreeBSD; see https://golang.org/issue/29508")
41 }
42 if final := os.Getenv("GOROOT_FINAL"); final != "" && runtime.GOROOT() != final {
43 t.Skip("gdb test can fail with GOROOT_FINAL pending")
44 }
45 }
46
47 func checkGdbVersion(t *testing.T) {
48
49 out, err := exec.Command("gdb", "--version").CombinedOutput()
50 if err != nil {
51 t.Skipf("skipping: error executing gdb: %v", err)
52 }
53 re := regexp.MustCompile(`([0-9]+)\.([0-9]+)`)
54 matches := re.FindSubmatch(out)
55 if len(matches) < 3 {
56 t.Skipf("skipping: can't determine gdb version from\n%s\n", out)
57 }
58 major, err1 := strconv.Atoi(string(matches[1]))
59 minor, err2 := strconv.Atoi(string(matches[2]))
60 if err1 != nil || err2 != nil {
61 t.Skipf("skipping: can't determine gdb version: %v, %v", err1, err2)
62 }
63 if major < 7 || (major == 7 && minor < 7) {
64 t.Skipf("skipping: gdb version %d.%d too old", major, minor)
65 }
66 t.Logf("gdb version %d.%d", major, minor)
67 }
68
69 func checkGdbPython(t *testing.T) {
70 if runtime.GOOS == "solaris" && testenv.Builder() != "solaris-amd64-smartosbuildlet" {
71 t.Skip("skipping gdb python tests on solaris; see golang.org/issue/20821")
72 }
73
74 cmd := exec.Command("gdb", "-nx", "-q", "--batch", "-iex", "python import sys; print('go gdb python support')")
75 out, err := cmd.CombinedOutput()
76
77 if err != nil {
78 t.Skipf("skipping due to issue running gdb: %v", err)
79 }
80 if strings.TrimSpace(string(out)) != "go gdb python support" {
81 t.Skipf("skipping due to lack of python gdb support: %s", out)
82 }
83 }
84
85 const helloSource = `
86 import "fmt"
87 import "runtime"
88 var gslice []string
89 func main() {
90 mapvar := make(map[string]string, 13)
91 mapvar["abc"] = "def"
92 mapvar["ghi"] = "jkl"
93 strvar := "abc"
94 ptrvar := &strvar
95 slicevar := make([]string, 0, 16)
96 slicevar = append(slicevar, mapvar["abc"])
97 fmt.Println("hi")
98 runtime.KeepAlive(ptrvar)
99 _ = ptrvar
100 gslice = slicevar
101 runtime.KeepAlive(mapvar)
102 } // END_OF_PROGRAM
103 `
104
105 func lastLine(src []byte) int {
106 eop := []byte("END_OF_PROGRAM")
107 for i, l := range bytes.Split(src, []byte("\n")) {
108 if bytes.Contains(l, eop) {
109 return i
110 }
111 }
112 return 0
113 }
114
115 func TestGdbPython(t *testing.T) {
116 testGdbPython(t, false)
117 }
118
119 func TestGdbPythonCgo(t *testing.T) {
120 if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" || runtime.GOARCH == "mips64" {
121 testenv.SkipFlaky(t, 18784)
122 }
123 testGdbPython(t, true)
124 }
125
126 func testGdbPython(t *testing.T, cgo bool) {
127 if cgo && !build.Default.CgoEnabled {
128 t.Skip("skipping because cgo is not enabled")
129 }
130
131 checkGdbEnvironment(t)
132 t.Parallel()
133 checkGdbVersion(t)
134 checkGdbPython(t)
135
136 dir, err := ioutil.TempDir("", "go-build")
137 if err != nil {
138 t.Fatalf("failed to create temp directory: %v", err)
139 }
140 defer os.RemoveAll(dir)
141
142 var buf bytes.Buffer
143 buf.WriteString("package main\n")
144 if cgo {
145 buf.WriteString(`import "C"` + "\n")
146 }
147 buf.WriteString(helloSource)
148
149 src := buf.Bytes()
150
151 err = ioutil.WriteFile(filepath.Join(dir, "main.go"), src, 0644)
152 if err != nil {
153 t.Fatalf("failed to create file: %v", err)
154 }
155 nLines := lastLine(src)
156
157 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
158 cmd.Dir = dir
159 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
160 if err != nil {
161 t.Fatalf("building source %v\n%s", err, out)
162 }
163
164 args := []string{"-nx", "-q", "--batch",
165 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
166 "-ex", "set startup-with-shell off",
167 }
168 if cgo {
169
170
171
172
173
174 args = append(args,
175 "-ex", "source "+filepath.Join(runtime.GOROOT(), "src", "runtime", "runtime-gdb.py"),
176 )
177 } else {
178 args = append(args,
179 "-ex", "info auto-load python-scripts",
180 )
181 }
182 args = append(args,
183 "-ex", "set python print-stack full",
184 "-ex", "br fmt.Println",
185 "-ex", "run",
186 "-ex", "echo BEGIN info goroutines\n",
187 "-ex", "info goroutines",
188 "-ex", "echo END\n",
189 "-ex", "up",
190 "-ex", "echo BEGIN print mapvar\n",
191 "-ex", "print mapvar",
192 "-ex", "echo END\n",
193 "-ex", "echo BEGIN print strvar\n",
194 "-ex", "print strvar",
195 "-ex", "echo END\n",
196 "-ex", "echo BEGIN info locals\n",
197 "-ex", "info locals",
198 "-ex", "echo END\n",
199 "-ex", "down",
200 "-ex", "echo BEGIN goroutine 1 bt\n",
201 "-ex", "goroutine 1 bt",
202 "-ex", "echo END\n",
203 "-ex", "echo BEGIN goroutine 2 bt\n",
204 "-ex", "goroutine 2 bt",
205 "-ex", "echo END\n",
206 "-ex", "clear fmt.Println",
207 "-ex", fmt.Sprintf("br main.go:%d", nLines),
208 "-ex", "c",
209 "-ex", "echo BEGIN goroutine 1 bt at the end\n",
210 "-ex", "goroutine 1 bt",
211 "-ex", "echo END\n",
212 filepath.Join(dir, "a.exe"),
213 )
214 got, _ := exec.Command("gdb", args...).CombinedOutput()
215 t.Logf("gdb output: %s\n", got)
216
217 firstLine := bytes.SplitN(got, []byte("\n"), 2)[0]
218 if string(firstLine) != "Loading Go Runtime support." {
219
220
221
222 cmd := exec.Command(testenv.GoToolPath(t), "env", "GOROOT")
223 cmd.Env = []string{}
224 out, err := cmd.CombinedOutput()
225 if err != nil && bytes.Contains(out, []byte("cannot find GOROOT")) {
226 t.Skipf("skipping because GOROOT=%s does not exist", runtime.GOROOT())
227 }
228
229 _, file, _, _ := runtime.Caller(1)
230
231 t.Logf("package testing source file: %s", file)
232 t.Fatalf("failed to load Go runtime support: %s\n%s", firstLine, got)
233 }
234
235
236 partRe := regexp.MustCompile(`(?ms)^BEGIN ([^\n]*)\n(.*?)\nEND`)
237 blocks := map[string]string{}
238 for _, subs := range partRe.FindAllSubmatch(got, -1) {
239 blocks[string(subs[1])] = string(subs[2])
240 }
241
242 infoGoroutinesRe := regexp.MustCompile(`\*\s+\d+\s+running\s+`)
243 if bl := blocks["info goroutines"]; !infoGoroutinesRe.MatchString(bl) {
244 t.Fatalf("info goroutines failed: %s", bl)
245 }
246
247 printMapvarRe1 := regexp.MustCompile(`\Q = map[string]string = {["abc"] = "def", ["ghi"] = "jkl"}\E$`)
248 printMapvarRe2 := regexp.MustCompile(`\Q = map[string]string = {["ghi"] = "jkl", ["abc"] = "def"}\E$`)
249 if bl := blocks["print mapvar"]; !printMapvarRe1.MatchString(bl) &&
250 !printMapvarRe2.MatchString(bl) {
251 t.Fatalf("print mapvar failed: %s", bl)
252 }
253
254 strVarRe := regexp.MustCompile(`\Q = "abc"\E$`)
255 if bl := blocks["print strvar"]; !strVarRe.MatchString(bl) {
256 t.Fatalf("print strvar failed: %s", bl)
257 }
258
259
260
261
262
263
264
265
266
267
268 infoLocalsRe := regexp.MustCompile(`slicevar *= *\[\]string *= *{"def"}`)
269 if bl := blocks["info locals"]; !infoLocalsRe.MatchString(bl) {
270 t.Fatalf("info locals failed: %s", bl)
271 }
272
273 btGoroutine1Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?fmt\.Println.+at`)
274 if bl := blocks["goroutine 1 bt"]; !btGoroutine1Re.MatchString(bl) {
275 t.Fatalf("goroutine 1 bt failed: %s", bl)
276 }
277
278 btGoroutine2Re := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?runtime.+at`)
279 if bl := blocks["goroutine 2 bt"]; !btGoroutine2Re.MatchString(bl) {
280 t.Fatalf("goroutine 2 bt failed: %s", bl)
281 }
282 btGoroutine1AtTheEndRe := regexp.MustCompile(`(?m)^#0\s+(0x[0-9a-f]+\s+in\s+)?main\.main.+at`)
283 if bl := blocks["goroutine 1 bt at the end"]; !btGoroutine1AtTheEndRe.MatchString(bl) {
284 t.Fatalf("goroutine 1 bt at the end failed: %s", bl)
285 }
286 }
287
288 const backtraceSource = `
289 package main
290
291 //go:noinline
292 func aaa() bool { return bbb() }
293
294 //go:noinline
295 func bbb() bool { return ccc() }
296
297 //go:noinline
298 func ccc() bool { return ddd() }
299
300 //go:noinline
301 func ddd() bool { return f() }
302
303 //go:noinline
304 func eee() bool { return true }
305
306 var f = eee
307
308 func main() {
309 _ = aaa()
310 }
311 `
312
313
314
315 func TestGdbBacktrace(t *testing.T) {
316 if runtime.GOOS == "netbsd" {
317 testenv.SkipFlaky(t, 15603)
318 }
319
320 checkGdbEnvironment(t)
321 t.Parallel()
322 checkGdbVersion(t)
323
324 dir, err := ioutil.TempDir("", "go-build")
325 if err != nil {
326 t.Fatalf("failed to create temp directory: %v", err)
327 }
328 defer os.RemoveAll(dir)
329
330
331 src := filepath.Join(dir, "main.go")
332 err = ioutil.WriteFile(src, []byte(backtraceSource), 0644)
333 if err != nil {
334 t.Fatalf("failed to create file: %v", err)
335 }
336 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
337 cmd.Dir = dir
338 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
339 if err != nil {
340 t.Fatalf("building source %v\n%s", err, out)
341 }
342
343
344 args := []string{"-nx", "-batch",
345 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
346 "-ex", "set startup-with-shell off",
347 "-ex", "break main.eee",
348 "-ex", "run",
349 "-ex", "backtrace",
350 "-ex", "continue",
351 filepath.Join(dir, "a.exe"),
352 }
353 got, _ := exec.Command("gdb", args...).CombinedOutput()
354
355
356 bt := []string{
357 "eee",
358 "ddd",
359 "ccc",
360 "bbb",
361 "aaa",
362 "main",
363 }
364 for i, name := range bt {
365 s := fmt.Sprintf("#%v.*main\\.%v", i, name)
366 re := regexp.MustCompile(s)
367 if found := re.Find(got) != nil; !found {
368 t.Errorf("could not find '%v' in backtrace", s)
369 t.Fatalf("gdb output:\n%v", string(got))
370 }
371 }
372 }
373
374 const autotmpTypeSource = `
375 package main
376
377 type astruct struct {
378 a, b int
379 }
380
381 func main() {
382 var iface interface{} = map[string]astruct{}
383 var iface2 interface{} = []astruct{}
384 println(iface, iface2)
385 }
386 `
387
388
389
390 func TestGdbAutotmpTypes(t *testing.T) {
391 checkGdbEnvironment(t)
392 t.Parallel()
393 checkGdbVersion(t)
394
395 dir, err := ioutil.TempDir("", "go-build")
396 if err != nil {
397 t.Fatalf("failed to create temp directory: %v", err)
398 }
399 defer os.RemoveAll(dir)
400
401
402 src := filepath.Join(dir, "main.go")
403 err = ioutil.WriteFile(src, []byte(autotmpTypeSource), 0644)
404 if err != nil {
405 t.Fatalf("failed to create file: %v", err)
406 }
407 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
408 cmd.Dir = dir
409 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
410 if err != nil {
411 t.Fatalf("building source %v\n%s", err, out)
412 }
413
414
415 args := []string{"-nx", "-batch",
416 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
417 "-ex", "set startup-with-shell off",
418 "-ex", "break main.main",
419 "-ex", "run",
420 "-ex", "step",
421 "-ex", "info types astruct",
422 filepath.Join(dir, "a.exe"),
423 }
424 got, _ := exec.Command("gdb", args...).CombinedOutput()
425
426 sgot := string(got)
427
428
429 types := []string{
430 "struct []main.astruct;",
431 "struct bucket<string,main.astruct>;",
432 "struct hash<string,main.astruct>;",
433 "struct main.astruct;",
434 "typedef struct hash<string,main.astruct> * map[string]main.astruct;",
435 }
436 for _, name := range types {
437 if !strings.Contains(sgot, name) {
438 t.Errorf("could not find %s in 'info typrs astruct' output", name)
439 t.Fatalf("gdb output:\n%v", sgot)
440 }
441 }
442 }
443
444 const constsSource = `
445 package main
446
447 const aConstant int = 42
448 const largeConstant uint64 = ^uint64(0)
449 const minusOne int64 = -1
450
451 func main() {
452 println("hello world")
453 }
454 `
455
456 func TestGdbConst(t *testing.T) {
457 checkGdbEnvironment(t)
458 t.Parallel()
459 checkGdbVersion(t)
460
461 dir, err := ioutil.TempDir("", "go-build")
462 if err != nil {
463 t.Fatalf("failed to create temp directory: %v", err)
464 }
465 defer os.RemoveAll(dir)
466
467
468 src := filepath.Join(dir, "main.go")
469 err = ioutil.WriteFile(src, []byte(constsSource), 0644)
470 if err != nil {
471 t.Fatalf("failed to create file: %v", err)
472 }
473 cmd := exec.Command(testenv.GoToolPath(t), "build", "-gcflags=all=-N -l", "-o", "a.exe")
474 cmd.Dir = dir
475 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
476 if err != nil {
477 t.Fatalf("building source %v\n%s", err, out)
478 }
479
480
481 args := []string{"-nx", "-batch",
482 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
483 "-ex", "set startup-with-shell off",
484 "-ex", "break main.main",
485 "-ex", "run",
486 "-ex", "print main.aConstant",
487 "-ex", "print main.largeConstant",
488 "-ex", "print main.minusOne",
489 "-ex", "print 'runtime._MSpanInUse'",
490 "-ex", "print 'runtime._PageSize'",
491 filepath.Join(dir, "a.exe"),
492 }
493 got, _ := exec.Command("gdb", args...).CombinedOutput()
494
495 sgot := strings.Replace(string(got), "\r\n", "\n", -1)
496
497 t.Logf("output %q", sgot)
498
499 if !strings.Contains(sgot, "\n$1 = 42\n$2 = 18446744073709551615\n$3 = -1\n$4 = 1 '\\001'\n$5 = 8192") {
500 t.Fatalf("output mismatch")
501 }
502 }
503
504 const panicSource = `
505 package main
506
507 import "runtime/debug"
508
509 func main() {
510 debug.SetTraceback("crash")
511 crash()
512 }
513
514 func crash() {
515 panic("panic!")
516 }
517 `
518
519
520
521 func TestGdbPanic(t *testing.T) {
522 checkGdbEnvironment(t)
523 t.Parallel()
524 checkGdbVersion(t)
525
526 dir, err := ioutil.TempDir("", "go-build")
527 if err != nil {
528 t.Fatalf("failed to create temp directory: %v", err)
529 }
530 defer os.RemoveAll(dir)
531
532
533 src := filepath.Join(dir, "main.go")
534 err = ioutil.WriteFile(src, []byte(panicSource), 0644)
535 if err != nil {
536 t.Fatalf("failed to create file: %v", err)
537 }
538 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe")
539 cmd.Dir = dir
540 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
541 if err != nil {
542 t.Fatalf("building source %v\n%s", err, out)
543 }
544
545
546 args := []string{"-nx", "-batch",
547 "-iex", "add-auto-load-safe-path " + filepath.Join(runtime.GOROOT(), "src", "runtime"),
548 "-ex", "set startup-with-shell off",
549 "-ex", "run",
550 "-ex", "backtrace",
551 filepath.Join(dir, "a.exe"),
552 }
553 got, _ := exec.Command("gdb", args...).CombinedOutput()
554
555
556 bt := []string{
557 `crash`,
558 `main`,
559 }
560 for _, name := range bt {
561 s := fmt.Sprintf("(#.* .* in )?main\\.%v", name)
562 re := regexp.MustCompile(s)
563 if found := re.Find(got) != nil; !found {
564 t.Errorf("could not find '%v' in backtrace", s)
565 t.Fatalf("gdb output:\n%v", string(got))
566 }
567 }
568 }
569
View as plain text