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)
+	}
+}