未验证 提交 f96663a2 编写于 作者: D Derek Parker 提交者: GitHub

cmd/dlv: Fix trace output (#2038)

* cmd/dlv,debugger: Improve dlv trace and trace command output

This patch improves the `dlv trace` subcommand output by reducing the
noise that is generated and providing clearer more concise information.

Also adds new tests closing a gap in our testing (we previously never
really tested this subcommand).

This patch also fixes the `dlv trace` REPL command to behave like the
subcommand in certain situations. If the tracepoint is for a function,
we now show function arguements and return values properly.

Also makes the overall output of the trace subcommand clearer.

Fixes #2027
上级 a33be446
......@@ -12,6 +12,9 @@ provided regular expression and output information when tracepoint is hit. This
is useful if you do not want to begin an entire debug session, but merely want
to know what functions your process is executing.
The output of the trace sub command is printed to stderr, so if you would like to
only see the output of the trace operations you can redirect stdout.
```
dlv trace [package] regexp
```
......
......@@ -10,6 +10,7 @@ import (
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/go-delve/delve/pkg/config"
......@@ -251,7 +252,10 @@ that package instead.`,
The trace sub command will set a tracepoint on every function matching the
provided regular expression and output information when tracepoint is hit. This
is useful if you do not want to begin an entire debug session, but merely want
to know what functions your process is executing.`,
to know what functions your process is executing.
The output of the trace sub command is printed to stderr, so if you would like to
only see the output of the trace operations you can redirect stdout.`,
Run: traceCmd,
}
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
......@@ -536,7 +540,7 @@ func traceCmd(cmd *cobra.Command, args []string) {
Stacktrace: traceStackDepth,
LoadArgs: &terminal.ShortLoadConfig,
})
if err != nil {
if err != nil && !isBreakpointExistsErr(err) {
fmt.Fprintln(os.Stderr, err)
return 1
}
......@@ -549,10 +553,11 @@ func traceCmd(cmd *cobra.Command, args []string) {
_, err = client.CreateBreakpoint(&api.Breakpoint{
Addr: addrs[i],
TraceReturn: true,
Stacktrace: traceStackDepth,
Line: -1,
LoadArgs: &terminal.ShortLoadConfig,
})
if err != nil {
if err != nil && !isBreakpointExistsErr(err) {
fmt.Fprintln(os.Stderr, err)
return 1
}
......@@ -561,16 +566,16 @@ func traceCmd(cmd *cobra.Command, args []string) {
cmds := terminal.DebugCommands(client)
t := terminal.New(client, nil)
defer t.Close()
err = cmds.Call("continue", t)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
cmds.Call("continue", t)
return 0
}()
os.Exit(status)
}
func isBreakpointExistsErr(err error) bool {
return strings.Contains(err.Error(), "Breakpoint exists")
}
func testCmd(cmd *cobra.Command, args []string) {
status := func() int {
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
......
......@@ -23,7 +23,6 @@ import (
"github.com/go-delve/delve/pkg/terminal"
"github.com/go-delve/delve/service/dap/daptest"
"github.com/go-delve/delve/service/rpc2"
"golang.org/x/tools/go/packages"
)
......@@ -557,3 +556,85 @@ func TestDap(t *testing.T) {
client.Close()
cmd.Wait()
}
func TestTrace(t *testing.T) {
dlvbin, tmpdir := getDlvBin(t)
defer os.RemoveAll(tmpdir)
expected := []byte("> goroutine(1): main.foo(99, 9801) => (9900)\n")
fixtures := protest.FindFixturesDir()
cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "issue573.go"), "foo")
rdr, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
cmd.Dir = filepath.Join(fixtures, "buildtest")
err = cmd.Start()
if err != nil {
t.Fatalf("error running trace: %v", err)
}
output, err := ioutil.ReadAll(rdr)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(output, expected) {
t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output))
}
cmd.Wait()
}
func TestTraceBreakpointExists(t *testing.T) {
dlvbin, tmpdir := getDlvBin(t)
defer os.RemoveAll(tmpdir)
fixtures := protest.FindFixturesDir()
// We always set breakpoints on some runtime functions at startup, so this would return with
// a breakpoints exists error.
// TODO: Perhaps we shouldn't be setting these default breakpoints in trace mode, however.
cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "issue573.go"), "runtime.*")
rdr, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
cmd.Dir = filepath.Join(fixtures, "buildtest")
err = cmd.Start()
if err != nil {
t.Fatalf("error running trace: %v", err)
}
defer cmd.Wait()
output, err := ioutil.ReadAll(rdr)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(output, []byte("Breakpoint exists")) {
t.Fatal("Breakpoint exists errors should be ignored")
}
}
func TestTracePrintStack(t *testing.T) {
dlvbin, tmpdir := getDlvBin(t)
defer os.RemoveAll(tmpdir)
fixtures := protest.FindFixturesDir()
cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), "--stack", "2", filepath.Join(fixtures, "issue573.go"), "foo")
rdr, err := cmd.StderrPipe()
if err != nil {
t.Fatal(err)
}
cmd.Dir = filepath.Join(fixtures, "buildtest")
err = cmd.Start()
if err != nil {
t.Fatalf("error running trace: %v", err)
}
defer cmd.Wait()
output, err := ioutil.ReadAll(rdr)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(output, []byte("Stack:")) && !bytes.Contains(output, []byte("main.main")) {
t.Fatal("stacktrace not printed")
}
}
// Package locspec implements code to parse a string into a specific
// location specification.
//
// Location spec examples:
//
// locStr ::= <filename>:<line> | <function>[:<line>] | /<regex>/ | (+|-)<offset> | <line> | *<address>
// * <filename> can be the full path of a file or just a suffix
// * <function> ::= <package>.<receiver type>.<name> | <package>.(*<receiver type>).<name> | <receiver type>.<name> | <package>.<name> | (*<receiver type>).<name> | <name>
// * <function> must be unambiguous
// * /<regex>/ will return a location for each function matched by regex
// * +<offset> returns a location for the line that is <offset> lines after the current line
// * -<offset> returns a location for the line that is <offset> lines before the current line
// * <line> returns a location for a line in the current file
// * *<address> returns the location corresponding to the specified address
package locspec
package debugger
package locspec
import (
"fmt"
......@@ -6,6 +6,7 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
......@@ -16,32 +17,44 @@ import (
const maxFindLocationCandidates = 5
// LocationSpec is an interface that represents a parsed location spec string.
type LocationSpec interface {
Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error)
// 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)
}
// NormalLocationSpec represents a basic location spec.
// This can be a file:line or func:line.
type NormalLocationSpec struct {
Base string
FuncBase *FuncLocationSpec
LineOffset int
}
// RegexLocationSpec represents a regular expression
// location expression such as /^myfunc$/.
type RegexLocationSpec struct {
FuncRegex string
}
// AddrLocationSpec represents an address when used
// as a location spec.
type AddrLocationSpec struct {
AddrExpr string
}
// OffsetLocationSpec represents a location spec that
// is an offset of the current location (file:line).
type OffsetLocationSpec struct {
Offset int
}
// LineLocationSpec represents a line number in the current file.
type LineLocationSpec struct {
Line int
}
// FuncLocationSpec represents a function in the target program.
type FuncLocationSpec struct {
PackageName string
AbsolutePackage bool
......@@ -50,7 +63,8 @@ type FuncLocationSpec struct {
BaseName string
}
func parseLocationSpec(locStr string) (LocationSpec, error) {
// Parse will turn locStr into a parsed LocationSpec.
func Parse(locStr string) (LocationSpec, error) {
rest := locStr
malformed := func(reason string) error {
......@@ -84,7 +98,7 @@ func parseLocationSpec(locStr string) (LocationSpec, error) {
}
case '*':
return &AddrLocationSpec{rest[1:]}, nil
return &AddrLocationSpec{AddrExpr: rest[1:]}, nil
default:
return parseLocationSpecDefault(locStr, rest)
......@@ -215,6 +229,7 @@ func stripReceiverDecoration(in string) string {
return in[2 : len(in)-1]
}
// Match will return whether the provided function matches the location spec.
func (spec *FuncLocationSpec) Match(sym proc.Function, packageMap map[string][]string) bool {
if spec.BaseName != sym.BaseName() {
return false
......@@ -250,15 +265,17 @@ func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool {
return partialPackageMatch(specPkg, symPkg)
}
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
funcs := d.target.BinInfo().Functions
// 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) {
funcs := scope.BinInfo.Functions
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil {
return nil, err
}
r := make([]api.Location, 0, len(matches))
for i := range matches {
addrs, _ := proc.FindFunctionLocation(d.target, matches[i], 0)
addrs, _ := proc.FindFunctionLocation(t, matches[i], 0)
if len(addrs) > 0 {
r = append(r, addressesToLocation(addrs))
}
......@@ -266,38 +283,40 @@ 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, includeNonExecutableLines bool) ([]api.Location, error) {
// 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) {
if scope == nil {
addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
if err != nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)")
}
return []api.Location{{PC: uint64(addr)}}, nil
} else {
v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true})
}
v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{FollowPointers: true})
if err != nil {
return nil, err
}
if v.Unreadable != nil {
return nil, v.Unreadable
}
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:
addr, _ := constant.Uint64Val(v.Value)
return []api.Location{{PC: addr}}, nil
case reflect.Func:
fn := scope.BinInfo.PCToFunc(uint64(v.Base))
pc, err := proc.FirstPCAfterPrologue(t, fn, false)
if err != nil {
return nil, err
}
if v.Unreadable != nil {
return nil, v.Unreadable
}
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:
addr, _ := constant.Uint64Val(v.Value)
return []api.Location{{PC: addr}}, nil
case reflect.Func:
fn := d.target.BinInfo().PCToFunc(uint64(v.Base))
pc, err := proc.FirstPCAfterPrologue(d.target, fn, false)
if err != nil {
return nil, err
}
return []api.Location{{PC: pc}}, nil
default:
return nil, fmt.Errorf("wrong expression kind: %v", v.Kind)
}
return []api.Location{{PC: pc}}, nil
default:
return nil, fmt.Errorf("wrong expression kind: %v", v.Kind)
}
}
// FileMatch is true if the path matches the location spec.
func (loc *NormalLocationSpec) FileMatch(path string) bool {
return partialPathMatch(loc.Base, path)
}
......@@ -318,11 +337,12 @@ func partialPathMatch(expr, path string) bool {
func partialPackageMatch(expr, path string) bool {
if len(expr) < len(path)-1 {
return strings.HasSuffix(path, expr) && (path[len(path)-len(expr)-1] == '/')
} else {
return expr == path
}
return expr == path
}
// AmbiguousLocationError is returned when the location spec
// should only return one location but returns multiple instead.
type AmbiguousLocationError struct {
Location string
CandidatesString []string
......@@ -342,11 +362,14 @@ 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, includeNonExecutableLines bool) ([]api.Location, error) {
// 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) {
limit := maxFindLocationCandidates
var candidateFiles []string
for _, file := range d.target.BinInfo().Sources {
if loc.FileMatch(file) || (len(d.processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, d.processArgs[0], file)) {
for _, file := range scope.BinInfo.Sources {
if loc.FileMatch(file) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], file)) {
candidateFiles = append(candidateFiles, file)
if len(candidateFiles) >= limit {
break
......@@ -358,8 +381,8 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
var candidateFuncs []string
if loc.FuncBase != nil {
for _, f := range d.target.BinInfo().Functions {
if !loc.FuncBase.Match(f, d.target.BinInfo().PackageMap) {
for _, f := range scope.BinInfo.Functions {
if !loc.FuncBase.Match(f, scope.BinInfo.PackageMap) {
continue
}
if loc.Base == f.Name {
......@@ -375,13 +398,13 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
}
if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
// if no result was found treat 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
// such.
addrSpec := &AddrLocationSpec{locStr}
locs, err := addrSpec.Find(d, scope, locStr, includeNonExecutableLines)
addrSpec := &AddrLocationSpec{AddrExpr: locStr}
locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines)
if err != nil {
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
return nil, fmt.Errorf("location \"%s\" not found", locStr)
}
return locs, nil
} else if matching > 1 {
......@@ -395,14 +418,14 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
if loc.LineOffset < 0 {
return nil, fmt.Errorf("Malformed breakpoint location, no line offset specified")
}
addrs, err = proc.FindFileLocation(d.target, candidateFiles[0], loc.LineOffset)
addrs, err = proc.FindFileLocation(t, 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
addrs, err = proc.FindFunctionLocation(d.target, candidateFuncs[0], loc.LineOffset)
addrs, err = proc.FindFunctionLocation(t, candidateFuncs[0], loc.LineOffset)
}
if err != nil {
......@@ -418,18 +441,19 @@ func addressesToLocation(addrs []uint64) api.Location {
return api.Location{PC: addrs[0], PCs: addrs}
}
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
// 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) {
if scope == 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 := d.target.BinInfo().PCToLine(scope.PC)
file, line, fn := scope.BinInfo.PCToLine(scope.PC)
if fn == nil {
return nil, fmt.Errorf("could not determine current location")
}
addrs, err := proc.FindFileLocation(d.target, file, line+loc.Offset)
addrs, err := proc.FindFileLocation(t, file, line+loc.Offset)
if includeNonExecutableLines {
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
return []api.Location{{File: file, Line: line + loc.Offset}}, nil
......@@ -438,15 +462,16 @@ func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
return []api.Location{addressesToLocation(addrs)}, err
}
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
// 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) {
if scope == nil {
return nil, fmt.Errorf("could not determine current location (scope is nil)")
}
file, _, fn := d.target.BinInfo().PCToLine(scope.PC)
file, _, fn := scope.BinInfo.PCToLine(scope.PC)
if fn == nil {
return nil, fmt.Errorf("could not determine current location")
}
addrs, err := proc.FindFileLocation(d.target, file, loc.Line)
addrs, err := proc.FindFileLocation(t, file, loc.Line)
if includeNonExecutableLines {
if _, isCouldNotFindLine := err.(*proc.ErrCouldNotFindLine); isCouldNotFindLine {
return []api.Location{{File: file, Line: loc.Line}}, nil
......@@ -454,3 +479,18 @@ func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
}
return []api.Location{addressesToLocation(addrs)}, err
}
func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
funcs := []string{}
for _, f := range allFuncs {
if regex.MatchString(f.Name) {
funcs = append(funcs, f.Name)
}
}
return funcs, nil
}
package debugger
package locspec
import (
"testing"
)
func parseLocationSpecNoError(t *testing.T, locstr string) LocationSpec {
spec, err := parseLocationSpec(locstr)
spec, err := Parse(locstr)
if err != nil {
t.Fatalf("Error parsing %q: %v", locstr, err)
}
......
......@@ -22,9 +22,10 @@ import (
"text/tabwriter"
"github.com/cosiner/argv"
"github.com/go-delve/delve/pkg/locspec"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/debugger"
"github.com/go-delve/delve/service/rpc2"
)
const optimizedFunctionWarning = "Warning: debugging optimized function"
......@@ -668,7 +669,7 @@ func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, flags
if err != nil {
return err
}
printStack(stack, "\t", false)
printStack(os.Stdout, stack, "\t", false)
}
}
return nil
......@@ -1187,10 +1188,7 @@ func (c *Commands) revCmd(t *Term, ctx callContext, args string) error {
}
ctx.Prefix = revPrefix
if err := c.CallWithContext(args, t, ctx); err != nil {
return err
}
return nil
return c.CallWithContext(args, t, ctx)
}
func (c *Commands) next(t *Term, ctx callContext, args string) error {
......@@ -1386,31 +1384,31 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
args := split2PartsBySpace(argstr)
requestedBp := &api.Breakpoint{}
locspec := ""
spec := ""
switch len(args) {
case 1:
locspec = argstr
spec = argstr
case 2:
if api.ValidBreakpointName(args[0]) == nil {
requestedBp.Name = args[0]
locspec = args[1]
spec = args[1]
} else {
locspec = argstr
spec = argstr
}
default:
return fmt.Errorf("address required")
}
requestedBp.Tracepoint = tracepoint
locs, err := t.client.FindLocation(ctx.Scope, locspec, true)
locs, err := t.client.FindLocation(ctx.Scope, spec, true)
if err != nil {
if requestedBp.Name == "" {
return err
}
requestedBp.Name = ""
locspec = argstr
spec = argstr
var err2 error
locs, err2 = t.client.FindLocation(ctx.Scope, locspec, true)
locs, err2 = t.client.FindLocation(ctx.Scope, spec, true)
if err2 != nil {
return err
}
......@@ -1418,6 +1416,9 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
for _, loc := range locs {
requestedBp.Addr = loc.PC
requestedBp.Addrs = loc.PCs
if tracepoint {
requestedBp.LoadArgs = &ShortLoadConfig
}
bp, err := t.client.CreateBreakpoint(requestedBp)
if err != nil {
......@@ -1426,6 +1427,40 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
}
var shouldSetReturnBreakpoints bool
loc, err := locspec.Parse(spec)
if err != nil {
return err
}
switch t := loc.(type) {
case *locspec.NormalLocationSpec:
shouldSetReturnBreakpoints = t.LineOffset == -1 && t.FuncBase != nil
case *locspec.RegexLocationSpec:
shouldSetReturnBreakpoints = true
}
if tracepoint && shouldSetReturnBreakpoints && locs[0].Function != nil {
for i := range locs {
if locs[i].Function == nil {
continue
}
addrs, err := t.client.(*rpc2.RPCClient).FunctionReturnLocations(locs[0].Function.Name())
if err != nil {
return err
}
for j := range addrs {
_, err = t.client.CreateBreakpoint(&api.Breakpoint{
Addr: addrs[j],
TraceReturn: true,
Line: -1,
LoadArgs: &ShortLoadConfig,
})
if err != nil {
return err
}
}
}
}
return nil
}
......@@ -1528,7 +1563,7 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
return err
}
fmt.Printf(api.PrettyExamineMemory(uintptr(address), memArea, priFmt))
fmt.Print(api.PrettyExamineMemory(uintptr(address), memArea, priFmt))
return nil
}
......@@ -1726,7 +1761,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
if err != nil {
return err
}
printStack(stack, "", sa.offsets)
printStack(os.Stdout, stack, "", sa.offsets)
if sa.ancestors > 0 {
ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth)
if err != nil {
......@@ -1738,7 +1773,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
fmt.Printf("\t%s\n", ancestor.Unreadable)
continue
}
printStack(ancestor.Stack, "\t", false)
printStack(os.Stdout, ancestor.Stack, "\t", false)
}
}
return nil
......@@ -1872,7 +1907,7 @@ func getLocation(t *Term, ctx callContext, args string, showContext bool) (file
return "", 0, false, err
}
if len(locs) > 1 {
return "", 0, false, debugger.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
return "", 0, false, locspec.AmbiguousLocationError{Location: args, CandidatesLocation: locs}
}
loc := locs[0]
if showContext {
......@@ -2000,7 +2035,7 @@ func digits(n int) int {
const stacktraceTruncatedMessage = "(truncated)"
func printStack(stack []api.Stackframe, ind string, offsets bool) {
func printStack(out io.Writer, stack []api.Stackframe, ind string, offsets bool) {
if len(stack) == 0 {
return
}
......@@ -2019,42 +2054,42 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
for i := range stack {
if stack[i].Err != "" {
fmt.Printf("%serror: %s\n", s, stack[i].Err)
fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
continue
}
fmt.Printf(fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
fmt.Printf("%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)
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)
if offsets {
fmt.Printf("%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
fmt.Fprintf(out, "%sframe: %+#x frame pointer %+#x\n", s, stack[i].FrameOffset, stack[i].FramePointerOffset)
}
for j, d := range stack[i].Defers {
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
s2 := strings.Repeat(" ", len(deferHeader))
if d.Unreadable != "" {
fmt.Printf("%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
fmt.Fprintf(out, "%s(unreadable defer: %s)\n", deferHeader, d.Unreadable)
continue
}
fmt.Printf("%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
fmt.Printf("%sat %s:%d\n", s2, d.DeferredLoc.File, d.DeferredLoc.Line)
fmt.Printf("%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), d.DeferLoc.File, d.DeferLoc.Line)
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)
}
for j := range stack[i].Arguments {
fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
}
for j := range stack[i].Locals {
fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
fmt.Fprintf(out, "%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
}
if extranl {
fmt.Println()
fmt.Fprintln(out)
}
}
if len(stack) > 0 && !stack[len(stack)-1].Bottom {
fmt.Printf("%s"+stacktraceTruncatedMessage+"\n", ind)
fmt.Fprintf(out, "%s"+stacktraceTruncatedMessage+"\n", ind)
}
}
......@@ -2107,7 +2142,6 @@ func printcontextLocation(loc api.Location) {
if loc.Function != nil && loc.Function.Optimized {
fmt.Println(optimizedFunctionWarning)
}
return
}
func printReturnValues(th *api.Thread) {
......@@ -2131,6 +2165,7 @@ func printcontextThread(t *Term, th *api.Thread) {
}
args := ""
var hasReturnValue bool
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
var arg []string
for _, ar := range th.BreakpointInfo.Arguments {
......@@ -2142,6 +2177,9 @@ func printcontextThread(t *Term, th *api.Thread) {
if (ar.Flags & api.VariableArgument) != 0 {
arg = append(arg, ar.SinglelineString())
}
if (ar.Flags & api.VariableReturnArgument) != 0 {
hasReturnValue = true
}
}
args = strings.Join(arg, ", ")
}
......@@ -2151,6 +2189,11 @@ func printcontextThread(t *Term, th *api.Thread) {
bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
}
if th.Breakpoint.Tracepoint || th.Breakpoint.TraceReturn {
printTracepoint(th, bpname, fn, args, hasReturnValue)
return
}
if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
fmt.Printf("> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n",
bpname,
......@@ -2206,7 +2249,29 @@ func printcontextThread(t *Term, th *api.Thread) {
if bpi.Stacktrace != nil {
fmt.Printf("\tStack:\n")
printStack(bpi.Stacktrace, "\t\t", false)
printStack(os.Stdout, bpi.Stacktrace, "\t\t", false)
}
}
}
func printTracepoint(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()
}
}
if th.Breakpoint.TraceReturn {
retVals := make([]string, 0, len(th.ReturnValues))
for _, v := range th.ReturnValues {
retVals = append(retVals, v.SinglelineString())
}
fmt.Fprintf(os.Stderr, " => (%s)\n", strings.Join(retVals, ","))
}
if th.Breakpoint.TraceReturn || !hasReturnValue {
if th.BreakpointInfo.Stacktrace != nil {
fmt.Fprintf(os.Stderr, "\tStack:\n")
printStack(os.Stderr, th.BreakpointInfo.Stacktrace, "\t\t", false)
}
}
}
......
......@@ -247,8 +247,8 @@ func TestExecuteFile(t *testing.T) {
}
func TestIssue354(t *testing.T) {
printStack([]api.Stackframe{}, "", false)
printStack([]api.Stackframe{
printStack(os.Stdout, []api.Stackframe{}, "", false)
printStack(os.Stdout, []api.Stackframe{
{Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil},
Bottom: true}}, "", false)
}
......@@ -263,12 +263,67 @@ func TestIssue411(t *testing.T) {
term.MustExec("trace _fixtures/math.go:9")
term.MustExec("continue")
out := term.MustExec("next")
if !strings.HasPrefix(out, "> main.main()") {
if !strings.HasPrefix(out, "> goroutine(1): main.main()") {
t.Fatalf("Wrong output for next: <%s>", out)
}
})
}
func TestTrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("test is not valid on ARM64")
}
test.AllowRecording(t)
withTestTerminal("issue573", t, func(term *FakeTerminal) {
term.MustExec("trace foo")
out, _ := term.Exec("continue")
// The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled.
// Therefore we assert about the call and return values separately.
if !strings.Contains(out, "> goroutine(1): main.foo(99, 9801)") {
t.Fatalf("Wrong output for tracepoint: %s", out)
}
if !strings.Contains(out, "=> (9900)") {
t.Fatalf("Wrong output for tracepoint return value: %s", out)
}
})
}
func TestTraceWithName(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("test is not valid on ARM64")
}
test.AllowRecording(t)
withTestTerminal("issue573", t, func(term *FakeTerminal) {
term.MustExec("trace foobar foo")
out, _ := term.Exec("continue")
// The output here is a little strange, but we don't filter stdout vs stderr so it gets jumbled.
// Therefore we assert about the call and return values separately.
if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") {
t.Fatalf("Wrong output for tracepoint: %s", out)
}
if !strings.Contains(out, "=> (9900)") {
t.Fatalf("Wrong output for tracepoint return value: %s", out)
}
})
}
func TestTraceOnNonFunctionEntry(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("test is not valid on ARM64")
}
test.AllowRecording(t)
withTestTerminal("issue573", t, func(term *FakeTerminal) {
term.MustExec("trace foobar issue573.go:19")
out, _ := term.Exec("continue")
if !strings.Contains(out, "> goroutine(1): [foobar] main.foo(99, 9801)") {
t.Fatalf("Wrong output for tracepoint: %s", out)
}
if strings.Contains(out, "=> (9900)") {
t.Fatalf("Tracepoint on non-function locspec should not have return value:\n%s", out)
}
})
}
func TestExitStatus(t *testing.T) {
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
term.Exec("continue")
......@@ -530,7 +585,7 @@ func TestOnPrefixLocals(t *testing.T) {
})
}
func countOccurrences(s string, needle string) int {
func countOccurrences(s, needle string) int {
count := 0
for {
idx := strings.Index(s, needle)
......
......@@ -252,8 +252,8 @@ type Variable struct {
Kind reflect.Kind `json:"kind"`
//Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
//Function variables will store the name of the function in this field
// Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
// Function variables will store the name of the function in this field
Value string `json:"value"`
// Number of elements in an array or a slice, number of keys for a map, number of struct members for a struct, length of strings
......
......@@ -36,8 +36,4 @@ type Config struct {
// DisconnectChan will be closed by the server when the client disconnects
DisconnectChan chan<- struct{}
// TTY is passed along to the target process on creation. Used to specify a
// TTY for that process.
TTY string
}
......@@ -17,6 +17,7 @@ import (
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/locspec"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/core"
......@@ -1069,7 +1070,18 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return regexFilterFuncs(filter, d.target.BinInfo().Functions)
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
funcs := []string{}
for _, f := range d.target.BinInfo().Functions {
if regex.MatchString(f.Name) {
funcs = append(funcs, f.Name)
}
}
return funcs, nil
}
// Types returns all type information in the binary.
......@@ -1097,21 +1109,6 @@ func (d *Debugger) Types(filter string) ([]string, error) {
return r, nil
}
func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
funcs := []string{}
for _, f := range allFuncs {
if regex.Match([]byte(f.Name)) {
funcs = append(funcs, f.Name)
}
}
return funcs, nil
}
// PackageVariables returns a list of package variables for the thread,
// optionally regexp filtered using regexp described in 'filter'.
func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadConfig) ([]api.Variable, error) {
......@@ -1443,14 +1440,14 @@ func (d *Debugger) FindLocation(scope api.EvalScope, locStr string, includeNonEx
return nil, err
}
loc, err := parseLocationSpec(locStr)
loc, err := locspec.Parse(locStr)
if err != nil {
return nil, err
}
s, _ := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame, scope.DeferredCall)
locs, err := loc.Find(d, s, locStr, includeNonExecutableLines)
locs, err := loc.Find(d.target, d.processArgs, s, locStr, includeNonExecutableLines)
for i := range locs {
if locs[i].PC == 0 {
continue
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册