terminal: let 'list' work on file:line exprs that don't map to code (#1728)

Make the 'list' command succeed for file:line expressions that don't
map to any instruction.
Adds an argument to the FindLocations API call that makes FindLocations
return if the expression can be parsed, even if it doesn't end up
matching any instruction in debug_line.
This commit is contained in:
Alessandro Arzilli 2019-10-25 18:59:18 +02:00 committed by Derek Parker
parent d064d1fe05
commit ccf57b9454
13 changed files with 92 additions and 38 deletions

@ -29,7 +29,7 @@ create_breakpoint(Breakpoint) | Equivalent to API call [CreateBreakpoint](https:
detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach)
disassemble(Scope, StartPC, EndPC, Flavour) | Equivalent to API call [Disassemble](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Disassemble)
eval(Scope, Expr, Cfg) | Equivalent to API call [Eval](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Eval)
find_location(Scope, Loc) | Equivalent to API call [FindLocation](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FindLocation)
find_location(Scope, Loc, IncludeNonExecutableLines) | Equivalent to API call [FindLocation](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FindLocation)
function_return_locations(FnName) | Equivalent to API call [FunctionReturnLocations](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FunctionReturnLocations)
get_breakpoint(Id, Name) | Equivalent to API call [GetBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.GetBreakpoint)
get_thread(Id) | Equivalent to API call [GetThread](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.GetThread)

@ -377,6 +377,19 @@ func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *Function) {
return f, ln, fn
}
type ErrCouldNotFindLine struct {
fileFound bool
filename string
lineno int
}
func (err *ErrCouldNotFindLine) Error() string {
if err.fileFound {
return fmt.Sprintf("could not find statement at %s:%d, please use a line with a statement", err.filename, err.lineno)
}
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) {
fileFound := false
@ -398,11 +411,7 @@ func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *Func
}
}
}
if fileFound {
return 0, nil, fmt.Errorf("could not find statement at %s:%d, please use a line with a statement", filename, lineno)
} else {
return 0, nil, fmt.Errorf("could not find file %s", filename)
}
return 0, nil, &ErrCouldNotFindLine{fileFound, filename, lineno}
}
// AllPCsForFileLine returns all PC addresses for the given filename:lineno.

@ -1182,7 +1182,7 @@ func clearAll(t *Term, ctx callContext, args string) error {
var locPCs map[uint64]struct{}
if args != "" {
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args)
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args, true)
if err != nil {
return err
}
@ -1282,7 +1282,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
}
requestedBp.Tracepoint = tracepoint
locs, err := t.client.FindLocation(ctx.Scope, locspec)
locs, err := t.client.FindLocation(ctx.Scope, locspec, true)
if err != nil {
if requestedBp.Name == "" {
return err
@ -1290,7 +1290,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
requestedBp.Name = ""
locspec = argstr
var err2 error
locs, err2 = t.client.FindLocation(ctx.Scope, locspec)
locs, err2 = t.client.FindLocation(ctx.Scope, locspec, true)
if err2 != nil {
return err
}
@ -1665,7 +1665,7 @@ func getLocation(t *Term, ctx callContext, args string, showContext bool) (file
return loc.File, loc.Line, true, nil
default:
locs, err := t.client.FindLocation(ctx.Scope, args)
locs, err := t.client.FindLocation(ctx.Scope, args, false)
if err != nil {
return "", 0, false, err
}
@ -1724,7 +1724,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
switch cmd {
case "":
locs, err := t.client.FindLocation(ctx.Scope, "+0")
locs, err := t.client.FindLocation(ctx.Scope, "+0", true)
if err != nil {
return err
}
@ -1744,7 +1744,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
}
disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), api.IntelFlavour)
case "-l":
locs, err := t.client.FindLocation(ctx.Scope, rest)
locs, err := t.client.FindLocation(ctx.Scope, rest, true)
if err != nil {
return err
}

@ -580,7 +580,7 @@ func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end
t.Logf("%q: %q", listcmd, outstr)
if !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) {
if cur >= 0 && !strings.Contains(lines[0], fmt.Sprintf(":%d", cur)) {
t.Fatalf("Could not find current line number in first output line: %q", lines[0])
}
@ -627,6 +627,8 @@ func TestListCmd(t *testing.T) {
if err == nil {
t.Fatalf("Expected error requesting 50th frame")
}
listIsAt(t, term, "list testvariables.go:1", -1, 1, 6)
listIsAt(t, term, "list testvariables.go:10000", -1, 0, 0)
})
}

