Further improve next command

* Fixes incorrect loop `next`ing behaviour
* Includes fix for determining return address
This commit is contained in:
Derek Parker 2014-07-11 14:52:55 -05:00
parent 3566fd5237
commit 07fec48272
4 changed files with 119 additions and 60 deletions

@ -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) {

@ -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)
}
}

@ -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.

@ -185,6 +185,7 @@ func TestNext(t *testing.T) {
{22, 25},
{25, 26},
{26, 30},
{30, 31},
}
fp, err := filepath.Abs("../_fixtures/testnextprog.go")