ogle/server: provide type-aware printer for DWARF-typed objects

LGTM=nigeltao
R=nigeltao
https://golang.org/cl/112270043
diff --git a/arch/arch.go b/arch/arch.go
index 6a2d405..7a2a65d 100644
--- a/arch/arch.go
+++ b/arch/arch.go
@@ -41,6 +41,27 @@
 	panic("no IntSize")
 }
 
+func (a *Architecture) IntN(buf []byte) int64 {
+	return int64(a.UintN(buf))
+}
+
+func (a *Architecture) UintN(buf []byte) uint64 {
+	u := uint64(0)
+	if a.ByteOrder == binary.LittleEndian {
+		shift := uint(0)
+		for _, c := range buf {
+			u |= uint64(c) << shift
+			shift += 8
+		}
+	} else {
+		for _, c := range buf {
+			u <<= 8
+			u |= uint64(c)
+		}
+	}
+	return u
+}
+
 func (a *Architecture) Uintptr(buf []byte) uint64 {
 	if len(buf) != a.PointerSize {
 		panic("bad PointerSize")
diff --git a/debug/dwarf/symbol.go b/debug/dwarf/symbol.go
index 3b0b8b6..2bd246b 100644
--- a/debug/dwarf/symbol.go
+++ b/debug/dwarf/symbol.go
@@ -8,8 +8,8 @@
 
 import "fmt"
 
-// LookupSym returns the address of the named symbol.
-func (data *Data) LookupSym(name string) (uint64, error) {
+// LookupFunction returns the address of the named symbol, a function.
+func (data *Data) LookupFunction(name string) (uint64, error) {
 	r := data.Reader()
 	for {
 		entry, err := r.Next()
@@ -23,7 +23,7 @@
 		if entry.Tag != TagSubprogram {
 			continue
 		}
-		nameAttr := entry.LookupAttr(AttrName)
+		nameAttr := entry.Val(AttrName)
 		if nameAttr == nil {
 			// TODO: this shouldn't be possible.
 			continue
@@ -31,7 +31,7 @@
 		if nameAttr.(string) != name {
 			continue
 		}
-		addrAttr := entry.LookupAttr(AttrLowpc)
+		addrAttr := entry.Val(AttrLowpc)
 		if addrAttr == nil {
 			return 0, fmt.Errorf("symbol %q has no LowPC attribute", name)
 		}
@@ -41,7 +41,30 @@
 		}
 		return addr, nil
 	}
-	return 0, fmt.Errorf("symbol %q not found", name)
+	return 0, fmt.Errorf("function %q not found", name)
+}
+
+// LookupEntry returns the Entry for the named symbol.
+func (data *Data) LookupEntry(name string) (*Entry, error) {
+	r := data.Reader()
+	for {
+		entry, err := r.Next()
+		if err != nil {
+			return nil, err
+		}
+		if entry == nil {
+			// TODO: why don't we get an error here?
+			break
+		}
+		nameAttr := entry.Val(AttrName)
+		if nameAttr == nil {
+			continue
+		}
+		if nameAttr.(string) == name {
+			return entry, nil
+		}
+	}
+	return nil, fmt.Errorf("entry for %q not found", name)
 }
 
 // LookupPC returns the name of a symbol at the specified PC.
@@ -50,7 +73,7 @@
 	if err != nil {
 		return "", err
 	}
-	nameAttr := entry.LookupAttr(AttrName)
+	nameAttr := entry.Val(AttrName)
 	if nameAttr == nil {
 		// TODO: this shouldn't be possible.
 		return "", fmt.Errorf("LookupPC: TODO")
@@ -78,8 +101,8 @@
 		if entry.Tag != TagSubprogram {
 			continue
 		}
-		lowpc, lok := entry.LookupAttr(AttrLowpc).(uint64)
-		highpc, hok := entry.LookupAttr(AttrHighpc).(uint64)
+		lowpc, lok := entry.Val(AttrLowpc).(uint64)
+		highpc, hok := entry.Val(AttrHighpc).(uint64)
 		if !lok || !hok || pc < lowpc || highpc <= pc {
 			continue
 		}
@@ -87,13 +110,3 @@
 	}
 	return nil, 0, fmt.Errorf("PC %#x not found", pc)
 }
-
-// LookupAttr returns the specified attribute for the entry.
-func (e *Entry) LookupAttr(a Attr) interface{} {
-	for _, f := range e.Field {
-		if f.Attr == a {
-			return f.Val
-		}
-	}
-	return nil
-}
diff --git a/program/program.go b/program/program.go
index 2bed9dd..5e51152 100644
--- a/program/program.go
+++ b/program/program.go
@@ -62,7 +62,7 @@
 	// expression to match a set of symbols.
 	Breakpoint(address string) error
 
-	// DeleteBreakpoint removes the breakpoint at to the specified
+	// DeleteBreakpoint removes the breakpoint at the specified
 	// address. TODO: Probably the wrong interface.
 	DeleteBreakpoint(address string) error
 
@@ -72,9 +72,12 @@
 	// Syntax:
 	//	re:regexp
 	//		Returns a list of symbol names that match the expression
-	//	sym:symbol
+	//	addr:symbol
 	//		Returns a one-element list holding the hexadecimal
 	//		("0x1234") value of the address of the symbol
+	//	val:symbol
+	//		Returns a one-element list holding the formatted
+	//		value of the symbol
 	//	0x1234, 01234, 467
 	//		Returns a one-element list holding the name of the
 	//		symbol ("main.foo") at that address (hex, octal, decimal).
diff --git a/program/server/dwarf.go b/program/server/dwarf.go
index 43fe851..04574f8 100644
--- a/program/server/dwarf.go
+++ b/program/server/dwarf.go
@@ -24,7 +24,7 @@
 		if entry.Tag != dwarf.TagSubprogram {
 			continue
 		}
-		nameAttr := entry.LookupAttr(dwarf.AttrName)
+		nameAttr := entry.Val(dwarf.AttrName)
 		if nameAttr == nil {
 			// TODO: this shouldn't be possible.
 			continue
@@ -38,8 +38,8 @@
 	return result, nil
 }
 
-func (s *Server) lookupSym(name string) (uint64, error) {
-	return s.dwarfData.LookupSym(name)
+func (s *Server) lookupFunction(name string) (uint64, error) {
+	return s.dwarfData.LookupFunction(name)
 }
 
 func (s *Server) lookupPC(pc uint64) (string, error) {
diff --git a/program/server/print.go b/program/server/print.go
new file mode 100644
index 0000000..ef458db
--- /dev/null
+++ b/program/server/print.go
@@ -0,0 +1,373 @@
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package server
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+
+	"code.google.com/p/ogle/arch"
+	"code.google.com/p/ogle/debug/dwarf"
+)
+
+// Routines to print a value using DWARF type descriptions.
+// TODO: Does this deserve its own package? It has no dependencies on Server.
+
+// A Printer pretty-prints a values in the target address space.
+// It can be reused after each printing operation to avoid unnecessary
+// allocations. However, it is not safe for concurrent access.
+type Printer struct {
+	err      error // Sticky error value.
+	peeker   Peeker
+	dwarf    *dwarf.Data
+	arch     *arch.Architecture
+	printBuf bytes.Buffer     // Accumulates the output.
+	tmp      []byte           // Temporary used for I/O.
+	visited  map[uintptr]bool // Prevents looping on cyclic data.
+}
+
+// printf prints to printBuf, unless there has been an error.
+func (p *Printer) printf(format string, args ...interface{}) {
+	if p.err != nil {
+		return
+	}
+	fmt.Fprintf(&p.printBuf, format, args...)
+}
+
+// errorf sets the sticky error for the printer, if not already set.
+func (p *Printer) errorf(format string, args ...interface{}) {
+	if p.err != nil {
+		return
+	}
+	p.err = fmt.Errorf(format, args...)
+}
+
+// ok checks the error. If it is the first non-nil error encountered,
+// it is printed to printBuf, parenthesized for discrimination, and remembered.
+func (p *Printer) ok(err error) bool {
+	if p.err == nil && err != nil {
+		p.printf("(%s)", err)
+		p.err = err
+	}
+	return p.err == nil
+}
+
+// peek reads len bytes at offset, leaving p.tmp with the data and sized appropriately.
+func (p *Printer) peek(offset uintptr, len int64) bool {
+	p.tmp = p.tmp[:len]
+	return p.ok(p.peeker.peek(offset, p.tmp))
+}
+
+// Peeker is like a read that probes the remote address space.
+type Peeker interface {
+	peek(offset uintptr, buf []byte) error
+}
+
+// NewPrinter returns a printer that can use the Peeker to access and print
+// values of the specified architecture described by the provided DWARF data.
+func NewPrinter(arch *arch.Architecture, dwarf *dwarf.Data, peeker Peeker) *Printer {
+	return &Printer{
+		peeker:  peeker,
+		arch:    arch,
+		dwarf:   dwarf,
+		visited: make(map[uintptr]bool),
+		tmp:     make([]byte, 100), // Enough for a largish string.
+	}
+}
+
+// reset resets the Printer. It must be called before starting a new
+// printing operation.
+func (p *Printer) reset() {
+	p.err = nil
+	p.printBuf.Reset()
+	// Just wipe the map rather than reallocating. It's almost always tiny.
+	for k := range p.visited {
+		delete(p.visited, k)
+	}
+}
+
+// Sprint returns the pretty-printed value of the item with the given name, such as "main.global".
+func (p *Printer) Sprint(name string) (string, error) {
+	entry, err := p.dwarf.LookupEntry(name)
+	if err != nil {
+		return "", err
+	}
+	p.reset()
+	switch entry.Tag {
+	case dwarf.TagVariable: // TODO: What other entries have global location attributes?
+		addr := uintptr(0)
+		iface := entry.Val(dwarf.AttrLocation)
+		if iface != nil {
+			addr = p.decodeLocation(iface.([]byte))
+		}
+		p.printEntryValueAt(entry, addr)
+	default:
+		p.errorf("unrecognized entry type %s", entry.Tag)
+	}
+	return p.printBuf.String(), p.err
+}
+
+// Figure 24 of DWARF v4.
+const (
+	locationAddr = 0x03
+)
+
+// decodeLocation decodes the dwarf data describing an address.
+func (p *Printer) decodeLocation(data []byte) uintptr {
+	switch data[0] {
+	case locationAddr:
+		return uintptr(p.arch.Uintptr(data[1:]))
+	default:
+		p.errorf("unimplemented location type %#x", data[0])
+	}
+	return 0
+}
+
+// SprintEntry returns the pretty-printed value of the item with the specified DWARF Entry and address.
+func (p *Printer) SprintEntry(entry *dwarf.Entry, addr uintptr) (string, error) {
+	p.reset()
+	p.printEntryValueAt(entry, addr)
+	return p.printBuf.String(), p.err
+}
+
+// printEntryValueAt pretty-prints the data at the specified addresss
+// using the type information in the Entry.
+func (p *Printer) printEntryValueAt(entry *dwarf.Entry, addr uintptr) {
+	if addr == 0 {
+		p.printf("<nil>")
+		return
+	}
+	switch entry.Tag {
+	case dwarf.TagVariable, dwarf.TagFormalParameter:
+		// OK
+	default:
+		p.errorf("unrecognized entry type %s", entry.Tag)
+		return
+	}
+	iface := entry.Val(dwarf.AttrType)
+	if iface == nil {
+		p.errorf("no type")
+		return
+	}
+	typ, err := p.dwarf.Type(iface.(dwarf.Offset))
+	if err != nil {
+		p.errorf("type lookup: %v", err)
+		return
+	}
+	p.printValueAt(typ, addr)
+}
+
+// printValueAt pretty-prints the data at the specified addresss
+// using the provided type information.
+func (p *Printer) printValueAt(typ dwarf.Type, addr uintptr) {
+	// Make sure we don't recur forever.
+	if p.visited[addr] {
+		p.printf("(@%x...)", addr)
+		return
+	}
+	switch typ := typ.(type) {
+	case *dwarf.BoolType:
+		if typ.ByteSize != 1 {
+			p.errorf("unrecognized bool size %d", typ.ByteSize)
+			return
+		}
+		if p.peek(addr, 1) {
+			p.printf("%t", p.tmp[0] != 0)
+		}
+	case *dwarf.PtrType:
+		// This type doesn't define the ByteSize attribute.
+		if p.peek(addr, int64(p.arch.PointerSize)) {
+			p.printf("%#x", p.arch.Uintptr(p.tmp))
+		}
+	case *dwarf.IntType:
+		// Sad we can't tell a rune from an int32.
+		if p.peek(addr, typ.ByteSize) {
+			p.printf("%d", p.arch.IntN(p.tmp))
+		}
+	case *dwarf.UintType:
+		if p.peek(addr, typ.ByteSize) {
+			p.printf("%d", p.arch.UintN(p.tmp))
+		}
+	case *dwarf.FloatType:
+		if !p.peek(addr, typ.ByteSize) {
+			return
+		}
+		switch typ.ByteSize {
+		case 4:
+			p.printf("%g", math.Float32frombits(uint32(p.arch.UintN(p.tmp))))
+		case 8:
+			p.printf("%g", math.Float64frombits(p.arch.UintN(p.tmp)))
+		default:
+			p.errorf("unrecognized float size %d", typ.ByteSize)
+		}
+	case *dwarf.ComplexType:
+		if !p.peek(addr, typ.ByteSize) {
+			return
+		}
+		switch typ.ByteSize {
+		case 8:
+			r := math.Float32frombits(uint32(p.arch.UintN(p.tmp[:4])))
+			i := math.Float32frombits(uint32(p.arch.UintN(p.tmp[4:8])))
+			p.printf("%g", complex(r, i))
+		case 16:
+			r := math.Float64frombits(p.arch.UintN(p.tmp[:8]))
+			i := math.Float64frombits(p.arch.UintN(p.tmp[8:16]))
+			p.printf("%g", complex(r, i))
+		default:
+			p.errorf("unrecognized complex size %d", typ.ByteSize)
+		}
+	case *dwarf.StructType:
+		p.visited[addr] = true
+		if typ.Kind != "struct" {
+			// Could be "class" or "union".
+			p.errorf("can't handle struct type %s", typ.Kind)
+			return
+		}
+		p.printf("%s {", typ.String())
+		for i, field := range typ.Field {
+			if i != 0 {
+				p.printf(", ")
+			}
+			p.printValueAt(field.Type, addr+uintptr(field.ByteOffset))
+		}
+		p.printf("}")
+	case *dwarf.ArrayType:
+		p.printArrayAt(typ, addr)
+	case *dwarf.MapType:
+		p.visited[addr] = true
+		p.printMapAt(typ, addr)
+	case *dwarf.SliceType:
+		p.visited[addr] = true
+		p.printSliceAt(typ, addr)
+	case *dwarf.StringType:
+		p.printStringAt(typ, addr)
+	case *dwarf.TypedefType:
+		p.errorf("unimplemented typedef type %T %v", typ, typ)
+	default:
+		// TODO: chan func interface
+		p.errorf("unimplemented type %v", typ)
+	}
+}
+
+func (p *Printer) printArrayAt(typ *dwarf.ArrayType, addr uintptr) {
+	elemType := typ.Type
+	length := typ.Count
+	stride := typ.StrideBitSize
+	if stride > 0 {
+		stride /= 8
+	} else {
+		stride = p.sizeof(elemType)
+		if stride < 0 {
+			p.errorf("array elements have no known size")
+		}
+	}
+	p.printf("%s{", typ)
+	n := length
+	if n > 100 {
+		n = 100 // TODO: Have a way to control this?
+	}
+	for i := int64(0); i < n; i++ {
+		if i != 0 {
+			p.printf(", ")
+		}
+		p.printValueAt(elemType, addr)
+		addr += uintptr(stride) // TODO: Alignment and padding - not given by Type
+	}
+	if n < length {
+		p.printf(", ...")
+	}
+	p.printf("}")
+}
+
+// TODO: Unimplemented.
+func (p *Printer) printMapAt(typ *dwarf.MapType, addr uintptr) {
+	// Maps are pointers to a struct type.
+	structType := typ.Type.(*dwarf.PtrType).Type.(*dwarf.StructType)
+	// Indirect through the pointer.
+	if !p.peek(addr, int64(p.arch.PointerSize)) {
+		return
+	}
+	addr = uintptr(p.arch.Uintptr(p.tmp[:p.arch.PointerSize]))
+	// Now read the struct.
+	if !p.peek(addr, structType.ByteSize) {
+		return
+	}
+	// TODO: unimplemented. Hard.
+	countField := structType.Field[0]
+	p.printf("%s with ", typ)
+	p.printValueAt(countField.Type, addr+uintptr(countField.ByteOffset))
+	p.printf(" elements")
+}
+
+func (p *Printer) printSliceAt(typ *dwarf.SliceType, addr uintptr) {
+	// Slices look like a struct with fields array *elemtype, len uint32/64, cap uint32/64.
+	// BUG: Slice header appears to have fields with ByteSize == 0
+	if !p.peek(addr, typ.ByteSize) {
+		p.errorf("slice header has no known size")
+		return
+	}
+	lo := typ.Field[0].ByteOffset
+	hi := lo + int64(p.arch.PointerSize)
+	ptr := uintptr(p.arch.UintN(p.tmp[lo:hi]))
+	lo = typ.Field[1].ByteOffset
+	hi = lo + int64(p.arch.IntSize)
+	length := p.arch.UintN(p.tmp[lo:hi])
+	// Capacity is unused here.
+	elemType := typ.ElemType
+	size := p.sizeof(elemType)
+	if size < 0 {
+		return
+	}
+	p.printf("%s{", typ)
+	for i := uint64(0); i < length; i++ {
+		if i != 0 {
+			p.printf(", ")
+		}
+		p.printValueAt(elemType, ptr)
+		ptr += uintptr(size) // TODO: Alignment and padding - not given by Type
+	}
+	p.printf("}")
+}
+
+func (p *Printer) printStringAt(typ *dwarf.StringType, addr uintptr) {
+	// Strings look like a struct with fields array *elemtype, len uint64.
+	// TODO uint64 on 386 too?
+	if !p.peek(addr, typ.ByteSize) {
+		p.errorf("string header has no known size")
+		return
+	}
+	// BUG: String header appears to have fields with ByteSize == 0
+	lo := typ.Field[0].ByteOffset
+	hi := lo + int64(p.arch.PointerSize)
+	ptr := p.arch.UintN(p.tmp[lo:hi])
+	lo = typ.Field[1].ByteOffset
+	hi = lo + int64(p.arch.IntSize) // TODO?
+	length := p.arch.UintN(p.tmp[lo:hi])
+	if length > uint64(cap(p.tmp)) {
+		if p.peek(uintptr(ptr), int64(cap(p.tmp))) {
+			p.printf("%q...", p.tmp)
+		}
+	} else {
+		if p.peek(uintptr(ptr), int64(length)) {
+			p.printf("%q", p.tmp[:length])
+		}
+	}
+}
+
+// sizeof returns the byte size of the type. It returns -1 if no size can be found.
+func (p *Printer) sizeof(typ dwarf.Type) int64 {
+	size := typ.Size() // Will be -1 if ByteSize is not set.
+	if size >= 0 {
+		return size
+	}
+	switch typ.(type) {
+	case *dwarf.PtrType:
+		// This is the only one we know of, but more may arise.
+		return int64(p.arch.PointerSize)
+	}
+	p.errorf("unknown size for %s", typ)
+	return -1
+}
diff --git a/program/server/server.go b/program/server/server.go
index 868a4d2..5d2e481 100644
--- a/program/server/server.go
+++ b/program/server/server.go
@@ -47,6 +47,7 @@
 	runtime     runtime
 	breakpoints map[uint64]breakpoint
 	files       []*file // Index == file descriptor.
+	printer     *Printer
 }
 
 // runtime are the addresses, in the target program's address space, of Go
@@ -64,6 +65,11 @@
 	externalthreadhandlerp uint64
 }
 
+// peek implements the Peeker interface required by the printer.
+func (s *Server) peek(offset uintptr, buf []byte) error {
+	return s.ptracePeek(s.stoppedPid, offset, buf)
+}
+
 // New parses the executable and builds local data structures for answering requests.
 // It returns a Server ready to serve requests about the executable.
 func New(executable string) (*Server, error) {
@@ -84,6 +90,7 @@
 		ec:          make(chan error),
 		breakpoints: make(map[uint64]breakpoint),
 	}
+	srv.printer = NewPrinter(architecture, dwarfData, srv)
 	go ptraceRun(srv.fc, srv.ec)
 	return srv, nil
 }
