Implement 'trace' subcommand

Allows a user to execute `dlv trace [regexp]` and Delve will execute the
program and output information on functions matching [regexp].
This commit is contained in:
Derek Parker 2015-07-12 15:18:14 -05:00
parent c6ca18ff07
commit 3cee10d8bc
8 changed files with 117 additions and 11 deletions

@ -8,10 +8,12 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/derekparker/delve/service" "github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api"
"github.com/derekparker/delve/service/rpc" "github.com/derekparker/delve/service/rpc"
"github.com/derekparker/delve/terminal" "github.com/derekparker/delve/terminal"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -82,6 +84,91 @@ starts and attaches to it, and enables you to immediately begin debugging your p
} }
rootCommand.AddCommand(runCommand) rootCommand.AddCommand(runCommand)
// 'trace' subcommand.
traceCommand := &cobra.Command{
Use: "trace [regexp]",
Short: "Compile and begin tracing program.",
Long: "Trace program execution. Will set a tracepoint on every function matching [regexp] and output information when tracepoint is hit.",
Run: func(cmd *cobra.Command, args []string) {
status := func() int {
const debugname = "debug"
goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
goBuild.Stderr = os.Stderr
err := goBuild.Run()
if err != nil {
return 1
}
fp, err := filepath.Abs("./" + debugname)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
return 1
}
defer os.Remove(fp)
processArgs := append([]string{"./" + debugname}, args...)
// Make a TCP listener
listener, err := net.Listen("tcp", Addr)
if err != nil {
fmt.Printf("couldn't start listener: %s\n", err)
return 1
}
defer listener.Close()
// Create and start a debugger server
server := rpc.NewServer(&service.Config{
Listener: listener,
ProcessArgs: processArgs,
}, Log)
if err := server.Run(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
sigChan := make(chan os.Signal)
signal.Notify(sigChan, sys.SIGINT)
client := rpc.NewClient(listener.Addr().String())
funcs, err := client.ListFunctions(args[0])
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
for i := range funcs {
_, err := client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true})
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
}
stateChan := client.Continue()
for {
select {
case state := <-stateChan:
if state.Err != nil {
fmt.Fprintln(os.Stderr, state.Err)
return 0
}
var args []string
var fname string
if state.CurrentThread != nil && state.CurrentThread.Function != nil {
fname = state.CurrentThread.Function.Name
}
if state.BreakpointInfo != nil {
for _, arg := range state.BreakpointInfo.Arguments {
args = append(args, arg.Value)
}
}
fmt.Printf("%s(%s) %s:%d\n", fname, strings.Join(args, ", "), state.CurrentThread.File, state.CurrentThread.Line)
case <-sigChan:
server.Stop(true)
return 1
}
}
return 0
}()
os.Exit(status)
},
}
rootCommand.AddCommand(traceCommand)
// 'test' subcommand. // 'test' subcommand.
testCommand := &cobra.Command{ testCommand := &cobra.Command{
Use: "test", Use: "test",

@ -55,7 +55,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) {
continue continue
} }
if lowpc <= pc && highpc >= pc { if lowpc <= pc && highpc > pc {
return entry, nil return entry, nil
} }
} }

