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
}