mirror of
https://github.com/go-delve/delve.git
synced 2025-10-27 20:23:41 +08:00
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:
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user