diff --git a/dwarf/line/state_machine.go b/dwarf/line/state_machine.go index 197e7584..fdf070d9 100644 --- a/dwarf/line/state_machine.go +++ b/dwarf/line/state_machine.go @@ -8,6 +8,13 @@ import ( "github.com/derekparker/dbg/dwarf/util" ) +type Location struct { + File string + Line int + Address uint64 + Delta int +} + type StateMachine struct { Dbl *DebugLineInfo File string @@ -18,6 +25,7 @@ type StateMachine struct { BasicBlock bool EndSeq bool LastWasStandard bool + LastDelta int } type opcodefn func(*StateMachine, *bytes.Buffer) @@ -66,18 +74,7 @@ func newStateMachine(dbl *DebugLineInfo) *StateMachine { // Returns the filename, line number and PC for the next executable line in // the traced program. -func (dbl *DebugLineInfo) NextLocAfterPC(pc uint64) (string, int, uint64) { - var ( - sm = newStateMachine(dbl) - buf = bytes.NewBuffer(dbl.Instructions) - ) - - executeUntilPC(sm, buf, pc) - - return sm.File, sm.Line, sm.Address -} - -func (dbl *DebugLineInfo) LoopExitLocation(pc uint64) (string, int, uint64) { +func (dbl *DebugLineInfo) NextLocAfterPC(pc uint64) *Location { var ( sm = newStateMachine(dbl) buf = bytes.NewBuffer(dbl.Instructions) @@ -85,6 +82,33 @@ func (dbl *DebugLineInfo) LoopExitLocation(pc uint64) (string, int, uint64) { executeUntilPC(sm, buf, pc) line := sm.Line + for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { + findAndExecOpcode(sm, buf, b) + + if sm.Line > line { + break + } + } + + return &Location{sm.File, sm.Line, sm.Address, sm.LastDelta} +} + +func (dbl *DebugLineInfo) LocationInfoForPC(pc uint64) *Location { + var ( + sm = newStateMachine(dbl) + buf = bytes.NewBuffer(dbl.Instructions) + ) + + executeUntilPC(sm, buf, pc) + + return &Location{sm.File, sm.Line, sm.Address, sm.LastDelta} +} + +func (dbl *DebugLineInfo) LoopEntryLocation(line int) *Location { + var ( + sm = newStateMachine(dbl) + buf = bytes.NewBuffer(dbl.Instructions) + ) for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { findAndExecOpcode(sm, buf, b) @@ -94,25 +118,48 @@ func (dbl *DebugLineInfo) LoopExitLocation(pc uint64) (string, int, uint64) { } } - return sm.File, sm.Line, sm.Address + return &Location{sm.File, sm.Line, sm.Address, sm.LastDelta} } -func executeUntilPC(sm *StateMachine, buf *bytes.Buffer, pc uint64) { +func (dbl *DebugLineInfo) LoopExitLocation(pc uint64) *Location { + var ( + line int + sm = newStateMachine(dbl) + buf = bytes.NewBuffer(dbl.Instructions) + ) + + executeUntilPC(sm, buf, pc) + line = sm.Line + for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { findAndExecOpcode(sm, buf, b) - if sm.Address > pc { - if sm.LastWasStandard { - b, err = buf.ReadByte() - if err != nil { - panic(err) - } - - findAndExecOpcode(sm, buf, b) - } + if sm.Line > line { break } } + + return &Location{sm.File, sm.Line, sm.Address, sm.LastDelta} +} + +func executeUntilPC(sm *StateMachine, buf *bytes.Buffer, pc uint64) { + var line int + + for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() { + findAndExecOpcode(sm, buf, b) + + if line != 0 && sm.Line > line { + break + } + + if sm.Address == pc { + if !sm.LastWasStandard { + break + } + + line = sm.Line + } + } } func findAndExecOpcode(sm *StateMachine, buf *bytes.Buffer, b byte) { @@ -136,7 +183,8 @@ func execSpecialOpcode(sm *StateMachine, instr byte) { sm.IsStmt = true } - sm.Line += int(sm.Dbl.Prologue.LineBase + int8(decoded%sm.Dbl.Prologue.LineRange)) + sm.LastDelta = int(sm.Dbl.Prologue.LineBase + int8(decoded%sm.Dbl.Prologue.LineRange)) + sm.Line += sm.LastDelta sm.Address += uint64(decoded / sm.Dbl.Prologue.LineRange) sm.BasicBlock = false sm.LastWasStandard = false @@ -174,8 +222,9 @@ func advancepc(sm *StateMachine, buf *bytes.Buffer) { } func advanceline(sm *StateMachine, buf *bytes.Buffer) { - l, _ := util.DecodeSLEB128(buf) - sm.Line += int(l) + line, _ := util.DecodeSLEB128(buf) + sm.Line += int(line) + sm.LastDelta = int(line) } func setfile(sm *StateMachine, buf *bytes.Buffer) { diff --git a/dwarf/line/state_machine_test.go b/dwarf/line/state_machine_test.go index 5db8b7c6..759b1f14 100644 --- a/dwarf/line/state_machine_test.go +++ b/dwarf/line/state_machine_test.go @@ -21,13 +21,13 @@ func TestNextLocAfterPC(t *testing.T) { pc, _, _ = gosym.LineToPC(testfile+".go", 20) ) - f, l, _ := dbl.NextLocAfterPC(pc) + loc := dbl.NextLocAfterPC(pc) - if f != testfile+".go" { - t.Fatal("File not returned correctly", f) + if loc.File != testfile+".go" { + t.Fatal("File not returned correctly", loc.File) } - if l != 22 { - t.Fatal("Line not returned correctly", l) + if loc.Line != 22 { + t.Fatal("Line not returned correctly", loc.Line) } } diff --git a/proctl/proctl_linux_amd64.go b/proctl/proctl_linux_amd64.go index fd8ff3eb..52b5773c 100644 --- a/proctl/proctl_linux_amd64.go +++ b/proctl/proctl_linux_amd64.go @@ -3,6 +3,7 @@ package proctl import ( + "bytes" "debug/elf" "debug/gosym" "encoding/binary" @@ -40,6 +41,16 @@ type BreakPoint struct { OriginalData []byte } +type BreakPointExistsError struct { + file string + line int + addr uintptr +} + +func (bpe BreakPointExistsError) Error() string { + return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr) +} + // Returns a new DebuggedProcess struct with sensible defaults. func NewDebugProcess(pid int) (*DebuggedProcess, error) { proc, err := os.FindProcess(pid) @@ -122,6 +133,10 @@ func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) { return nil, err } + if bytes.Equal(originalData, int3) { + return nil, BreakPointExistsError{f, l, addr} + } + _, err = syscall.PtracePokeData(dbp.Pid, addr, int3) if err != nil { return nil, err @@ -196,47 +211,41 @@ func (dbp *DebuggedProcess) Step() (err error) { // Step over function calls. func (dbp *DebuggedProcess) Next() error { + addrs := make([]uint64, 0, 3) pc, err := dbp.CurrentPC() if err != nil { return err } - _, l, _ := dbp.GoSymTable.PCToLine(pc) - fde, _ := dbp.FrameEntries.FDEForPC(pc) - _, nl, addr := dbp.DebugLine.NextLocAfterPC(pc) - - if !fde.AddressRange.Cover(addr) { - offset := fde.ReturnAddressOffset(pc) - addr = dbp.ReturnAddressFromOffset(offset) + fde, err := dbp.FrameEntries.FDEForPC(pc) + if err != nil { + return err } - if nl < l { - // We are likely in a loop, set a breakpoint at the - // first instruction following the loop. - _, _, loopaddr := dbp.DebugLine.LoopExitLocation(pc) - _, ok := dbp.PCtoBP(loopaddr) - if !ok { - _, err = dbp.Break(uintptr(loopaddr)) - if err != nil { + loc := dbp.DebugLine.NextLocAfterPC(pc - 1) + if !fde.AddressRange.Cover(loc.Address) { + // Next line is outside current frame, use return addr. + addr := dbp.ReturnAddressFromOffset(fde.ReturnAddressOffset(pc)) + loc = dbp.DebugLine.LocationInfoForPC(addr) + } + addrs = append(addrs, loc.Address) + + if loc.Delta < 0 { + // We are likely in a loop, set breakpoints at entry and exit. + entry := dbp.DebugLine.LoopEntryLocation(loc.Line) + exit := dbp.DebugLine.LoopExitLocation(pc - 1) + addrs = append(addrs, entry.Address, exit.Address) + } + + for _, addr := range addrs { + if _, err := dbp.Break(uintptr(addr)); err != nil { + if _, ok := err.(BreakPointExistsError); !ok { return err } } } - _, ok := dbp.PCtoBP(addr) - if !ok { - _, err = dbp.Break(uintptr(addr)) - if err != nil { - return err - } - } - - err = dbp.Continue() - if err != nil { - return err - } - - return nil + return dbp.Continue() } // Continue process until next breakpoint. diff --git a/proctl/proctl_test.go b/proctl/proctl_test.go index 4a98c1a1..b7ce5140 100644 --- a/proctl/proctl_test.go +++ b/proctl/proctl_test.go @@ -185,6 +185,7 @@ func TestNext(t *testing.T) { {22, 25}, {25, 26}, {26, 30}, + {30, 31}, } fp, err := filepath.Abs("../_fixtures/testnextprog.go")