diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index f4a286e234..5136f6b405 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -30,7 +30,7 @@ detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-d 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) examine_memory(Address, Length) | Equivalent to API call [ExamineMemory](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ExamineMemory) -find_location(Scope, Loc, IncludeNonExecutableLines) | Equivalent to API call [FindLocation](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.FindLocation) +find_location(Scope, Loc, IncludeNonExecutableLines, SubstitutePathRules) | 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) diff --git a/pkg/locspec/locations.go b/pkg/locspec/locations.go index decc892e4a..16fdaf0744 100644 --- a/pkg/locspec/locations.go +++ b/pkg/locspec/locations.go @@ -20,7 +20,7 @@ const maxFindLocationCandidates = 5 // LocationSpec is an interface that represents a parsed location spec string. type LocationSpec interface { // Find returns all locations that match the location spec. - Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) + Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) } // NormalLocationSpec represents a basic location spec. @@ -267,7 +267,7 @@ func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool { // 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. -func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) { +func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { funcs := scope.BinInfo.Functions matches, err := regexFilterFuncs(loc.FuncRegex, funcs) if err != nil { @@ -284,7 +284,7 @@ func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalS } // 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) ([]api.Location, error) { +func (loc *AddrLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { if scope == nil { addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64) if err != nil { @@ -365,12 +365,16 @@ func (ale AmbiguousLocationError) Error() string { // 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 // implemented (such as regex, or addr). -func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) { +func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) { limit := maxFindLocationCandidates var candidateFiles []string - for _, file := range scope.BinInfo.Sources { - if loc.FileMatch(file) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], file)) { - candidateFiles = append(candidateFiles, file) + for _, sourceFile := range scope.BinInfo.Sources { + substFile := sourceFile + if len(substitutePathRules) > 0 { + substFile = SubstitutePath(sourceFile, substitutePathRules) + } + if loc.FileMatch(substFile) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], substFile)) { + candidateFiles = append(candidateFiles, sourceFile) if len(candidateFiles) >= limit { break } @@ -402,7 +406,7 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope // expression that the user forgot to prefix with '*', try treating it as // such. addrSpec := &AddrLocationSpec{AddrExpr: locStr} - locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines) + locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines, nil) if err != nil { return nil, fmt.Errorf("location \"%s\" not found", locStr) } @@ -434,6 +438,40 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope return []api.Location{addressesToLocation(addrs)}, nil } +func crossPlatformPath(path string) string { + if runtime.GOOS == "windows" { + return strings.ToLower(path) + } + return path +} + +// SubstitutePath applies the specified path substitution rules to path. +func SubstitutePath(path string, rules [][2]string) string { + path = crossPlatformPath(path) + // On windows paths returned from headless server are as c:/dir/dir + // though os.PathSeparator is '\\' + + separator := "/" //make it default + if strings.Contains(path, "\\") { //dependent on the path + separator = "\\" + } + for _, r := range rules { + from := crossPlatformPath(r[0]) + to := r[1] + + if !strings.HasSuffix(from, separator) { + from = from + separator + } + if !strings.HasSuffix(to, separator) { + to = to + separator + } + if strings.HasPrefix(path, from) { + return strings.Replace(path, from, to, 1) + } + } + return path +} + func addressesToLocation(addrs []uint64) api.Location { if len(addrs) <= 0 { return api.Location{} @@ -442,7 +480,7 @@ func addressesToLocation(addrs []uint64) api.Location { } // 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) ([]api.Location, error) { +func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { if scope == nil { return nil, fmt.Errorf("could not determine current location (scope is nil)") } @@ -463,7 +501,7 @@ func (loc *OffsetLocationSpec) Find(t *proc.Target, _ []string, scope *proc.Eval } // 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) ([]api.Location, error) { +func (loc *LineLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, _ string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { if scope == nil { return nil, fmt.Errorf("could not determine current location (scope is nil)") } diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 254236198a..1761f3c16b 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -607,10 +607,10 @@ func threads(t *Term, ctx callContext, args string) error { } if th.Function != nil { fmt.Printf("%sThread %d at %#v %s:%d %s\n", - prefix, th.ID, th.PC, shortenFilePath(th.File), + prefix, th.ID, th.PC, t.formatPath(th.File), th.Line, th.Function.Name()) } else { - fmt.Printf("%sThread %s\n", prefix, formatThread(th)) + fmt.Printf("%sThread %s\n", prefix, t.formatThread(th)) } } return nil @@ -667,7 +667,7 @@ func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, flags if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID { prefix = "* " } - fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) + fmt.Printf("%sGoroutine %s\n", prefix, t.formatGoroutine(g, fgl)) if flags&printGoroutinesLabels != 0 { writeGoroutineLabels(os.Stdout, g, "\t") } @@ -676,7 +676,7 @@ func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, flags if err != nil { return err } - printStack(os.Stdout, stack, "\t", false) + printStack(t, os.Stdout, stack, "\t", false) } } return nil @@ -840,7 +840,7 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi } printcontext(t, state) th := stack[frame] - fmt.Printf("Frame %d: %s:%d (PC: %x)\n", frame, shortenFilePath(th.File), th.Line, th.PC) + fmt.Printf("Frame %d: %s:%d (PC: %x)\n", frame, t.formatPath(th.File), th.Line, th.PC) printfile(t, th.File, th.Line, true) return nil } @@ -867,18 +867,18 @@ func printscope(t *Term) error { return err } - fmt.Printf("Thread %s\n", formatThread(state.CurrentThread)) + fmt.Printf("Thread %s\n", t.formatThread(state.CurrentThread)) if state.SelectedGoroutine != nil { - writeGoroutineLong(os.Stdout, state.SelectedGoroutine, "") + writeGoroutineLong(t, os.Stdout, state.SelectedGoroutine, "") } return nil } -func formatThread(th *api.Thread) string { +func (t *Term) formatThread(th *api.Thread) string { if th == nil { return "" } - return fmt.Sprintf("%d at %s:%d", th.ID, shortenFilePath(th.File), th.Line) + return fmt.Sprintf("%d at %s:%d", th.ID, t.formatPath(th.File), th.Line) } type formatGoroutineLoc int @@ -890,11 +890,11 @@ const ( fglStart ) -func formatLocation(loc api.Location) string { - return fmt.Sprintf("%s:%d %s (%#v)", shortenFilePath(loc.File), loc.Line, loc.Function.Name(), loc.PC) +func (t *Term) formatLocation(loc api.Location) string { + return fmt.Sprintf("%s:%d %s (%#v)", t.formatPath(loc.File), loc.Line, loc.Function.Name(), loc.PC) } -func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string { +func (t *Term) formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string { if g == nil { return "" } @@ -921,16 +921,16 @@ func formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string { if g.ThreadID != 0 { thread = fmt.Sprintf(" (thread %d)", g.ThreadID) } - return fmt.Sprintf("%d - %s: %s%s", g.ID, locname, formatLocation(loc), thread) + return fmt.Sprintf("%d - %s: %s%s", g.ID, locname, t.formatLocation(loc), thread) } -func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) { +func writeGoroutineLong(t *Term, w io.Writer, g *api.Goroutine, prefix string) { fmt.Fprintf(w, "%sGoroutine %d:\n%s\tRuntime: %s\n%s\tUser: %s\n%s\tGo: %s\n%s\tStart: %s\n", prefix, g.ID, - prefix, formatLocation(g.CurrentLoc), - prefix, formatLocation(g.UserCurrentLoc), - prefix, formatLocation(g.GoStatementLoc), - prefix, formatLocation(g.StartLoc)) + prefix, t.formatLocation(g.CurrentLoc), + prefix, t.formatLocation(g.UserCurrentLoc), + prefix, t.formatLocation(g.GoStatementLoc), + prefix, t.formatLocation(g.StartLoc)) writeGoroutineLabels(w, g, prefix+"\t") } @@ -1041,7 +1041,7 @@ func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newA return err } for i := range discarded { - fmt.Printf("Discarded %s at %s: %v\n", formatBreakpointName(discarded[i].Breakpoint, false), formatBreakpointLocation(discarded[i].Breakpoint), discarded[i].Reason) + fmt.Printf("Discarded %s at %s: %v\n", formatBreakpointName(discarded[i].Breakpoint, false), t.formatBreakpointLocation(discarded[i].Breakpoint), discarded[i].Reason) } return nil } @@ -1342,7 +1342,7 @@ func clear(t *Term, ctx callContext, args string) error { if err != nil { return err } - fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp)) + fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) return nil } @@ -1354,7 +1354,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, true) + locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args, true, t.substitutePathRules()) if err != nil { return err } @@ -1380,9 +1380,9 @@ func clearAll(t *Term, ctx callContext, args string) error { _, err := t.client.ClearBreakpoint(bp.ID) if err != nil { - fmt.Printf("Couldn't delete %s at %s: %s\n", formatBreakpointName(bp, false), formatBreakpointLocation(bp), err) + fmt.Printf("Couldn't delete %s at %s: %s\n", formatBreakpointName(bp, false), t.formatBreakpointLocation(bp), err) } - fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp)) + fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) } return nil } @@ -1401,7 +1401,7 @@ func breakpoints(t *Term, ctx callContext, args string) error { } sort.Sort(byID(breakPoints)) for _, bp := range breakPoints { - fmt.Printf("%s at %v (%d)\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp), bp.TotalHitCount) + fmt.Printf("%s at %v (%d)\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp), bp.TotalHitCount) var attrs []string if bp.Cond != "" { @@ -1457,7 +1457,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err } requestedBp.Tracepoint = tracepoint - locs, err := t.client.FindLocation(ctx.Scope, spec, true) + locs, err := t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) if err != nil { if requestedBp.Name == "" { return err @@ -1465,7 +1465,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err requestedBp.Name = "" spec = argstr var err2 error - locs, err2 = t.client.FindLocation(ctx.Scope, spec, true) + locs, err2 = t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) if err2 != nil { return err } @@ -1482,7 +1482,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err return err } - fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp)) + fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp)) } var shouldSetReturnBreakpoints bool @@ -1829,7 +1829,7 @@ func stackCommand(t *Term, ctx callContext, args string) error { if err != nil { return err } - printStack(os.Stdout, stack, "", sa.offsets) + printStack(t, os.Stdout, stack, "", sa.offsets) if sa.ancestors > 0 { ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth) if err != nil { @@ -1841,7 +1841,7 @@ func stackCommand(t *Term, ctx callContext, args string) error { fmt.Printf("\t%s\n", ancestor.Unreadable) continue } - printStack(os.Stdout, ancestor.Stack, "\t", false) + printStack(t, os.Stdout, ancestor.Stack, "\t", false) } } return nil @@ -1970,7 +1970,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, false) + locs, err := t.client.FindLocation(ctx.Scope, args, false, t.substitutePathRules()) if err != nil { return "", 0, false, err } @@ -2041,7 +2041,7 @@ func disassCommand(t *Term, ctx callContext, args string) error { switch cmd { case "": - locs, err := t.client.FindLocation(ctx.Scope, "+0", true) + locs, err := t.client.FindLocation(ctx.Scope, "+0", true, t.substitutePathRules()) if err != nil { return err } @@ -2061,7 +2061,7 @@ func disassCommand(t *Term, ctx callContext, args string) error { } disasm, disasmErr = t.client.DisassembleRange(ctx.Scope, uint64(startpc), uint64(endpc), flavor) case "-l": - locs, err := t.client.FindLocation(ctx.Scope, rest, true) + locs, err := t.client.FindLocation(ctx.Scope, rest, true, t.substitutePathRules()) if err != nil { return err } @@ -2103,7 +2103,7 @@ func digits(n int) int { const stacktraceTruncatedMessage = "(truncated)" -func printStack(out io.Writer, stack []api.Stackframe, ind string, offsets bool) { +func printStack(t *Term, out io.Writer, stack []api.Stackframe, ind string, offsets bool) { if len(stack) == 0 { return } @@ -2126,7 +2126,7 @@ func printStack(out io.Writer, stack []api.Stackframe, ind string, offsets bool) continue } fmt.Fprintf(out, fmtstr, ind, i, stack[i].PC, stack[i].Function.Name()) - fmt.Fprintf(out, "%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line) + fmt.Fprintf(out, "%sat %s:%d\n", s, t.formatPath(stack[i].File), stack[i].Line) if offsets { fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset) @@ -2140,8 +2140,8 @@ func printStack(out io.Writer, stack []api.Stackframe, ind string, offsets bool) continue } fmt.Fprintf(out, "%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name()) - fmt.Fprintf(out, "%sat %s:%d\n", s2, d.DeferredLoc.File, d.DeferredLoc.Line) - fmt.Fprintf(out, "%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), d.DeferLoc.File, d.DeferLoc.Line) + fmt.Fprintf(out, "%sat %s:%d\n", s2, t.formatPath(d.DeferredLoc.File), d.DeferredLoc.Line) + fmt.Fprintf(out, "%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), t.formatPath(d.DeferLoc.File), d.DeferLoc.Line) } for j := range stack[i].Arguments { @@ -2187,7 +2187,7 @@ func printcontext(t *Term, state *api.DebuggerState) { } } if th == nil { - printcontextLocation(state.SelectedGoroutine.CurrentLoc) + printcontextLocation(t, state.SelectedGoroutine.CurrentLoc) return } } @@ -2205,8 +2205,8 @@ func printcontext(t *Term, state *api.DebuggerState) { } } -func printcontextLocation(loc api.Location) { - fmt.Printf("> %s() %s:%d (PC: %#v)\n", loc.Function.Name(), shortenFilePath(loc.File), loc.Line, loc.PC) +func printcontextLocation(t *Term, loc api.Location) { + fmt.Printf("> %s() %s:%d (PC: %#v)\n", loc.Function.Name(), t.formatPath(loc.File), loc.Line, loc.PC) if loc.Function != nil && loc.Function.Optimized { fmt.Println(optimizedFunctionWarning) } @@ -2227,7 +2227,7 @@ func printcontextThread(t *Term, th *api.Thread) { fn := th.Function if th.Breakpoint == nil { - printcontextLocation(api.Location{PC: th.PC, File: th.File, Line: th.Line, Function: th.Function}) + printcontextLocation(t, api.Location{PC: th.PC, File: th.File, Line: th.Line, Function: th.Function}) printReturnValues(th) return } @@ -2258,7 +2258,7 @@ func printcontextThread(t *Term, th *api.Thread) { } if th.Breakpoint.Tracepoint || th.Breakpoint.TraceReturn { - printTracepoint(th, bpname, fn, args, hasReturnValue) + printTracepoint(t, th, bpname, fn, args, hasReturnValue) return } @@ -2267,7 +2267,7 @@ func printcontextThread(t *Term, th *api.Thread) { bpname, fn.Name(), args, - shortenFilePath(th.File), + t.formatPath(th.File), th.Line, th.GoroutineID, hitCount, @@ -2278,7 +2278,7 @@ func printcontextThread(t *Term, th *api.Thread) { bpname, fn.Name(), args, - shortenFilePath(th.File), + t.formatPath(th.File), th.Line, th.Breakpoint.TotalHitCount, th.PC) @@ -2288,10 +2288,10 @@ func printcontextThread(t *Term, th *api.Thread) { } printReturnValues(th) - printBreakpointInfo(th, false) + printBreakpointInfo(t, th, false) } -func printBreakpointInfo(th *api.Thread, tracepointOnNewline bool) { +func printBreakpointInfo(t *Term, th *api.Thread, tracepointOnNewline bool) { if th.BreakpointInfo == nil { return } @@ -2313,7 +2313,7 @@ func printBreakpointInfo(th *api.Thread, tracepointOnNewline bool) { if bpi.Goroutine != nil { tracepointnl() - writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t") + writeGoroutineLong(t, os.Stdout, bpi.Goroutine, "\t") } for _, v := range bpi.Variables { @@ -2340,17 +2340,17 @@ func printBreakpointInfo(th *api.Thread, tracepointOnNewline bool) { if bpi.Stacktrace != nil { tracepointnl() fmt.Printf("\tStack:\n") - printStack(os.Stdout, bpi.Stacktrace, "\t\t", false) + printStack(t, os.Stdout, bpi.Stacktrace, "\t\t", false) } } -func printTracepoint(th *api.Thread, bpname string, fn *api.Function, args string, hasReturnValue bool) { +func printTracepoint(t *Term, th *api.Thread, bpname string, fn *api.Function, args string, hasReturnValue bool) { if th.Breakpoint.Tracepoint { fmt.Fprintf(os.Stderr, "> goroutine(%d): %s%s(%s)", th.GoroutineID, bpname, fn.Name(), args) if !hasReturnValue { fmt.Println() } - printBreakpointInfo(th, !hasReturnValue) + printBreakpointInfo(t, th, !hasReturnValue) } if th.Breakpoint.TraceReturn { retVals := make([]string, 0, len(th.ReturnValues)) @@ -2362,7 +2362,7 @@ func printTracepoint(th *api.Thread, bpname string, fn *api.Function, args strin if th.Breakpoint.TraceReturn || !hasReturnValue { if th.BreakpointInfo != nil && th.BreakpointInfo.Stacktrace != nil { fmt.Fprintf(os.Stderr, "\tStack:\n") - printStack(os.Stderr, th.BreakpointInfo.Stacktrace, "\t\t", false) + printStack(t, os.Stderr, th.BreakpointInfo.Stacktrace, "\t\t", false) } } } @@ -2479,13 +2479,6 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error { return t.client.AmendBreakpoint(bp) } -// shortenFilePath take a full file path and attempts to shorten -// it by replacing the current directory to './'. -func shortenFilePath(fullPath string) string { - workingDir, _ := os.Getwd() - return strings.Replace(fullPath, workingDir, ".", 1) -} - func (c *Commands) executeFile(t *Term, name string) error { fh, err := os.Open(name) if err != nil { @@ -2625,7 +2618,7 @@ func formatBreakpointName(bp *api.Breakpoint, upcase bool) string { return fmt.Sprintf("%s %s", thing, id) } -func formatBreakpointLocation(bp *api.Breakpoint) string { +func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string { var out bytes.Buffer if len(bp.Addrs) > 0 { for i, addr := range bp.Addrs { @@ -2640,7 +2633,7 @@ func formatBreakpointLocation(bp *api.Breakpoint) string { fmt.Fprintf(&out, "%#x", bp.Addr) } fmt.Fprintf(&out, " for ") - p := shortenFilePath(bp.File) + p := t.formatPath(bp.File) if bp.FunctionName != "" { fmt.Fprintf(&out, "%s() ", bp.FunctionName) } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 43ae875bcd..5669c98d5b 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -247,8 +247,8 @@ func TestExecuteFile(t *testing.T) { } func TestIssue354(t *testing.T) { - printStack(os.Stdout, []api.Stackframe{}, "", false) - printStack(os.Stdout, []api.Stackframe{ + printStack(&Term{}, os.Stdout, []api.Stackframe{}, "", false) + printStack(&Term{}, os.Stdout, []api.Stackframe{ {Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil}, Bottom: true}}, "", false) } diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go index b4f2bff59e..b6855382c1 100644 --- a/pkg/terminal/starbind/starlark_mapping.go +++ b/pkg/terminal/starbind/starlark_mapping.go @@ -514,6 +514,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { return starlark.None, decorateError(thread, err) } } + if len(args) > 3 && args[3] != starlark.None { + err := unmarshalStarlarkValue(args[3], &rpcArgs.SubstitutePathRules, "SubstitutePathRules") + if err != nil { + return starlark.None, decorateError(thread, err) + } + } for _, kv := range kwargs { var err error switch kv[0].(starlark.String) { @@ -523,6 +529,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { err = unmarshalStarlarkValue(kv[1], &rpcArgs.Loc, "Loc") case "IncludeNonExecutableLines": err = unmarshalStarlarkValue(kv[1], &rpcArgs.IncludeNonExecutableLines, "IncludeNonExecutableLines") + case "SubstitutePathRules": + err = unmarshalStarlarkValue(kv[1], &rpcArgs.SubstitutePathRules, "SubstitutePathRules") default: err = fmt.Errorf("unknown argument %q", kv[0]) } diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 27f67f3796..c042859f33 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -6,7 +6,6 @@ import ( "net/rpc" "os" "os/signal" - "runtime" "strings" "sync" "syscall" @@ -14,6 +13,7 @@ import ( "github.com/peterh/liner" "github.com/go-delve/delve/pkg/config" + "github.com/go-delve/delve/pkg/locspec" "github.com/go-delve/delve/pkg/terminal/starbind" "github.com/go-delve/delve/service" "github.com/go-delve/delve/service/api" @@ -60,6 +60,8 @@ type Term struct { starlarkEnv *starbind.Env + substitutePathRulesCache [][2]string + // quitContinue is set to true by exitCommand to signal that the process // should be resumed before quitting. quitContinue bool @@ -287,40 +289,33 @@ func (t *Term) Println(prefix, str string) { // in the order they are defined, first rule that matches is used for // substitution. func (t *Term) substitutePath(path string) string { - path = crossPlatformPath(path) if t.conf == nil { return path } + return locspec.SubstitutePath(path, t.substitutePathRules()) +} - // On windows paths returned from headless server are as c:/dir/dir - // though os.PathSeparator is '\\' - - separator := "/" //make it default - if strings.Index(path, "\\") != -1 { //dependent on the path - separator = "\\" +func (t *Term) substitutePathRules() [][2]string { + if t.substitutePathRulesCache != nil { + return t.substitutePathRulesCache } + if t.conf == nil || t.conf.SubstitutePath == nil { + return nil + } + spr := make([][2]string, 0, len(t.conf.SubstitutePath)) for _, r := range t.conf.SubstitutePath { - from := crossPlatformPath(r.From) - to := r.To - - if !strings.HasSuffix(from, separator) { - from = from + separator - } - if !strings.HasSuffix(to, separator) { - to = to + separator - } - if strings.HasPrefix(path, from) { - return strings.Replace(path, from, to, 1) - } + spr = append(spr, [2]string{r.From, r.To}) } - return path + t.substitutePathRulesCache = spr + return spr } -func crossPlatformPath(path string) string { - if runtime.GOOS == "windows" { - return strings.ToLower(path) - } - return path +// formatPath applies path substitution rules and shortens the resulting +// path by replacing the current directory with './' +func (t *Term) formatPath(path string) string { + path = t.substitutePath(path) + workingDir, _ := os.Getwd() + return strings.Replace(path, workingDir, ".", 1) } func (t *Term) promptForInput() (string, error) { diff --git a/service/client.go b/service/client.go index 8fd863b8ef..296317c133 100644 --- a/service/client.go +++ b/service/client.go @@ -130,7 +130,7 @@ type Client interface { // * *
returns the location corresponding to the specified address // NOTE: this function does not actually set breakpoints. // If findInstruction is true FindLocation will only return locations that correspond to instructions. - FindLocation(scope api.EvalScope, loc string, findInstruction bool) ([]api.Location, error) + FindLocation(scope api.EvalScope, loc string, findInstruction bool, substitutePathRules [][2]string) ([]api.Location, error) // Disassemble code between startPC and endPC DisassembleRange(scope api.EvalScope, startPC, endPC uint64, flavour api.AssemblyFlavour) (api.AsmInstructions, error) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 6e4ced703e..98a42f1362 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -1443,7 +1443,7 @@ func (d *Debugger) CurrentPackage() (string, error) { } // FindLocation will find the location specified by 'locStr'. -func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, includeNonExecutableLines bool) ([]api.Location, error) { +func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) { d.targetMutex.Lock() defer d.targetMutex.Unlock() @@ -1458,7 +1458,7 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in s, _ := proc.ConvertEvalScope(d.target, goid, frame, deferredCall) - locs, err := loc.Find(d.target, d.processArgs, s, locStr, includeNonExecutableLines) + locs, err := loc.Find(d.target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules) for i := range locs { if locs[i].PC == 0 { continue diff --git a/service/rpc1/server.go b/service/rpc1/server.go index 376620ef3f..c2ec5a7877 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -304,7 +304,7 @@ type FindLocationArgs struct { func (c *RPCServer) FindLocation(args FindLocationArgs, answer *[]api.Location) error { var err error - *answer, err = c.debugger.FindLocation(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Loc, false) + *answer, err = c.debugger.FindLocation(args.Scope.GoroutineID, args.Scope.Frame, args.Scope.DeferredCall, args.Loc, false, nil) return err } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 7de4dcb9fb..fc95a3c955 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -356,9 +356,9 @@ func (c *RPCClient) AttachedToExistingProcess() bool { return out.Answer } -func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool) ([]api.Location, error) { +func (c *RPCClient) FindLocation(scope api.EvalScope, loc string, findInstructions bool, substitutePathRules [][2]string) ([]api.Location, error) { var out FindLocationOut - err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions}, &out) + err := c.call("FindLocation", FindLocationIn{scope, loc, !findInstructions, substitutePathRules}, &out) return out.Locations, err } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 6ec0de6550..b467b828b4 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -600,6 +600,13 @@ type FindLocationIn struct { Scope api.EvalScope Loc string IncludeNonExecutableLines bool + + // SubstitutePathRules is a slice of source code path substitution rules, + // the first entry of each pair is the path of a directory as it appears in + // the executable file (i.e. the location of a source file when the program + // was compiled), the second entry of each pair is the location of the same + // directory on the client system. + SubstitutePathRules [][2]string } type FindLocationOut struct { @@ -621,7 +628,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.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Loc, arg.IncludeNonExecutableLines) + out.Locations, err = c.debugger.FindLocation(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Loc, arg.IncludeNonExecutableLines, arg.SubstitutePathRules) return err } diff --git a/service/test/common_test.go b/service/test/common_test.go index 1e217e1e39..344136e55a 100644 --- a/service/test/common_test.go +++ b/service/test/common_test.go @@ -80,7 +80,7 @@ type locationFinder1 interface { } type locationFinder2 interface { - FindLocation(api.EvalScope, string, bool) ([]api.Location, error) + FindLocation(api.EvalScope, string, bool, [][2]string) ([]api.Location, error) } func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 { @@ -91,7 +91,7 @@ func findLocationHelper(t *testing.T, c interface{}, loc string, shouldErr bool, case locationFinder1: locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc) case locationFinder2: - locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false) + locs, err = c.FindLocation(api.EvalScope{GoroutineID: -1}, loc, false, nil) default: t.Errorf("unexpected type %T passed to findLocationHelper", c) } diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index fa8b8ed0d1..9ff22d819b 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -766,6 +766,23 @@ func TestClientServer_FindLocations(t *testing.T) { findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:33", 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) + sep := "/" + if strings.Contains(locsNoSubst[0].File, "\\") { + sep = "\\" + } + substRules := [][2]string{[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]) + locsSubst, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "nonexistent/locationsprog.go:35", false, substRules) + if err != nil { + t.Fatalf("FindLocation(locationsprog.go:35) with substitute rules: %v", err) + } + t.Logf("FindLocation(\"/nonexistent/path/locationsprog.go:35\") -> %#v", locsSubst) + if locsNoSubst[0].PC != locsSubst[0].PC { + t.Fatalf("FindLocation with substitute rules mismatch %#v %#v", locsNoSubst[0], locsSubst[0]) + } + }) withTestClient2("testnextdefer", t, func(c service.Client) { @@ -1022,7 +1039,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{GoroutineID: gid}, "+1", false) + _, err = c.FindLocation(api.EvalScope{GoroutineID: gid}, "+1", false, nil) assertError(err, t, "FindLocation()") _, err = c.DisassemblePC(api.EvalScope{GoroutineID: -1}, 0x40100, api.IntelFlavour) assertError(err, t, "DisassemblePC()") @@ -1039,7 +1056,7 @@ func TestDisasm(t *testing.T) { state := <-ch assertNoError(state.Err, t, "Continue()") - locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", false) + locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", false, nil) assertNoError(err, t, "FindLocation()") if len(locs) != 1 { t.Fatalf("wrong number of locations for main.main: %d", len(locs)) @@ -1301,7 +1318,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{GoroutineID: -1}, "issue406.go:146", false) + locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "issue406.go:146", false, nil) assertNoError(err, t, "FindLocation()") _, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC}) assertNoError(err, t, "CreateBreakpoint()") @@ -1709,7 +1726,7 @@ func TestAcceptMulticlient(t *testing.T) { } func mustHaveDebugCalls(t *testing.T, c service.Client) { - locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "runtime.debugCallV1", false) + locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "runtime.debugCallV1", false, nil) if len(locs) == 0 || err != nil { t.Skip("function calls not supported on this version of go") } @@ -1753,7 +1770,7 @@ func TestClientServerFunctionCallBadPos(t *testing.T) { } withTestClient2("fncall", t, func(c service.Client) { mustHaveDebugCalls(t, c) - loc, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "fmt/print.go:649", false) + loc, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "fmt/print.go:649", false, nil) assertNoError(err, t, "could not find location") _, err = c.CreateBreakpoint(&api.Breakpoint{File: loc[0].File, Line: loc[0].Line}) @@ -1890,7 +1907,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", true) + locs, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "main.main", true, nil) assertNoError(err, t, "FindLocation") t.Logf("FindLocation: %#v", locs) text, err := c.DisassemblePC(api.EvalScope{GoroutineID: -1}, locs[0].PC, api.IntelFlavour)