x/debug: add a function for reading the current (not-dead) goroutines from a target program.

Moves some string-reading code from print.go into a separate function in
peek.go.  Also adds functions for reading string fields in structs, and
C-style NUL-terminated strings,

Change-Id: I399960891ce7d3ab0c989b94021db284dc095a59
Reviewed-on: https://go-review.googlesource.com/19248
Reviewed-by: Dave Day <djd@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index cab7f94..1e44705 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -458,6 +458,14 @@
 		log.Fatalf("Resume: %v", err)
 	}
 
+	gs, err := prog.Goroutines()
+	if err != nil {
+		t.Fatalf("Goroutines(): got error %s", err)
+	}
+	for _, g := range gs {
+		fmt.Println(g)
+	}
+
 	frames, err := prog.Frames(100)
 	if err != nil {
 		log.Fatalf("prog.Frames error: %v", err)
diff --git a/ogle/program/client/client.go b/ogle/program/client/client.go
index 6083d04..51bf1a5 100644
--- a/ogle/program/client/client.go
+++ b/ogle/program/client/client.go
@@ -241,6 +241,13 @@
 	return resp.Frames, err
 }
 
+func (p *Program) Goroutines() ([]*program.Goroutine, error) {
+	req := proxyrpc.GoroutinesRequest{}
+	var resp proxyrpc.GoroutinesResponse
+	err := p.client.Call("Server.Goroutines", &req, &resp)
+	return resp.Goroutines, err
+}
+
 func (p *Program) VarByName(name string) (program.Var, error) {
 	req := proxyrpc.VarByNameRequest{Name: name}
 	var resp proxyrpc.VarByNameResponse
diff --git a/ogle/program/local/local.go b/ogle/program/local/local.go
index 55f8c12..ed9bdc8 100644
--- a/ogle/program/local/local.go
+++ b/ogle/program/local/local.go
@@ -126,6 +126,13 @@
 	return resp.Frames, err
 }
 
+func (l *Local) Goroutines() ([]*program.Goroutine, error) {
+	req := proxyrpc.GoroutinesRequest{}
+	var resp proxyrpc.GoroutinesResponse
+	err := l.s.Goroutines(&req, &resp)
+	return resp.Goroutines, err
+}
+
 func (l *Local) VarByName(name string) (program.Var, error) {
 	req := proxyrpc.VarByNameRequest{Name: name}
 	var resp proxyrpc.VarByNameResponse
diff --git a/ogle/program/program.go b/ogle/program/program.go
index b7feb20..6ac49c9 100644
--- a/ogle/program/program.go
+++ b/ogle/program/program.go
@@ -6,6 +6,7 @@
 package program // import "golang.org/x/debug/ogle/program"
 
 import (
+	"fmt"
 	"io"
 )
 
@@ -105,6 +106,41 @@
 	// MapElement returns Vars for the key and value of a map element specified by
 	// a 0-based index.
 	MapElement(m Map, index uint64) (Var, Var, error)
+
+	// Goroutines gets the current goroutines.
+	Goroutines() ([]*Goroutine, error)
+}
+
+type Goroutine struct {
+	ID           int64
+	Status       GoroutineStatus
+	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.
+}
+
+type GoroutineStatus byte
+
+const (
+	Running GoroutineStatus = iota
+	Queued
+	Blocked
+)
+
+func (g GoroutineStatus) String() string {
+	switch g {
+	case Running:
+		return "running"
+	case Queued:
+		return "queued"
+	case Blocked:
+		return "blocked"
+	}
+	return "invalid status"
+}
+
+func (g *Goroutine) String() string {
+	return fmt.Sprintf("goroutine %d [%s] %s -> %s", g.ID, g.StatusString, g.Caller, g.Function)
 }
 
 // A reference to a variable in a program.
diff --git a/ogle/program/proxyrpc/proxyrpc.go b/ogle/program/proxyrpc/proxyrpc.go
index 8ed02b4..b6636f8 100644
--- a/ogle/program/proxyrpc/proxyrpc.go
+++ b/ogle/program/proxyrpc/proxyrpc.go
@@ -156,3 +156,10 @@
 	Key   program.Var
 	Value program.Var
 }