@@ -388,14 +395,22 @@
 		}
 		return s.lookupRE(re)
 
-	case strings.HasPrefix(expr, "sym:"):
+	case strings.HasPrefix(expr, "addr:"):
 		// Symbol lookup. Return address.
-		addr, err := s.lookupSym(expr[4:])
+		addr, err := s.lookupFunction(expr[5:])
 		if err != nil {
 			return nil, err
 		}
 		return []string{fmt.Sprintf("%#x", addr)}, nil
 
+	case strings.HasPrefix(expr, "val:"):
+		// Symbol lookup. Return formatted value.
+		value, err := s.printer.Sprint(expr[4:])
+		if err != nil {
+			return nil, err
+		}
+		return []string{value}, nil
+
 	case strings.HasPrefix(expr, "src:"):
 		// Numerical address. Return file.go:123.
 		addr, err := strconv.ParseUint(expr[4:], 0, 0)
@@ -437,7 +452,7 @@
 // and evaluates it as an address.
 func (s *Server) evalAddress(expr string) (uint64, error) {
 	// Might be a symbol.
-	addr, err := s.lookupSym(expr)
+	addr, err := s.lookupFunction(expr) // TODO: might not be a function
 	if err == nil {
 		return addr, nil
 	}
@@ -507,33 +522,21 @@
 				return fmt.Errorf("FormalParameter has children, expected none")
 			}
 
