service, terminal: Named breakpoints and breakpoint conditions
Implements #109 and #120
This commit is contained in:
parent
8f85b6cbae
commit
3be65a4c1f
@ -19,6 +19,7 @@ type Breakpoint struct {
|
||||
|
||||
Addr uint64 // Address breakpoint is set for.
|
||||
OriginalData []byte // If software breakpoint, the data we replace with breakpoint instruction.
|
||||
Name string // User defined name of the breakpoint
|
||||
ID int // Monotonically increasing ID.
|
||||
Temp bool // Whether this is a temp breakpoint (for next'ing).
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/gosym"
|
||||
"go/constant"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"golang.org/x/debug/dwarf"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -14,6 +17,7 @@ import (
|
||||
// an api.Breakpoint.
|
||||
func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
b := &Breakpoint{
|
||||
Name: bp.Name,
|
||||
ID: bp.ID,
|
||||
FunctionName: bp.FunctionName,
|
||||
File: bp.File,
|
||||
@ -31,6 +35,10 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
b.HitCount[strconv.Itoa(idx)] = bp.HitCount[idx]
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
printer.Fprint(&buf, token.NewFileSet(), bp.Cond)
|
||||
b.Cond = buf.String()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/derekparker/delve/proc"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// DebuggerState represents the current context of the debugger.
|
||||
@ -25,6 +29,8 @@ type DebuggerState struct {
|
||||
type Breakpoint struct {
|
||||
// ID is a unique identifier for the breakpoint.
|
||||
ID int `json:"id"`
|
||||
// User defined name of the breakpoint
|
||||
Name string `json:"name"`
|
||||
// Addr is the address of the breakpoint.
|
||||
Addr uint64 `json:"addr"`
|
||||
// File is the source file for the breakpoint.
|
||||
@ -35,6 +41,9 @@ type Breakpoint struct {
|
||||
// may not always be available.
|
||||
FunctionName string `json:"functionName,omitempty"`
|
||||
|
||||
// Breakpoint condition
|
||||
Cond string
|
||||
|
||||
// tracepoint flag
|
||||
Tracepoint bool `json:"continue"`
|
||||
// number of stack frames to retrieve
|
||||
@ -49,6 +58,20 @@ type Breakpoint struct {
|
||||
TotalHitCount uint64 `json:"totalHitCount"`
|
||||
}
|
||||
|
||||
func ValidBreakpointName(name string) error {
|
||||
if _, err := strconv.Atoi(name); err == nil {
|
||||
return errors.New("breakpoint name can not be a number")
|
||||
}
|
||||
|
||||
for _, ch := range name {
|
||||
if !(unicode.IsLetter(ch) || unicode.IsDigit(ch)) {
|
||||
return fmt.Errorf("invalid character in breakpoint name '%c'", ch)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Thread is a thread within the debugged process.
|
||||
type Thread struct {
|
||||
// ID is a unique identifier for the thread.
|
||||
|
@ -36,12 +36,19 @@ type Client interface {
|
||||
|
||||
// GetBreakpoint gets a breakpoint by ID.
|
||||
GetBreakpoint(id int) (*api.Breakpoint, error)
|
||||
// GetBreakpointByName gets a breakpoint by name.
|
||||
GetBreakpointByName(name string) (*api.Breakpoint, error)
|
||||
// CreateBreakpoint creates a new breakpoint.
|
||||
CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error)
|
||||
// ListBreakpoints gets all breakpoints.
|
||||
ListBreakpoints() ([]*api.Breakpoint, error)
|
||||
// ClearBreakpoint deletes a breakpoint by ID.
|
||||
ClearBreakpoint(id int) (*api.Breakpoint, error)
|
||||
// ClearBreakpointByName deletes a breakpoint by name
|
||||
ClearBreakpointByName(name string) (*api.Breakpoint, error)
|
||||
// Allows user to update an existing breakpoint for example to change the information
|
||||
// retrieved when the breakpoint is hit or to change, add or remove the break condition
|
||||
AmendBreakpoint(*api.Breakpoint) error
|
||||
|
||||
// ListThreads lists all threads.
|
||||
ListThreads() ([]*api.Thread, error)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"debug/gosym"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/parser"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@ -100,11 +101,12 @@ func (d *Debugger) Restart() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not launch process: %s", err)
|
||||
}
|
||||
for addr, bp := range d.process.Breakpoints {
|
||||
if bp.Temp {
|
||||
continue
|
||||
for _, oldBp := range d.Breakpoints() {
|
||||
newBp, err := p.SetBreakpoint(oldBp.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := p.SetBreakpoint(addr); err != nil {
|
||||
if err := copyBreakpointInfo(newBp, oldBp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -150,6 +152,16 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
addr uint64
|
||||
err error
|
||||
)
|
||||
|
||||
if requestedBp.Name != "" {
|
||||
if err = api.ValidBreakpointName(requestedBp.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.FindBreakpointByName(requestedBp.Name) != nil {
|
||||
return nil, errors.New("breakpoint name already exists")
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(requestedBp.File) > 0:
|
||||
fileName := requestedBp.File
|
||||
@ -182,16 +194,41 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bp.Tracepoint = requestedBp.Tracepoint
|
||||
bp.Goroutine = requestedBp.Goroutine
|
||||
bp.Stacktrace = requestedBp.Stacktrace
|
||||
bp.Variables = requestedBp.Variables
|
||||
bp.Cond = nil
|
||||
if err := copyBreakpointInfo(bp, requestedBp); err != nil {
|
||||
if _, err1 := d.process.ClearBreakpoint(bp.Addr); err1 != nil {
|
||||
err = fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
createdBp = api.ConvertBreakpoint(bp)
|
||||
log.Printf("created breakpoint: %#v", createdBp)
|
||||
return createdBp, nil
|
||||
}
|
||||
|
||||
func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
|
||||
original := d.findBreakpoint(amend.ID)
|
||||
if original == nil {
|
||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||
}
|
||||
if err := api.ValidBreakpointName(amend.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return copyBreakpointInfo(original, amend)
|
||||
}
|
||||
|
||||
func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err error) {
|
||||
bp.Name = requested.Name
|
||||
bp.Tracepoint = requested.Tracepoint
|
||||
bp.Goroutine = requested.Goroutine
|
||||
bp.Stacktrace = requested.Stacktrace
|
||||
bp.Variables = requested.Variables
|
||||
bp.Cond = nil
|
||||
if requested.Cond != "" {
|
||||
bp.Cond, err = parser.ParseExpr(requested.Cond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ClearBreakpoint clears a breakpoint.
|
||||
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
var clearedBp *api.Breakpoint
|
||||
@ -218,7 +255,15 @@ func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
||||
|
||||
// FindBreakpoint returns the breakpoint specified by 'id'.
|
||||
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
for _, bp := range d.Breakpoints() {
|
||||
bp := d.findBreakpoint(id)
|
||||
if bp == nil {
|
||||
return nil
|
||||
}
|
||||
return api.ConvertBreakpoint(bp)
|
||||
}
|
||||
|
||||
func (d *Debugger) findBreakpoint(id int) *proc.Breakpoint {
|
||||
for _, bp := range d.process.Breakpoints {
|
||||
if bp.ID == id {
|
||||
return bp
|
||||
}
|
||||
@ -226,6 +271,16 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindBreakpointByName returns the breakpoint specified by 'name'
|
||||
func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
|
||||
for _, bp := range d.Breakpoints() {
|
||||
if bp.Name == name {
|
||||
return bp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Threads returns the threads of the target process.
|
||||
func (d *Debugger) Threads() ([]*api.Thread, error) {
|
||||
if d.process.Exited() {
|
||||
|
@ -139,6 +139,12 @@ func (c *RPCClient) GetBreakpoint(id int) (*api.Breakpoint, error) {
|
||||
return breakpoint, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) {
|
||||
breakpoint := new(api.Breakpoint)
|
||||
err := c.call("GetBreakpointByName", name, breakpoint)
|
||||
return breakpoint, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) {
|
||||
newBreakpoint := new(api.Breakpoint)
|
||||
err := c.call("CreateBreakpoint", breakPoint, &newBreakpoint)
|
||||
@ -157,6 +163,17 @@ func (c *RPCClient) ClearBreakpoint(id int) (*api.Breakpoint, error) {
|
||||
return bp, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ClearBreakpointByName(name string) (*api.Breakpoint, error) {
|
||||
bp := new(api.Breakpoint)
|
||||
err := c.call("ClearBreakpointByName", name, bp)
|
||||
return bp, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error {
|
||||
err := c.call("AmendBreakpoint", bp, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ListThreads() ([]*api.Thread, error) {
|
||||
var threads []*api.Thread
|
||||
err := c.call("ListThreads", nil, &threads)
|
||||
|
@ -121,6 +121,15 @@ func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) GetBreakpointByName(name string, breakpoint *api.Breakpoint) error {
|
||||
bp := s.debugger.FindBreakpointByName(name)
|
||||
if bp == nil {
|
||||
return fmt.Errorf("no breakpoint with name %s", name)
|
||||
}
|
||||
*breakpoint = *bp
|
||||
return nil
|
||||
}
|
||||
|
||||
type StacktraceGoroutineArgs struct {
|
||||
Id int
|
||||
Depth int
|
||||
@ -163,6 +172,24 @@ func (s *RPCServer) ClearBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) ClearBreakpointByName(name string, breakpoint *api.Breakpoint) error {
|
||||
bp := s.debugger.FindBreakpointByName(name)
|
||||
if bp == nil {
|
||||
return fmt.Errorf("no breakpoint with name %s", name)
|
||||
}
|
||||
deleted, err := s.debugger.ClearBreakpoint(bp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*breakpoint = *deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) AmendBreakpoint(amend *api.Breakpoint, unused *int) error {
|
||||
*unused = 0
|
||||
return s.debugger.AmendBreakpoint(amend)
|
||||
}
|
||||
|
||||
func (s *RPCServer) ListThreads(arg interface{}, threads *[]*api.Thread) (err error) {
|
||||
*threads, err = s.debugger.Threads()
|
||||
return err
|
||||
|
@ -97,6 +97,35 @@ func TestRestart_afterExit(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRestart_breakpointPreservation(t *testing.T) {
|
||||
withTestClient("continuetestprog", t, func(c service.Client) {
|
||||
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true})
|
||||
assertNoError(err, t, "CreateBreakpoint()")
|
||||
stateCh := c.Continue()
|
||||
|
||||
state := <- stateCh
|
||||
if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint {
|
||||
t.Fatalf("Wrong breakpoint: %#v\n", state.CurrentThread.Breakpoint)
|
||||
}
|
||||
state = <- stateCh
|
||||
if !state.Exited {
|
||||
t.Fatal("Did not exit after first tracepoint")
|
||||
}
|
||||
|
||||
t.Log("Restart")
|
||||
c.Restart()
|
||||
stateCh = c.Continue()
|
||||
state = <- stateCh
|
||||
if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint {
|
||||
t.Fatalf("Wrong breakpoint (after restart): %#v\n", state.CurrentThread.Breakpoint)
|
||||
}
|
||||
state = <- stateCh
|
||||
if !state.Exited {
|
||||
t.Fatal("Did not exit after first tracepoint (after restart)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRestart_duringStop(t *testing.T) {
|
||||
withTestClient("continuetestprog", t, func(c service.Client) {
|
||||
origPid := c.ProcessPid()
|
||||
@ -966,3 +995,33 @@ func TestNegativeStackDepthBug(t *testing.T) {
|
||||
assertError(err, t, "Stacktrace()")
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServer_CondBreakpoint(t *testing.T) {
|
||||
withTestClient("parallel_next", t, func(c service.Client) {
|
||||
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1})
|
||||
assertNoError(err, t, "CreateBreakpoint()")
|
||||
bp.Cond = "n == 7"
|
||||
assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 1")
|
||||
bp, err = c.GetBreakpoint(bp.ID)
|
||||
assertNoError(err, t, "GetBreakpoint() 1")
|
||||
bp.Variables = append(bp.Variables, "n")
|
||||
assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 2")
|
||||
bp, err = c.GetBreakpoint(bp.ID)
|
||||
assertNoError(err, t, "GetBreakpoint() 2")
|
||||
if bp.Cond == "" {
|
||||
t.Fatalf("No condition set on breakpoint %#v", bp)
|
||||
}
|
||||
if len(bp.Variables) != 1 {
|
||||
t.Fatalf("Wrong number of expressions to evaluate on breakpoint %#v", bp)
|
||||
}
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
|
||||
nvar, err := c.EvalVariable(api.EvalScope{-1, 0}, "n")
|
||||
assertNoError(err, t, "EvalVariable()")
|
||||
|
||||
if nvar.SinglelineString() != "7" {
|
||||
t.Fatalf("Stopped on wrong goroutine %s\n", nvar.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ func DebugCommands(client service.Client) *Commands {
|
||||
|
||||
c.cmds = []command{
|
||||
{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
|
||||
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <linespec> [-stack <n>|-goroutine|<variable name>]*"},
|
||||
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break [name] <linespec>"},
|
||||
{aliases: []string{"trace", "t"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break."},
|
||||
{aliases: []string{"restart", "r"}, cmdFn: restart, helpMsg: "Restart process."},
|
||||
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
|
||||
@ -85,6 +85,8 @@ func DebugCommands(client service.Client) *Commands {
|
||||
{aliases: []string{"frame"}, cmdFn: frame, helpMsg: "Sets current stack frame (0 is the top of the stack)"},
|
||||
{aliases: []string{"source"}, cmdFn: c.sourceCommand, helpMsg: "Executes a file containing a list of delve commands"},
|
||||
{aliases: []string{"disassemble", "disass"}, cmdFn: g0f0(disassCommand), helpMsg: "Displays disassembly of specific function or address range: disassemble [-a <start> <end>] [-l <locspec>]"},
|
||||
{aliases: []string{"on"}, cmdFn: onCmd, helpMsg: "on <breakpoint name or id> <command>. Executes command when the specified breakpoint is hit (supported commands: print <expression>, stack [<depth>] [-full] and goroutine)"},
|
||||
{aliases: []string{"condition", "cond"}, cmdFn: conditionCmd, helpMsg: "cond <breakpoint name or id> <boolean expression>. Specifies that the breakpoint or tracepoint should break only if the boolean expression is true."},
|
||||
}
|
||||
|
||||
return c
|
||||
@ -499,14 +501,16 @@ func clear(t *Term, args string) error {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
}
|
||||
id, err := strconv.Atoi(args)
|
||||
var bp *api.Breakpoint
|
||||
if err == nil {
|
||||
bp, err = t.client.ClearBreakpoint(id)
|
||||
} else {
|
||||
bp, err = t.client.ClearBreakpointByName(args)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bp, err := t.client.ClearBreakpoint(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
|
||||
fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -537,9 +541,9 @@ func clearAll(t *Term, args string) error {
|
||||
|
||||
_, err := t.client.ClearBreakpoint(bp.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("Couldn't delete breakpoint %d at %#v %s:%d: %s\n", bp.ID, bp.Addr, ShortenFilePath(bp.File), bp.Line, err)
|
||||
fmt.Printf("Couldn't delete %s at %s: %s\n", formatBreakpointName(bp, false), formatBreakpointLocation(bp), err)
|
||||
}
|
||||
fmt.Printf("Breakpoint %d cleared at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
|
||||
fmt.Printf("%s cleared at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -558,61 +562,60 @@ func breakpoints(t *Term, args string) error {
|
||||
}
|
||||
sort.Sort(ByID(breakPoints))
|
||||
for _, bp := range breakPoints {
|
||||
thing := "Breakpoint"
|
||||
if bp.Tracepoint {
|
||||
thing = "Tracepoint"
|
||||
}
|
||||
fmt.Printf("%s %d at %#v %s:%d (%d)\n", thing, bp.ID, bp.Addr, ShortenFilePath(bp.File), bp.Line, bp.TotalHitCount)
|
||||
fmt.Printf("%s at %v (%d)\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp), bp.TotalHitCount)
|
||||
|
||||
var attrs []string
|
||||
if bp.Cond != "" {
|
||||
attrs = append(attrs, fmt.Sprintf("\tcond %s", bp.Cond))
|
||||
}
|
||||
if bp.Stacktrace > 0 {
|
||||
attrs = append(attrs, "-stack")
|
||||
attrs = append(attrs, strconv.Itoa(bp.Stacktrace))
|
||||
attrs = append(attrs, fmt.Sprintf("\tstack %d", bp.Stacktrace))
|
||||
}
|
||||
if bp.Goroutine {
|
||||
attrs = append(attrs, "-goroutine")
|
||||
attrs = append(attrs, "\tgoroutine")
|
||||
}
|
||||
for i := range bp.Variables {
|
||||
attrs = append(attrs, bp.Variables[i])
|
||||
attrs = append(attrs, fmt.Sprintf("\tprint %s", bp.Variables[i]))
|
||||
}
|
||||
if len(attrs) > 0 {
|
||||
fmt.Printf("\t%s\n", strings.Join(attrs, " "))
|
||||
fmt.Printf("%s\n", strings.Join(attrs, "\n"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
|
||||
args := strings.Split(argstr, " ")
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("address required, specify either a function name or <file:line>")
|
||||
}
|
||||
requestedBp := &api.Breakpoint{}
|
||||
args := strings.SplitN(argstr, " ", 2)
|
||||
|
||||
for i := 1; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "-stack":
|
||||
i++
|
||||
n, err := strconv.Atoi(args[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("argument of -stack must be a number")
|
||||
}
|
||||
requestedBp.Stacktrace = n
|
||||
case "-goroutine":
|
||||
requestedBp.Goroutine = true
|
||||
default:
|
||||
requestedBp.Variables = append(requestedBp.Variables, args[i])
|
||||
requestedBp := &api.Breakpoint{}
|
||||
locspec := ""
|
||||
switch len(args) {
|
||||
case 1:
|
||||
locspec = argstr
|
||||
case 2:
|
||||
if api.ValidBreakpointName(args[0]) == nil {
|
||||
requestedBp.Name = args[0]
|
||||
locspec = args[1]
|
||||
} else {
|
||||
locspec = argstr
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("address required")
|
||||
}
|
||||
|
||||
requestedBp.Tracepoint = tracepoint
|
||||
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, args[0])
|
||||
locs, err := t.client.FindLocation(api.EvalScope{GoroutineID: -1, Frame: 0}, locspec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
thing := "Breakpoint"
|
||||
if tracepoint {
|
||||
thing = "Tracepoint"
|
||||
if requestedBp.Name == "" {
|
||||
return err
|
||||
}
|
||||
requestedBp.Name = ""
|
||||
locspec = argstr
|
||||
var err2 error
|
||||
locs, err2 = t.client.FindLocation(api.EvalScope{-1, 0}, locspec)
|
||||
if err2 != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, loc := range locs {
|
||||
requestedBp.Addr = loc.PC
|
||||
@ -622,7 +625,7 @@ func setBreakpoint(t *Term, tracepoint bool, argstr string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, ShortenFilePath(bp.File), bp.Line)
|
||||
fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), formatBreakpointLocation(bp))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -960,8 +963,14 @@ func printcontextThread(t *Term, th *api.Thread) {
|
||||
args = strings.Join(arg, ", ")
|
||||
}
|
||||
|
||||
bpname := ""
|
||||
if th.Breakpoint.Name != "" {
|
||||
bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
|
||||
}
|
||||
|
||||
if hitCount, ok := th.Breakpoint.HitCount[strconv.Itoa(th.GoroutineID)]; ok {
|
||||
fmt.Printf("> %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,
|
||||
fn.Name,
|
||||
args,
|
||||
ShortenFilePath(th.File),
|
||||
@ -971,7 +980,8 @@ func printcontextThread(t *Term, th *api.Thread) {
|
||||
th.Breakpoint.TotalHitCount,
|
||||
th.PC)
|
||||
} else {
|
||||
fmt.Printf("> %s(%s) %s:%d (hits total:%d) (PC: %#v)\n",
|
||||
fmt.Printf("> %s%s(%s) %s:%d (hits total:%d) (PC: %#v)\n",
|
||||
bpname,
|
||||
fn.Name,
|
||||
args,
|
||||
ShortenFilePath(th.File),
|
||||
@ -1053,6 +1063,65 @@ func exitCommand(t *Term, args string) error {
|
||||
return ExitRequestError{}
|
||||
}
|
||||
|
||||
func getBreakpointByIDOrName(t *Term, arg string) (bp *api.Breakpoint, err error) {
|
||||
if id, err := strconv.Atoi(arg); err == nil {
|
||||
bp, err = t.client.GetBreakpoint(id)
|
||||
} else {
|
||||
bp, err = t.client.GetBreakpointByName(arg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func onCmd(t *Term, argstr string) error {
|
||||
args := strings.SplitN(argstr, " ", 3)
|
||||
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
}
|
||||
|
||||
bp, err := getBreakpointByIDOrName(t, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
case "p", "print":
|
||||
if len(args) < 3 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
}
|
||||
bp.Variables = append(bp.Variables, args[2])
|
||||
case "stack", "bt":
|
||||
depth, _, err := parseStackArgs(args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bp.Stacktrace = depth
|
||||
case "goroutine":
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("too many arguments")
|
||||
}
|
||||
bp.Goroutine = true
|
||||
}
|
||||
|
||||
return t.client.AmendBreakpoint(bp)
|
||||
}
|
||||
|
||||
func conditionCmd(t *Term, argstr string) error {
|
||||
args := strings.SplitN(argstr, " ", 2)
|
||||
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
}
|
||||
|
||||
bp, err := getBreakpointByIDOrName(t, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bp.Cond = args[1]
|
||||
|
||||
return t.client.AmendBreakpoint(bp)
|
||||
}
|
||||
|
||||
// ShortenFilePath take a full file path and attempts to shorten
|
||||
// it by replacing the current directory to './'.
|
||||
func ShortenFilePath(fullPath string) string {
|
||||
@ -1088,3 +1157,27 @@ func (c *Commands) executeFile(t *Term, name string) error {
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
||||
thing := "breakpoint"
|
||||
if bp.Tracepoint {
|
||||
thing = "tracepoint"
|
||||
}
|
||||
if upcase {
|
||||
thing = strings.Title(thing)
|
||||
}
|
||||
id := bp.Name
|
||||
if id == "" {
|
||||
id = strconv.Itoa(bp.ID)
|
||||
}
|
||||
return fmt.Sprintf("%s %s", thing, id)
|
||||
}
|
||||
|
||||
func formatBreakpointLocation(bp *api.Breakpoint) string {
|
||||
p := ShortenFilePath(bp.File)
|
||||
if bp.FunctionName != "" {
|
||||
return fmt.Sprintf("%#v for %s() %s:%d", bp.Addr, bp.FunctionName, p, bp.Line)
|
||||
} else {
|
||||
return fmt.Sprintf("%#v for %s:%d", bp.Addr, p, bp.Line)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user