+
+type GoroutinesRequest struct {
+}
+
+type GoroutinesResponse struct {
+	Goroutines []*program.Goroutine
+}
diff --git a/ogle/program/server/peek.go b/ogle/program/server/peek.go
index e61a974..936b3ab 100644
--- a/ogle/program/server/peek.go
+++ b/ogle/program/server/peek.go
@@ -54,6 +54,50 @@
 	return s.arch.UintN(buf), nil
 }
 
+// peekString reads a string of the given type at the given address.
+// At most byteLimit bytes will be read.  If the string is longer, "..." is appended.
+func (s *Server) peekString(typ *dwarf.StringType, a uint64, byteLimit uint64) (string, error) {
+	ptr, err := s.peekPtrStructField(&typ.StructType, a, "str")
+	if err != nil {
+		return "", err
+	}
+	length, err := s.peekUintOrIntStructField(&typ.StructType, a, "len")
+	if err != nil {
+		return "", err
+	}
+	if length > byteLimit {
+		buf := make([]byte, byteLimit, byteLimit+3)
+		if err := s.peekBytes(ptr, buf); err != nil {
+			return "", err
+		} else {
+			buf = append(buf, '.', '.', '.')
+			return string(buf), nil
+		}
+	} else {
+		buf := make([]byte, length)
+		if err := s.peekBytes(ptr, buf); err != nil {
+			return "", err
+		} else {
+			return string(buf), nil
+		}
+	}
+}
+
+// peekCString reads a NUL-terminated string at the given address.
+// At most byteLimit bytes will be read.  If the string is longer, "..." is appended.
+// peekCString never returns errors; if an error occurs, the string will be truncated in some way.
+func (s *Server) peekCString(a uint64, byteLimit uint64) string {
+	buf := make([]byte, byteLimit, byteLimit+3)
+	s.peekBytes(a, buf)
+	for i, c := range buf {
+		if c == 0 {
+			return string(buf[0:i])
+		}
+	}
+	buf = append(buf, '.', '.', '.')
+	return string(buf)
+}
+
 // peekPtrStructField reads a pointer in the field fieldName of the struct
 // of type t at addr.
 func (s *Server) peekPtrStructField(t *dwarf.StructType, addr uint64, fieldName string) (uint64, error) {
@@ -122,6 +166,21 @@
 	return s.peekInt(addr+uint64(f.ByteOffset), it.ByteSize)
 }
 
+// peekStringStructField reads a string field from the struct of the given type
+// at the given address.
+// At most byteLimit bytes will be read.  If the string is longer, "..." is appended.
+func (s *Server) peekStringStructField(t *dwarf.StructType, addr uint64, fieldName string, byteLimit uint64) (string, error) {
+	f, err := getField(t, fieldName)
+	if err != nil {
+		return "", fmt.Errorf("reading field %s: %s", fieldName, err)
+	}
+	st, ok := followTypedefs(f.Type).(*dwarf.StringType)
+	if !ok {
+		return "", fmt.Errorf("field %s is not a string", fieldName)
+	}
+	return s.peekString(st, addr+uint64(f.ByteOffset), byteLimit)
+}
+
 // peekMapLocationAndType returns the address and DWARF type of the underlying
 // struct of a map variable.
 func (s *Server) peekMapLocationAndType(t *dwarf.MapType, a uint64) (uint64, *dwarf.StructType, error) {
diff --git a/ogle/program/server/print.go b/ogle/program/server/print.go
index 6c4be72..0ec0e5c 100644
--- a/ogle/program/server/print.go
+++ b/ogle/program/server/print.go
@@ -490,32 +490,11 @@
 }
 
 func (p *Printer) printStringAt(typ *dwarf.StringType, a uint64) {
-	// BUG: String header appears to have fields with ByteSize == 0
-	ptr, err := p.server.peekPtrStructField(&typ.StructType, a, "str")
-	if err != nil {
-		p.errorf("reading string: %s", err)
-		return
-	}
-	length, err := p.server.peekUintOrIntStructField(&typ.StructType, a, "len")
-	if err != nil {
-		p.errorf("reading string: %s", err)
-		return
-	}
 	const maxStringSize = 100
-	if length > maxStringSize {
-		buf := make([]byte, maxStringSize)
-		if err := p.server.peekBytes(ptr, buf); err != nil {
-			p.errorf("reading string: %s", err)
-		} else {
-			p.printf("%q...", string(buf))
-		}
+	if s, err := p.server.peekString(typ, a, maxStringSize); err != nil {
+		p.errorf("reading string: %s", err)
 	} else {
-		buf := make([]byte, length)
-		if err := p.server.peekBytes(ptr, buf); err != nil {
-			p.errorf("reading string: %s", err)
-		} else {
-			p.printf("%q", string(buf))
-		}
+		p.printf("%q", s)
 	}
 }
 
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go
index ecffc18..ffa72c3 100644
--- a/ogle/program/server/server.go
+++ b/ogle/program/server/server.go
@@ -10,6 +10,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"os"
 	"regexp"
@@ -178,6 +179,8 @@
 		c.errc <- s.handleValue(req, c.resp.(*proxyrpc.ValueResponse))
 	case *proxyrpc.MapElementRequest:
 		c.errc <- s.handleMapElement(req, c.resp.(*proxyrpc.MapElementResponse))
+	case *proxyrpc.GoroutinesRequest:
+		c.errc <- s.handleGoroutines(req, c.resp.(*proxyrpc.GoroutinesResponse))
 	default:
 		panic(fmt.Sprintf("unexpected call request type %T", c.req))
 	}