@ -470,6 +470,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
return starlark.None, decorateError(thread, err)
}
}
if len(args) > 2 && args[2] != starlark.None {
err := unmarshalStarlarkValue(args[2], &rpcArgs.IncludeNonExecutableLines, "IncludeNonExecutableLines")
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
for _, kv := range kwargs {
var err error
switch kv[0].(starlark.String) {
@ -477,6 +483,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Scope, "Scope")
case "Loc":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Loc, "Loc")
case "IncludeNonExecutableLines":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.IncludeNonExecutableLines, "IncludeNonExecutableLines")
default:
err = fmt.Errorf("unknown argument %q", kv[0])
}

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

@ -1145,7 +1145,7 @@ func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
}
// FindLocation will find the location specified by 'locStr'.
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Location, error) {
func (d *Debugger) FindLocation(scope api.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
@ -1160,8 +1160,11 @@ func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Locat
s, _ := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame, scope.DeferredCall)
locs, err := loc.Find(d, s, locStr)
locs, err := loc.Find(d, s, locStr, includeNonExecutableLines)
for i := range locs {
if locs[i].PC == 0 {
continue
}
file, line, fn := d.target.BinInfo().PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line

@ -17,7 +17,7 @@ import (
const maxFindLocationCandidates = 5
type LocationSpec interface {
Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error)
Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error)
}
type NormalLocationSpec struct {
@ -242,7 +242,7 @@ func (spec *FuncLocationSpec) Match(sym proc.Function) bool {
return true
}
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
funcs := d.target.BinInfo().Functions
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil {
@ -258,7 +258,7 @@ func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr st
return r, nil
}
func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
if scope == nil {
addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
if err != nil {
@ -330,7 +330,7 @@ func (ale AmbiguousLocationError) Error() string {
return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
}
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
limit := maxFindLocationCandidates
var candidateFiles []string
for _, file := range d.target.BinInfo().Sources {
@ -367,7 +367,7 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
// expression that the user forgot to prefix with '*', try treating it as
// such.
addrSpec := &AddrLocationSpec{locStr}
locs, err := addrSpec.Find(d, scope, locStr)
locs, err := addrSpec.Find(d, scope, locStr, includeNonExecutableLines)
if err != nil {
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
}
@ -384,6 +384,11 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
}
addr, 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)
}
@ -394,7 +399,7 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
return []api.Location{{PC: addr}}, nil
}
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)")
}
@ -406,10 +411,15 @@ func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
return nil, fmt.Errorf("could not determine current location")
}
addr, 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
}
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)")
}
@ -418,5 +428,10 @@ func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
return nil, fmt.Errorf("could not determine current location")
}
addr, 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
}

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

@ -328,9 +328,9 @@ func (c *RPCClient) AttachedToExistingProcess() bool {
return out.Answer
}
func (c *RPCClient) FindLocation(scope api.EvalScope, loc string) ([]api.Location, error) {
func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool) ([]api.Location, error) {
var out FindLocationOut
err := c.call("FindLocation", FindLocationIn{scope, loc}, &out)
err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions}, &out)
return out.Locations, err
}

