proc,dwarf/line: support is_stmt and prologue_end flags

Go1.11 uses the is_stmt flag of .debug_line to communicate which
assembly instructions are good places for breakpoints, we should
respect this flag.

These changes were introduced by:
* https://go-review.googlesource.com/c/go/+/102435/

Additionally when setting next breakpoints ignore all PC addresses that
belong to the same line as the one currently under at the cursor. This
matches the behavior of gdb and avoids stopping multiple times at the
heading line of a for statement with go1.11.

Change: https://go-review.googlesource.com/c/go/+/110416 adds the
prologue_end flag to the .debug_line section to communicate the end of
the stack-split prologue. We should use it instead of pattern matching
the disassembly when available.

Fixes #550

type of interfaces
'c7cde8b'.
This commit is contained in:
aarzilli
2018-03-25 10:04:32 +02:00
committed by Derek Parker
parent 77056de757
commit 5155ef047f
23 changed files with 210 additions and 68 deletions

View File

@ -5,6 +5,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"log"
"github.com/derekparker/delve/pkg/dwarf/util"
)
@ -17,22 +18,25 @@ type Location struct {
}
type StateMachine struct {
dbl *DebugLineInfo
file string
line int
address uint64
column uint
isStmt bool
basicBlock bool
endSeq bool
lastWasStandard bool
lastDelta int
dbl *DebugLineInfo
file string
line int
address uint64
column uint
isStmt bool
basicBlock bool
endSeq bool
lastDelta int
prologueEnd bool
epilogueBegin bool
// valid is true if the current value of the state machine is the address of
// an instruction (using the terminology used by DWARF spec the current
// value of the state machine should be appended to the matrix representing
// the compilation unit)
valid bool
lastOpcodeKind opcodeKind
started bool
buf *bytes.Buffer // remaining instructions
@ -45,6 +49,14 @@ type StateMachine struct {
lastLine int
}
type opcodeKind uint8
const (
specialOpcode opcodeKind = iota
standardOpcode
extendedOpcode
)
type opcodefn func(*StateMachine, *bytes.Buffer)
// Special opcodes
@ -58,6 +70,8 @@ const (
DW_LNS_set_basic_block = 7
DW_LNS_const_add_pc = 8
DW_LNS_fixed_advance_pc = 9
DW_LNS_prologue_end = 10
DW_LNS_epilogue_begin = 11
)
// Extended opcodes
@ -77,6 +91,8 @@ var standardopcodes = map[byte]opcodefn{
DW_LNS_set_basic_block: setbasicblock,
DW_LNS_const_add_pc: constaddpc,
DW_LNS_fixed_advance_pc: fixedadvancepc,
DW_LNS_prologue_end: prologueend,
DW_LNS_epilogue_begin: epiloguebegin,
}
var extendedopcodes = map[byte]opcodefn{
@ -91,7 +107,8 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
for op := range standardopcodes {
opcodes[op] = standardopcodes[op]
}
return &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes}
sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1)}
return sm
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
@ -102,34 +119,20 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64)
}
var (
foundFile bool
lastAddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
lastAddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
)
for {
if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors {
log.Printf("AllPCsForFileLine error: %v", err)
}
break
}
if foundFile && sm.file != f {
return
}
if sm.line == l && sm.file == f && sm.address != lastAddr {
foundFile = true
if sm.valid {
pcs = append(pcs, sm.address)
}
// Keep going until we're on a different line. We only care about
// when a line comes back around (i.e. for loop) so get to next line,
// and try to find the line we care about again.
for {
if err := sm.next(); err != nil {
break
}
if l != sm.line {
break
}
}
if sm.line == l && sm.file == f && sm.address != lastAddr && sm.isStmt && sm.valid {
pcs = append(pcs, sm.address)
lastAddr = sm.address
}
}
return
@ -137,7 +140,8 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64)
var NoSourceError = errors.New("no source available")
func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error) {
// AllPCsBetween returns all PC addresses between begin and end (including both begin and end) that have the is_stmt flag set and do not belong to excludeFile:excludeLine
func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile string, excludeLine int) ([]uint64, error) {
if lineInfo == nil {
return nil, NoSourceError
}
@ -150,6 +154,9 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error
for {
if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors {
log.Printf("AllPCsBetween error: %v", err)
}
break
}
if !sm.valid {
@ -158,7 +165,7 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error
if sm.address > end {
break
}
if sm.address >= begin && sm.address > lastaddr {
if (sm.address >= begin && sm.address > lastaddr) && sm.isStmt && ((sm.file != excludeFile) || (sm.line != excludeLine)) {
lastaddr = sm.address
pcs = append(pcs, sm.address)
}
@ -175,6 +182,17 @@ func (sm *StateMachine) copy() *StateMachine {
return &r
}
func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) {
sm = lineInfo.stateMachineCache[basePC]
if sm == nil {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm.PCToLine(basePC)
lineInfo.stateMachineCache[basePC] = sm
}
sm = sm.copy()
return
}
// PCToLine returns the filename and line number associated with pc.
// If pc isn't found inside lineInfo's table it will return the filename and
// line number associated with the closest PC address preceding pc.
@ -198,13 +216,7 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
// As a last resort start from the start of the debug_line section.
sm = lineInfo.lastMachineCache[basePC]
if sm == nil || sm.lastAddress > pc {
sm = lineInfo.stateMachineCache[basePC]
if sm == nil {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm.PCToLine(basePC)
lineInfo.stateMachineCache[basePC] = sm
}
sm = sm.copy()
sm = lineInfo.stateMachineForEntry(basePC)
lineInfo.lastMachineCache[basePC] = sm
}
}
@ -216,6 +228,9 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
if !sm.started {
if err := sm.next(); err != nil {
if sm.dbl.logSuppressedErrors {
log.Printf("PCToLine error: %v", err)
}
return "", 0, false
}
}
@ -232,6 +247,9 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
}
}
if err := sm.next(); err != nil {
if sm.dbl.logSuppressedErrors {
log.Printf("PCToLine error: %v", err)
}
break
}
}
@ -252,8 +270,15 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
)
// if no instruction marked is_stmt is found fallback to the first
// instruction assigned to the filename:line.
var fallbackPC uint64
for {
if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors {
log.Printf("LineToPC error: %v", err)
}
break
}
if foundFile && sm.file != filename {
@ -262,11 +287,36 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
if sm.line == lineno && sm.file == filename {
foundFile = true
if sm.valid {
return sm.address
if sm.isStmt {
return sm.address
} else if fallbackPC == 0 {
fallbackPC = sm.address
}
}
}
}
return 0
return fallbackPC
}
// PrologueEndPC returns the first PC address marked as prologue_end in the half open interval [start, end)
func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file string, line int, ok bool) {
sm := lineInfo.stateMachineForEntry(start)
for {
if sm.valid {
if sm.address >= end {
return 0, "", 0, false
}
if sm.prologueEnd {
return sm.address, sm.file, sm.line, true
}
}
if err := sm.next(); err != nil {
if lineInfo.logSuppressedErrors {
log.Printf("PrologueEnd error: %v", err)
}
return 0, "", 0, false
}
}
}
func (sm *StateMachine) next() error {
@ -282,12 +332,21 @@ func (sm *StateMachine) next() error {
sm.isStmt = false
sm.basicBlock = false
}
if sm.lastOpcodeKind == specialOpcode {
sm.basicBlock = false
sm.prologueEnd = false
sm.epilogueBegin = false
}
b, err := sm.buf.ReadByte()
if err != nil {
return err
}
if int(b) < len(sm.opcodes) {
sm.lastWasStandard = b != 0
if b == 0 {
sm.lastOpcodeKind = extendedOpcode
} else {
sm.lastOpcodeKind = standardOpcode
}
sm.valid = false
sm.opcodes[b](sm, sm.buf)
} else if b < sm.dbl.Prologue.OpcodeBase {
@ -309,15 +368,11 @@ func execSpecialOpcode(sm *StateMachine, instr byte) {
decoded = opcode - sm.dbl.Prologue.OpcodeBase
)
if sm.dbl.Prologue.InitialIsStmt == uint8(1) {
sm.isStmt = true
}
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) * uint64(sm.dbl.Prologue.MinInstrLength)
sm.basicBlock = false
sm.lastWasStandard = false
sm.lastOpcodeKind = specialOpcode
sm.valid = true
}
@ -400,3 +455,11 @@ func definefile(sm *StateMachine, buf *bytes.Buffer) {
entry := readFileEntry(sm.dbl, sm.buf, false)
sm.definedFiles = append(sm.definedFiles, entry)
}
func prologueend(sm *StateMachine, buf *bytes.Buffer) {
sm.prologueEnd = true
}
func epiloguebegin(sm *StateMachine, buf *bytes.Buffer) {
sm.epilogueBegin = true
}