@@ -833,3 +836,192 @@
 	}
 	return nil
 }
+
+func (s *Server) Goroutines(req *proxyrpc.GoroutinesRequest, resp *proxyrpc.GoroutinesResponse) error {
+	return s.call(s.otherc, req, resp)
+}
+
+const invalidStatus program.GoroutineStatus = 99
+
+var (
+	gStatus = [...]program.GoroutineStatus{
+		0: program.Queued,  // _Gidle
+		1: program.Queued,  // _Grunnable
+		2: program.Running, // _Grunning
+		3: program.Blocked, // _Gsyscall
+		4: program.Blocked, // _Gwaiting
+		5: invalidStatus,   // _Gmoribund_unused
+		6: invalidStatus,   // _Gdead
+		7: invalidStatus,   // _Genqueue
+		8: program.Running, // _Gcopystack
+	}
+	gScanStatus = [...]program.GoroutineStatus{
+		0: invalidStatus,   // _Gscan + _Gidle
+		1: program.Queued,  // _Gscanrunnable
+		2: program.Running, // _Gscanrunning
+		3: program.Blocked, // _Gscansyscall
+		4: program.Blocked, // _Gscanwaiting
+		5: invalidStatus,   // _Gscan + _Gmoribund_unused
+		6: invalidStatus,   // _Gscan + _Gdead
+		7: program.Queued,  // _Gscanenqueue
+	}
+	gStatusString = [...]string{
+		0: "idle",
+		1: "runnable",
+		2: "running",
+		3: "syscall",
+		4: "waiting",
+		8: "copystack",
+	}
+	gScanStatusString = [...]string{
+		1: "scanrunnable",
+		2: "scanrunning",
+		3: "scansyscall",
+		4: "scanwaiting",
+		7: "scanenqueue",
+	}
+)
+
+func (s *Server) handleGoroutines(req *proxyrpc.GoroutinesRequest, resp *proxyrpc.GoroutinesResponse) error {
+	// Get DWARF type information for runtime.g.
+	ge, err := s.dwarfData.LookupEntry("runtime.g")
+	if err != nil {
+		return err
+	}
+	t, err := s.dwarfData.Type(ge.Offset)
+	if err != nil {
+		return err
+	}
+	gType, ok := followTypedefs(t).(*dwarf.StructType)
+	if !ok {
+		return errors.New("runtime.g is not a struct")
+	}
+
+	// Read runtime.allg.
+	allgEntry, err := s.dwarfData.LookupEntry("runtime.allg")
+	if err != nil {
+		return err
+	}
+	allgAddr, err := s.dwarfData.EntryLocation(allgEntry)
+	if err != nil {
+		return err
+	}
+	allg, err := s.peekPtr(allgAddr)
+	if err != nil {
+		return fmt.Errorf("reading allg: %v", err)
+	}
+
+	// Read runtime.allglen.
+	allglenEntry, err := s.dwarfData.LookupEntry("runtime.allglen")
+	if err != nil {
+		return err
+	}
+	off, err := s.dwarfData.EntryTypeOffset(allglenEntry)
+	if err != nil {
+		return err
+	}
+	allglenType, err := s.dwarfData.Type(off)
+	if err != nil {
+		return err
+	}
+	allglenAddr, err := s.dwarfData.EntryLocation(allglenEntry)
+	if err != nil {
+		return err
+	}
+	var allglen uint64
+	switch followTypedefs(allglenType).(type) {
+	case *dwarf.UintType, *dwarf.IntType:
+		allglen, err = s.peekUint(allglenAddr, allglenType.Common().ByteSize)
+		if err != nil {
+			return fmt.Errorf("reading allglen: %v", err)
+		}
+	default:
+		// Some runtimes don't specify the type for allglen.  Assume it's uint32.
+		allglen, err = s.peekUint(allglenAddr, 4)
+		if err != nil {
+			return fmt.Errorf("reading allglen: %v", err)
+		}
+		if allglen != 0 {
+			break
+		}
+		// Zero?  Let's try uint64.
+		allglen, err = s.peekUint(allglenAddr, 8)
+		if err != nil {
+			return fmt.Errorf("reading allglen: %v", err)
+		}
+	}
+
+	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))
+		if err != nil {
+			return err
+		}
+		gr := program.Goroutine{}
+
+		// Read status from the field named "atomicstatus" or "status".
+		status, err := s.peekUintStructField(gType, g, "atomicstatus")
+		if err != nil {
+			status, err = s.peekUintOrIntStructField(gType, g, "status")
+		}
+		if err != nil {
+			return err
+		}
+		if status == 6 {
+			// _Gdead.
+			continue
+		}
+		gr.Status = invalidStatus
+		if status < uint64(len(gStatus)) {
+			gr.Status = gStatus[status]
+			gr.StatusString = gStatusString[status]
+		} else if status^0x1000 < uint64(len(gScanStatus)) {
+			gr.Status = gScanStatus[status^0x1000]
+			gr.StatusString = gScanStatusString[status^0x1000]
+		}
+		if gr.Status == invalidStatus {
+			return fmt.Errorf("unexpected goroutine status 0x%x", status)
+		}
+		if status == 4 || status == 0x1004 {
+			// _Gwaiting or _Gscanwaiting.
+			// Try reading waitreason to get a better value for StatusString.
+			// Depending on the runtime, waitreason may be a Go string or a C string.
+			if waitreason, err := s.peekStringStructField(gType, g, "waitreason", 80); err == nil {
+				if waitreason != "" {
+					gr.StatusString = waitreason
+				}
+			} else if ptr, err := s.peekPtrStructField(gType, g, "waitreason"); err == nil {
+				waitreason := s.peekCString(ptr, 80)
+				if waitreason != "" {
+					gr.StatusString = waitreason
+				}
+			}
+		}
+
+		gr.ID, err = s.peekIntStructField(gType, g, "goid")
+		if err != nil {
+			return err
+		}
+
+		// Best-effort attempt to get the names of the goroutine function and the
+		// function that created the goroutine.  They aren't always available.
+		functionName := func(pc uint64) string {
+			entry, _, err := s.dwarfData.EntryForPC(pc)
+			if err != nil {
+				return ""
+			}
+			name, _ := entry.Val(dwarf.AttrName).(string)
+			return name
+		}
+		if startpc, err := s.peekUintStructField(gType, g, "startpc"); err == nil {
+			gr.Function = functionName(startpc)
+		}
+		if gopc, err := s.peekUintStructField(gType, g, "gopc"); err == nil {
+			gr.Caller = functionName(gopc)
+		}
+
+		resp.Goroutines = append(resp.Goroutines, &gr)
+	}
+
+	return nil
+}