proc,debugger: implement logical breakpoints (#1717)
Modifies FindFileLocation, FindFunctionLocation and LineToPC as well as service/debugger to support inlining and introduces the concept of logical breakpoints. For inlined functions FindFileLocation, FindFunctionLocation and LineToPC will now return one PC address for each inlining and one PC for the concrete implementation of the function (if present). A proc.Breakpoint will continue to represent a physical breakpoint, at a single memory location. Breakpoints returned by service/debugger, however, will represent logical breakpoints and may be associated with multiple memory locations and, therefore, multiple proc.Breakpoints. The necessary logic is introduced in service/debugger so that a change to a logical breakpoint will be mirrored to all its physical breakpoints and physical breakpoints are aggregated into a single logical breakpoint when returned.
This commit is contained in:
parent
1c5e8abf34
commit
222deeec36
@ -112,33 +112,6 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
|
||||
return sm
|
||||
}
|
||||
|
||||
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
|
||||
// could be split amongst 2 PCs.
|
||||
func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) {
|
||||
if lineInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
lastAddr uint64
|
||||
sm = newStateMachine(lineInfo, lineInfo.Instructions)
|
||||
)
|
||||
|
||||
for {
|
||||
if err := sm.next(); err != nil {
|
||||
if lineInfo.Logf != nil {
|
||||
lineInfo.Logf("AllPCsForFileLine error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if sm.line == l && sm.file == f && sm.address != lastAddr && sm.isStmt && sm.valid {
|
||||
pcs = append(pcs, sm.address)
|
||||
lastAddr = sm.address
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AllPCsForFileLines Adds all PCs for a given file and set (domain of map) of lines
|
||||
// to the map value corresponding to each line.
|
||||
func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64) {
|
||||
@ -237,6 +210,13 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
|
||||
panic(fmt.Errorf("basePC after pc %#x %#x", basePC, pc))
|
||||
}
|
||||
|
||||
sm := lineInfo.stateMachineFor(basePC, pc)
|
||||
|
||||
file, line, _ := sm.PCToLine(pc)
|
||||
return file, line
|
||||
}
|
||||
|
||||
func (lineInfo *DebugLineInfo) stateMachineFor(basePC, pc uint64) *StateMachine {
|
||||
var sm *StateMachine
|
||||
if basePC == 0 {
|
||||
sm = newStateMachine(lineInfo, lineInfo.Instructions)
|
||||
@ -246,14 +226,12 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
|
||||
// machine stopped at the entry point of the function.
|
||||
// As a last resort start from the start of the debug_line section.
|
||||
sm = lineInfo.lastMachineCache[basePC]
|
||||
if sm == nil || sm.lastAddress > pc {
|
||||
if sm == nil || sm.lastAddress >= pc {
|
||||
sm = lineInfo.stateMachineForEntry(basePC)
|
||||
lineInfo.lastMachineCache[basePC] = sm
|
||||
}
|
||||
}
|
||||
|
||||
file, line, _ := sm.PCToLine(pc)
|
||||
return file, line
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) {
|
||||
@ -320,6 +298,42 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
|
||||
return fallbackPC
|
||||
}
|
||||
|
||||
// LineToPCIn returns the first PC for filename:lineno in the interval [startPC, endPC).
|
||||
// This function is used to find the instruction corresponding to
|
||||
// filename:lineno for a function that has been inlined.
|
||||
// basePC will be used for caching, it's normally the entry point for the
|
||||
// function containing pc.
|
||||
func (lineInfo *DebugLineInfo) LineToPCIn(filename string, lineno int, basePC, startPC, endPC uint64) uint64 {
|
||||
if lineInfo == nil {
|
||||
return 0
|
||||
}
|
||||
if basePC > startPC {
|
||||
panic(fmt.Errorf("basePC after startPC %#x %#x", basePC, startPC))
|
||||
}
|
||||
|
||||
sm := lineInfo.stateMachineFor(basePC, startPC)
|
||||
|
||||
for {
|
||||
if sm.valid && sm.started {
|
||||
if sm.address >= endPC {
|
||||
return 0
|
||||
}
|
||||
if sm.line == lineno && sm.file == filename && sm.address >= startPC && sm.isStmt {
|
||||
return sm.address
|
||||
}
|
||||
}
|
||||
if err := sm.next(); err != nil {
|
||||
if lineInfo.Logf != nil && err != io.EOF {
|
||||
lineInfo.Logf("LineToPC error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if lineInfo == nil {
|
||||
@ -345,6 +359,33 @@ func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file
|
||||
}
|
||||
}
|
||||
|
||||
// FirstStmtForLine looks in the half open interval [start, end) for the
|
||||
// first PC address marked as stmt for the line at address 'start'.
|
||||
func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, file string, line int, ok bool) {
|
||||
first := true
|
||||
sm := lineInfo.stateMachineForEntry(start)
|
||||
for {
|
||||
if sm.valid {
|
||||
if sm.address >= end {
|
||||
return 0, "", 0, false
|
||||
}
|
||||
if first {
|
||||
first = false
|
||||
file, line = sm.file, sm.line
|
||||
}
|
||||
if sm.isStmt && sm.file == file && sm.line == line {
|
||||
return sm.address, sm.file, sm.line, true
|
||||
}
|
||||
}
|
||||
if err := sm.next(); err != nil {
|
||||
if lineInfo.Logf != nil {
|
||||
lineInfo.Logf("StmtAfter error: %v", err)
|
||||
}
|
||||
return 0, "", 0, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *StateMachine) next() error {
|
||||
sm.started = true
|
||||
if sm.valid {
|
||||
|
@ -80,6 +80,11 @@ type BinaryInfo struct {
|
||||
// consts[off] lists all the constants with the type defined at offset off.
|
||||
consts constantsMap
|
||||
|
||||
// inlinedCallLines maps a file:line pair, corresponding to the header line
|
||||
// of a function to a list of PC addresses where an inlined call to that
|
||||
// function starts.
|
||||
inlinedCallLines map[fileLine][]uint64
|
||||
|
||||
logger *logrus.Entry
|
||||
}
|
||||
|
||||
@ -107,32 +112,32 @@ type compileUnit struct {
|
||||
lowPC uint64
|
||||
ranges [][2]uint64
|
||||
|
||||
entry *dwarf.Entry // debug_info entry describing this compile unit
|
||||
isgo bool // true if this is the go compile unit
|
||||
lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit
|
||||
concreteInlinedFns []inlinedFn // list of concrete inlined functions within this compile unit
|
||||
optimized bool // this compile unit is optimized
|
||||
producer string // producer attribute
|
||||
entry *dwarf.Entry // debug_info entry describing this compile unit
|
||||
isgo bool // true if this is the go compile unit
|
||||
lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit
|
||||
optimized bool // this compile unit is optimized
|
||||
producer string // producer attribute
|
||||
|
||||
offset dwarf.Offset // offset of the entry describing the compile unit
|
||||
|
||||
image *Image // parent image of this compilation unit.
|
||||
}
|
||||
|
||||
type fileLine struct {
|
||||
file string
|
||||
line int
|
||||
}
|
||||
|
||||
// dwarfRef is a reference to a Debug Info Entry inside a shared object.
|
||||
type dwarfRef struct {
|
||||
imageIndex int
|
||||
offset dwarf.Offset
|
||||
}
|
||||
|
||||
// inlinedFn represents a concrete inlined function, e.g.
|
||||
// an entry for the generated code of an inlined function.
|
||||
type inlinedFn struct {
|
||||
Name string // Name of the function that was inlined
|
||||
LowPC, HighPC uint64 // Address range of the generated inlined instructions
|
||||
CallFile string // File of the call site of the inlined function
|
||||
CallLine int64 // Line of the call site of the inlined function
|
||||
Parent *Function // The function that contains this inlined function
|
||||
// InlinedCall represents a concrete inlined call to a function.
|
||||
type InlinedCall struct {
|
||||
cu *compileUnit
|
||||
LowPC, HighPC uint64 // Address range of the generated inlined instructions
|
||||
}
|
||||
|
||||
// Function describes a function in the target program.
|
||||
@ -141,6 +146,9 @@ type Function struct {
|
||||
Entry, End uint64 // same as DW_AT_lowpc and DW_AT_highpc
|
||||
offset dwarf.Offset
|
||||
cu *compileUnit
|
||||
|
||||
// InlinedCalls lists all inlined calls to this function
|
||||
InlinedCalls []InlinedCall
|
||||
}
|
||||
|
||||
// PackageName returns the package part of the symbol name,
|
||||
@ -389,39 +397,64 @@ func (err *ErrCouldNotFindLine) Error() string {
|
||||
return fmt.Sprintf("could not find file %s", err.filename)
|
||||
}
|
||||
|
||||
// LineToPC converts a file:line into a memory address.
|
||||
func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *Function, err error) {
|
||||
// LineToPC converts a file:line into a list of matching memory addresses,
|
||||
// corresponding to the first instruction matching the specified file:line
|
||||
// in the containing function and all its inlined calls.
|
||||
func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pcs []uint64, err error) {
|
||||
fileFound := false
|
||||
var pc uint64
|
||||
for _, cu := range bi.compileUnits {
|
||||
if cu.lineInfo.Lookup[filename] != nil {
|
||||
fileFound = true
|
||||
pc := cu.lineInfo.LineToPC(filename, lineno)
|
||||
if pc == 0 {
|
||||
// Check to see if this file:line belongs to the call site
|
||||
// of an inlined function.
|
||||
for _, ifn := range cu.concreteInlinedFns {
|
||||
if strings.Contains(ifn.CallFile, filename) && ifn.CallLine == int64(lineno) {
|
||||
return ifn.LowPC, ifn.Parent, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if fn := bi.PCToFunc(pc); fn != nil {
|
||||
return pc, fn, nil
|
||||
}
|
||||
if cu.lineInfo.Lookup[filename] == nil {
|
||||
continue
|
||||
}
|
||||
fileFound = true
|
||||
pc = cu.lineInfo.LineToPC(filename, lineno)
|
||||
if pc != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, nil, &ErrCouldNotFindLine{fileFound, filename, lineno}
|
||||
|
||||
if pc == 0 {
|
||||
// Check if the line contained a call to a function that was inlined, in
|
||||
// that case it's possible for the line itself to not appear in debug_line
|
||||
// at all, but it will still be in debug_info as the call site for an
|
||||
// inlined subroutine entry.
|
||||
if pcs := bi.inlinedCallLines[fileLine{filename, lineno}]; len(pcs) != 0 {
|
||||
return pcs, nil
|
||||
}
|
||||
return nil, &ErrCouldNotFindLine{fileFound, filename, lineno}
|
||||
}
|
||||
// The code above will find the first occurence of an instruction
|
||||
// corresponding to filename:line. If the function corresponding to that
|
||||
// instruction has been inlined we don't just want to return the first
|
||||
// occurence (which could be either the concrete version of the function or
|
||||
// one of the inlinings) but instead:
|
||||
// - the first instruction corresponding to filename:line in the concrete
|
||||
// version of the function
|
||||
// - the first instruction corresponding to filename:line in each inlined
|
||||
// instance of the function.
|
||||
fn := bi.PCToInlineFunc(pc)
|
||||
if fn == nil {
|
||||
return []uint64{pc}, nil
|
||||
}
|
||||
pcs = make([]uint64, 0, len(fn.InlinedCalls)+1)
|
||||
pcs = appendLineToPCIn(pcs, filename, lineno, fn.cu, fn, fn.Entry, fn.End)
|
||||
for _, call := range fn.InlinedCalls {
|
||||
pcs = appendLineToPCIn(pcs, filename, lineno, call.cu, bi.PCToFunc(call.LowPC), call.LowPC, call.HighPC)
|
||||
}
|
||||
return pcs, nil
|
||||
}
|
||||
|
||||
// AllPCsForFileLine returns all PC addresses for the given filename:lineno.
|
||||
func (bi *BinaryInfo) AllPCsForFileLine(filename string, lineno int) []uint64 {
|
||||
r := make([]uint64, 0, 1)
|
||||
for _, cu := range bi.compileUnits {
|
||||
if cu.lineInfo.Lookup[filename] != nil {
|
||||
r = append(r, cu.lineInfo.AllPCsForFileLine(filename, lineno)...)
|
||||
}
|
||||
func appendLineToPCIn(pcs []uint64, filename string, lineno int, cu *compileUnit, containingFn *Function, lowPC, highPC uint64) []uint64 {
|
||||
var entry uint64
|
||||
if containingFn != nil {
|
||||
entry = containingFn.Entry
|
||||
}
|
||||
return r
|
||||
pc := cu.lineInfo.LineToPCIn(filename, lineno, entry, lowPC, highPC)
|
||||
if pc != 0 {
|
||||
return append(pcs, pc)
|
||||
}
|
||||
return pcs
|
||||
}
|
||||
|
||||
// AllPCsForFileLines returns a map providing all PC addresses for filename and each line in linenos
|
||||
@ -438,7 +471,8 @@ func (bi *BinaryInfo) AllPCsForFileLines(filename string, linenos []int) map[int
|
||||
return r
|
||||
}
|
||||
|
||||
// PCToFunc returns the function containing the given PC address
|
||||
// PCToFunc returns the concrete function containing the given PC address.
|
||||
// If the PC address belongs to an inlined call it will return the containing function.
|
||||
func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
|
||||
i := sort.Search(len(bi.Functions), func(i int) bool {
|
||||
fn := bi.Functions[i]
|
||||
@ -453,6 +487,29 @@ func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PCToInlineFunc returns the function containing the given PC address.
|
||||
// If the PC address belongs to an inlined call it will return the inlined function.
|
||||
func (bi *BinaryInfo) PCToInlineFunc(pc uint64) *Function {
|
||||
fn := bi.PCToFunc(pc)
|
||||
irdr := reader.InlineStack(fn.cu.image.dwarf, fn.offset, reader.ToRelAddr(pc, fn.cu.image.StaticBase))
|
||||
var inlineFnEntry *dwarf.Entry
|
||||
for irdr.Next() {
|
||||
inlineFnEntry = irdr.Entry()
|
||||
}
|
||||
|
||||
if inlineFnEntry == nil {
|
||||
return fn
|
||||
}
|
||||
|
||||
e, _ := reader.LoadAbstractOrigin(inlineFnEntry, fn.cu.image.dwarfReader)
|
||||
fnname, okname := e.Val(dwarf.AttrName).(string)
|
||||
if !okname {
|
||||
return fn
|
||||
}
|
||||
|
||||
return bi.LookupFunc[fnname]
|
||||
}
|
||||
|
||||
// PCToImage returns the image containing the given PC address.
|
||||
func (bi *BinaryInfo) PCToImage(pc uint64) *Image {
|
||||
fn := bi.PCToFunc(pc)
|
||||
@ -1281,6 +1338,10 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
|
||||
if bi.packageMap == nil {
|
||||
bi.packageMap = make(map[string]string)
|
||||
}
|
||||
if bi.inlinedCallLines == nil {
|
||||
bi.inlinedCallLines = make(map[fileLine][]uint64)
|
||||
}
|
||||
|
||||
image.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE)
|
||||
|
||||
ctxt := newLoadDebugInfoMapsContext(bi, image)
|
||||
@ -1440,7 +1501,6 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex
|
||||
|
||||
case dwarf.TagSubprogram:
|
||||
inlined := false
|
||||
|
||||
if inval, ok := entry.Val(dwarf.AttrInline).(int64); ok {
|
||||
inlined = inval == 1
|
||||
}
|
||||
@ -1496,11 +1556,11 @@ func (bi *BinaryInfo) addAbstractSubprogram(entry *dwarf.Entry, ctxt *loadDebugI
|
||||
}
|
||||
|
||||
if entry.Children {
|
||||
bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu, &fn)
|
||||
bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
|
||||
}
|
||||
|
||||
bi.Functions = append(bi.Functions, fn)
|
||||
ctxt.abstractOriginNameTable[entry.Offset] = name
|
||||
ctxt.abstractOriginTable[entry.Offset] = len(bi.Functions) - 1
|
||||
}
|
||||
|
||||
// addConcreteInlinedSubprogram adds the concrete entry of a subprogram that was also inlined.
|
||||
@ -1514,7 +1574,7 @@ func (bi *BinaryInfo) addConcreteInlinedSubprogram(entry *dwarf.Entry, originOff
|
||||
return
|
||||
}
|
||||
|
||||
name, ok := ctxt.abstractOriginNameTable[originOffset]
|
||||
originIdx, ok := ctxt.abstractOriginTable[originOffset]
|
||||
if !ok {
|
||||
bi.logger.Errorf("Error reading debug_info: could not find abstract origin of concrete inlined subprogram at %#x (origin offset %#x)", entry.Offset, originOffset)
|
||||
if entry.Children {
|
||||
@ -1523,21 +1583,18 @@ func (bi *BinaryInfo) addConcreteInlinedSubprogram(entry *dwarf.Entry, originOff
|
||||
return
|
||||
}
|
||||
|
||||
fn := Function{
|
||||
Name: name,
|
||||
Entry: lowpc, End: highpc,
|
||||
offset: entry.Offset,
|
||||
cu: cu,
|
||||
}
|
||||
bi.Functions = append(bi.Functions, fn)
|
||||
fn := &bi.Functions[originIdx]
|
||||
fn.offset = entry.Offset
|
||||
fn.Entry = lowpc
|
||||
fn.End = highpc
|
||||
|
||||
if entry.Children {
|
||||
bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu, &fn)
|
||||
bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
|
||||
}
|
||||
}
|
||||
|
||||
// addConcreteSubprogram adds a concrete subprogram (a normal subprogram
|
||||
// that doesn't have abstract or inlined entries).
|
||||
// that doesn't have abstract or inlined entries)
|
||||
func (bi *BinaryInfo) addConcreteSubprogram(entry *dwarf.Entry, ctxt *loadDebugInfoMapsContext, reader *reader.Reader, cu *compileUnit) {
|
||||
lowpc, highpc, ok := subprogramEntryRange(entry, cu.image)
|
||||
if !ok {
|
||||
@ -1567,7 +1624,7 @@ func (bi *BinaryInfo) addConcreteSubprogram(entry *dwarf.Entry, ctxt *loadDebugI
|
||||
bi.Functions = append(bi.Functions, fn)
|
||||
|
||||
if entry.Children {
|
||||
bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu, &fn)
|
||||
bi.loadDebugInfoMapsInlinedCalls(ctxt, reader, cu)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1592,7 +1649,7 @@ func subprogramEntryRange(entry *dwarf.Entry, image *Image) (lowpc, highpc uint6
|
||||
return lowpc, highpc, ok
|
||||
}
|
||||
|
||||
func (bi *BinaryInfo) loadDebugInfoMapsInlinedCalls(ctxt *loadDebugInfoMapsContext, reader *reader.Reader, cu *compileUnit, parentFn *Function) {
|
||||
func (bi *BinaryInfo) loadDebugInfoMapsInlinedCalls(ctxt *loadDebugInfoMapsContext, reader *reader.Reader, cu *compileUnit) {
|
||||
for {
|
||||
entry, err := reader.Next()
|
||||
if err != nil {
|
||||
@ -1610,12 +1667,13 @@ func (bi *BinaryInfo) loadDebugInfoMapsInlinedCalls(ctxt *loadDebugInfoMapsConte
|
||||
continue
|
||||
}
|
||||
|
||||
name, ok := ctxt.abstractOriginNameTable[originOffset]
|
||||
originIdx, ok := ctxt.abstractOriginTable[originOffset]
|
||||
if !ok {
|
||||
bi.logger.Errorf("Error reading debug_info: could not find abstract origin (%#x) of inlined call at %#x", originOffset, entry.Offset)
|
||||
reader.SkipChildren()
|
||||
continue
|
||||
}
|
||||
fn := &bi.Functions[originIdx]
|
||||
|
||||
lowpc, highpc, ok := subprogramEntryRange(entry, cu.image)
|
||||
if !ok {
|
||||
@ -1643,14 +1701,14 @@ func (bi *BinaryInfo) loadDebugInfoMapsInlinedCalls(ctxt *loadDebugInfoMapsConte
|
||||
}
|
||||
callfile := cu.lineInfo.FileNames[callfileidx-1].Path
|
||||
|
||||
cu.concreteInlinedFns = append(cu.concreteInlinedFns, inlinedFn{
|
||||
Name: name,
|
||||
LowPC: lowpc,
|
||||
HighPC: highpc,
|
||||
CallFile: callfile,
|
||||
CallLine: callline,
|
||||
Parent: parentFn,
|
||||
fn.InlinedCalls = append(fn.InlinedCalls, InlinedCall{
|
||||
cu: cu,
|
||||
LowPC: lowpc,
|
||||
HighPC: highpc,
|
||||
})
|
||||
|
||||
fl := fileLine{callfile, int(callline)}
|
||||
bi.inlinedCallLines[fl] = append(bi.inlinedCallLines[fl], lowpc)
|
||||
}
|
||||
reader.SkipChildren()
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Breakpoint represents a breakpoint. Stores information on the break
|
||||
// Breakpoint represents a physical breakpoint. Stores information on the break
|
||||
// point including the byte of data that originally was stored at that
|
||||
// address.
|
||||
type Breakpoint struct {
|
||||
@ -20,7 +20,7 @@ type Breakpoint struct {
|
||||
Addr uint64 // Address breakpoint is set for.
|
||||
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
|
||||
Name string // User defined name of the breakpoint
|
||||
ID int // Monotonically increasing ID.
|
||||
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
|
||||
|
||||
// Kind describes whether this is an internal breakpoint (for next'ing or
|
||||
// stepping).
|
||||
@ -82,7 +82,7 @@ const (
|
||||
)
|
||||
|
||||
func (bp *Breakpoint) String() string {
|
||||
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.ID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
|
||||
return fmt.Sprintf("Breakpoint %d at %#v %s:%d (%d)", bp.LogicalID, bp.Addr, bp.File, bp.Line, bp.TotalHitCount)
|
||||
}
|
||||
|
||||
// BreakpointExistsError is returned when trying to set a breakpoint at
|
||||
@ -269,11 +269,11 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
|
||||
|
||||
if kind != UserBreakpoint {
|
||||
bpmap.internalBreakpointIDCounter++
|
||||
newBreakpoint.ID = bpmap.internalBreakpointIDCounter
|
||||
newBreakpoint.LogicalID = bpmap.internalBreakpointIDCounter
|
||||
newBreakpoint.internalCond = cond
|
||||
} else {
|
||||
bpmap.breakpointIDCounter++
|
||||
newBreakpoint.ID = bpmap.breakpointIDCounter
|
||||
newBreakpoint.LogicalID = bpmap.breakpointIDCounter
|
||||
newBreakpoint.Cond = cond
|
||||
}
|
||||
|
||||
@ -282,11 +282,11 @@ func (bpmap *BreakpointMap) Set(addr uint64, kind BreakpointKind, cond ast.Expr,
|
||||
return newBreakpoint, nil
|
||||
}
|
||||
|
||||
// SetWithID creates a breakpoint at addr, with the specified ID.
|
||||
// SetWithID creates a breakpoint at addr, with the specified logical ID.
|
||||
func (bpmap *BreakpointMap) SetWithID(id int, addr uint64, writeBreakpoint WriteBreakpointFn) (*Breakpoint, error) {
|
||||
bp, err := bpmap.Set(addr, UserBreakpoint, nil, writeBreakpoint)
|
||||
if err == nil {
|
||||
bp.ID = id
|
||||
bp.LogicalID = id
|
||||
bpmap.breakpointIDCounter--
|
||||
}
|
||||
return bp, err
|
||||
|
@ -52,10 +52,20 @@ func assertNoError(err error, t testing.TB, s string) {
|
||||
}
|
||||
|
||||
func setFunctionBreakpoint(p proc.Process, t *testing.T, fname string) *proc.Breakpoint {
|
||||
addr, err := proc.FindFunctionLocation(p, fname, 0)
|
||||
assertNoError(err, t, fmt.Sprintf("FindFunctionLocation(%s)", fname))
|
||||
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x) function %s", addr, fname))
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
|
||||
addrs, err := proc.FindFunctionLocation(p, fname, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err)
|
||||
}
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFunctionBreakpoint(%s): too many results %v", f, l, fname, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err)
|
||||
}
|
||||
return bp
|
||||
}
|
||||
|
||||
@ -109,18 +119,28 @@ func TestRestartDuringStop(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func setFileBreakpoint(p proc.Process, t *testing.T, file string, line int) *proc.Breakpoint {
|
||||
addr, _, err := p.BinInfo().LineToPC(file, line)
|
||||
assertNoError(err, t, "LineToPC")
|
||||
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x) - %s:%d", addr, file, line))
|
||||
func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, lineno int) *proc.Breakpoint {
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
|
||||
addrs, err := proc.FindFileLocation(p, fixture.Source, lineno)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFileLocation(%s, %d): %v", f, l, fixture.Source, lineno, err)
|
||||
}
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many results %v", f, l, fixture.Source, lineno, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err)
|
||||
}
|
||||
return bp
|
||||
}
|
||||
|
||||
func TestReverseBreakpointCounts(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestRecording("bpcountstest", t, func(p *gdbserial.Process, fixture protest.Fixture) {
|
||||
endbp := setFileBreakpoint(p, t, fixture.Source, 28)
|
||||
endbp := setFileBreakpoint(p, t, fixture, 28)
|
||||
assertNoError(proc.Continue(p), t, "Continue()")
|
||||
loc, _ := p.CurrentThread().Location()
|
||||
if loc.PC != endbp.Addr {
|
||||
@ -129,8 +149,8 @@ func TestReverseBreakpointCounts(t *testing.T) {
|
||||
|
||||
p.ClearBreakpoint(endbp.Addr)
|
||||
assertNoError(p.Direction(proc.Backward), t, "Switching to backward direction")
|
||||
bp := setFileBreakpoint(p, t, fixture.Source, 12)
|
||||
startbp := setFileBreakpoint(p, t, fixture.Source, 20)
|
||||
bp := setFileBreakpoint(p, t, fixture, 12)
|
||||
startbp := setFileBreakpoint(p, t, fixture, 20)
|
||||
|
||||
countLoop:
|
||||
for {
|
||||
|
@ -77,15 +77,21 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri
|
||||
|
||||
// FindFileLocation returns the PC for a given file:line.
|
||||
// Assumes that `file` is normalized to lower case and '/' on Windows.
|
||||
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
|
||||
pc, fn, err := p.BinInfo().LineToPC(fileName, lineno)
|
||||
func FindFileLocation(p Process, fileName string, lineno int) ([]uint64, error) {
|
||||
pcs, err := p.BinInfo().LineToPC(fileName, lineno)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return nil, err
|
||||
}
|
||||
if fn.Entry == pc {
|
||||
pc, _ = FirstPCAfterPrologue(p, fn, true)
|
||||
var fn *Function
|
||||
for i := range pcs {
|
||||
if fn == nil || pcs[i] < fn.Entry || pcs[i] >= fn.End {
|
||||
fn = p.BinInfo().PCToFunc(pcs[i])
|
||||
}
|
||||
if fn != nil && fn.Entry == pcs[i] {
|
||||
pcs[i], _ = FirstPCAfterPrologue(p, fn, true)
|
||||
}
|
||||
}
|
||||
return pc, nil
|
||||
return pcs, nil
|
||||
}
|
||||
|
||||
// ErrFunctionNotFound is returned when failing to find the
|
||||
@ -100,19 +106,34 @@ func (err *ErrFunctionNotFound) Error() string {
|
||||
|
||||
// FindFunctionLocation finds address of a function's line
|
||||
// If lineOffset is passed FindFunctionLocation will return the address of that line
|
||||
func FindFunctionLocation(p Process, funcName string, lineOffset int) (uint64, error) {
|
||||
func FindFunctionLocation(p Process, funcName string, lineOffset int) ([]uint64, error) {
|
||||
bi := p.BinInfo()
|
||||
origfn := bi.LookupFunc[funcName]
|
||||
if origfn == nil {
|
||||
return 0, &ErrFunctionNotFound{funcName}
|
||||
return nil, &ErrFunctionNotFound{funcName}
|
||||
}
|
||||
|
||||
if lineOffset <= 0 {
|
||||
return FirstPCAfterPrologue(p, origfn, false)
|
||||
r := make([]uint64, 0, len(origfn.InlinedCalls)+1)
|
||||
if origfn.Entry > 0 {
|
||||
// add concrete implementation of the function
|
||||
pc, err := FirstPCAfterPrologue(p, origfn, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = append(r, pc)
|
||||
}
|
||||
// add inlined calls to the function
|
||||
for _, call := range origfn.InlinedCalls {
|
||||
r = append(r, call.LowPC)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return nil, &ErrFunctionNotFound{funcName}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
filename, lineno := origfn.cu.lineInfo.PCToLine(origfn.Entry, origfn.Entry)
|
||||
breakAddr, _, err := bi.LineToPC(filename, lineno+lineOffset)
|
||||
return breakAddr, err
|
||||
return bi.LineToPC(filename, lineno+lineOffset)
|
||||
}
|
||||
|
||||
// Next continues execution until the next source line.
|
||||
@ -704,12 +725,12 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
|
||||
// createUnrecoveredPanicBreakpoint creates the unrecoverable-panic breakpoint.
|
||||
// This function is meant to be called by implementations of the Process interface.
|
||||
func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
||||
panicpc, err := FindFunctionLocation(p, "runtime.startpanic", 0)
|
||||
panicpcs, err := FindFunctionLocation(p, "runtime.startpanic", 0)
|
||||
if _, isFnNotFound := err.(*ErrFunctionNotFound); isFnNotFound {
|
||||
panicpc, err = FindFunctionLocation(p, "runtime.fatalpanic", 0)
|
||||
panicpcs, err = FindFunctionLocation(p, "runtime.fatalpanic", 0)
|
||||
}
|
||||
if err == nil {
|
||||
bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpc, writeBreakpoint)
|
||||
bp, err := p.Breakpoints().SetWithID(unrecoveredPanicID, panicpcs[0], writeBreakpoint)
|
||||
if err == nil {
|
||||
bp.Name = UnrecoveredPanic
|
||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
||||
@ -718,9 +739,9 @@ func createUnrecoveredPanicBreakpoint(p Process, writeBreakpoint WriteBreakpoint
|
||||
}
|
||||
|
||||
func createFatalThrowBreakpoint(p Process, writeBreakpoint WriteBreakpointFn) {
|
||||
fatalpc, err := FindFunctionLocation(p, "runtime.fatalthrow", 0)
|
||||
fatalpcs, err := FindFunctionLocation(p, "runtime.fatalthrow", 0)
|
||||
if err == nil {
|
||||
bp, err := p.Breakpoints().SetWithID(fatalThrowID, fatalpc, writeBreakpoint)
|
||||
bp, err := p.Breakpoints().SetWithID(fatalThrowID, fatalpcs[0], writeBreakpoint)
|
||||
if err == nil {
|
||||
bp.Name = FatalThrow
|
||||
}
|
||||
@ -752,9 +773,8 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error
|
||||
// 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 && pc >= fn.Entry && pc < fn.End {
|
||||
return pc, nil
|
||||
if pc2, _, _, ok := fn.cu.lineInfo.FirstStmtForLine(fn.Entry, fn.End); ok {
|
||||
return pc2, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,11 +176,14 @@ func setFunctionBreakpoint(p proc.Process, t testing.TB, fname string) *proc.Bre
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
|
||||
addr, err := proc.FindFunctionLocation(p, fname, 0)
|
||||
addrs, err := proc.FindFunctionLocation(p, fname, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFunctionBreakpoint(%s): too many results %v", f, l, fname, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err)
|
||||
}
|
||||
@ -191,11 +194,14 @@ func setFileBreakpoint(p proc.Process, t *testing.T, path string, lineno int) *p
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
|
||||
addr, err := proc.FindFileLocation(p, path, lineno)
|
||||
addrs, err := proc.FindFileLocation(p, path, lineno)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFileLocation(%s, %d): %v", f, l, path, lineno, err)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many results %v", f, l, path, lineno, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err)
|
||||
}
|
||||
@ -203,23 +209,29 @@ func setFileBreakpoint(p proc.Process, t *testing.T, path string, lineno int) *p
|
||||
}
|
||||
|
||||
func findFunctionLocation(p proc.Process, t *testing.T, fnname string) uint64 {
|
||||
addr, err := proc.FindFunctionLocation(p, fnname, 0)
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
addrs, err := proc.FindFunctionLocation(p, fnname, 0)
|
||||
if err != nil {
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fnname, err)
|
||||
}
|
||||
return addr
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): too many results %v", f, l, fnname, addrs)
|
||||
}
|
||||
return addrs[0]
|
||||
}
|
||||
|
||||
func findFileLocation(p proc.Process, t *testing.T, file string, lineno int) uint64 {
|
||||
addr, err := proc.FindFileLocation(p, file, lineno)
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
addrs, err := proc.FindFileLocation(p, file, lineno)
|
||||
if err != nil {
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
t.Fatalf("%s:%d: FindFileLocation(%s, %d): %v", f, l, file, lineno, err)
|
||||
}
|
||||
return addr
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: FindFileLocation(%s, %d): too many results %v", f, l, file, lineno, addrs)
|
||||
}
|
||||
return addrs[0]
|
||||
}
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
@ -359,7 +371,7 @@ type nextTest struct {
|
||||
func countBreakpoints(p proc.Process) int {
|
||||
bpcount := 0
|
||||
for _, bp := range p.Breakpoints().M {
|
||||
if bp.ID >= 0 {
|
||||
if bp.LogicalID >= 0 {
|
||||
bpcount++
|
||||
}
|
||||
}
|
||||
@ -1031,11 +1043,11 @@ func TestContinueMulti(t *testing.T) {
|
||||
}
|
||||
assertNoError(err, t, "Continue()")
|
||||
|
||||
if bp := p.CurrentThread().Breakpoint(); bp.ID == bp1.ID {
|
||||
if bp := p.CurrentThread().Breakpoint(); bp.LogicalID == bp1.LogicalID {
|
||||
mainCount++
|
||||
}
|
||||
|
||||
if bp := p.CurrentThread().Breakpoint(); bp.ID == bp2.ID {
|
||||
if bp := p.CurrentThread().Breakpoint(); bp.LogicalID == bp2.LogicalID {
|
||||
sayhiCount++
|
||||
}
|
||||
}
|
||||
@ -3626,7 +3638,8 @@ func TestInlinedStacktraceAndVariables(t *testing.T) {
|
||||
}
|
||||
|
||||
withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p proc.Process, fixture protest.Fixture) {
|
||||
pcs := p.BinInfo().AllPCsForFileLine(fixture.Source, 7)
|
||||
pcs, err := p.BinInfo().LineToPC(fixture.Source, 7)
|
||||
assertNoError(err, t, "LineToPC")
|
||||
if len(pcs) < 2 {
|
||||
t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 7, len(pcs), pcs)
|
||||
}
|
||||
@ -3774,15 +3787,17 @@ func TestInlineBreakpoint(t *testing.T) {
|
||||
t.Skip("inlining not supported")
|
||||
}
|
||||
withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p proc.Process, fixture protest.Fixture) {
|
||||
pc, fn, err := p.BinInfo().LineToPC(fixture.Source, 17)
|
||||
if pc == 0 {
|
||||
t.Fatal("unable to get PC for inlined function call")
|
||||
pcs, err := p.BinInfo().LineToPC(fixture.Source, 17)
|
||||
t.Logf("%#v\n", pcs)
|
||||
if len(pcs) != 1 {
|
||||
t.Fatalf("unable to get PC for inlined function call: %v", pcs)
|
||||
}
|
||||
fn := p.BinInfo().PCToFunc(pcs[0])
|
||||
expectedFn := "main.main"
|
||||
if fn.Name != expectedFn {
|
||||
t.Fatalf("incorrect function returned, expected %s, got %s", expectedFn, fn.Name)
|
||||
}
|
||||
_, err = p.SetBreakpoint(pc, proc.UserBreakpoint, nil)
|
||||
_, err = p.SetBreakpoint(pcs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to set breakpoint: %v", err)
|
||||
}
|
||||
@ -4290,7 +4305,7 @@ func TestCallConcurrent(t *testing.T) {
|
||||
returned := testCallConcurrentCheckReturns(p, t, gid1, -1)
|
||||
|
||||
curthread := p.CurrentThread()
|
||||
if curbp := curthread.Breakpoint(); curbp.Breakpoint == nil || curbp.ID != bp.ID || returned > 0 {
|
||||
if curbp := curthread.Breakpoint(); curbp.Breakpoint == nil || curbp.LogicalID != bp.LogicalID || returned > 0 {
|
||||
t.Logf("skipping test, the call injection terminated before we hit a breakpoint in a different thread")
|
||||
return
|
||||
}
|
||||
|
@ -73,16 +73,16 @@ func (v packageVarsByAddr) Less(i int, j int) bool { return v[i].addr < v[j].add
|
||||
func (v packageVarsByAddr) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
|
||||
|
||||
type loadDebugInfoMapsContext struct {
|
||||
ardr *reader.Reader
|
||||
abstractOriginNameTable map[dwarf.Offset]string
|
||||
knownPackageVars map[string]struct{}
|
||||
ardr *reader.Reader
|
||||
abstractOriginTable map[dwarf.Offset]int
|
||||
knownPackageVars map[string]struct{}
|
||||
}
|
||||
|
||||
func newLoadDebugInfoMapsContext(bi *BinaryInfo, image *Image) *loadDebugInfoMapsContext {
|
||||
ctxt := &loadDebugInfoMapsContext{}
|
||||
|
||||
ctxt.ardr = image.DwarfReader()
|
||||
ctxt.abstractOriginNameTable = make(map[dwarf.Offset]string)
|
||||
ctxt.abstractOriginTable = make(map[dwarf.Offset]int)
|
||||
|
||||
ctxt.knownPackageVars = map[string]struct{}{}
|
||||
for _, v := range bi.packageVars {
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
b := &Breakpoint{
|
||||
Name: bp.Name,
|
||||
ID: bp.ID,
|
||||
ID: bp.LogicalID,
|
||||
FunctionName: bp.FunctionName,
|
||||
File: bp.File,
|
||||
Line: bp.Line,
|
||||
@ -31,6 +31,7 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
|
||||
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
|
||||
TotalHitCount: bp.TotalHitCount,
|
||||
Addrs: []uint64{bp.Addr},
|
||||
}
|
||||
|
||||
b.HitCount = map[string]uint64{}
|
||||
@ -45,6 +46,28 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
return b
|
||||
}
|
||||
|
||||
// ConvertBreakpoints converts a slice of physical breakpoints into a slice
|
||||
// of logical breakpoints.
|
||||
// The input must be sorted by increasing LogicalID
|
||||
func ConvertBreakpoints(bps []*proc.Breakpoint) []*Breakpoint {
|
||||
if len(bps) <= 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*Breakpoint, 0, len(bps))
|
||||
for _, bp := range bps {
|
||||
if len(r) > 0 {
|
||||
if r[len(r)-1].ID == bp.LogicalID {
|
||||
r[len(r)-1].Addrs = append(r[len(r)-1].Addrs, bp.Addr)
|
||||
continue
|
||||
} else if r[len(r)-1].ID > bp.LogicalID {
|
||||
panic("input not sorted")
|
||||
}
|
||||
}
|
||||
r = append(r, ConvertBreakpoint(bp))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ConvertThread converts a proc.Thread into an
|
||||
// api thread.
|
||||
func ConvertThread(th proc.Thread) *Thread {
|
||||
|
@ -39,15 +39,17 @@ type DebuggerState struct {
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// Breakpoint addresses a location at which process execution may be
|
||||
// Breakpoint addresses a set of locations at which process execution may be
|
||||
// suspended.
|
||||
type Breakpoint struct {
|
||||
// ID is a unique identifier for the breakpoint.
|
||||
ID int `json:"id"`
|
||||
// User defined name of the breakpoint
|
||||
// User defined name of the breakpoint.
|
||||
Name string `json:"name"`
|
||||
// Addr is the address of the breakpoint.
|
||||
// Addr is deprecated, use Addrs.
|
||||
Addr uint64 `json:"addr"`
|
||||
// Addrs is the list of addresses for this breakpoint.
|
||||
Addrs []uint64 `json:"addrs"`
|
||||
// File is the source file for the breakpoint.
|
||||
File string `json:"file"`
|
||||
// Line is a line in File for the breakpoint.
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -326,24 +327,25 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
||||
return nil, fmt.Errorf("could not launch process: %s", err)
|
||||
}
|
||||
discarded := []api.DiscardedBreakpoint{}
|
||||
for _, oldBp := range d.breakpoints() {
|
||||
for _, oldBp := range api.ConvertBreakpoints(d.breakpoints()) {
|
||||
if oldBp.ID < 0 {
|
||||
continue
|
||||
}
|
||||
if len(oldBp.File) > 0 {
|
||||
var err error
|
||||
oldBp.Addr, err = proc.FindFileLocation(p, oldBp.File, oldBp.Line)
|
||||
addrs, err := proc.FindFileLocation(p, oldBp.File, oldBp.Line)
|
||||
if err != nil {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
|
||||
continue
|
||||
}
|
||||
}
|
||||
newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
|
||||
return nil, err
|
||||
createLogicalBreakpoint(p, addrs, oldBp)
|
||||
} else {
|
||||
newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
d.target = p
|
||||
@ -413,9 +415,8 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
defer d.processMutex.Unlock()
|
||||
|
||||
var (
|
||||
createdBp *api.Breakpoint
|
||||
addr uint64
|
||||
err error
|
||||
addrs []uint64
|
||||
err error
|
||||
)
|
||||
|
||||
if requestedBp.Name != "" {
|
||||
@ -429,7 +430,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
|
||||
switch {
|
||||
case requestedBp.TraceReturn:
|
||||
addr = requestedBp.Addr
|
||||
addrs = []uint64{requestedBp.Addr}
|
||||
case len(requestedBp.File) > 0:
|
||||
fileName := requestedBp.File
|
||||
if runtime.GOOS == "windows" {
|
||||
@ -442,30 +443,57 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
}
|
||||
}
|
||||
}
|
||||
addr, err = proc.FindFileLocation(d.target, fileName, requestedBp.Line)
|
||||
addrs, err = proc.FindFileLocation(d.target, fileName, requestedBp.Line)
|
||||
case len(requestedBp.FunctionName) > 0:
|
||||
addr, err = proc.FindFunctionLocation(d.target, requestedBp.FunctionName, requestedBp.Line)
|
||||
addrs, err = proc.FindFunctionLocation(d.target, requestedBp.FunctionName, requestedBp.Line)
|
||||
default:
|
||||
addr = requestedBp.Addr
|
||||
addrs = []uint64{requestedBp.Addr}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bp, err := d.target.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
createdBp, err := createLogicalBreakpoint(d.target, addrs, requestedBp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := copyBreakpointInfo(bp, requestedBp); err != nil {
|
||||
if _, err1 := d.target.ClearBreakpoint(bp.Addr); err1 != nil {
|
||||
err = fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
|
||||
d.log.Infof("created breakpoint: %#v", createdBp)
|
||||
return createdBp, nil
|
||||
}
|
||||
|
||||
// createLogicalBreakpoint creates one physical breakpoint for each address
|
||||
// in addrs and associates all of them with the same logical breakpoint.
|
||||
func createLogicalBreakpoint(p proc.Process, addrs []uint64, requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
bps := make([]*proc.Breakpoint, len(addrs))
|
||||
var err error
|
||||
for i := range addrs {
|
||||
bps[i], err = p.SetBreakpoint(addrs[i], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if i > 0 {
|
||||
bps[i].LogicalID = bps[0].LogicalID
|
||||
}
|
||||
err = copyBreakpointInfo(bps[i], requestedBp)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
for _, bp := range bps {
|
||||
if bp == nil {
|
||||
continue
|
||||
}
|
||||
if _, err1 := p.ClearBreakpoint(bp.Addr); err1 != nil {
|
||||
err = fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
createdBp = api.ConvertBreakpoint(bp)
|
||||
d.log.Infof("created breakpoint: %#v", createdBp)
|
||||
return createdBp, nil
|
||||
createdBp := api.ConvertBreakpoints(bps)
|
||||
return createdBp[0], nil // we created a single logical breakpoint, the slice here will always have len == 1
|
||||
}
|
||||
|
||||
// AmendBreakpoint will update the breakpoint with the matching ID.
|
||||
@ -473,14 +501,19 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
|
||||
d.processMutex.Lock()
|
||||
defer d.processMutex.Unlock()
|
||||
|
||||
original := d.findBreakpoint(amend.ID)
|
||||
if original == nil {
|
||||
originals := d.findBreakpoint(amend.ID)
|
||||
if originals == nil {
|
||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||
}
|
||||
if err := api.ValidBreakpointName(amend.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyBreakpointInfo(original, amend)
|
||||
for _, original := range originals {
|
||||
if err := copyBreakpointInfo(original, amend); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CancelNext will clear internal breakpoints, thus cancelling the 'next',
|
||||
@ -524,16 +557,17 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
|
||||
func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
||||
d.processMutex.Lock()
|
||||
defer d.processMutex.Unlock()
|
||||
return d.breakpoints()
|
||||
return api.ConvertBreakpoints(d.breakpoints())
|
||||
}
|
||||
|
||||
func (d *Debugger) breakpoints() []*api.Breakpoint {
|
||||
bps := []*api.Breakpoint{}
|
||||
func (d *Debugger) breakpoints() []*proc.Breakpoint {
|
||||
bps := []*proc.Breakpoint{}
|
||||
for _, bp := range d.target.Breakpoints().M {
|
||||
if bp.IsUser() {
|
||||
bps = append(bps, api.ConvertBreakpoint(bp))
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
sort.Sort(breakpointsByLogicalID(bps))
|
||||
return bps
|
||||
}
|
||||
|
||||
@ -541,21 +575,21 @@ func (d *Debugger) breakpoints() []*api.Breakpoint {
|
||||
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
d.processMutex.Lock()
|
||||
defer d.processMutex.Unlock()
|
||||
|
||||
bp := d.findBreakpoint(id)
|
||||
if bp == nil {
|
||||
bps := api.ConvertBreakpoints(d.findBreakpoint(id))
|
||||
if len(bps) <= 0 {
|
||||
return nil
|
||||
}
|
||||
return api.ConvertBreakpoint(bp)
|
||||
return bps[0]
|
||||
}
|
||||
|
||||
func (d *Debugger) findBreakpoint(id int) *proc.Breakpoint {
|
||||
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
|
||||
var bps []*proc.Breakpoint
|
||||
for _, bp := range d.target.Breakpoints().M {
|
||||
if bp.ID == id {
|
||||
return bp
|
||||
if bp.LogicalID == id {
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return bps
|
||||
}
|
||||
|
||||
// FindBreakpointByName returns the breakpoint specified by 'name'
|
||||
@ -566,12 +600,18 @@ func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
|
||||
}
|
||||
|
||||
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
||||
var bps []*proc.Breakpoint
|
||||
for _, bp := range d.breakpoints() {
|
||||
if bp.Name == name {
|
||||
return bp
|
||||
bps = append(bps, bp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if len(bps) == 0 {
|
||||
return nil
|
||||
}
|
||||
sort.Sort(breakpointsByLogicalID(bps))
|
||||
r := api.ConvertBreakpoints(bps)
|
||||
return r[0] // there can only be one logical breakpoint with the same name
|
||||
}
|
||||
|
||||
// Threads returns the threads of the target process.
|
||||
@ -1304,3 +1344,15 @@ func go11DecodeErrorCheck(err error) error {
|
||||
|
||||
return fmt.Errorf("executables built by Go 1.11 or later need Delve built by Go 1.11 or later")
|
||||
}
|
||||
|
||||
type breakpointsByLogicalID []*proc.Breakpoint
|
||||
|
||||
func (v breakpointsByLogicalID) Len() int { return len(v) }
|
||||
func (v breakpointsByLogicalID) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
|
||||
func (v breakpointsByLogicalID) Less(i, j int) bool {
|
||||
if v[i].LogicalID == v[j].LogicalID {
|
||||
return v[i].Addr < v[j].Addr
|
||||
}
|
||||
return v[i].LogicalID < v[j].LogicalID
|
||||
}
|
||||
|
@ -250,8 +250,8 @@ func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr st
|
||||
}
|
||||
r := make([]api.Location, 0, len(matches))
|
||||
for i := range matches {
|
||||
addr, err := proc.FindFunctionLocation(d.target, matches[i], 0)
|
||||
if err == nil {
|
||||
addrs, _ := proc.FindFunctionLocation(d.target, matches[i], 0)
|
||||
for _, addr := range addrs {
|
||||
r = append(r, api.Location{PC: addr})
|
||||
}
|
||||
}
|
||||
@ -377,26 +377,37 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
|
||||
}
|
||||
|
||||
// len(candidateFiles) + len(candidateFuncs) == 1
|
||||
var addr uint64
|
||||
var addrs []uint64
|
||||
var err error
|
||||
if len(candidateFiles) == 1 {
|
||||
if loc.LineOffset < 0 {
|
||||
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
|
||||
}
|
||||
addr, err = proc.FindFileLocation(d.target, candidateFiles[0], loc.LineOffset)
|
||||
addrs, err = proc.FindFileLocation(d.target, candidateFiles[0], loc.LineOffset)
|
||||
if includeNonExecutableLines {
|
||||
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
|
||||
return []api.Location{{File: candidateFiles[0], Line: loc.LineOffset}}, nil
|
||||
}
|
||||
}
|
||||
} else { // len(candidateFUncs) == 1
|
||||
addr, err = proc.FindFunctionLocation(d.target, candidateFuncs[0], loc.LineOffset)
|
||||
addrs, err = proc.FindFunctionLocation(d.target, candidateFuncs[0], loc.LineOffset)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []api.Location{{PC: addr}}, nil
|
||||
return addressesToLocations(addrs), nil
|
||||
}
|
||||
|
||||
func addressesToLocations(addrs []uint64) []api.Location {
|
||||
if addrs == nil {
|
||||
return nil
|
||||
}
|
||||
r := make([]api.Location, len(addrs))
|
||||
for i := range addrs {
|
||||
r[i] = api.Location{PC: addrs[i]}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
|
||||
@ -410,13 +421,13 @@ func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("could not determine current location")
|
||||
}
|
||||
addr, err := proc.FindFileLocation(d.target, file, line+loc.Offset)
|
||||
addrs, err := proc.FindFileLocation(d.target, file, line+loc.Offset)
|
||||
if includeNonExecutableLines {
|
||||
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
|
||||
return []api.Location{{File: file, Line: line + loc.Offset}}, nil
|
||||
}
|
||||
}
|
||||
return []api.Location{{PC: addr}}, err
|
||||
return addressesToLocations(addrs), err
|
||||
}
|
||||
|
||||
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
|
||||
@ -427,11 +438,11 @@ func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("could not determine current location")
|
||||
}
|
||||
addr, err := proc.FindFileLocation(d.target, file, loc.Line)
|
||||
addrs, err := proc.FindFileLocation(d.target, file, loc.Line)
|
||||
if includeNonExecutableLines {
|
||||
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
|
||||
return []api.Location{{File: file, Line: loc.Line}}, nil
|
||||
}
|
||||
}
|
||||
return []api.Location{{PC: addr}}, err
|
||||
return addressesToLocations(addrs), err
|
||||
}
|
||||
|
@ -1070,11 +1070,14 @@ func setFunctionBreakpoint(p proc.Process, t testing.TB, fname string) *proc.Bre
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
|
||||
addr, err := proc.FindFunctionLocation(p, fname, 0)
|
||||
addrs, err := proc.FindFunctionLocation(p, fname, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFunctionBreakpoint(%s): too many results %v", f, l, fname, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFunctionLocation(%s): %v", f, l, fname, err)
|
||||
}
|
||||
@ -1352,11 +1355,14 @@ func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, li
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
f = filepath.Base(f)
|
||||
|
||||
addr, err := proc.FindFileLocation(p, fixture.Source, lineno)
|
||||
addrs, err := proc.FindFileLocation(p, fixture.Source, lineno)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: FindFileLocation(%s, %d): %v", f, l, fixture.Source, lineno, err)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
|
||||
if len(addrs) != 1 {
|
||||
t.Fatalf("%s:%d: setFileLineBreakpoint(%s, %d): too many results %v", f, l, fixture.Source, lineno, addrs)
|
||||
}
|
||||
bp, err := p.SetBreakpoint(addrs[0], proc.UserBreakpoint, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s:%d: SetBreakpoint: %v", f, l, err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user