From dc3bef36354c7977cfd9e4459e1e6f31bc8624a6 Mon Sep 17 00:00:00 2001 From: Hana Kim Date: Tue, 16 Jan 2018 15:31:12 -0500 Subject: [PATCH] runtime/gdb: use goroutine atomicstatus to determine the state Previously find_goroutine determined whether a goroutine is stopped by checking the sched.sp field. This heuristic doesn't always hold but causes find_goroutine to return bogus pc/sp info for running goroutines. This change uses the atomicstatus bit to determine the state which is more accurate. R=go1.11 Change-Id: I537d432d9e0363257120a196ce2ba52da2970f59 Reviewed-on: https://go-review.googlesource.com/49691 Reviewed-by: Austin Clements --- src/runtime/runtime-gdb.py | 6 ++++-- src/runtime/runtime-gdb_test.go | 32 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/runtime/runtime-gdb.py b/src/runtime/runtime-gdb.py index 3e8e1aaa7d..cd16a6cbab 100644 --- a/src/runtime/runtime-gdb.py +++ b/src/runtime/runtime-gdb.py @@ -462,9 +462,11 @@ def find_goroutine(goid): return None, None # Get the goroutine's saved state. pc, sp = ptr['sched']['pc'], ptr['sched']['sp'] - # If the goroutine is stopped, sched.sp will be non-0. - if sp != 0: + status = ptr['atomicstatus']&~G_SCAN + # Goroutine is not running nor in syscall, so use the info in goroutine + if status != G_RUNNING and status != G_SYSCALL: return pc.cast(vp), sp.cast(vp) + # If the goroutine is in a syscall, use syscallpc/sp. pc, sp = ptr['syscallpc'], ptr['syscallsp'] if sp != 0: diff --git a/src/runtime/runtime-gdb_test.go b/src/runtime/runtime-gdb_test.go index 5e0508631f..c96bb95222 100644 --- a/src/runtime/runtime-gdb_test.go +++ b/src/runtime/runtime-gdb_test.go @@ -87,13 +87,24 @@ func main() { ptrvar := &strvar slicevar := make([]string, 0, 16) slicevar = append(slicevar, mapvar["abc"]) - fmt.Println("hi") // line 13 + 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) } @@ -128,11 +139,13 @@ func testGdbPython(t *testing.T, cgo bool) { } buf.WriteString(helloSource) - src := filepath.Join(dir, "main.go") - err = ioutil.WriteFile(src, buf.Bytes(), 0644) + 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 @@ -168,9 +181,16 @@ func testGdbPython(t *testing.T, cgo bool) { "-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." { @@ -232,6 +252,10 @@ func testGdbPython(t *testing.T, cgo bool) { 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 = ` -- 2.50.0