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 }