terminal: restore breakpoints set with line offset on restart (#3425)

Change FindLocation so it can return a substitute location expression
and propagate it to pkg/terminal/command.
When breakpoints are set using the syntax :<lineno> or +<lineno>
produce a substitute location expression that doesn't depend on having
a valid scope and can be used to restore the breakpoint.

Fixes #3423
This commit is contained in:
Alessandro Arzilli 2023-07-20 12:29:59 +02:00 committed by GitHub
parent 8023fa956e
commit ca611db449
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 111 additions and 68 deletions

@ -20,7 +20,7 @@ const maxFindLocationCandidates = 5
// LocationSpec is an interface that represents a parsed location spec string. // LocationSpec is an interface that represents a parsed location spec string.
type LocationSpec interface { type LocationSpec interface {
// Find returns all locations that match the location spec. // Find returns all locations that match the location spec.
Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error)
} }
// NormalLocationSpec represents a basic location spec. // NormalLocationSpec represents a basic location spec.
@ -269,15 +269,15 @@ func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool {
// Find will search all functions in the target program and filter them via the // Find will search all functions in the target program and filter them via the
// regex location spec. Only functions matching the regex will be returned. // regex location spec. Only functions matching the regex will be returned.
func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
if scope == nil { if scope == nil {
//TODO(aarzilli): this needs only the list of function we should make it work //TODO(aarzilli): this needs only the list of function we should make it work
return nil, fmt.Errorf("could not determine location (scope is nil)") return nil, "", fmt.Errorf("could not determine location (scope is nil)")
} }
funcs := scope.BinInfo.Functions funcs := scope.BinInfo.Functions
matches, err := regexFilterFuncs(loc.FuncRegex, funcs) matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
r := make([]api.Location, 0, len(matches)) r := make([]api.Location, 0, len(matches))
for i := range matches { for i := range matches {
@ -286,39 +286,39 @@ func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalS
r = append(r, addressesToLocation(addrs)) r = append(r, addressesToLocation(addrs))
} }
} }
return r, nil return r, "", nil
} }
// Find returns the locations specified via the address location spec. // Find returns the locations specified via the address location spec.
func (loc *AddrLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { func (loc *AddrLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
if scope == nil { if scope == nil {
addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64) addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)") return nil, "", fmt.Errorf("could not determine current location (scope is nil)")
} }
return []api.Location{{PC: uint64(addr)}}, nil return []api.Location{{PC: uint64(addr)}}, "", nil
} }
v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true}) v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true})
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
if v.Unreadable != nil { if v.Unreadable != nil {
return nil, v.Unreadable return nil, "", v.Unreadable
} }
switch v.Kind { switch v.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
addr, _ := constant.Uint64Val(v.Value) addr, _ := constant.Uint64Val(v.Value)
return []api.Location{{PC: addr}}, nil return []api.Location{{PC: addr}}, "", nil
case reflect.Func: case reflect.Func:
fn := scope.BinInfo.PCToFunc(uint64(v.Base)) fn := scope.BinInfo.PCToFunc(uint64(v.Base))
pc, err := proc.FirstPCAfterPrologue(t, fn, false) pc, err := proc.FirstPCAfterPrologue(t, fn, false)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
return []api.Location{{PC: pc}}, nil return []api.Location{{PC: pc}}, v.Name, nil
default: default:
return nil, fmt.Errorf("wrong expression kind: %v", v.Kind) return nil, "", fmt.Errorf("wrong expression kind: %v", v.Kind)
} }
} }
@ -371,7 +371,7 @@ func (ale AmbiguousLocationError) Error() string {
// Find will return a list of locations that match the given location spec. // Find will return a list of locations that match the given location spec.
// This matches each other location spec that does not already have its own spec // This matches each other location spec that does not already have its own spec
// implemented (such as regex, or addr). // implemented (such as regex, or addr).
func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) { func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error) {
limit := maxFindLocationCandidates limit := maxFindLocationCandidates
var candidateFiles []string var candidateFiles []string
for _, sourceFile := range t.BinInfo().Sources { for _, sourceFile := range t.BinInfo().Sources {
@ -396,19 +396,19 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 { if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
if scope == nil { if scope == nil {
return nil, fmt.Errorf("location %q not found", locStr) return nil, "", fmt.Errorf("location %q not found", locStr)
} }
// if no result was found this locations string could be an // if no result was found this locations string could be an
// expression that the user forgot to prefix with '*', try treating it as // expression that the user forgot to prefix with '*', try treating it as
// such. // such.
addrSpec := &AddrLocationSpec{AddrExpr: locStr} addrSpec := &AddrLocationSpec{AddrExpr: locStr}
locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines, nil) locs, subst, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("location %q not found", locStr) return nil, "", fmt.Errorf("location %q not found", locStr)
} }
return locs, nil return locs, subst, nil
} else if matching > 1 { } else if matching > 1 {
return nil, AmbiguousLocationError{Location: locStr, CandidatesString: append(candidateFiles, candidateFuncs...)} return nil, "", AmbiguousLocationError{Location: locStr, CandidatesString: append(candidateFiles, candidateFuncs...)}
} }
// len(candidateFiles) + len(candidateFuncs) == 1 // len(candidateFiles) + len(candidateFuncs) == 1
@ -417,12 +417,12 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
if len(candidateFiles) == 1 { if len(candidateFiles) == 1 {
if loc.LineOffset < 0 { if loc.LineOffset < 0 {
//lint:ignore ST1005 backwards compatibility //lint:ignore ST1005 backwards compatibility
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified") return nil, "", fmt.Errorf("Malformed breakpoint location, no line offset specified")
} }
addrs, err = proc.FindFileLocation(t, candidateFiles[0], loc.LineOffset) addrs, err = proc.FindFileLocation(t, candidateFiles[0], loc.LineOffset)
if includeNonExecutableLines { if includeNonExecutableLines {
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine { if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
return []api.Location{{File: candidateFiles[0], Line: loc.LineOffset}}, nil return []api.Location{{File: candidateFiles[0], Line: loc.LineOffset}}, "", nil
} }
} }
} else { // len(candidateFuncs) == 1 } else { // len(candidateFuncs) == 1
@ -430,9 +430,9 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
} }
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
return []api.Location{addressesToLocation(addrs)}, nil return []api.Location{addressesToLocation(addrs)}, "", nil
} }
func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int) []string { func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int) []string {
@ -585,42 +585,48 @@ func addressesToLocation(addrs []uint64) api.Location {
} }
// Find returns the location after adding the offset amount to the current line number. // Find returns the location after adding the offset amount to the current line number.
func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
if scope == nil { if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)") return nil, "", fmt.Errorf("could not determine current location (scope is nil)")
}
if loc.Offset == 0 {
return []api.Location{{PC: scope.PC}}, nil
} }
file, line, fn := scope.BinInfo.PCToLine(scope.PC) file, line, fn := scope.BinInfo.PCToLine(scope.PC)
if fn == nil { if loc.Offset == 0 {
return nil, fmt.Errorf("could not determine current location") subst := ""
if fn != nil {
subst = fmt.Sprintf("%s:%d", file, line)
} }
return []api.Location{{PC: scope.PC}}, subst, nil
}
if fn == nil {
return nil, "", fmt.Errorf("could not determine current location")
}
subst := fmt.Sprintf("%s:%d", file, line+loc.Offset)
addrs, err := proc.FindFileLocation(t, file, line+loc.Offset) addrs, err := proc.FindFileLocation(t, file, line+loc.Offset)
if includeNonExecutableLines { if includeNonExecutableLines {
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine { if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
return []api.Location{{File: file, Line: line + loc.Offset}}, nil return []api.Location{{File: file, Line: line + loc.Offset}}, subst, nil
} }
} }
return []api.Location{addressesToLocation(addrs)}, err return []api.Location{addressesToLocation(addrs)}, subst, err
} }
// Find will return the location at the given line in the current file. // Find will return the location at the given line in the current file.
func (loc *LineLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { func (loc *LineLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, string, error) {
if scope == nil { if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)") return nil, "", fmt.Errorf("could not determine current location (scope is nil)")
} }
file, _, fn := scope.BinInfo.PCToLine(scope.PC) file, _, fn := scope.BinInfo.PCToLine(scope.PC)
if fn == nil { if fn == nil {
return nil, fmt.Errorf("could not determine current location") return nil, "", fmt.Errorf("could not determine current location")
} }
subst := fmt.Sprintf("%s:%d", file, loc.Line)
addrs, err := proc.FindFileLocation(t, file, loc.Line) addrs, err := proc.FindFileLocation(t, file, loc.Line)
if includeNonExecutableLines { if includeNonExecutableLines {
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine { if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
return []api.Location{{File: file, Line: loc.Line}}, nil return []api.Location{{File: file, Line: loc.Line}}, subst, nil
} }
} }
return []api.Location{addressesToLocation(addrs)}, err return []api.Location{addressesToLocation(addrs)}, subst, err
} }
func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) { func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {

@ -1634,7 +1634,7 @@ func clearAll(t *Term, ctx callContext, args string) error {
var locPCs map[uint64]struct{} var locPCs map[uint64]struct{}
if args != "" { if args != "" {
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args, true, t.substitutePathRules()) locs, _, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args, true, t.substitutePathRules())
if err != nil { if err != nil {
return err return err
} }
@ -1790,14 +1790,16 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
} }
requestedBp.Tracepoint = tracepoint requestedBp.Tracepoint = tracepoint
locs, findLocErr := t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) locs, substSpec, findLocErr := t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules())
if findLocErr != nil && requestedBp.Name != "" { if findLocErr != nil && requestedBp.Name != "" {
requestedBp.Name = "" requestedBp.Name = ""
spec = argstr spec = argstr
var err2 error var err2 error
locs, err2 = t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) var substSpec2 string
locs, substSpec2, err2 = t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules())
if err2 == nil { if err2 == nil {
findLocErr = nil findLocErr = nil
substSpec = substSpec2
} }
} }
if findLocErr != nil && shouldAskToSuspendBreakpoint(t) { if findLocErr != nil && shouldAskToSuspendBreakpoint(t) {
@ -1824,6 +1826,9 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
if findLocErr != nil { if findLocErr != nil {
return nil, findLocErr return nil, findLocErr
} }
if substSpec != "" {
spec = substSpec
}
created := []*api.Breakpoint{} created := []*api.Breakpoint{}
for _, loc := range locs { for _, loc := range locs {
@ -2431,7 +2436,7 @@ func getLocation(t *Term, ctx callContext, args string, showContext bool) (file
return loc.File, loc.Line, true, nil return loc.File, loc.Line, true, nil
default: default:
locs, err := t.client.FindLocation(ctx.Scope, args, false, t.substitutePathRules()) locs, _, err := t.client.FindLocation(ctx.Scope, args, false, t.substitutePathRules())
if err != nil { if err != nil {
return "", 0, false, err return "", 0, false, err
} }
@ -2505,7 +2510,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
switch cmd { switch cmd {
case "": case "":
locs, err := t.client.FindLocation(ctx.Scope, "+0", true, t.substitutePathRules()) locs, _, err := t.client.FindLocation(ctx.Scope, "+0", true, t.substitutePathRules())
if err != nil { if err != nil {
return err return err
} }
@ -2525,7 +2530,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
} }
disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), flavor) disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), flavor)
case "-l": case "-l":
locs, err := t.client.FindLocation(ctx.Scope, rest, true, t.substitutePathRules()) locs, _, err := t.client.FindLocation(ctx.Scope, rest, true, t.substitutePathRules())
if err != nil { if err != nil {
return err return err
} }

@ -1419,3 +1419,29 @@ func TestCreateBreakpointByLocExpr(t *testing.T) {
} }
}) })
} }
func TestRestartBreakpoints(t *testing.T) {
// Tests that breakpoints set using just a line number and with a line
// offset are preserved after restart. See issue #3423.
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
term.MustExec("break main.main")
term.MustExec("continue")
term.MustExec("break 9")
term.MustExec("break +1")
out := term.MustExec("breakpoints")
t.Log("breakpoints before:\n", out)
term.MustExec("restart")
out = term.MustExec("breakpoints")
t.Log("breakpoints after:\n", out)
bps, err := term.client.ListBreakpoints(false)
assertNoError(t, err, "ListBreakpoints")
for _, bp := range bps {
if bp.ID < 0 {
continue
}
if bp.Addr == 0 {
t.Fatalf("breakpoint %d has address 0", bp.ID)
}
}
})
}

