trace command
This commit is contained in:
parent
c267eda9ba
commit
3a96d8eed7
@ -20,6 +20,12 @@ type Breakpoint struct {
|
|||||||
Temp bool // Whether this is a temp breakpoint (for next'ing).
|
Temp bool // Whether this is a temp breakpoint (for next'ing).
|
||||||
hardware bool // Breakpoint using CPU debug registers.
|
hardware bool // Breakpoint using CPU debug registers.
|
||||||
reg int // If hardware breakpoint, what debug register it belongs to.
|
reg int // If hardware breakpoint, what debug register it belongs to.
|
||||||
|
|
||||||
|
// Breakpoint informations
|
||||||
|
Tracepoint bool // tracepoint flag
|
||||||
|
Stacktrace int // number of stack frames to retrieve
|
||||||
|
Goroutine bool // retrieve goroutine information
|
||||||
|
Symbols []string // variables to evaluate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bp *Breakpoint) String() string {
|
func (bp *Breakpoint) String() string {
|
||||||
|
@ -301,7 +301,7 @@ func (dbp *Process) next() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := dbp.CurrentThread.getG()
|
g, err := dbp.CurrentThread.GetG()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -343,7 +343,7 @@ func (dbp *Process) next() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tg, err := thread.getG()
|
tg, err := thread.GetG()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -466,7 +466,7 @@ func (dbp *Process) GoroutinesInfo() ([]*G, error) {
|
|||||||
if dbp.Threads[i].blocked() {
|
if dbp.Threads[i].blocked() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
g, _ := dbp.Threads[i].getG()
|
g, _ := dbp.Threads[i].GetG()
|
||||||
if g != nil {
|
if g != nil {
|
||||||
threadg[g.Id] = dbp.Threads[i]
|
threadg[g.Id] = dbp.Threads[i]
|
||||||
}
|
}
|
||||||
|
@ -452,7 +452,7 @@ func TestStacktrace(t *testing.T) {
|
|||||||
|
|
||||||
for i := range stacks {
|
for i := range stacks {
|
||||||
assertNoError(p.Continue(), t, "Continue()")
|
assertNoError(p.Continue(), t, "Continue()")
|
||||||
locations, err := p.CurrentThread.Stacktrace(40)
|
_, locations, err := p.CurrentThread.Stacktrace(40)
|
||||||
assertNoError(err, t, "Stacktrace()")
|
assertNoError(err, t, "Stacktrace()")
|
||||||
|
|
||||||
if len(locations) != len(stacks[i])+2 {
|
if len(locations) != len(stacks[i])+2 {
|
||||||
@ -500,7 +500,7 @@ func TestStacktraceGoroutine(t *testing.T) {
|
|||||||
mainCount := 0
|
mainCount := 0
|
||||||
|
|
||||||
for _, g := range gs {
|
for _, g := range gs {
|
||||||
locations, _ := p.GoroutineStacktrace(g, 40)
|
_, locations, _ := p.GoroutineStacktrace(g, 40)
|
||||||
assertNoError(err, t, "GoroutineStacktrace()")
|
assertNoError(err, t, "GoroutineStacktrace()")
|
||||||
|
|
||||||
if stackMatch(mainStack, locations) {
|
if stackMatch(mainStack, locations) {
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
// Takes an offset from RSP and returns the address of the
|
// Takes an offset from RSP and returns the address of the
|
||||||
// instruction the currect function is going to return to.
|
// instruction the currect function is going to return to.
|
||||||
func (thread *Thread) ReturnAddress() (uint64, error) {
|
func (thread *Thread) ReturnAddress() (uint64, error) {
|
||||||
locations, err := thread.Stacktrace(1)
|
_, locations, err := thread.Stacktrace(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -16,25 +16,30 @@ func (thread *Thread) ReturnAddress() (uint64, error) {
|
|||||||
|
|
||||||
// Returns the stack trace for thread
|
// Returns the stack trace for thread
|
||||||
// Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses
|
// Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses
|
||||||
func (thread *Thread) Stacktrace(depth int) ([]Location, error) {
|
func (thread *Thread) Stacktrace(depth int) (*Location, []Location, error) {
|
||||||
|
loc, err := thread.Location()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
regs, err := thread.Registers()
|
regs, err := thread.Registers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
locations, err := thread.dbp.stacktrace(regs.PC(), regs.SP(), depth)
|
locations, err := thread.dbp.stacktrace(regs.PC(), regs.SP(), depth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return locations, nil
|
return loc, locations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the stack trace for a goroutine
|
// Returns the stack trace for a goroutine
|
||||||
// Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses
|
// Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses
|
||||||
func (dbp *Process) GoroutineStacktrace(g *G, depth int) ([]Location, error) {
|
func (dbp *Process) GoroutineStacktrace(g *G, depth int) (*Location, []Location, error) {
|
||||||
if g.thread != nil {
|
if g.thread != nil {
|
||||||
return g.thread.Stacktrace(depth)
|
return g.thread.Stacktrace(depth)
|
||||||
}
|
}
|
||||||
return dbp.stacktrace(g.PC, g.SP, depth)
|
locs, err := dbp.stacktrace(g.PC, g.SP, depth)
|
||||||
|
return dbp.GoroutineLocation(g), locs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) GoroutineLocation(g *G) *Location {
|
func (dbp *Process) GoroutineLocation(g *G) *Location {
|
||||||
|
@ -192,7 +192,7 @@ func (thread *Thread) next(curpc uint64, fde *frame.FrameDescriptionEntry, file
|
|||||||
if !covered {
|
if !covered {
|
||||||
fn := thread.dbp.goSymTable.PCToFunc(ret)
|
fn := thread.dbp.goSymTable.PCToFunc(ret)
|
||||||
if fn != nil && fn.Name == "runtime.goexit" {
|
if fn != nil && fn.Name == "runtime.goexit" {
|
||||||
g, err := thread.getG()
|
g, err := thread.GetG()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -257,7 +257,7 @@ func (thread *Thread) SetPC(pc uint64) error {
|
|||||||
// current instruction stream. The instructions are obviously arch/os dependant, as they
|
// current instruction stream. The instructions are obviously arch/os dependant, as they
|
||||||
// vary on how thread local storage is implemented, which MMU register is used and
|
// vary on how thread local storage is implemented, which MMU register is used and
|
||||||
// what the offset into thread local storage is.
|
// what the offset into thread local storage is.
|
||||||
func (thread *Thread) getG() (g *G, err error) {
|
func (thread *Thread) GetG() (g *G, err error) {
|
||||||
var pcInt uint64
|
var pcInt uint64
|
||||||
pcInt, err = thread.PC()
|
pcInt, err = thread.PC()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -13,6 +13,10 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
|||||||
File: bp.File,
|
File: bp.File,
|
||||||
Line: bp.Line,
|
Line: bp.Line,
|
||||||
Addr: bp.Addr,
|
Addr: bp.Addr,
|
||||||
|
Tracepoint: bp.Tracepoint,
|
||||||
|
Stacktrace: bp.Stacktrace,
|
||||||
|
Goroutine: bp.Goroutine,
|
||||||
|
Symbols: bp.Symbols,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,13 @@ type DebuggerState struct {
|
|||||||
Breakpoint *Breakpoint `json:"breakPoint,omitempty"`
|
Breakpoint *Breakpoint `json:"breakPoint,omitempty"`
|
||||||
// CurrentThread is the currently selected debugger thread.
|
// CurrentThread is the currently selected debugger thread.
|
||||||
CurrentThread *Thread `json:"currentThread,omitempty"`
|
CurrentThread *Thread `json:"currentThread,omitempty"`
|
||||||
|
// Information requested by the current breakpoint
|
||||||
|
BreakpointInfo *BreakpointInfo `json:"breakPointInfo,omitrempty"`
|
||||||
// Exited indicates whether the debugged process has exited.
|
// Exited indicates whether the debugged process has exited.
|
||||||
Exited bool `json:"exited"`
|
Exited bool `json:"exited"`
|
||||||
|
|
||||||
|
// Filled by RPCClient.Continue, indicates an error
|
||||||
|
Err error `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Breakpoint addresses a location at which process execution may be
|
// Breakpoint addresses a location at which process execution may be
|
||||||
@ -25,6 +30,15 @@ type Breakpoint struct {
|
|||||||
// FunctionName is the name of the function at the current breakpoint, and
|
// FunctionName is the name of the function at the current breakpoint, and
|
||||||
// may not always be available.
|
// may not always be available.
|
||||||
FunctionName string `json:"functionName,omitempty"`
|
FunctionName string `json:"functionName,omitempty"`
|
||||||
|
|
||||||
|
// tracepoint flag
|
||||||
|
Tracepoint bool `json:"continue"`
|
||||||
|
// number of stack frames to retrieve
|
||||||
|
Stacktrace int `json:"stacktrace"`
|
||||||
|
// retrieve goroutine information
|
||||||
|
Goroutine bool `json:"goroutine"`
|
||||||
|
// variables to evaluate
|
||||||
|
Symbols []string `json:"symbols,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread is a thread within the debugged process.
|
// Thread is a thread within the debugged process.
|
||||||
@ -92,6 +106,13 @@ type DebuggerCommand struct {
|
|||||||
ThreadID int `json:"threadID,omitempty"`
|
ThreadID int `json:"threadID,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Informations about the current breakpoint
|
||||||
|
type BreakpointInfo struct {
|
||||||
|
Stacktrace []Location `json:"stacktrace,omitempty"`
|
||||||
|
Goroutine *Goroutine `json:"goroutine,omitempty"`
|
||||||
|
Variables []Variable `json:"variables,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Continue resumes process execution.
|
// Continue resumes process execution.
|
||||||
Continue = "continue"
|
Continue = "continue"
|
||||||
|
@ -14,7 +14,7 @@ type Client interface {
|
|||||||
GetState() (*api.DebuggerState, error)
|
GetState() (*api.DebuggerState, error)
|
||||||
|
|
||||||
// Continue resumes process execution.
|
// Continue resumes process execution.
|
||||||
Continue() (*api.DebuggerState, error)
|
Continue() <-chan *api.DebuggerState
|
||||||
// Next continues to the next source line, not entering function calls.
|
// Next continues to the next source line, not entering function calls.
|
||||||
Next() (*api.DebuggerState, error)
|
Next() (*api.DebuggerState, error)
|
||||||
// Step continues to the next source line, entering function calls.
|
// Step continues to the next source line, entering function calls.
|
||||||
@ -62,5 +62,5 @@ type Client interface {
|
|||||||
ListGoroutines() ([]*api.Goroutine, error)
|
ListGoroutines() ([]*api.Goroutine, error)
|
||||||
|
|
||||||
// Returns stacktrace
|
// Returns stacktrace
|
||||||
Stacktrace(goroutineId, depth int) ([]*api.Location, error)
|
Stacktrace(goroutineId, depth int) ([]api.Location, error)
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,10 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
bp.Tracepoint = requestedBp.Tracepoint
|
||||||
|
bp.Goroutine = requestedBp.Goroutine
|
||||||
|
bp.Stacktrace = requestedBp.Stacktrace
|
||||||
|
bp.Symbols = requestedBp.Symbols
|
||||||
createdBp = api.ConvertBreakpoint(bp)
|
createdBp = api.ConvertBreakpoint(bp)
|
||||||
log.Printf("created breakpoint: %#v", createdBp)
|
log.Printf("created breakpoint: %#v", createdBp)
|
||||||
return createdBp, nil
|
return createdBp, nil
|
||||||
@ -165,6 +169,20 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
|||||||
case api.Continue:
|
case api.Continue:
|
||||||
log.Print("continuing")
|
log.Print("continuing")
|
||||||
err = d.process.Continue()
|
err = d.process.Continue()
|
||||||
|
if err != nil {
|
||||||
|
if _, exited := err.(proc.ProcessExitedError); exited {
|
||||||
|
return d.State()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := d.State()
|
||||||
|
if err != nil {
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
err = d.collectBreakpointInformation(state)
|
||||||
|
return state, err
|
||||||
|
|
||||||
case api.Next:
|
case api.Next:
|
||||||
log.Print("nexting")
|
log.Print("nexting")
|
||||||
err = d.process.Next()
|
err = d.process.Next()
|
||||||
@ -186,6 +204,45 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
|
|||||||
return d.State()
|
return d.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error {
|
||||||
|
if state == nil || state.Breakpoint == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bp := state.Breakpoint
|
||||||
|
bpi := &api.BreakpointInfo{}
|
||||||
|
state.BreakpointInfo = bpi
|
||||||
|
|
||||||
|
if bp.Goroutine {
|
||||||
|
g, err := d.process.CurrentThread.GetG()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bpi.Goroutine = api.ConvertGoroutine(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bp.Stacktrace > 0 {
|
||||||
|
rawloc, rawlocs, err := d.process.CurrentThread.Stacktrace(bp.Stacktrace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bpi.Stacktrace = convertStacktrace(rawloc, rawlocs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bp.Symbols) > 0 {
|
||||||
|
bpi.Variables = make([]api.Variable, len(bp.Symbols))
|
||||||
|
}
|
||||||
|
for i := range bp.Symbols {
|
||||||
|
v, err := d.process.CurrentThread.EvalVariable(bp.Symbols[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bpi.Variables[i] = api.ConvertVar(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Debugger) Sources(filter string) ([]string, error) {
|
func (d *Debugger) Sources(filter string) ([]string, error) {
|
||||||
regex, err := regexp.Compile(filter)
|
regex, err := regexp.Compile(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -314,11 +371,7 @@ func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if goroutineId < 0 {
|
if goroutineId < 0 {
|
||||||
rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
|
rawloc, rawlocs, err = d.process.CurrentThread.Stacktrace(depth)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawloc, err = d.process.CurrentThread.Location()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -329,11 +382,10 @@ func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
|||||||
}
|
}
|
||||||
for _, g := range gs {
|
for _, g := range gs {
|
||||||
if g.Id == goroutineId {
|
if g.Id == goroutineId {
|
||||||
rawlocs, err = d.process.GoroutineStacktrace(g, depth)
|
rawloc, rawlocs, err = d.process.GoroutineStacktrace(g, depth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rawloc = d.process.GoroutineLocation(g)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,6 +395,10 @@ func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return convertStacktrace(rawloc, rawlocs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStacktrace(rawloc *proc.Location, rawlocs []proc.Location) []api.Location {
|
||||||
locations := make([]api.Location, 0, len(rawlocs)+1)
|
locations := make([]api.Location, 0, len(rawlocs)+1)
|
||||||
|
|
||||||
locations = append(locations, api.ConvertLocation(*rawloc))
|
locations = append(locations, api.ConvertLocation(*rawloc))
|
||||||
@ -351,5 +407,5 @@ func (d *Debugger) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
|||||||
locations = append(locations, api.ConvertLocation(rawlocs[i]))
|
locations = append(locations, api.ConvertLocation(rawlocs[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return locations, nil
|
return locations
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,21 @@ func (c *RPCClient) GetState() (*api.DebuggerState, error) {
|
|||||||
return state, err
|
return state, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) Continue() (*api.DebuggerState, error) {
|
func (c *RPCClient) Continue() <- chan *api.DebuggerState {
|
||||||
state := new(api.DebuggerState)
|
ch := make(chan *api.DebuggerState)
|
||||||
err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, state)
|
go func() {
|
||||||
return state, err
|
for {
|
||||||
|
state := new(api.DebuggerState)
|
||||||
|
err := c.call("Command", &api.DebuggerCommand{Name: api.Continue}, state)
|
||||||
|
state.Err = err
|
||||||
|
ch <- state
|
||||||
|
if err != nil || state.Breakpoint == nil || !state.Breakpoint.Tracepoint {
|
||||||
|
close(ch)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) Next() (*api.DebuggerState, error) {
|
func (c *RPCClient) Next() (*api.DebuggerState, error) {
|
||||||
@ -171,8 +182,8 @@ func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
|
|||||||
return goroutines, err
|
return goroutines, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) Stacktrace(goroutineId, depth int) ([]*api.Location, error) {
|
func (c *RPCClient) Stacktrace(goroutineId, depth int) ([]api.Location, error) {
|
||||||
var locations []*api.Location
|
var locations []api.Location
|
||||||
err := c.call("StacktraceGoroutine", &StacktraceGoroutineArgs{Id: 1, Depth: depth}, &locations)
|
err := c.call("StacktraceGoroutine", &StacktraceGoroutineArgs{Id: 1, Depth: depth}, &locations)
|
||||||
return locations, err
|
return locations, err
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package servicetest
|
package servicetest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
protest "github.com/derekparker/delve/proc/test"
|
protest "github.com/derekparker/delve/proc/test"
|
||||||
@ -68,12 +69,12 @@ func TestClientServer_exit(t *testing.T) {
|
|||||||
if e, a := false, state.Exited; e != a {
|
if e, a := false, state.Exited; e != a {
|
||||||
t.Fatalf("Expected exited %v, got %v", e, a)
|
t.Fatalf("Expected exited %v, got %v", e, a)
|
||||||
}
|
}
|
||||||
state, err = c.Continue()
|
state = <-c.Continue()
|
||||||
if err == nil {
|
if state.Err != nil {
|
||||||
t.Fatalf("Expected error after continue, got none")
|
t.Fatalf("Error after continue: %v\n", err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "exited") {
|
if !state.Exited {
|
||||||
t.Fatal("Expected exit message")
|
t.Fatalf("Expected exit after continue: %v", state)
|
||||||
}
|
}
|
||||||
state, err = c.GetState()
|
state, err = c.GetState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,9 +96,10 @@ func TestClientServer_step(t *testing.T) {
|
|||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stateBefore, err := c.Continue()
|
statech := c.Continue()
|
||||||
if err != nil {
|
stateBefore := <-statech
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
if stateBefore.Err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", stateBefore.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stateAfter, err := c.Step()
|
stateAfter, err := c.Step()
|
||||||
@ -122,9 +124,9 @@ func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
|
|||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := c.Continue()
|
state := <-c.Continue()
|
||||||
if err != nil {
|
if state.Err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", state.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.ClearBreakpoint(bp.ID)
|
_, err = c.ClearBreakpoint(bp.ID)
|
||||||
@ -194,7 +196,7 @@ func TestClientServer_breakpointInMainThread(t *testing.T) {
|
|||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := c.Continue()
|
state := <-c.Continue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
||||||
}
|
}
|
||||||
@ -215,9 +217,9 @@ func TestClientServer_breakpointInSeparateGoroutine(t *testing.T) {
|
|||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := c.Continue()
|
state := <-c.Continue()
|
||||||
if err != nil {
|
if state.Err != nil {
|
||||||
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, l := state.CurrentThread.File, state.CurrentThread.Line
|
f, l := state.CurrentThread.File, state.CurrentThread.Line
|
||||||
@ -276,9 +278,9 @@ func TestClientServer_switchThread(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
state, err := c.Continue()
|
state := <-c.Continue()
|
||||||
if err != nil {
|
if state.Err != nil {
|
||||||
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
var nt int
|
var nt int
|
||||||
@ -307,25 +309,30 @@ func TestClientServer_switchThread(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientServer_infoLocals(t *testing.T) {
|
func testProgPath(t *testing.T, name string) string {
|
||||||
withTestClient("testnextprog", t, func(c service.Client) {
|
fp, err := filepath.Abs(fmt.Sprintf("_fixtures/%s.go", name))
|
||||||
fp, err := filepath.Abs("_fixtures/testnextprog.go")
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(fp); err != nil {
|
||||||
|
fp, err = filepath.Abs(fmt.Sprintf("../../_fixtures/%s.go", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(fp); err != nil {
|
}
|
||||||
fp, err = filepath.Abs("../../_fixtures/testnextprog.go")
|
return fp
|
||||||
if err != nil {
|
}
|
||||||
t.Fatal(err)
|
|
||||||
}
|
func TestClientServer_infoLocals(t *testing.T) {
|
||||||
}
|
withTestClient("testnextprog", t, func(c service.Client) {
|
||||||
_, err = c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
|
fp := testProgPath(t, "testnextprog")
|
||||||
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
state, err := c.Continue()
|
state := <-c.Continue()
|
||||||
if err != nil {
|
if state.Err != nil {
|
||||||
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
||||||
}
|
}
|
||||||
locals, err := c.ListLocalVariables()
|
locals, err := c.ListLocalVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -339,23 +346,14 @@ func TestClientServer_infoLocals(t *testing.T) {
|
|||||||
|
|
||||||
func TestClientServer_infoArgs(t *testing.T) {
|
func TestClientServer_infoArgs(t *testing.T) {
|
||||||
withTestClient("testnextprog", t, func(c service.Client) {
|
withTestClient("testnextprog", t, func(c service.Client) {
|
||||||
fp, err := filepath.Abs("_fixtures/testnextprog.go")
|
fp := testProgPath(t, "testnextprog")
|
||||||
if err != nil {
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47})
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(fp); err != nil {
|
|
||||||
fp, err = filepath.Abs("../../_fixtures/testnextprog.go")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
state, err := c.Continue()
|
state := <-c.Continue()
|
||||||
if err != nil {
|
if state.Err != nil {
|
||||||
t.Fatalf("Unexpected error: %v, state: %#v", err, state)
|
t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state)
|
||||||
}
|
}
|
||||||
regs, err := c.ListRegisters()
|
regs, err := c.ListRegisters()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -373,3 +371,56 @@ func TestClientServer_infoArgs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientServer_traceContinue(t *testing.T) {
|
||||||
|
withTestClient("integrationprog", t, func(c service.Client) {
|
||||||
|
fp := testProgPath(t, "integrationprog")
|
||||||
|
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Symbols: []string{"i"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
contch := c.Continue()
|
||||||
|
for state := range contch {
|
||||||
|
if state.Breakpoint != nil {
|
||||||
|
count++
|
||||||
|
|
||||||
|
t.Logf("%v", state)
|
||||||
|
|
||||||
|
bpi := state.BreakpointInfo
|
||||||
|
|
||||||
|
if bpi.Goroutine == nil {
|
||||||
|
t.Fatalf("No goroutine information")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bpi.Stacktrace) <= 0 {
|
||||||
|
t.Fatalf("No stacktrace\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bpi.Variables) != 1 {
|
||||||
|
t.Fatalf("Wrong number of variables returned: %d", len(bpi.Variables))
|
||||||
|
}
|
||||||
|
|
||||||
|
if bpi.Variables[0].Name != "i" {
|
||||||
|
t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bpi.Variables[0].Value != strconv.Itoa(count-1) {
|
||||||
|
t.Fatalf("Wrong variable value %s (%d)", bpi.Variables[0].Value, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state.Exited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Logf("%v", state)
|
||||||
|
if state.Err != nil {
|
||||||
|
t.Fatalf("Unexpected error during continue: %v\n", state.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != 3 {
|
||||||
|
t.Fatalf("Wrong number of continues hit: %d\n", count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -46,7 +46,8 @@ func DebugCommands(client service.Client) *Commands {
|
|||||||
|
|
||||||
c.cmds = []command{
|
c.cmds = []command{
|
||||||
{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
|
{aliases: []string{"help"}, cmdFn: c.help, helpMsg: "Prints the help message."},
|
||||||
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "Set break point at the entry point of a function, or at a specific file/line. Example: break foo.go:13"},
|
{aliases: []string{"break", "b"}, cmdFn: breakpoint, helpMsg: "break <address> [-stack <n>|-goroutine|<variable name>]*\nSet break point at the entry point of a function, or at a specific file/line.\nWhen the breakpoint is reached the value of the specified variables will be printed, if -stack is specified the stack trace of the current goroutine will be printed, if -goroutine is specified informations about the current goroutine will be printed. Example: break foo.go:13"},
|
||||||
|
{aliases: []string{"trace"}, cmdFn: tracepoint, helpMsg: "Set tracepoint, takes the same arguments as break"},
|
||||||
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
|
{aliases: []string{"continue", "c"}, cmdFn: cont, helpMsg: "Run until breakpoint or program termination."},
|
||||||
{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
|
{aliases: []string{"step", "si"}, cmdFn: step, helpMsg: "Single step through program."},
|
||||||
{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
|
{aliases: []string{"next", "n"}, cmdFn: next, helpMsg: "Step over to next source line."},
|
||||||
@ -186,21 +187,27 @@ func goroutines(client service.Client, args ...string) error {
|
|||||||
}
|
}
|
||||||
fmt.Printf("[%d goroutines]\n", len(gs))
|
fmt.Printf("[%d goroutines]\n", len(gs))
|
||||||
for _, g := range gs {
|
for _, g := range gs {
|
||||||
var fname string
|
fmt.Printf("Goroutine %s\n", formatGoroutine(g))
|
||||||
if g.Function != nil {
|
|
||||||
fname = g.Function.Name
|
|
||||||
}
|
|
||||||
fmt.Printf("Goroutine %d - %s:%d %s (%#v)\n", g.ID, g.File, g.Line, fname, g.PC)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cont(client service.Client, args ...string) error {
|
func formatGoroutine(g *api.Goroutine) string {
|
||||||
state, err := client.Continue()
|
fname := ""
|
||||||
if err != nil {
|
if g.Function != nil {
|
||||||
return err
|
fname = g.Function.Name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d - %s:%d %s (%#v)\n", g.ID, g.File, g.Line, fname, g.PC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cont(client service.Client, args ...string) error {
|
||||||
|
statech := client.Continue()
|
||||||
|
for state := range statech {
|
||||||
|
if state.Err != nil {
|
||||||
|
return state.Err
|
||||||
|
}
|
||||||
|
printcontext(state)
|
||||||
}
|
}
|
||||||
printcontext(state)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,16 +275,37 @@ func breakpoints(client service.Client, args ...string) error {
|
|||||||
}
|
}
|
||||||
sort.Sort(ById(breakPoints))
|
sort.Sort(ById(breakPoints))
|
||||||
for _, bp := range breakPoints {
|
for _, bp := range breakPoints {
|
||||||
fmt.Printf("Breakpoint %d at %#v %s:%d\n", bp.ID, bp.Addr, bp.File, bp.Line)
|
thing := "Breakpoint"
|
||||||
|
if bp.Tracepoint {
|
||||||
|
thing = "Tracepoint"
|
||||||
|
}
|
||||||
|
fmt.Printf("%s %d at %#v %s:%d\n", thing, bp.ID, bp.Addr, bp.File, bp.Line)
|
||||||
|
|
||||||
|
var attrs []string
|
||||||
|
if bp.Stacktrace > 0 {
|
||||||
|
attrs = append(attrs, "-stack")
|
||||||
|
attrs = append(attrs, strconv.Itoa(bp.Stacktrace))
|
||||||
|
}
|
||||||
|
if bp.Goroutine {
|
||||||
|
attrs = append(attrs, "-goroutine")
|
||||||
|
}
|
||||||
|
for i := range bp.Symbols {
|
||||||
|
attrs = append(attrs, bp.Symbols[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attrs) > 0 {
|
||||||
|
fmt.Printf("\t%s\n", strings.Join(attrs, " "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func breakpoint(client service.Client, args ...string) error {
|
func breakpointIntl(client service.Client, tracepoint bool, args ...string) error {
|
||||||
if len(args) != 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("argument must be either a function name or <file:line>")
|
return fmt.Errorf("address required, specify either a function name or <file:line>")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedBp := &api.Breakpoint{}
|
requestedBp := &api.Breakpoint{}
|
||||||
tokens := strings.Split(args[0], ":")
|
tokens := strings.Split(args[0], ":")
|
||||||
switch {
|
switch {
|
||||||
@ -295,15 +323,46 @@ func breakpoint(client service.Client, args ...string) error {
|
|||||||
return fmt.Errorf("invalid line reference")
|
return fmt.Errorf("invalid line reference")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.Symbols = append(requestedBp.Symbols, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedBp.Tracepoint = tracepoint
|
||||||
|
|
||||||
bp, err := client.CreateBreakpoint(requestedBp)
|
bp, err := client.CreateBreakpoint(requestedBp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Breakpoint %d set at %#v for %s %s:%d\n", bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
|
thing := "Breakpoint"
|
||||||
|
if tracepoint {
|
||||||
|
thing = "Tracepoint"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s %d set at %#v for %s %s:%d\n", thing, bp.ID, bp.Addr, bp.FunctionName, bp.File, bp.Line)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func breakpoint(client service.Client, args ...string) error {
|
||||||
|
return breakpointIntl(client, false, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tracepoint(client service.Client, args ...string) error {
|
||||||
|
return breakpointIntl(client, true, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func printVar(client service.Client, args ...string) error {
|
func printVar(client service.Client, args ...string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("not enough arguments")
|
return fmt.Errorf("not enough arguments")
|
||||||
@ -441,14 +500,18 @@ func stackCommand(client service.Client, args ...string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
printStack(stack, "")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStack(stack []api.Location, ind string) {
|
||||||
for i := range stack {
|
for i := range stack {
|
||||||
name := "(nil)"
|
name := "(nil)"
|
||||||
if stack[i].Function != nil {
|
if stack[i].Function != nil {
|
||||||
name = stack[i].Function.Name
|
name = stack[i].Function.Name
|
||||||
}
|
}
|
||||||
fmt.Printf("%d. %s\n\t%s:%d (%#v)\n", i, name, stack[i].File, stack[i].Line, stack[i].PC)
|
fmt.Printf("%s%d. %s %s:%d (%#v)\n", ind, i, name, stack[i].File, stack[i].Line, stack[i].PC)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printcontext(state *api.DebuggerState) error {
|
func printcontext(state *api.DebuggerState) error {
|
||||||
@ -471,6 +534,29 @@ func printcontext(state *api.DebuggerState) error {
|
|||||||
}
|
}
|
||||||
fmt.Printf("current loc: %s %s:%d\n", fn, 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 {
|
||||||
|
bpi := state.BreakpointInfo
|
||||||
|
|
||||||
|
if bpi.Goroutine != nil {
|
||||||
|
fmt.Printf("\tGoroutine %s\n", formatGoroutine(bpi.Goroutine))
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := make([]string, len(bpi.Variables))
|
||||||
|
for i, v := range bpi.Variables {
|
||||||
|
ss[i] = fmt.Sprintf("%s: <%v>", v.Name, v.Value)
|
||||||
|
}
|
||||||
|
fmt.Printf("\t%s\n", strings.Join(ss, ", "))
|
||||||
|
|
||||||
|
if bpi.Stacktrace != nil {
|
||||||
|
fmt.Printf("\tStack:\n")
|
||||||
|
printStack(bpi.Stacktrace, "\t\t")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.Breakpoint != nil && state.Breakpoint.Tracepoint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
file, err := os.Open(state.CurrentThread.File)
|
file, err := os.Open(state.CurrentThread.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
Loading…
Reference in New Issue
Block a user