diff --git a/dwarf/line/state_machine.go b/dwarf/line/state_machine.go index 0235f255..64c30e71 100644 --- a/dwarf/line/state_machine.go +++ b/dwarf/line/state_machine.go @@ -105,6 +105,25 @@ func (dbl *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) { return } +func (dbl *DebugLineInfo) AllPCsBetween(begin, end uint64) []uint64 { + var ( + pcs []uint64 + sm = newStateMachine(dbl) + buf = bytes.NewBuffer(dbl.Instructions) + ) + + for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { + findAndExecOpcode(sm, buf, b) + if sm.address > end { + break + } + if sm.address >= begin && sm.address <= end { + pcs = append(pcs, sm.address) + } + } + return pcs +} + func findAndExecOpcode(sm *StateMachine, buf *bytes.Buffer, b byte) { switch { case b == 0: diff --git a/proctl/breakpoints.go b/proctl/breakpoints.go index e840deed..f7b37a16 100644 --- a/proctl/breakpoints.go +++ b/proctl/breakpoints.go @@ -130,6 +130,14 @@ func (dbp *DebuggedProcess) setBreakpoint(tid int, addr uint64, temp bool) (*Bre return dbp.BreakPoints[addr], nil } +type NoBreakPointError struct { + addr uint64 +} + +func (nbp NoBreakPointError) Error() string { + return fmt.Sprintf("no breakpoint at %#v", nbp.addr) +} + func (dbp *DebuggedProcess) clearBreakpoint(tid int, addr uint64) (*BreakPoint, error) { thread := dbp.Threads[tid] // Check for hardware breakpoint @@ -154,5 +162,5 @@ func (dbp *DebuggedProcess) clearBreakpoint(tid int, addr uint64) (*BreakPoint, delete(dbp.BreakPoints, addr) return bp, nil } - return nil, fmt.Errorf("no breakpoint at %#v", addr) + return nil, NoBreakPointError{addr: addr} } diff --git a/proctl/proctl.go b/proctl/proctl.go index 0f07d3c3..46963815 100644 --- a/proctl/proctl.go +++ b/proctl/proctl.go @@ -31,7 +31,6 @@ type DebuggedProcess struct { HWBreakPoints [4]*BreakPoint BreakPoints map[uint64]*BreakPoint Threads map[int]*ThreadContext - CurrentBreakpoint *BreakPoint CurrentThread *ThreadContext dwarf *dwarf.Data goSymTable *gosym.Table @@ -305,12 +304,9 @@ func (dbp *DebuggedProcess) next() error { if goroutineExiting { break } - if dbp.CurrentBreakpoint != nil { - bp, err := dbp.Clear(dbp.CurrentBreakpoint.Addr) - if err != nil { - return err - } - if !bp.hardware { + if thread.CurrentBreakpoint != nil { + bp := thread.CurrentBreakpoint + if bp != nil && !bp.hardware { if err = thread.SetPC(bp.Addr); err != nil { return err } @@ -322,7 +318,7 @@ func (dbp *DebuggedProcess) next() error { return err } // Make sure we're on the same goroutine. - // TODO(dp) take into account goroutine exit. + // TODO(dp) better take into account goroutine exit. if tg.Id == curg.Id { if dbp.CurrentThread != thread { dbp.SwitchThread(thread.Id) @@ -450,6 +446,11 @@ func (dbp *DebuggedProcess) CurrentPC() (uint64, error) { return dbp.CurrentThread.CurrentPC() } +// Returns the PC of the current thread. +func (dbp *DebuggedProcess) CurrentBreakpoint() *BreakPoint { + return dbp.CurrentThread.CurrentBreakpoint +} + // Returns the value of the named symbol. func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) { return dbp.CurrentThread.EvalSymbol(name) @@ -527,6 +528,7 @@ func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) { return &dbp, nil } + func (dbp *DebuggedProcess) clearTempBreakpoints() error { for _, bp := range dbp.HWBreakPoints { if bp != nil && bp.Temp { @@ -545,6 +547,7 @@ func (dbp *DebuggedProcess) clearTempBreakpoints() error { } return nil } + func (dbp *DebuggedProcess) handleBreakpointOnThread(id int) (*ThreadContext, error) { thread, ok := dbp.Threads[id] if !ok { @@ -557,13 +560,13 @@ func (dbp *DebuggedProcess) handleBreakpointOnThread(id int) (*ThreadContext, er // Check for hardware breakpoint for _, bp := range dbp.HWBreakPoints { if bp != nil && bp.Addr == pc { - dbp.CurrentBreakpoint = bp + thread.CurrentBreakpoint = bp return thread, nil } } // Check to see if we have hit a software breakpoint. if bp, ok := dbp.BreakPoints[pc-1]; ok { - dbp.CurrentBreakpoint = bp + thread.CurrentBreakpoint = bp return thread, nil } return thread, nil @@ -575,7 +578,9 @@ func (dbp *DebuggedProcess) run(fn func() error) error { } dbp.running = true dbp.halt = false - dbp.CurrentBreakpoint = nil + for _, th := range dbp.Threads { + th.CurrentBreakpoint = nil + } defer func() { dbp.running = false }() if err := fn(); err != nil { if _, ok := err.(ManualStopError); !ok { diff --git a/proctl/threads.go b/proctl/threads.go index 2e77408f..c6decbc4 100644 --- a/proctl/threads.go +++ b/proctl/threads.go @@ -14,11 +14,12 @@ import ( // a whole, and Status represents the last result of a `wait` call // on this thread. type ThreadContext struct { - Id int - Process *DebuggedProcess - Status *sys.WaitStatus - running bool - os *OSSpecificDetails + Id int + Process *DebuggedProcess + Status *sys.WaitStatus + CurrentBreakpoint *BreakPoint + running bool + os *OSSpecificDetails } // An interface for a generic register type. The @@ -182,7 +183,7 @@ func (thread *ThreadContext) Next() (err error) { return err } } else { - if err = thread.cnext(curpc, fde, f, l); err != nil { + if err = thread.cnext(curpc, fde); err != nil { return err } } @@ -233,20 +234,30 @@ func (thread *ThreadContext) next(curpc uint64, fde *frame.FrameDescriptionEntry } return GoroutineExitingError{goid: g.Id} } - pcs = append(pcs, ret) } + pcs = append(pcs, ret) + return thread.setNextTempBreakpoints(curpc, pcs) +} - // Set a breakpoint at every line reachable from our location. - for _, pc := range pcs { - // Do not set breakpoint at our current location. - if pc == curpc { +// Set a breakpoint at every reachable location, as well as the return address. Without +// the benefit of an AST we can't be sure we're not at a branching statement and thus +// cannot accurately predict where we may end up. +func (thread *ThreadContext) cnext(curpc uint64, fde *frame.FrameDescriptionEntry) error { + pcs := thread.Process.lineInfo.AllPCsBetween(fde.Begin(), fde.End()) + ret, err := thread.ReturnAddress() + if err != nil { + return err + } + pcs = append(pcs, ret) + return thread.setNextTempBreakpoints(curpc, pcs) +} + +func (thread *ThreadContext) setNextTempBreakpoints(curpc uint64, pcs []uint64) error { + for i := range pcs { + if pcs[i] == curpc || pcs[i] == curpc-1 { continue } - // If the PC is not covered by our frame, set breakpoint at return address. - if !fde.Cover(pc) { - pc = ret - } - if _, err := thread.Process.TempBreak(pc); err != nil { + if _, err := thread.Process.TempBreak(pcs[i]); err != nil { if err, ok := err.(BreakPointExistsError); !ok { return err } @@ -255,12 +266,6 @@ func (thread *ThreadContext) next(curpc uint64, fde *frame.FrameDescriptionEntry return nil } -func (thread *ThreadContext) cnext(curpc uint64, fde *frame.FrameDescriptionEntry, file string, line int) error { - // TODO(dp) We are not in a Go source file, we should fall back on DWARF line information - // and use that to set breakpoints. - return nil -} - func (thread *ThreadContext) SetPC(pc uint64) error { regs, err := thread.Registers() if err != nil { diff --git a/proctl/threads_darwin.go b/proctl/threads_darwin.go index eabd4306..6e3c1053 100644 --- a/proctl/threads_darwin.go +++ b/proctl/threads_darwin.go @@ -50,7 +50,7 @@ func (t *ThreadContext) blocked() bool { // TODO(dp) cache the func pc to remove this lookup pc, _ := t.CurrentPC() fn := t.Process.goSymTable.PCToFunc(pc) - if fn != nil && (fn.Name == "runtime.mach_semaphore_wait") { + if fn != nil && (fn.Name == "runtime.mach_semaphore_wait" || fn.Name == "runtime.usleep") { return true } return false