ogle/program/server: update topOfStack to work with Go 1.4 binaries.

TBR=r
R=r, rsc
https://golang.org/cl/137460043
diff --git a/debug/dwarf/frame.go b/debug/dwarf/frame.go
index a93e816..185a500 100644
--- a/debug/dwarf/frame.go
+++ b/debug/dwarf/frame.go
@@ -165,7 +165,7 @@
 	if length <= 0 {
 		if length == 0 {
 			// EOF.
-			return 0, false, fmt.Errorf("PC not found in PC/SP table")
+			return 0, false, fmt.Errorf("PC %#x not found in PC/SP table", pc)
 		}
 		return 0, false, fmt.Errorf("bad FDE length %d", length)
 	}
diff --git a/debug/dwarf/symbol.go b/debug/dwarf/symbol.go
index 2bd246b..4cc2032 100644
--- a/debug/dwarf/symbol.go
+++ b/debug/dwarf/symbol.go
@@ -8,45 +8,10 @@
 
 import "fmt"
 
-// 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()
-		if err != nil {
-			return 0, err
-		}
-		if entry == nil {
-			// TODO: why don't we get an error here?
-			break
-		}
-		if entry.Tag != TagSubprogram {
-			continue
-		}
-		nameAttr := entry.Val(AttrName)
-		if nameAttr == nil {
-			// TODO: this shouldn't be possible.
-			continue
-		}
-		if nameAttr.(string) != name {
-			continue
-		}
-		addrAttr := entry.Val(AttrLowpc)
-		if addrAttr == nil {
-			return 0, fmt.Errorf("symbol %q has no LowPC attribute", name)
-		}
-		addr, ok := addrAttr.(uint64)
-		if !ok {
-			return 0, fmt.Errorf("symbol %q has non-uint64 LowPC attribute", name)
-		}
-		return addr, nil
-	}
-	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()
+// lookupEntry returns the Entry for the name. If tag is non-zero, only entries
+// with that tag are considered.
+func (d *Data) lookupEntry(name string, tag Tag) (*Entry, error) {
+	r := d.Reader()
 	for {
 		entry, err := r.Next()
 		if err != nil {
@@ -56,6 +21,9 @@
 			// TODO: why don't we get an error here?
 			break
 		}
+		if tag != 0 && tag != entry.Tag {
+			continue
+		}
 		nameAttr := entry.Val(AttrName)
 		if nameAttr == nil {
 			continue
@@ -64,12 +32,66 @@
 			return entry, nil
 		}
 	}
-	return nil, fmt.Errorf("entry for %q not found", name)
+	return nil, fmt.Errorf("DWARF entry for %q not found", name)
+}
+
+// LookupEntry returns the Entry for the named symbol.
+func (d *Data) LookupEntry(name string) (*Entry, error) {
+	return d.lookupEntry(name, 0)
+}
+
+// LookupFunction returns the address of the named symbol, a function.
+func (d *Data) LookupFunction(name string) (uint64, error) {
+	entry, err := d.lookupEntry(name, TagSubprogram)
+	if err != nil {
+		return 0, err
+	}
+	addrAttr := entry.Val(AttrLowpc)
+	if addrAttr == nil {
+		return 0, fmt.Errorf("symbol %q has no LowPC attribute", name)
+	}
+	addr, ok := addrAttr.(uint64)
+	if !ok {
+		return 0, fmt.Errorf("symbol %q has non-uint64 LowPC attribute", name)
+	}
+	return addr, nil
+}
+
+// TODO: should LookupVariable handle both globals and locals? Locals don't
+// necessarily have a fixed address. They may be in a register, or otherwise
+// move around. Should this function be renamed LookupGlobal?
+
+// LookupVariable returns the location of a named symbol, a variable.
+func (d *Data) LookupVariable(name string) (uint64, error) {
+	entry, err := d.lookupEntry(name, TagVariable)
+	if err != nil {
+		return 0, err
+	}
+	loc, _ := entry.Val(AttrLocation).([]byte)
+	if len(loc) == 0 {
+		return 0, fmt.Errorf("name %q has no Location attribute", name)
+	}
+	// TODO: implement the DWARF Location bytecode. What we have here only
+	// recognizes a program with a single literal opAddr bytecode.
+	// TODO: is d.unit[0] always the right unit to take?
+	if asize := d.unit[0].asize; loc[0] == opAddr || len(loc) == 1+asize {
+		switch asize {
+		case 1:
+			return uint64(loc[1]), nil
+		case 2:
+			return uint64(d.order.Uint16(loc[1:])), nil
+		case 4:
+			return uint64(d.order.Uint32(loc[1:])), nil
+		case 8:
+			return d.order.Uint64(loc[1:]), nil
+		}
+	}
+	return 0, fmt.Errorf("DWARF: unimplemented Location op")
 }
 
 // LookupPC returns the name of a symbol at the specified PC.
