x/debug: read stack frames of goroutines.
Add some more fields and a String method to program.Frame, so they can
be printed similarly to how the runtime prints them.
Move stack-walking code to a separate function.
Change-Id: I3bc8a24296890733d13823a58b7eabd42050ca02
Reviewed-on: https://go-review.googlesource.com/19408
Reviewed-by: Dave Day <djd@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index 1e44705..729fa75 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -464,6 +464,9 @@
}
for _, g := range gs {
fmt.Println(g)
+ for _, f := range g.StackFrames {
+ fmt.Println(f)
+ }
}
frames, err := prog.Frames(100)
diff --git a/ogle/program/program.go b/ogle/program/program.go
index 6ac49c9..72c0389 100644
--- a/ogle/program/program.go
+++ b/ogle/program/program.go
@@ -8,6 +8,7 @@
import (
"fmt"
"io"
+ "strings"
)
// Program is the interface to a (possibly remote) program being debugged.
@@ -117,6 +118,7 @@
StatusString string // A human-readable string explaining the status in more detail.
Function string // Name of the goroutine function.
Caller string // Name of the function that created this goroutine.
+ StackFrames []Frame
}
type GoroutineStatus byte
@@ -284,12 +286,24 @@
Line uint64
// Function is the name of this frame's function.
Function string
+ // FunctionStart is the starting PC of the function.
+ FunctionStart uint64
// Params contains the function's parameters.
Params []Param
// Vars contains the function's local variables.
Vars []LocalVar
}
+func (f Frame) String() string {
+ params := make([]string, len(f.Params))
+ for i, p := range f.Params {
+ params[i] = p.Name // TODO: more information
+ }
+ p := strings.Join(params, ", ")
+ off := f.PC - f.FunctionStart
+ return fmt.Sprintf("%s(%s)\n\t%s:%d +0x%x", f.Function, p, f.File, f.Line, off)
+}
+
// Param is a parameter of a function.
type Param struct {
Name string
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go
index ffa72c3..2d86704 100644
--- a/ogle/program/server/server.go
+++ b/ogle/program/server/server.go
@@ -16,6 +16,7 @@
"regexp"
"strconv"
"strings"
+ "sync"
"syscall"
"golang.org/x/debug/dwarf"
@@ -55,6 +56,10 @@
breakpoints map[uint64]breakpoint
files []*file // Index == file descriptor.
printer *Printer
+
+ // goroutineStack reads the stack of a (non-running) goroutine.
+ goroutineStack func(uint64) ([]program.Frame, error)
+ goroutineStackOnce sync.Once
}
// peek implements the Peeker interface required by the printer.
@@ -614,40 +619,47 @@
if err != nil {
return err
}
- pc, sp := regs.Rip, regs.Rsp
+ resp.Frames, err = s.walkStack(regs.Rip, regs.Rsp, req.Count)
+ return err
+}
+
+// walkStack returns up to the requested number of stack frames.
+func (s *Server) walkStack(pc, sp uint64, count int) ([]program.Frame, error) {
+ var frames []program.Frame
var buf [8]byte
b := new(bytes.Buffer)
r := s.dwarfData.Reader()
// TODO: handle walking over a split stack.
- for i := 0; i < req.Count; i++ {
+ for i := 0; i < count; i++ {
b.Reset()
file, line, err := s.dwarfData.PCToLine(pc)
if err != nil {
- return err
+ return frames, err
}
fpOffset, err := s.dwarfData.PCToSPOffset(pc)
if err != nil {
- return err
+ return frames, err
}
fp := sp + uint64(fpOffset)
entry, funcEntry, err := s.entryForPC(pc)
if err != nil {
- return err
+ return frames, err
}
frame := program.Frame{
- PC: pc,
- SP: sp,
- File: file,
- Line: line,
+ PC: pc,
+ SP: sp,
+ File: file,
+ Line: line,
+ FunctionStart: funcEntry,
}
frame.Function, _ = entry.Val(dwarf.AttrName).(string)
r.Seek(entry.Offset)
for {
entry, err := r.Next()
if err != nil {
- return err
+ return frames, err
}
if entry.Tag == 0 {
break
@@ -664,7 +676,7 @@
}
}
}
- resp.Frames = append(resp.Frames, frame)
+ frames = append(frames, frame)
// Walk to the caller's PC and SP.
if s.topOfStack(funcEntry) {
@@ -672,11 +684,11 @@
}
err = s.ptracePeek(s.stoppedPid, uintptr(fp-uint64(s.arch.PointerSize)), buf[:s.arch.PointerSize])
if err != nil {
- return fmt.Errorf("ptracePeek: %v", err)
+ return frames, fmt.Errorf("ptracePeek: %v", err)
}
pc, sp = s.arch.Uintptr(buf[:s.arch.PointerSize]), fp
}
- return nil
+ return frames, nil
}
// parseParameterOrLocal parses the entry for a function parameter or local
@@ -951,6 +963,9 @@
}
}
+ // Initialize s.goroutineStack.
+ s.goroutineStackOnce.Do(func() { s.goroutineStackInit(gType) })
+
for i := uint64(0); i < allglen; i++ {
// allg is an array of pointers to g structs. Read allg[i].
g, err := s.peekPtr(allg + i*uint64(s.arch.PointerSize))
@@ -1019,9 +1034,80 @@
if gopc, err := s.peekUintStructField(gType, g, "gopc"); err == nil {
gr.Caller = functionName(gopc)
}
+ if gr.Status != program.Running {
+ // TODO: running goroutines too.
+ gr.StackFrames, _ = s.goroutineStack(g)
+ }
resp.Goroutines = append(resp.Goroutines, &gr)
}
return nil
}
+
+// TODO: let users specify how many frames they want. 10 will be enough to
+// determine the reason a goroutine is blocked.
+const goroutineStackFrameCount = 10
+
+// goroutineStackInit initializes s.goroutineStack.
+func (s *Server) goroutineStackInit(gType *dwarf.StructType) {
+ // If we fail to read the DWARF data needed for s.goroutineStack, calling it
+ // will always return the error that occurred during initialization.
+ var err error // err is captured by the func below.
+ s.goroutineStack = func(gAddr uint64) ([]program.Frame, error) {
+ return nil, err
+ }
+
+ // Get g field "sched", which contains fields pc and sp.
+ schedField, err := getField(gType, "sched")
+ if err != nil {
+ return
+ }
+ schedOffset := uint64(schedField.ByteOffset)
+ schedType, ok := followTypedefs(schedField.Type).(*dwarf.StructType)
+ if !ok {
+ err = errors.New(`g field "sched" has the wrong type`)
+ return
+ }
+
+ // Get the size of the pc and sp fields and their offsets inside the g struct,
+ // so we can quickly peek those values for each goroutine later.
+ var (
+ schedPCOffset, schedSPOffset uint64
+ schedPCByteSize, schedSPByteSize int64
+ )
+ for _, x := range []struct {
+ field string
+ offset *uint64
+ bytesize *int64
+ }{
+ {"pc", &schedPCOffset, &schedPCByteSize},
+ {"sp", &schedSPOffset, &schedSPByteSize},
+ } {
+ var f *dwarf.StructField
+ f, err = getField(schedType, x.field)
+ if err != nil {
+ return
+ }
+ *x.offset = schedOffset + uint64(f.ByteOffset)
+ switch t := followTypedefs(f.Type).(type) {
+ case *dwarf.UintType, *dwarf.IntType:
+ *x.bytesize = t.Common().ByteSize
+ default:
+ err = fmt.Errorf("gobuf field %q has the wrong type", x.field)
+ return
+ }
+ }
+
+ s.goroutineStack = func(gAddr uint64) ([]program.Frame, error) {
+ schedPC, err := s.peekUint(gAddr+schedPCOffset, schedPCByteSize)
+ if err != nil {
+ return nil, err
+ }
+ schedSP, err := s.peekUint(gAddr+schedSPOffset, schedSPByteSize)
+ if err != nil {
+ return nil, err
+ }
+ return s.walkStack(schedPC, schedSP, goroutineStackFrameCount)
+ }
+}