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:
Alessandro Arzilli 2019-11-01 20:41:06 +01:00 committed by Derek Parker
parent 1c5e8abf34
commit 222deeec36
12 changed files with 465 additions and 217 deletions

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