-func (data *Data) LookupPC(pc uint64) (string, error) {
-	entry, _, err := data.EntryForPC(pc)
+func (d *Data) LookupPC(pc uint64) (string, error) {
+	entry, _, err := d.EntryForPC(pc)
 	if err != nil {
 		return "", err
 	}
@@ -86,9 +108,9 @@
 }
 
 // EntryForPC returns the entry and address for a symbol at the specified PC.
-func (data *Data) EntryForPC(pc uint64) (entry *Entry, lowpc uint64, err error) {
+func (d *Data) EntryForPC(pc uint64) (entry *Entry, lowpc uint64, err error) {
 	// TODO: do something better than a linear scan?
-	r := data.Reader()
+	r := d.Reader()
 	for {
 		entry, err := r.Next()
 		if err != nil {
diff --git a/program/server/dwarf.go b/program/server/dwarf.go
index 04574f8..feaf174 100644
--- a/program/server/dwarf.go
+++ b/program/server/dwarf.go
@@ -42,6 +42,10 @@
 	return s.dwarfData.LookupFunction(name)
 }
 
+func (s *Server) lookupVariable(name string) (uint64, error) {
+	return s.dwarfData.LookupVariable(name)
+}
+
 func (s *Server) lookupPC(pc uint64) (string, error) {
 	return s.dwarfData.LookupPC(pc)
 }
diff --git a/program/server/ptrace.go b/program/server/ptrace.go
index ce2ef59..068e234 100644
--- a/program/server/ptrace.go
+++ b/program/server/ptrace.go
@@ -7,7 +7,7 @@
 import (
 	"fmt"
 	"os"
-	rt "runtime"
+	"runtime"
 	"syscall"
 	"time"
 )
@@ -19,7 +19,7 @@
 	if cap(fc) != 0 || cap(ec) != 0 {
 		panic("ptraceRun was given buffered channels")
 	}
-	rt.LockOSThread()
+	runtime.LockOSThread()
 	for f := range fc {
 		ec <- f()
 	}
diff --git a/program/server/server.go b/program/server/server.go
index 3536379..9a1a4db 100644
--- a/program/server/server.go
+++ b/program/server/server.go
@@ -15,11 +15,10 @@
 	"strings"
 	"syscall"
 
+	"code.google.com/p/ogle/arch"
 	"code.google.com/p/ogle/debug/dwarf"
 	"code.google.com/p/ogle/debug/elf"
 	"code.google.com/p/ogle/debug/macho"
-
-	"code.google.com/p/ogle/arch"
 	"code.google.com/p/ogle/program"
 	"code.google.com/p/ogle/program/proxyrpc"
 )
@@ -45,29 +44,14 @@
 	fc chan func() error
 	ec chan error
 
-	proc        *os.Process
-	procIsUp    bool
-	stoppedPid  int
-	stoppedRegs syscall.PtraceRegs
-	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
-// runtime functions such as runtime·lessstack.
-type runtime struct {
-	evaluated bool
-	evalErr   error
-
-	goexit                 uint64
-	mstart                 uint64
-	mcall                  uint64
-	morestack              uint64
-	lessstack              uint64
-	_rt0_go                uint64
-	externalthreadhandlerp uint64
+	proc            *os.Process
+	procIsUp        bool
+	stoppedPid      int
+	stoppedRegs     syscall.PtraceRegs
+	topOfStackAddrs []uint64
+	breakpoints     map[uint64]breakpoint
+	files           []*file // Index == file descriptor.
+	printer         *Printer
 }
 
 // peek implements the Peeker interface required by the printer.
@@ -275,8 +259,7 @@
 		s.procIsUp = false
 		s.stoppedPid = 0
 		s.stoppedRegs = syscall.PtraceRegs{}
-		s.runtime.evaluated = false
-		s.runtime.evalErr = nil
+		s.topOfStackAddrs = nil
 	}
 	p, err := s.startProcess(s.executable, nil, &os.ProcAttr{
 		Files: []*os.File{
@@ -553,11 +536,10 @@
 
 func (s *Server) handleFrames(req *proxyrpc.FramesRequest, resp *proxyrpc.FramesResponse) error {
 	// TODO: verify that we're stopped.
-	if !s.runtime.evaluated {
-		s.evaluateRuntime()
-	}
-	if s.runtime.evalErr != nil {
-		return s.runtime.evalErr
+	if s.topOfStackAddrs == nil {
+		if err := s.evaluateTopOfStackAddrs(); err != nil {
+			return err
+		}
 	}
 
 	regs := syscall.PtraceRegs{}
@@ -637,43 +619,63 @@
 	return nil
 }
 
-func (s *Server) evaluateRuntime() {
-	s.runtime.evaluated = true
-	s.runtime.evalErr = nil
+func (s *Server) evaluateTopOfStackAddrs() error {
+	var (
+		lookup   func(name string) (uint64, error)
+		indirect bool
+		names    []string
+	)
+	if _, err := s.lookupVariable("runtime.rt0_goPC"); err != nil {
+		// Look for a Go 1.3 binary (or earlier version).
+		lookup, indirect, names = s.lookupFunction, false, []string{
+			"runtime.goexit",
+			"runtime.mstart",
+			"runtime.mcall",
+			"runtime.morestack",
+			"runtime.lessstack",
+			"_rt0_go",
+		}
+	} else {
+		// Look for a Go 1.4 binary (or later version).
+		lookup, indirect, names = s.lookupVariable, true, []string{
+			"runtime.goexitPC",
+			"runtime.mstartPC",
+			"runtime.mcallPC",
+			"runtime.morestackPC",
+			"runtime.rt0_goPC",
+		}
+	}
+	// TODO: also look for runtime.externalthreadhandlerp, on Windows.
 
-	addrs := [...]struct {
-		name            string
-		p               *uint64
-		windowsSpecific bool
-	}{
-		{"runtime.goexit", &s.runtime.goexit, false},
-		{"runtime.mstart", &s.runtime.mstart, false},
-		{"runtime.mcall", &s.runtime.mcall, false},
-		{"runtime.morestack", &s.runtime.morestack, false},
-		{"runtime.lessstack", &s.runtime.lessstack, false},
-		{"_rt0_go", &s.runtime._rt0_go, false},
-		{"runtime.externalthreadhandlerp", &s.runtime.externalthreadhandlerp, true},
-	}
-	for _, a := range addrs {
-		if a.windowsSpecific {
-			// TODO: determine if the traced binary is for Windows.
-			*a.p = 0
-			continue
+	addrs := make([]uint64, 0, len(names))
+	for _, name := range names {
+		addr, err := lookup(name)
+		if err != nil {
+			return err
 		}
-		*a.p, s.runtime.evalErr = s.lookupFunction(a.name)
-		if s.runtime.evalErr != nil {
-			return
+		addrs = append(addrs, addr)
+	}
+
+	if indirect {
+		buf := make([]byte, s.arch.PointerSize)
+		for i, addr := range addrs {
+			if err := s.ptracePeek(s.stoppedPid, uintptr(addr), buf); err != nil {
+				return fmt.Errorf("ptracePeek: %v", err)
+			}
+			addrs[i] = s.arch.Uintptr(buf)
 		}
 	}
+
+	s.topOfStackAddrs = addrs
+	return nil
 }
 
 // topOfStack is the out-of-process equivalent of runtime·topofstack.
 func (s *Server) topOfStack(funcEntry uint64) bool {
-	return funcEntry == s.runtime.goexit ||
-		funcEntry == s.runtime.mstart ||
-		funcEntry == s.runtime.mcall ||
-		funcEntry == s.runtime.morestack ||
-		funcEntry == s.runtime.lessstack ||
-		funcEntry == s.runtime._rt0_go ||
-		(s.runtime.externalthreadhandlerp != 0 && funcEntry == s.runtime.externalthreadhandlerp)
+	for _, addr := range s.topOfStackAddrs {
+		if addr == funcEntry {
+			return true
+		}
+	}
+	return false
 }