@ -143,7 +143,7 @@ type Client interface {
// * *<address> returns the location corresponding to the specified address // * *<address> returns the location corresponding to the specified address
// NOTE: this function does not actually set breakpoints. // NOTE: this function does not actually set breakpoints.
// If findInstruction is true FindLocation will only return locations that correspond to instructions. // If findInstruction is true FindLocation will only return locations that correspond to instructions.
FindLocation(scope api.EvalScope, loc string, findInstruction bool, substitutePathRules [][2]string) ([]api.Location, error) FindLocation(scope api.EvalScope, loc string, findInstruction bool, substitutePathRules [][2]string) ([]api.Location, string, error)
// DisassembleRange disassemble code between startPC and endPC // DisassembleRange disassemble code between startPC and endPC
DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error)

@ -737,7 +737,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint, locExpr string,
return nil, err return nil, err
} }
setbp.Expr = func(t *proc.Target) []uint64 { setbp.Expr = func(t *proc.Target) []uint64 {
locs, err := loc.Find(t, d.processArgs, nil, locExpr, false, substitutePathRules) locs, _, err := loc.Find(t, d.processArgs, nil, locExpr, false, substitutePathRules)
if err != nil || len(locs) != 1 { if err != nil || len(locs) != 1 {
logflags.DebuggerLogger().Debugf("could not evaluate breakpoint expression %q: %v (number of results %d)", locExpr, err, len(locs)) logflags.DebuggerLogger().Debugf("could not evaluate breakpoint expression %q: %v (number of results %d)", locExpr, err, len(locs))
return nil return nil
@ -1921,17 +1921,17 @@ func (d *Debugger) CurrentPackage() (string, error) {
} }
// FindLocation will find the location specified by 'locStr'. // FindLocation will find the location specified by 'locStr'.
func (d *Debugger) FindLocation(goid int64, frame, deferredCall int, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) { func (d *Debugger) FindLocation(goid int64, frame, deferredCall int, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
if _, err := d.target.Valid(); err != nil { if _, err := d.target.Valid(); err != nil {
return nil, err return nil, "", err
} }
loc, err := locspec.Parse(locStr) loc, err := locspec.Parse(locStr)
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
return d.findLocation(goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules) return d.findLocation(goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
@ -1949,18 +1949,23 @@ func (d *Debugger) FindLocationSpec(goid int64, frame, deferredCall int, locStr
return nil, err return nil, err
} }
return d.findLocation(goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules) locs, _, err := d.findLocation(goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
return locs, err
} }
func (d *Debugger) findLocation(goid int64, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) { func (d *Debugger) findLocation(goid int64, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, string, error) {
locations := []api.Location{} locations := []api.Location{}
t := proc.ValidTargets{Group: d.target} t := proc.ValidTargets{Group: d.target}
subst := ""
for t.Next() { for t.Next() {
pid := t.Pid() pid := t.Pid()
s, _ := proc.ConvertEvalScope(t.Target, goid, frame, deferredCall) s, _ := proc.ConvertEvalScope(t.Target, goid, frame, deferredCall)
locs, err := locSpec.Find(t.Target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules) locs, s1, err := locSpec.Find(t.Target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
if s1 != "" {
subst = s1
}
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
for i := range locs { for i := range locs {
if locs[i].PC == 0 { if locs[i].PC == 0 {
@ -1977,7 +1982,7 @@ func (d *Debugger) findLocation(goid int64, frame, deferredCall int, locStr stri
} }
locations = append(locations, locs...) locations = append(locations, locs...)
} }
return locations, nil return locations, subst, nil
} }
// Disassemble code between startPC and endPC. // Disassemble code between startPC and endPC.

@ -306,7 +306,7 @@ type FindLocationArgs struct {
func (c *RPCServer) FindLocation(args FindLocationArgs, answer *[]api.Location) error { func (c *RPCServer) FindLocation(args FindLocationArgs, answer *[]api.Location) error {
var err error var err error
*answer, err = c.debugger.FindLocation(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Loc, false, nil) *answer, _, err = c.debugger.FindLocation(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Loc, false, nil)
return err return err
} }

@ -414,10 +414,10 @@ func (c *RPCClient) AttachedToExistingProcess() bool {
return out.Answer return out.Answer
} }
func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool, substitutePathRules [][2]string) ([]api.Location, error) { func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool, substitutePathRules [][2]string) ([]api.Location, string, error) {
var out FindLocationOut var out FindLocationOut
err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions, substitutePathRules}, &out) err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions, substitutePathRules}, &out)
return out.Locations, err return out.Locations, out.SubstituteLocExpr, err
} }
// DisassembleRange disassembles code between startPC and endPC // DisassembleRange disassembles code between startPC and endPC

@ -706,6 +706,7 @@ type FindLocationIn struct {
type FindLocationOut struct { type FindLocationOut struct {
Locations []api.Location Locations []api.Location
SubstituteLocExpr string // if this isn't an empty string it should be passed as the location expression for CreateBreakpoint instead of the original location expression
} }
// FindLocation returns concrete location information described by a location expression. // FindLocation returns concrete location information described by a location expression.
@ -723,7 +724,7 @@ type FindLocationOut struct {
// NOTE: this function does not actually set breakpoints. // NOTE: this function does not actually set breakpoints.
func (c *RPCServer) FindLocation(arg FindLocationIn, out *FindLocationOut) error { func (c *RPCServer) FindLocation(arg FindLocationIn, out *FindLocationOut) error {
var err error var err error
out.Locations, err = c.debugger.FindLocation(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Loc, arg.IncludeNonExecutableLines, arg.SubstitutePathRules) out.Locations, out.SubstituteLocExpr, err = c.debugger.FindLocation(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Loc, arg.IncludeNonExecutableLines, arg.SubstitutePathRules)
return err return err
} }

@ -89,7 +89,7 @@ type locationFinder1 interface {
} }
type locationFinder2 interface { type locationFinder2 interface {
FindLocation(api.EvalScope, string, bool, [][2]string) ([]api.Location, error) FindLocation(api.EvalScope, string, bool, [][2]string) ([]api.Location, string, error)
} }
func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 { func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
@ -100,7 +100,7 @@ func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool,
case locationFinder1: case locationFinder1:
locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc) locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc)
case locationFinder2: case locationFinder2:
locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false, nil) locs, _, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false, nil)
default: default:
t.Errorf("unexpected type %T passed to findLocationHelper", c) t.Errorf("unexpected type %T passed to findLocationHelper", c)
} }