-			location := uintptr(0)
+			offset := int64(0)
+			name := "arg"
 			for _, f := range entry.Field {
 				switch f.Attr {
 				case dwarf.AttrLocation:
-					offset := evalLocation(f.Val.([]uint8))
-					location = uintptr(int64(fp) + offset)
-					fmt.Fprintf(b, "(%d(FP))", offset)
+					offset = evalLocation(f.Val.([]uint8))
 				case dwarf.AttrName:
-					fmt.Fprintf(b, " %s", f.Val.(string))
-				case dwarf.AttrType:
-					t, err := s.dwarfData.Type(f.Val.(dwarf.Offset))
-					if err == nil {
-						fmt.Fprintf(b, "[%v]", t)
-					}
-					if t.String() != "int" || t.Size() != int64(s.arch.IntSize) {
-						break
-					}
-					if location == 0 {
-						return fmt.Errorf("no location for FormalParameter")
-					}
-					err = s.ptracePeek(s.stoppedPid, location, buf[:s.arch.IntSize])
-					if err != nil {
-						return err
-					}
-					fmt.Fprintf(b, "==%#x", s.arch.Int(buf[:s.arch.IntSize]))
+					name = f.Val.(string)
 				}
 			}
+			str, err := s.printer.SprintEntry(entry, uintptr(fp)+uintptr(offset))
+			fmt.Fprintf(b, "%s (%d(FP)) = %s ", name, offset, str)
+			if err != nil {
+				fmt.Fprintf(b, "(%s) ", err)
+			}
 		}
 		resp.Frames = append(resp.Frames, program.Frame{
 			S: b.String(),
@@ -575,7 +578,7 @@
 			*a.p = 0
 			continue
 		}
-		*a.p, s.runtime.evalErr = s.lookupSym(a.name)
+		*a.p, s.runtime.evalErr = s.lookupFunction(a.name)
 		if s.runtime.evalErr != nil {
 			return
 		}