@ -568,8 +568,9 @@ func (c *RPCServer) AttachedToExistingProcess(arg AttachedToExistingProcessIn, o
}
type FindLocationIn struct {
Scope api.EvalScope
Loc string
Scope api.EvalScope
Loc string
IncludeNonExecutableLines bool
}
type FindLocationOut struct {
@ -591,7 +592,7 @@ type FindLocationOut struct {
// NOTE: this function does not actually set breakpoints.
func (c *RPCServer) FindLocation(arg FindLocationIn, out *FindLocationOut) error {
var err error
out.Locations, err = c.debugger.FindLocation(arg.Scope, arg.Loc)
out.Locations, err = c.debugger.FindLocation(arg.Scope, arg.Loc, arg.IncludeNonExecutableLines)
return err
}

@ -75,12 +75,27 @@ func countBreakpoints(t *testing.T, c BreakpointLister) int {
return bpcount
}
type LocationFinder interface {
type locationFinder1 interface {
FindLocation(api.EvalScope, string) ([]api.Location, error)
}
func findLocationHelper(t *testing.T, c LocationFinder, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, loc)
type locationFinder2 interface {
FindLocation(api.EvalScope, string, bool) ([]api.Location, error)
}
func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 {
var locs []api.Location
var err error
switch c := c.(type) {
case locationFinder1:
locs, err = c.FindLocation(api.EvalScope{-1, 0, 0}, loc)
case locationFinder2:
locs, err = c.FindLocation(api.EvalScope{-1, 0, 0}, loc, false)
default:
t.Errorf("unexpected type %T passed to findLocationHelper", c)
}
t.Logf("FindLocation(\"%s\") → %v\n", loc, locs)
if shouldErr {

@ -924,7 +924,7 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, 0, &normalLoadConfig)
assertError(err, t, "Stacktrace()")
_, err = c.FindLocation(api.EvalScope{gid, 0, 0}, "+1")
_, err = c.FindLocation(api.EvalScope{gid, 0, 0}, "+1", false)
assertError(err, t, "FindLocation()")
_, err = c.DisassemblePC(api.EvalScope{-1, 0, 0}, 0x40100, api.IntelFlavour)
assertError(err, t, "DisassemblePC()")
@ -941,7 +941,7 @@ func TestDisasm(t *testing.T) {
state := <-ch
assertNoError(state.Err, t, "Continue()")
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "main.main")
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "main.main", false)
assertNoError(err, t, "FindLocation()")
if len(locs) != 1 {
t.Fatalf("wrong number of locations for main.main: %d", len(locs))
@ -1192,7 +1192,7 @@ func TestTypesCommand(t *testing.T) {
func TestIssue406(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("issue406", t, func(c service.Client) {
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "issue406.go:146")
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "issue406.go:146", false)
assertNoError(err, t, "FindLocation()")
_, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC})
assertNoError(err, t, "CreateBreakpoint()")
@ -1545,7 +1545,7 @@ func TestAcceptMulticlient(t *testing.T) {
}
func mustHaveDebugCalls(t *testing.T, c service.Client) {
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "runtime.debugCallV1")
locs, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "runtime.debugCallV1", false)
if len(locs) == 0 || err != nil {
t.Skip("function calls not supported on this version of go")
}
@ -1589,7 +1589,7 @@ func TestClientServerFunctionCallBadPos(t *testing.T) {
}
withTestClient2("fncall", t, func(c service.Client) {
mustHaveDebugCalls(t, c)
loc, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "fmt/print.go:649")
loc, err := c.FindLocation(api.EvalScope{-1, 0, 0}, "fmt/print.go:649", false)
assertNoError(err, t, "could not find location")
_, err = c.CreateBreakpoint(&api.Breakpoint{File: loc[0].File, Line: loc[0].Line})
@ -1723,7 +1723,7 @@ func TestUnknownMethodCall(t *testing.T) {
func TestIssue1703(t *testing.T) {
// Calling Disassemble when there is no current goroutine should work.
withTestClient2("testnextprog", t, func(c service.Client) {
locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main")
locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", true)
assertNoError(err, t, "FindLocation")
t.Logf("FindLocation: %#v", locs)
text, err := c.DisassemblePC(api.EvalScope{GoroutineID: -1}, locs[0].PC, api.IntelFlavour)