@ -997,14 +997,14 @@ func TestClientServer_FindLocations(t *testing.T) {
findLocationHelper(t, c, `*amap["k"]`, false, 1, findLocationHelper(t, c, `amap["k"]`, false, 1, 0)[0]) findLocationHelper(t, c, `*amap["k"]`, false, 1, findLocationHelper(t, c, `amap["k"]`, false, 1, 0)[0])
locsNoSubst, _ := c.FindLocation(api.EvalScope{GoroutineID: -1}, "_fixtures/locationsprog.go:35", false, nil) locsNoSubst, _, _ := c.FindLocation(api.EvalScope{GoroutineID: -1}, "_fixtures/locationsprog.go:35", false, nil)
sep := "/" sep := "/"
if strings.Contains(locsNoSubst[0].File, "\\") { if strings.Contains(locsNoSubst[0].File, "\\") {
sep = "\\" sep = "\\"
} }
substRules := [][2]string{{strings.Replace(locsNoSubst[0].File, "locationsprog.go", "", 1), strings.Replace(locsNoSubst[0].File, "_fixtures"+sep+"locationsprog.go", "nonexistent", 1)}} substRules := [][2]string{{strings.Replace(locsNoSubst[0].File, "locationsprog.go", "", 1), strings.Replace(locsNoSubst[0].File, "_fixtures"+sep+"locationsprog.go", "nonexistent", 1)}}
t.Logf("substitute rules: %q -> %q", substRules[0][0], substRules[0][1]) t.Logf("substitute rules: %q -> %q", substRules[0][0], substRules[0][1])
locsSubst, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "nonexistent/locationsprog.go:35", false, substRules) locsSubst, _, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "nonexistent/locationsprog.go:35", false, substRules)
if err != nil { if err != nil {
t.Fatalf("FindLocation(locationsprog.go:35) with substitute rules: %v", err) t.Fatalf("FindLocation(locationsprog.go:35) with substitute rules: %v", err)
} }
@ -1114,7 +1114,7 @@ func TestClientServer_FindLocations(t *testing.T) {
} }
func findLocationHelper2(t *testing.T, c service.Client, loc string, checkLoc *api.Location) *api.Location { func findLocationHelper2(t *testing.T, c service.Client, loc string, checkLoc *api.Location) *api.Location {
locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false, nil) locs, _, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false, nil)
if err != nil { if err != nil {
t.Fatalf("FindLocation(%q) -> error %v", loc, err) t.Fatalf("FindLocation(%q) -> error %v", loc, err)
} }
@ -1360,7 +1360,7 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListGoroutines()") assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, 0, &normalLoadConfig) _, err = c.Stacktrace(gid, 10, 0, &normalLoadConfig)
assertError(err, t, "Stacktrace()") assertError(err, t, "Stacktrace()")
_, err = c.FindLocation(api.EvalScope{GoroutineID: gid}, "+1", false, nil) _, _, err = c.FindLocation(api.EvalScope{GoroutineID: gid}, "+1", false, nil)
assertError(err, t, "FindLocation()") assertError(err, t, "FindLocation()")
_, err = c.DisassemblePC(api.EvalScope{GoroutineID: -1}, 0x40100, api.IntelFlavour) _, err = c.DisassemblePC(api.EvalScope{GoroutineID: -1}, 0x40100, api.IntelFlavour)
assertError(err, t, "DisassemblePC()") assertError(err, t, "DisassemblePC()")
@ -1380,7 +1380,7 @@ func TestDisasm(t *testing.T) {
state := <-ch state := <-ch
assertNoError(state.Err, t, "Continue()") assertNoError(state.Err, t, "Continue()")
locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", false, nil) locs, _, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", false, nil)
assertNoError(err, t, "FindLocation()") assertNoError(err, t, "FindLocation()")
if len(locs) != 1 { if len(locs) != 1 {
t.Fatalf("wrong number of locations for main.main: %d", len(locs)) t.Fatalf("wrong number of locations for main.main: %d", len(locs))
@ -1639,7 +1639,7 @@ func TestTypesCommand(t *testing.T) {
func TestIssue406(t *testing.T) { func TestIssue406(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestClient2("issue406", t, func(c service.Client) { withTestClient2("issue406", t, func(c service.Client) {
locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "issue406.go:146", false, nil) locs, _, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "issue406.go:146", false, nil)
assertNoError(err, t, "FindLocation()") assertNoError(err, t, "FindLocation()")
_, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC}) _, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC})
assertNoError(err, t, "CreateBreakpoint()") assertNoError(err, t, "CreateBreakpoint()")
@ -2253,7 +2253,7 @@ func TestUnknownMethodCall(t *testing.T) {
func TestIssue1703(t *testing.T) { func TestIssue1703(t *testing.T) {
// Calling Disassemble when there is no current goroutine should work. // Calling Disassemble when there is no current goroutine should work.
withTestClient2("testnextprog", t, func(c service.Client) { withTestClient2("testnextprog", t, func(c service.Client) {
locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", true, nil) locs, _, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", true, nil)
assertNoError(err, t, "FindLocation") assertNoError(err, t, "FindLocation")
t.Logf("FindLocation: %#v", locs) t.Logf("FindLocation: %#v", locs)
text, err := c.DisassemblePC(api.EvalScope{GoroutineID: -1}, locs[0].PC, api.IntelFlavour) text, err := c.DisassemblePC(api.EvalScope{GoroutineID: -1}, locs[0].PC, api.IntelFlavour)