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:
parent
77056de757
commit
5155ef047f
@ -36,6 +36,7 @@ Pass flags to the program you are debugging using `--`, for example:
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -36,6 +36,7 @@ dlv attach pid [executable]
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -31,6 +31,7 @@ dlv connect addr
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -35,6 +35,7 @@ dlv core <executable> <core>
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -42,6 +42,7 @@ dlv debug [package]
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -36,6 +36,7 @@ dlv exec <path/to/binary>
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -35,6 +35,7 @@ dlv replay [trace directory]
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -31,6 +31,7 @@ dlv run
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -42,6 +42,7 @@ dlv test [package]
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -44,6 +44,7 @@ dlv trace [package] regexp
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -31,6 +31,7 @@ dlv version
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
@ -4,7 +4,7 @@ import "fmt"
|
||||
|
||||
func inlineThis(a int) int {
|
||||
z := a * a
|
||||
return z + a/a
|
||||
return z + a
|
||||
}
|
||||
|
||||
func initialize(a, b *int) {
|
||||
|
@ -93,6 +93,7 @@ func New(docCall bool) *cobra.Command {
|
||||
debugger Log debugger commands
|
||||
gdbwire Log connection to gdbserial backend
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
Defaults to "debugger" when logging is enabled with --log.`)
|
||||
RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
|
||||
RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.")
|
||||
|
@ -32,6 +32,9 @@ type DebugLineInfo struct {
|
||||
|
||||
// lastMachineCache[pc] is a state machine stopped at an address after pc
|
||||
lastMachineCache map[uint64]*StateMachine
|
||||
|
||||
// logSuppressedErrors enables logging of otherwise suppressed errors
|
||||
logSuppressedErrors bool
|
||||
}
|
||||
|
||||
type FileEntry struct {
|
||||
@ -143,3 +146,8 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool)
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// LogSuppressedErrors enables or disables logging of suppressed errors
|
||||
func (dbl *DebugLineInfo) LogSuppressedErrors(v bool) {
|
||||
dbl.logSuppressedErrors = v
|
||||
}
|
||||
|
@ -55,10 +55,14 @@ func grabDebugLineSection(p string, t *testing.T) []byte {
|
||||
}
|
||||
|
||||
const (
|
||||
lineBaseGo14 int8 = -1
|
||||
lineBaseGo18 int8 = -4
|
||||
lineRangeGo14 uint8 = 4
|
||||
lineRangeGo18 uint8 = 10
|
||||
lineBaseGo14 int8 = -1
|
||||
lineBaseGo18 int8 = -4
|
||||
lineRangeGo14 uint8 = 4
|
||||
lineRangeGo18 uint8 = 10
|
||||
versionGo14 uint16 = 2
|
||||
versionGo111 uint16 = 3
|
||||
opcodeBaseGo14 uint8 = 10
|
||||
opcodeBaseGo111 uint8 = 11
|
||||
)
|
||||
|
||||
func testDebugLinePrologueParser(p string, t *testing.T) {
|
||||
@ -70,7 +74,7 @@ func testDebugLinePrologueParser(p string, t *testing.T) {
|
||||
for _, dbl := range debugLines {
|
||||
prologue := dbl.Prologue
|
||||
|
||||
if prologue.Version != uint16(2) {
|
||||
if prologue.Version != versionGo14 && prologue.Version != versionGo111 {
|
||||
t.Fatal("Version not parsed correctly", prologue.Version)
|
||||
}
|
||||
|
||||
@ -94,11 +98,11 @@ func testDebugLinePrologueParser(p string, t *testing.T) {
|
||||
t.Fatal("Line Range not parsed correctly", prologue.LineRange)
|
||||
}
|
||||
|
||||
if prologue.OpcodeBase != uint8(10) {
|
||||
if prologue.OpcodeBase != opcodeBaseGo14 && prologue.OpcodeBase != opcodeBaseGo111 {
|
||||
t.Fatal("Opcode Base not parsed correctly", prologue.OpcodeBase)
|
||||
}
|
||||
|
||||
lengths := []uint8{0, 1, 1, 1, 1, 0, 0, 0, 1}
|
||||
lengths := []uint8{0, 1, 1, 1, 1, 0, 0, 0, 1, 0}
|
||||
for i, l := range prologue.StdOpLengths {
|
||||
if l != lengths[i] {
|
||||
t.Fatal("Length not parsed correctly", l)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
var debugger = false
|
||||
var gdbWire = false
|
||||
var lldbServerOutput = false
|
||||
var suppressedErrors = false
|
||||
var debugLineErrors = false
|
||||
|
||||
// GdbWire returns true if the gdbserial package should log all the packets
|
||||
// exchanged with the stub.
|
||||
@ -26,6 +28,12 @@ func LLDBServerOutput() bool {
|
||||
return lldbServerOutput
|
||||
}
|
||||
|
||||
// DebugLineErrors returns true if pkg/dwarf/line should log its recoverable
|
||||
// errors.
|
||||
func DebugLineErrors() bool {
|
||||
return debugLineErrors
|
||||
}
|
||||
|
||||
var errLogstrWithoutLog = errors.New("--log-output specified without --log")
|
||||
|
||||
// Setup sets debugger flags based on the contents of logstr.
|
||||
@ -48,6 +56,8 @@ func Setup(log bool, logstr string) error {
|
||||
gdbWire = true
|
||||
case "lldbout":
|
||||
lldbServerOutput = true
|
||||
case "debuglineerr":
|
||||
debugLineErrors = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -136,9 +136,12 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn
|
||||
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry
|
||||
func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
|
||||
// firstPCAfterPrologueDisassembly returns the address of the first
|
||||
// instruction after the prologue for function fn by disassembling fn and
|
||||
// matching the instructions against known split-stack prologue patterns.
|
||||
// If sameline is set firstPCAfterPrologueDisassembly will always return an
|
||||
// address associated with the same line as fn.Entry
|
||||
func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (uint64, error) {
|
||||
var mem MemoryReadWriter = p.CurrentThread()
|
||||
breakpoints := p.Breakpoints()
|
||||
bi := p.BinInfo()
|
||||
|
@ -983,6 +983,9 @@ func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []b
|
||||
}
|
||||
|
||||
func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
|
||||
if p.exited {
|
||||
return nil, &proc.ProcessExitedError{Pid: p.conn.pid}
|
||||
}
|
||||
return p.breakpoints.Set(addr, kind, cond, p.writeBreakpoint)
|
||||
}
|
||||
|
||||
|
@ -328,7 +328,7 @@ func StepOut(dbp Process) error {
|
||||
if selg != nil {
|
||||
deferPCEntry := selg.DeferPC()
|
||||
if deferPCEntry != 0 {
|
||||
_, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry)
|
||||
deferfn := dbp.BinInfo().PCToFunc(deferPCEntry)
|
||||
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -565,3 +565,38 @@ func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpoint
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FirstPCAfterPrologue returns the address of the first
|
||||
// instruction after the prologue for function fn.
|
||||
// If sameline is set FirstPCAfterPrologue will always return an
|
||||
// address associated with the same line as fn.Entry.
|
||||
func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
|
||||
pc, _, line, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End)
|
||||
if ok {
|
||||
if !sameline {
|
||||
return pc, nil
|
||||
} else {
|
||||
_, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
|
||||
if entryLine == line {
|
||||
return pc, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pc, err := firstPCAfterPrologueDisassembly(p, fn, sameline)
|
||||
if err != nil {
|
||||
return fn.Entry, err
|
||||
}
|
||||
|
||||
if pc == fn.Entry {
|
||||
// Look for the first instruction with the stmt flag set, so that setting a
|
||||
// breakpoint with file:line and with the function name always result on
|
||||
// the same instruction being selected.
|
||||
entryFile, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry)
|
||||
if pc, _, err := p.BinInfo().LineToPC(entryFile, entryLine); err == nil {
|
||||
return pc, nil
|
||||
}
|
||||
}
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
@ -897,6 +897,9 @@ func stackMatch(stack []loc, locations []proc.Stackframe, skipRuntime bool) bool
|
||||
|
||||
func TestStacktraceGoroutine(t *testing.T) {
|
||||
mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}}
|
||||
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
|
||||
mainStack[0].line = 15
|
||||
}
|
||||
agoroutineStacks := [][]loc{
|
||||
{{8, "main.agoroutine"}},
|
||||
{{9, "main.agoroutine"}},
|
||||
@ -3618,6 +3621,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) {
|
||||
t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 6, len(pcs), pcs)
|
||||
}
|
||||
for _, pc := range pcs {
|
||||
t.Logf("setting breakpoint at %#x\n", pc)
|
||||
_, err := p.SetBreakpoint(pc, proc.UserBreakpoint, nil)
|
||||
assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x)", pc))
|
||||
}
|
||||
@ -3628,7 +3632,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) {
|
||||
assertNoError(err, t, "ThreadStacktrace")
|
||||
t.Logf("Stacktrace:\n")
|
||||
for i := range frames {
|
||||
t.Logf("\t%s at %s:%d\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line)
|
||||
t.Logf("\t%s at %s:%d (%#x)\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line, frames[i].Current.PC)
|
||||
}
|
||||
|
||||
if err := checkFrame(frames[0], "main.inlineThis", fixture.Source, 7, true); err != nil {
|
||||
@ -3655,7 +3659,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) {
|
||||
assertNoError(err, t, "ThreadStacktrace (2)")
|
||||
t.Logf("Stacktrace 2:\n")
|
||||
for i := range frames {
|
||||
t.Logf("\t%s at %s:%d\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line)
|
||||
t.Logf("\t%s at %s:%d (%#x)\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line, frames[i].Current.PC)
|
||||
}
|
||||
|
||||
if err := checkFrame(frames[0], "main.inlineThis", fixture.Source, 7, true); err != nil {
|
||||
@ -3716,8 +3720,6 @@ func TestInlineStepOver(t *testing.T) {
|
||||
}
|
||||
testseq2Args(".", []string{}, protest.EnableInlining, t, "testinline", "", []seqTest{
|
||||
{contContinue, 18},
|
||||
{contNext, 18},
|
||||
{contNext, 19},
|
||||
{contNext, 19},
|
||||
{contNext, 20},
|
||||
})
|
||||
|
@ -209,7 +209,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
||||
if selg != nil {
|
||||
deferPCEntry := selg.DeferPC()
|
||||
if deferPCEntry != 0 {
|
||||
_, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry)
|
||||
deferfn := dbp.BinInfo().PCToFunc(deferPCEntry)
|
||||
var err error
|
||||
deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false)
|
||||
if err != nil {
|
||||
@ -231,7 +231,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
|
||||
}
|
||||
|
||||
// Add breakpoints on all the lines in the current function
|
||||
pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1)
|
||||
pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1, topframe.Current.File, topframe.Current.Line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/derekparker/delve/pkg/dwarf/line"
|
||||
"github.com/derekparker/delve/pkg/dwarf/op"
|
||||
"github.com/derekparker/delve/pkg/dwarf/reader"
|
||||
"github.com/derekparker/delve/pkg/logflags"
|
||||
)
|
||||
|
||||
// The kind field in runtime._type is a reflect.Kind value plus
|
||||
@ -217,6 +218,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou
|
||||
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
|
||||
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
|
||||
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]))
|
||||
cu.lineInfo.LogSuppressedErrors(logflags.DebugLineErrors())
|
||||
}
|
||||
if producer, _ := entry.Val(dwarf.AttrProducer).(string); cu.isgo && producer != "" {
|
||||
semicolon := strings.Index(producer, ";")
|
||||
|
Loading…
Reference in New Issue
Block a user