@ -770,7 +770,6 @@ func (thread *Thread) readMemory(addr uintptr, size int) ([]byte, error) {
if size == 0 { if size == 0 {
return nil, nil return nil, nil
} }
buf := make([]byte, size) buf := make([]byte, size)
_, err := readMemory(thread, addr, buf) _, err := readMemory(thread, addr, buf)
if err != nil { if err != nil {

@ -2,6 +2,8 @@ package api
import ( import (
"debug/gosym" "debug/gosym"
"os"
"strings"
"github.com/derekparker/delve/proc" "github.com/derekparker/delve/proc"
) )
@ -11,7 +13,7 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
return &Breakpoint{ return &Breakpoint{
ID: bp.ID, ID: bp.ID,
FunctionName: bp.FunctionName, FunctionName: bp.FunctionName,
File: bp.File, File: shortenFilePath(bp.File),
Line: bp.Line, Line: bp.Line,
Addr: bp.Addr, Addr: bp.Addr,
Tracepoint: bp.Tracepoint, Tracepoint: bp.Tracepoint,
@ -41,7 +43,7 @@ func ConvertThread(th *proc.Thread) *Thread {
return &Thread{ return &Thread{
ID: th.Id, ID: th.Id,
PC: pc, PC: pc,
File: file, File: shortenFilePath(file),
Line: line, Line: line,
Function: function, Function: function,
} }
@ -74,7 +76,7 @@ func ConvertGoroutine(g *proc.G) *Goroutine {
return &Goroutine{ return &Goroutine{
ID: g.Id, ID: g.Id,
PC: g.PC, PC: g.PC,
File: g.File, File: shortenFilePath(g.File),
Line: g.Line, Line: g.Line,
Function: ConvertFunction(g.Func), Function: ConvertFunction(g.Func),
} }
@ -83,8 +85,13 @@ func ConvertGoroutine(g *proc.G) *Goroutine {
func ConvertLocation(loc proc.Location) Location { func ConvertLocation(loc proc.Location) Location {
return Location{ return Location{
PC: loc.PC, PC: loc.PC,
File: loc.File, File: shortenFilePath(loc.File),
Line: loc.Line, Line: loc.Line,
Function: ConvertFunction(loc.Fn), Function: ConvertFunction(loc.Fn),
} }
} }
func shortenFilePath(fullPath string) string {
workingDir, _ := os.Getwd()
return strings.Replace(fullPath, workingDir, ".", 1)
}

@ -112,6 +112,7 @@ type BreakpointInfo struct {
Stacktrace []Location `json:"stacktrace,omitempty"` Stacktrace []Location `json:"stacktrace,omitempty"`
Goroutine *Goroutine `json:"goroutine,omitempty"` Goroutine *Goroutine `json:"goroutine,omitempty"`
Variables []Variable `json:"variables,omitempty"` Variables []Variable `json:"variables,omitempty"`
Arguments []Variable `json:"arguments,omitempty"`
} }
const ( const (

@ -272,6 +272,10 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
} }
bpi.Variables[i] = api.ConvertVar(v) bpi.Variables[i] = api.ConvertVar(v)
} }
vars, err := d.FunctionArguments(d.process.CurrentThread.Id)
if err == nil {
bpi.Arguments = vars
}
return nil return nil
} }
@ -370,7 +374,7 @@ func (d *Debugger) FunctionArguments(threadID int) ([]api.Variable, error) {
for _, v := range pv { for _, v := range pv {
vars = append(vars, api.ConvertVar(v)) vars = append(vars, api.ConvertVar(v))
} }
return vars, err return vars, nil
} }
func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) { func (d *Debugger) EvalVariableInThread(threadID int, symbol string) (*api.Variable, error) {

@ -66,7 +66,7 @@ func (c *RPCClient) Continue() <-chan *api.DebuggerState {
state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus) state.Err = fmt.Errorf("Process %d has exited with status %d", c.ProcessPid(), state.ExitStatus)
} }
ch <- state ch <- state
if err != nil || state.Breakpoint == nil || !state.Breakpoint.Tracepoint { if err != nil || state.Exited || state.Breakpoint == nil || !state.Breakpoint.Tracepoint {
close(ch) close(ch)
return return
} }

@ -537,11 +537,19 @@ func printcontext(state *api.DebuggerState) error {
var context []string var context []string
fn := "" var fn *api.Function
if state.CurrentThread.Function != nil { if state.CurrentThread.Function != nil {
fn = state.CurrentThread.Function.Name fn = state.CurrentThread.Function
}
if state.Breakpoint != nil && state.Breakpoint.Tracepoint {
var args []string
for _, arg := range state.CurrentThread.Function.Args {
args = append(args, arg.Value)
}
fmt.Printf("%s(%s) %s:%d\n", fn.Name, strings.Join(args, ", "), state.CurrentThread.File, state.CurrentThread.Line)
} else {
fmt.Printf("%s() %s:%d\n", fn.Name, state.CurrentThread.File, state.CurrentThread.Line)
} }
fmt.Printf("current loc: %s %s:%d\n", fn, state.CurrentThread.File, state.CurrentThread.Line)
if state.BreakpointInfo != nil { if state.BreakpointInfo != nil {
bpi := state.BreakpointInfo bpi := state.BreakpointInfo