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
This commit is contained in:
parent
a33be4466f
commit
f96663a243
@ -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
|
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.
|
||||||
|
|
||||||
```
|
```
|
||||||
dlv trace [package] regexp
|
dlv trace [package] regexp
|
||||||
```
|
```
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/config"
|
"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
|
The trace sub command will set a tracepoint on every function matching the
|
||||||
provided regular expression and output information when tracepoint is hit. This
|
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
|
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,
|
Run: traceCmd,
|
||||||
}
|
}
|
||||||
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
|
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
|
||||||
@ -536,7 +540,7 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
Stacktrace: traceStackDepth,
|
Stacktrace: traceStackDepth,
|
||||||
LoadArgs: &terminal.ShortLoadConfig,
|
LoadArgs: &terminal.ShortLoadConfig,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil && !isBreakpointExistsErr(err) {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -549,10 +553,11 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
_, err = client.CreateBreakpoint(&api.Breakpoint{
|
_, err = client.CreateBreakpoint(&api.Breakpoint{
|
||||||
Addr: addrs[i],
|
Addr: addrs[i],
|
||||||
TraceReturn: true,
|
TraceReturn: true,
|
||||||
|
Stacktrace: traceStackDepth,
|
||||||
Line: -1,
|
Line: -1,
|
||||||
LoadArgs: &terminal.ShortLoadConfig,
|
LoadArgs: &terminal.ShortLoadConfig,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil && !isBreakpointExistsErr(err) {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -561,16 +566,16 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
cmds := terminal.DebugCommands(client)
|
cmds := terminal.DebugCommands(client)
|
||||||
t := terminal.New(client, nil)
|
t := terminal.New(client, nil)
|
||||||
defer t.Close()
|
defer t.Close()
|
||||||
err = cmds.Call("continue", t)
|
cmds.Call("continue", t)
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}()
|
}()
|
||||||
os.Exit(status)
|
os.Exit(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isBreakpointExistsErr(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "Breakpoint exists")
|
||||||
|
}
|
||||||
|
|
||||||
func testCmd(cmd *cobra.Command, args []string) {
|
func testCmd(cmd *cobra.Command, args []string) {
|
||||||
status := func() int {
|
status := func() int {
|
||||||
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
|
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/pkg/terminal"
|
||||||
"github.com/go-delve/delve/service/dap/daptest"
|
"github.com/go-delve/delve/service/dap/daptest"
|
||||||
"github.com/go-delve/delve/service/rpc2"
|
"github.com/go-delve/delve/service/rpc2"
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -557,3 +556,85 @@ func TestDap(t *testing.T) {
|
|||||||
client.Close()
|
client.Close()
|
||||||
cmd.Wait()
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
15
pkg/locspec/doc.go
Normal file
15
pkg/locspec/doc.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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
|
@ -1,4 +1,4 @@
|
|||||||
package debugger
|
package locspec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -16,32 +17,44 @@ import (
|
|||||||
|
|
||||||
const maxFindLocationCandidates = 5
|
const maxFindLocationCandidates = 5
|
||||||
|
|
||||||
|
// LocationSpec is an interface that represents a parsed location spec string.
|
||||||
type LocationSpec interface {
|
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 {
|
type NormalLocationSpec struct {
|
||||||
Base string
|
Base string
|
||||||
FuncBase *FuncLocationSpec
|
FuncBase *FuncLocationSpec
|
||||||
LineOffset int
|
LineOffset int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegexLocationSpec represents a regular expression
|
||||||
|
// location expression such as /^myfunc$/.
|
||||||
type RegexLocationSpec struct {
|
type RegexLocationSpec struct {
|
||||||
FuncRegex string
|
FuncRegex string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddrLocationSpec represents an address when used
|
||||||
|
// as a location spec.
|
||||||
type AddrLocationSpec struct {
|
type AddrLocationSpec struct {
|
||||||
AddrExpr string
|
AddrExpr string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OffsetLocationSpec represents a location spec that
|
||||||
|
// is an offset of the current location (file:line).
|
||||||
type OffsetLocationSpec struct {
|
type OffsetLocationSpec struct {
|
||||||
Offset int
|
Offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LineLocationSpec represents a line number in the current file.
|
||||||
type LineLocationSpec struct {
|
type LineLocationSpec struct {
|
||||||
Line int
|
Line int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuncLocationSpec represents a function in the target program.
|
||||||
type FuncLocationSpec struct {
|
type FuncLocationSpec struct {
|
||||||
PackageName string
|
PackageName string
|
||||||
AbsolutePackage bool
|
AbsolutePackage bool
|
||||||
@ -50,7 +63,8 @@ type FuncLocationSpec struct {
|
|||||||
BaseName string
|
BaseName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLocationSpec(locStr string) (LocationSpec, error) {
|
// Parse will turn locStr into a parsed LocationSpec.
|
||||||
|
func Parse(locStr string) (LocationSpec, error) {
|
||||||
rest := locStr
|
rest := locStr
|
||||||
|
|
||||||
malformed := func(reason string) error {
|
malformed := func(reason string) error {
|
||||||
@ -84,7 +98,7 @@ func parseLocationSpec(locStr string) (LocationSpec, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case '*':
|
case '*':
|
||||||
return &AddrLocationSpec{rest[1:]}, nil
|
return &AddrLocationSpec{AddrExpr: rest[1:]}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return parseLocationSpecDefault(locStr, rest)
|
return parseLocationSpecDefault(locStr, rest)
|
||||||
@ -215,6 +229,7 @@ func stripReceiverDecoration(in string) string {
|
|||||||
return in[2 : len(in)-1]
|
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 {
|
func (spec *FuncLocationSpec) Match(sym proc.Function, packageMap map[string][]string) bool {
|
||||||
if spec.BaseName != sym.BaseName() {
|
if spec.BaseName != sym.BaseName() {
|
||||||
return false
|
return false
|
||||||
@ -250,15 +265,17 @@ func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool {
|
|||||||
return partialPackageMatch(specPkg, symPkg)
|
return partialPackageMatch(specPkg, symPkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool) ([]api.Location, error) {
|
// Find will search all functions in the target program and filter them via the
|
||||||
funcs := d.target.BinInfo().Functions
|
// 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)
|
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 {
|
||||||
addrs, _ := proc.FindFunctionLocation(d.target, matches[i], 0)
|
addrs, _ := proc.FindFunctionLocation(t, matches[i], 0)
|
||||||
if len(addrs) > 0 {
|
if len(addrs) > 0 {
|
||||||
r = append(r, addressesToLocation(addrs))
|
r = append(r, addressesToLocation(addrs))
|
||||||
}
|
}
|
||||||
@ -266,38 +283,40 @@ func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr st
|
|||||||
return r, nil
|
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 {
|
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
|
||||||
} 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if v.Unreadable != nil {
|
return []api.Location{{PC: pc}}, nil
|
||||||
return nil, v.Unreadable
|
default:
|
||||||
}
|
return nil, fmt.Errorf("wrong expression kind: %v", 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:
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileMatch is true if the path matches the location spec.
|
||||||
func (loc *NormalLocationSpec) FileMatch(path string) bool {
|
func (loc *NormalLocationSpec) FileMatch(path string) bool {
|
||||||
return partialPathMatch(loc.Base, path)
|
return partialPathMatch(loc.Base, path)
|
||||||
}
|
}
|
||||||
@ -318,11 +337,12 @@ func partialPathMatch(expr, path string) bool {
|
|||||||
func partialPackageMatch(expr, path string) bool {
|
func partialPackageMatch(expr, path string) bool {
|
||||||
if len(expr) < len(path)-1 {
|
if len(expr) < len(path)-1 {
|
||||||
return strings.HasSuffix(path, expr) && (path[len(path)-len(expr)-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 {
|
type AmbiguousLocationError struct {
|
||||||
Location string
|
Location string
|
||||||
CandidatesString []string
|
CandidatesString []string
|
||||||
@ -342,11 +362,14 @@ func (ale AmbiguousLocationError) Error() string {
|
|||||||
return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
|
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
|
limit := maxFindLocationCandidates
|
||||||
var candidateFiles []string
|
var candidateFiles []string
|
||||||
for _, file := range d.target.BinInfo().Sources {
|
for _, file := range scope.BinInfo.Sources {
|
||||||
if loc.FileMatch(file) || (len(d.processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, d.processArgs[0], file)) {
|
if loc.FileMatch(file) || (len(processArgs) >= 1 && tryMatchRelativePathByProc(loc.Base, processArgs[0], file)) {
|
||||||
candidateFiles = append(candidateFiles, file)
|
candidateFiles = append(candidateFiles, file)
|
||||||
if len(candidateFiles) >= limit {
|
if len(candidateFiles) >= limit {
|
||||||
break
|
break
|
||||||
@ -358,8 +381,8 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
|
|||||||
|
|
||||||
var candidateFuncs []string
|
var candidateFuncs []string
|
||||||
if loc.FuncBase != nil {
|
if loc.FuncBase != nil {
|
||||||
for _, f := range d.target.BinInfo().Functions {
|
for _, f := range scope.BinInfo.Functions {
|
||||||
if !loc.FuncBase.Match(f, d.target.BinInfo().PackageMap) {
|
if !loc.FuncBase.Match(f, scope.BinInfo.PackageMap) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if loc.Base == f.Name {
|
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 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
|
// expression that the user forgot to prefix with '*', try treating it as
|
||||||
// such.
|
// such.
|
||||||
addrSpec := &AddrLocationSpec{locStr}
|
addrSpec := &AddrLocationSpec{AddrExpr: locStr}
|
||||||
locs, err := addrSpec.Find(d, scope, locStr, includeNonExecutableLines)
|
locs, err := addrSpec.Find(t, processArgs, scope, locStr, includeNonExecutableLines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Location \"%s\" not found", locStr)
|
return nil, fmt.Errorf("location \"%s\" not found", locStr)
|
||||||
}
|
}
|
||||||
return locs, nil
|
return locs, nil
|
||||||
} else if matching > 1 {
|
} else if matching > 1 {
|
||||||
@ -395,14 +418,14 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
|
|||||||
if loc.LineOffset < 0 {
|
if loc.LineOffset < 0 {
|
||||||
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(d.target, 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
|
||||||
addrs, err = proc.FindFunctionLocation(d.target, candidateFuncs[0], loc.LineOffset)
|
addrs, err = proc.FindFunctionLocation(t, candidateFuncs[0], loc.LineOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -418,18 +441,19 @@ func addressesToLocation(addrs []uint64) api.Location {
|
|||||||
return api.Location{PC: addrs[0], PCs: addrs}
|
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 {
|
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 {
|
if loc.Offset == 0 {
|
||||||
return []api.Location{{PC: scope.PC}}, nil
|
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 {
|
if fn == nil {
|
||||||
return nil, fmt.Errorf("could not determine current location")
|
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 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}}, nil
|
||||||
@ -438,15 +462,16 @@ func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
|
|||||||
return []api.Location{addressesToLocation(addrs)}, err
|
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 {
|
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 := d.target.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")
|
||||||
}
|
}
|
||||||
addrs, err := proc.FindFileLocation(d.target, 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}}, nil
|
||||||
@ -454,3 +479,18 @@ func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
|
|||||||
}
|
}
|
||||||
return []api.Location{addressesToLocation(addrs)}, err
|
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
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package debugger
|
package locspec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseLocationSpecNoError(t *testing.T, locstr string) LocationSpec {
|
func parseLocationSpecNoError(t *testing.T, locstr string) LocationSpec {
|
||||||
spec, err := parseLocationSpec(locstr)
|
spec, err := Parse(locstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error parsing %q: %v", locstr, err)
|
t.Fatalf("Error parsing %q: %v", locstr, err)
|
||||||
}
|
}
|
@ -22,9 +22,10 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/cosiner/argv"
|
"github.com/cosiner/argv"
|
||||||
|
"github.com/go-delve/delve/pkg/locspec"
|
||||||
"github.com/go-delve/delve/service"
|
"github.com/go-delve/delve/service"
|
||||||
"github.com/go-delve/delve/service/api"
|
"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"
|
const optimizedFunctionWarning = "Warning: debugging optimized function"
|
||||||
@ -668,7 +669,7 @@ func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, flags
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printStack(stack, "\t", false)
|
printStack(os.Stdout, stack, "\t", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -1187,10 +1188,7 @@ func (c *Commands) revCmd(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.Prefix = revPrefix
|
ctx.Prefix = revPrefix
|
||||||
if err := c.CallWithContext(args, t, ctx); err != nil {
|
return c.CallWithContext(args, t, ctx)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) next(t *Term, ctx callContext, args string) error {
|
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)
|
args := split2PartsBySpace(argstr)
|
||||||
|
|
||||||
requestedBp := &api.Breakpoint{}
|
requestedBp := &api.Breakpoint{}
|
||||||
locspec := ""
|
spec := ""
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 1:
|
case 1:
|
||||||
locspec = argstr
|
spec = argstr
|
||||||
case 2:
|
case 2:
|
||||||
if api.ValidBreakpointName(args[0]) == nil {
|
if api.ValidBreakpointName(args[0]) == nil {
|
||||||
requestedBp.Name = args[0]
|
requestedBp.Name = args[0]
|
||||||
locspec = args[1]
|
spec = args[1]
|
||||||
} else {
|
} else {
|
||||||
locspec = argstr
|
spec = argstr
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("address required")
|
return fmt.Errorf("address required")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedBp.Tracepoint = tracepoint
|
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 err != nil {
|
||||||
if requestedBp.Name == "" {
|
if requestedBp.Name == "" {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
requestedBp.Name = ""
|
requestedBp.Name = ""
|
||||||
locspec = argstr
|
spec = argstr
|
||||||
var err2 error
|
var err2 error
|
||||||
locs, err2 = t.client.FindLocation(ctx.Scope, locspec, true)
|
locs, err2 = t.client.FindLocation(ctx.Scope, spec, true)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1418,6 +1416,9 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) err
|
|||||||
for _, loc := range locs {
|
for _, loc := range locs {
|
||||||
requestedBp.Addr = loc.PC
|
requestedBp.Addr = loc.PC
|
||||||
requestedBp.Addrs = loc.PCs
|
requestedBp.Addrs = loc.PCs
|
||||||
|
if tracepoint {
|
||||||
|
requestedBp.LoadArgs = &ShortLoadConfig
|
||||||
|
}
|
||||||
|
|
||||||
bp, err := t.client.CreateBreakpoint(requestedBp)
|
bp, err := t.client.CreateBreakpoint(requestedBp)
|
||||||
if err != nil {
|
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))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1528,7 +1563,7 @@ func examineMemoryCmd(t *Term, ctx callContext, args string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(api.PrettyExamineMemory(uintptr(address), memArea, priFmt))
|
fmt.Print(api.PrettyExamineMemory(uintptr(address), memArea, priFmt))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1726,7 +1761,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
printStack(stack, "", sa.offsets)
|
printStack(os.Stdout, stack, "", sa.offsets)
|
||||||
if sa.ancestors > 0 {
|
if sa.ancestors > 0 {
|
||||||
ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth)
|
ancestors, err := t.client.Ancestors(ctx.Scope.GoroutineID, sa.ancestors, sa.ancestorDepth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1738,7 +1773,7 @@ func stackCommand(t *Term, ctx callContext, args string) error {
|
|||||||
fmt.Printf("\t%s\n", ancestor.Unreadable)
|
fmt.Printf("\t%s\n", ancestor.Unreadable)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
printStack(ancestor.Stack, "\t", false)
|
printStack(os.Stdout, ancestor.Stack, "\t", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -1872,7 +1907,7 @@ func getLocation(t *Term, ctx callContext, args string, showContext bool) (file
|
|||||||
return "", 0, false, err
|
return "", 0, false, err
|
||||||
}
|
}
|
||||||
if len(locs) > 1 {
|
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]
|
loc := locs[0]
|
||||||
if showContext {
|
if showContext {
|
||||||
@ -2000,7 +2035,7 @@ func digits(n int) int {
|
|||||||
|
|
||||||
const stacktraceTruncatedMessage = "(truncated)"
|
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 {
|
if len(stack) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2019,42 +2054,42 @@ func printStack(stack []api.Stackframe, ind string, offsets bool) {
|
|||||||
|
|
||||||
for i := range stack {
|
for i := range stack {
|
||||||
if stack[i].Err != "" {
|
if stack[i].Err != "" {
|
||||||
fmt.Printf("%serror: %s\n", s, stack[i].Err)
|
fmt.Fprintf(out, "%serror: %s\n", s, stack[i].Err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf(fmtstr, ind, i, stack[i].PC, stack[i].Function.Name())
|
fmt.Fprintf(out, 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, "%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)
|
||||||
|
|
||||||
if offsets {
|
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 {
|
for j, d := range stack[i].Defers {
|
||||||
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
|
deferHeader := fmt.Sprintf("%s defer %d: ", s, j+1)
|
||||||
s2 := strings.Repeat(" ", len(deferHeader))
|
s2 := strings.Repeat(" ", len(deferHeader))
|
||||||
if d.Unreadable != "" {
|
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
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf("%s%#016x in %s\n", deferHeader, d.DeferredLoc.PC, d.DeferredLoc.Function.Name())
|
fmt.Fprintf(out, "%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.Fprintf(out, "%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, "%sdeferred by %s at %s:%d\n", s2, d.DeferLoc.Function.Name(), d.DeferLoc.File, d.DeferLoc.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := range stack[i].Arguments {
|
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 {
|
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 {
|
if extranl {
|
||||||
fmt.Println()
|
fmt.Fprintln(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(stack) > 0 && !stack[len(stack)-1].Bottom {
|
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 {
|
if loc.Function != nil && loc.Function.Optimized {
|
||||||
fmt.Println(optimizedFunctionWarning)
|
fmt.Println(optimizedFunctionWarning)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printReturnValues(th *api.Thread) {
|
func printReturnValues(th *api.Thread) {
|
||||||
@ -2131,6 +2165,7 @@ func printcontextThread(t *Term, th *api.Thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args := ""
|
args := ""
|
||||||
|
var hasReturnValue bool
|
||||||
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
|
if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig {
|
||||||
var arg []string
|
var arg []string
|
||||||
for _, ar := range th.BreakpointInfo.Arguments {
|
for _, ar := range th.BreakpointInfo.Arguments {
|
||||||
@ -2142,6 +2177,9 @@ func printcontextThread(t *Term, th *api.Thread) {
|
|||||||
if (ar.Flags & api.VariableArgument) != 0 {
|
if (ar.Flags & api.VariableArgument) != 0 {
|
||||||
arg = append(arg, ar.SinglelineString())
|
arg = append(arg, ar.SinglelineString())
|
||||||
}
|
}
|
||||||
|
if (ar.Flags & api.VariableReturnArgument) != 0 {
|
||||||
|
hasReturnValue = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
args = strings.Join(arg, ", ")
|
args = strings.Join(arg, ", ")
|
||||||
}
|
}
|
||||||
@ -2151,6 +2189,11 @@ func printcontextThread(t *Term, th *api.Thread) {
|
|||||||
bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
|
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 {
|
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",
|
fmt.Printf("> %s%s(%s) %s:%d (hits goroutine(%d):%d total:%d) (PC: %#v)\n",
|
||||||
bpname,
|
bpname,
|
||||||
@ -2206,7 +2249,29 @@ func printcontextThread(t *Term, th *api.Thread) {
|
|||||||
|
|
||||||
if bpi.Stacktrace != nil {
|
if bpi.Stacktrace != nil {
|
||||||
fmt.Printf("\tStack:\n")
|
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) {
|
func TestIssue354(t *testing.T) {
|
||||||
printStack([]api.Stackframe{}, "", false)
|
printStack(os.Stdout, []api.Stackframe{}, "", false)
|
||||||
printStack([]api.Stackframe{
|
printStack(os.Stdout, []api.Stackframe{
|
||||||
{Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil},
|
{Location: api.Location{PC: 0, File: "irrelevant.go", Line: 10, Function: nil},
|
||||||
Bottom: true}}, "", false)
|
Bottom: true}}, "", false)
|
||||||
}
|
}
|
||||||
@ -263,12 +263,67 @@ func TestIssue411(t *testing.T) {
|
|||||||
term.MustExec("trace _fixtures/math.go:9")
|
term.MustExec("trace _fixtures/math.go:9")
|
||||||
term.MustExec("continue")
|
term.MustExec("continue")
|
||||||
out := term.MustExec("next")
|
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)
|
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) {
|
func TestExitStatus(t *testing.T) {
|
||||||
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
|
withTestTerminal("continuetestprog", t, func(term *FakeTerminal) {
|
||||||
term.Exec("continue")
|
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
|
count := 0
|
||||||
for {
|
for {
|
||||||
idx := strings.Index(s, needle)
|
idx := strings.Index(s, needle)
|
||||||
|
@ -252,8 +252,8 @@ type Variable struct {
|
|||||||
|
|
||||||
Kind reflect.Kind `json:"kind"`
|
Kind reflect.Kind `json:"kind"`
|
||||||
|
|
||||||
//Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
|
// 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
|
// Function variables will store the name of the function in this field
|
||||||
Value string `json:"value"`
|
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
|
// 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 will be closed by the server when the client disconnects
|
||||||
DisconnectChan chan<- struct{}
|
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/dwarf/op"
|
||||||
"github.com/go-delve/delve/pkg/goversion"
|
"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/logflags"
|
||||||
"github.com/go-delve/delve/pkg/proc"
|
"github.com/go-delve/delve/pkg/proc"
|
||||||
"github.com/go-delve/delve/pkg/proc/core"
|
"github.com/go-delve/delve/pkg/proc/core"
|
||||||
@ -1069,7 +1070,18 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
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.
|
// Types returns all type information in the binary.
|
||||||
@ -1097,21 +1109,6 @@ func (d *Debugger) Types(filter string) ([]string, error) {
|
|||||||
return r, nil
|
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,
|
// PackageVariables returns a list of package variables for the thread,
|
||||||
// optionally regexp filtered using regexp described in 'filter'.
|
// optionally regexp filtered using regexp described in 'filter'.
|
||||||
func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadConfig) ([]api.Variable, error) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
loc, err := parseLocationSpec(locStr)
|
loc, err := locspec.Parse(locStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, _ := proc.ConvertEvalScope(d.target, scope.GoroutineID, scope.Frame, scope.DeferredCall)
|
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 {
|
for i := range locs {
|
||||||
if locs[i].PC == 0 {
|
if locs[i].PC == 0 {
|
||||||
continue
|
continue
|
||||||
|
Loading…
Reference in New Issue
Block a user