
Change the examinemem command to have a new format 'raw' that just prints the raw memory bytes. Change the transcript command to add a new flag that disables prompt echo to the output file. Fixes #3706
1149 lines
30 KiB
Go
1149 lines
30 KiB
Go
package rpc2
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/go-delve/delve/pkg/dwarf/op"
|
|
"github.com/go-delve/delve/pkg/proc"
|
|
"github.com/go-delve/delve/service"
|
|
"github.com/go-delve/delve/service/api"
|
|
"github.com/go-delve/delve/service/debugger"
|
|
)
|
|
|
|
type RPCServer struct {
|
|
// config is all the information necessary to start the debugger and server.
|
|
config *service.Config
|
|
// debugger is a debugger service.
|
|
debugger *debugger.Debugger
|
|
}
|
|
|
|
func NewServer(config *service.Config, debugger *debugger.Debugger) *RPCServer {
|
|
return &RPCServer{config, debugger}
|
|
}
|
|
|
|
type ProcessPidIn struct {
|
|
}
|
|
|
|
type ProcessPidOut struct {
|
|
Pid int
|
|
}
|
|
|
|
// ProcessPid returns the pid of the process we are debugging.
|
|
func (s *RPCServer) ProcessPid(arg ProcessPidIn, out *ProcessPidOut) error {
|
|
out.Pid = s.debugger.ProcessPid()
|
|
return nil
|
|
}
|
|
|
|
type LastModifiedIn struct {
|
|
}
|
|
|
|
type LastModifiedOut struct {
|
|
Time time.Time
|
|
}
|
|
|
|
func (s *RPCServer) LastModified(arg LastModifiedIn, out *LastModifiedOut) error {
|
|
out.Time = s.debugger.LastModified()
|
|
return nil
|
|
}
|
|
|
|
type DetachIn struct {
|
|
Kill bool
|
|
}
|
|
|
|
type DetachOut struct {
|
|
}
|
|
|
|
// Detach detaches the debugger, optionally killing the process.
|
|
func (s *RPCServer) Detach(arg DetachIn, out *DetachOut) error {
|
|
return s.debugger.Detach(arg.Kill)
|
|
}
|
|
|
|
type RestartIn struct {
|
|
// Position to restart from, if it starts with 'c' it's a checkpoint ID,
|
|
// otherwise it's an event number. Only valid for recorded targets.
|
|
Position string
|
|
|
|
// ResetArgs tell whether NewArgs and NewRedirects should take effect.
|
|
ResetArgs bool
|
|
// NewArgs are arguments to launch a new process. They replace only the
|
|
// argv[1] and later. Argv[0] cannot be changed.
|
|
NewArgs []string
|
|
|
|
// When Rerecord is set the target will be rerecorded
|
|
Rerecord bool
|
|
|
|
// When Rebuild is set the process will be build again
|
|
Rebuild bool
|
|
|
|
NewRedirects [3]string
|
|
}
|
|
|
|
type RestartOut struct {
|
|
DiscardedBreakpoints []api.DiscardedBreakpoint
|
|
}
|
|
|
|
// Restart restarts program.
|
|
func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) {
|
|
close(cb.SetupDoneChan())
|
|
if s.config.Debugger.AttachPid != 0 {
|
|
cb.Return(nil, errors.New("cannot restart process Delve did not create"))
|
|
return
|
|
}
|
|
var out RestartOut
|
|
var err error
|
|
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.NewRedirects, arg.Rebuild)
|
|
cb.Return(out, err)
|
|
}
|
|
|
|
type StateIn struct {
|
|
// If NonBlocking is true State will return immediately even if the target process is running.
|
|
NonBlocking bool
|
|
}
|
|
|
|
type StateOut struct {
|
|
State *api.DebuggerState
|
|
}
|
|
|
|
// State returns the current debugger state.
|
|
func (s *RPCServer) State(arg StateIn, cb service.RPCCallback) {
|
|
close(cb.SetupDoneChan())
|
|
var out StateOut
|
|
st, err := s.debugger.State(arg.NonBlocking)
|
|
if err != nil {
|
|
cb.Return(nil, err)
|
|
return
|
|
}
|
|
out.State = st
|
|
cb.Return(out, nil)
|
|
}
|
|
|
|
type CommandOut struct {
|
|
State api.DebuggerState
|
|
}
|
|
|
|
// Command interrupts, continues and steps through the program.
|
|
func (s *RPCServer) Command(command api.DebuggerCommand, cb service.RPCCallback) {
|
|
st, err := s.debugger.Command(&command, cb.SetupDoneChan(), cb.DisconnectChan())
|
|
if err != nil {
|
|
cb.Return(nil, err)
|
|
return
|
|
}
|
|
var out CommandOut
|
|
out.State = *st
|
|
cb.Return(out, nil)
|
|
}
|
|
|
|
type GetBufferedTracepointsIn struct {
|
|
}
|
|
|
|
type GetBufferedTracepointsOut struct {
|
|
TracepointResults []api.TracepointResult
|
|
}
|
|
|
|
func (s *RPCServer) GetBufferedTracepoints(arg GetBufferedTracepointsIn, out *GetBufferedTracepointsOut) error {
|
|
out.TracepointResults = s.debugger.GetBufferedTracepoints()
|
|
return nil
|
|
}
|
|
|
|
type GetBreakpointIn struct {
|
|
Id int
|
|
Name string
|
|
}
|
|
|
|
type GetBreakpointOut struct {
|
|
Breakpoint api.Breakpoint
|
|
}
|
|
|
|
// GetBreakpoint gets a breakpoint by Name (if Name is not an empty string) or by ID.
|
|
func (s *RPCServer) GetBreakpoint(arg GetBreakpointIn, out *GetBreakpointOut) error {
|
|
var bp *api.Breakpoint
|
|
if arg.Name != "" {
|
|
bp = s.debugger.FindBreakpointByName(arg.Name)
|
|
if bp == nil {
|
|
return fmt.Errorf("no breakpoint with name %s", arg.Name)
|
|
}
|
|
} else {
|
|
bp = s.debugger.FindBreakpoint(arg.Id)
|
|
if bp == nil {
|
|
return fmt.Errorf("no breakpoint with id %d", arg.Id)
|
|
}
|
|
}
|
|
out.Breakpoint = *bp
|
|
return nil
|
|
}
|
|
|
|
type StacktraceIn struct {
|
|
Id int64
|
|
Depth int
|
|
Full bool
|
|
Defers bool // read deferred functions (equivalent to passing StacktraceReadDefers in Opts)
|
|
Opts api.StacktraceOptions
|
|
Cfg *api.LoadConfig
|
|
}
|
|
|
|
type StacktraceOut struct {
|
|
Locations []api.Stackframe
|
|
}
|
|
|
|
// Stacktrace returns stacktrace of goroutine Id up to the specified Depth.
|
|
//
|
|
// If Full is set it will also the variable of all local variables
|
|
// and function arguments of all stack frames.
|
|
func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error {
|
|
cfg := arg.Cfg
|
|
if cfg == nil && arg.Full {
|
|
cfg = &api.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
|
|
}
|
|
if arg.Defers {
|
|
arg.Opts |= api.StacktraceReadDefers
|
|
}
|
|
var err error
|
|
rawlocs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Locations, err = s.debugger.ConvertStacktrace(rawlocs, api.LoadConfigToProc(cfg))
|
|
return err
|
|
}
|
|
|
|
type AncestorsIn struct {
|
|
GoroutineID int64
|
|
NumAncestors int
|
|
Depth int
|
|
}
|
|
|
|
type AncestorsOut struct {
|
|
Ancestors []api.Ancestor
|
|
}
|
|
|
|
// Ancestors returns the stacktraces for the ancestors of a goroutine.
|
|
func (s *RPCServer) Ancestors(arg AncestorsIn, out *AncestorsOut) error {
|
|
var err error
|
|
out.Ancestors, err = s.debugger.Ancestors(arg.GoroutineID, arg.NumAncestors, arg.Depth)
|
|
return err
|
|
}
|
|
|
|
type ListBreakpointsIn struct {
|
|
All bool
|
|
}
|
|
|
|
type ListBreakpointsOut struct {
|
|
Breakpoints []*api.Breakpoint
|
|
}
|
|
|
|
// ListBreakpoints gets all breakpoints.
|
|
func (s *RPCServer) ListBreakpoints(arg ListBreakpointsIn, out *ListBreakpointsOut) error {
|
|
out.Breakpoints = s.debugger.Breakpoints(arg.All)
|
|
return nil
|
|
}
|
|
|
|
type CreateBreakpointIn struct {
|
|
Breakpoint api.Breakpoint
|
|
|
|
LocExpr string
|
|
SubstitutePathRules [][2]string
|
|
Suspended bool
|
|
}
|
|
|
|
type CreateBreakpointOut struct {
|
|
Breakpoint api.Breakpoint
|
|
}
|
|
|
|
// CreateBreakpoint creates a new breakpoint. The client is expected to populate `CreateBreakpointIn`
|
|
// with an `api.Breakpoint` struct describing where to set the breakpoint. For more information on
|
|
// how to properly request a breakpoint via the `api.Breakpoint` struct see the documentation for
|
|
// `debugger.CreateBreakpoint` here: https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint.
|
|
func (s *RPCServer) CreateBreakpoint(arg CreateBreakpointIn, out *CreateBreakpointOut) error {
|
|
if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil {
|
|
return err
|
|
}
|
|
createdbp, err := s.debugger.CreateBreakpoint(&arg.Breakpoint, arg.LocExpr, arg.SubstitutePathRules, arg.Suspended)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Breakpoint = *createdbp
|
|
return nil
|
|
}
|
|
|
|
type CreateEBPFTracepointIn struct {
|
|
FunctionName string
|
|
}
|
|
|
|
type CreateEBPFTracepointOut struct {
|
|
Breakpoint api.Breakpoint
|
|
}
|
|
|
|
func (s *RPCServer) CreateEBPFTracepoint(arg CreateEBPFTracepointIn, out *CreateEBPFTracepointOut) error {
|
|
return s.debugger.CreateEBPFTracepoint(arg.FunctionName)
|
|
}
|
|
|
|
type ClearBreakpointIn struct {
|
|
Id int
|
|
Name string
|
|
}
|
|
|
|
type ClearBreakpointOut struct {
|
|
Breakpoint *api.Breakpoint
|
|
}
|
|
|
|
// ClearBreakpoint deletes a breakpoint by Name (if Name is not an
|
|
// empty string) or by ID.
|
|
func (s *RPCServer) ClearBreakpoint(arg ClearBreakpointIn, out *ClearBreakpointOut) error {
|
|
var bp *api.Breakpoint
|
|
if arg.Name != "" {
|
|
bp = s.debugger.FindBreakpointByName(arg.Name)
|
|
if bp == nil {
|
|
return fmt.Errorf("no breakpoint with name %s", arg.Name)
|
|
}
|
|
} else {
|
|
bp = s.debugger.FindBreakpoint(arg.Id)
|
|
if bp == nil {
|
|
return fmt.Errorf("no breakpoint with id %d", arg.Id)
|
|
}
|
|
}
|
|
deleted, err := s.debugger.ClearBreakpoint(bp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Breakpoint = deleted
|
|
return nil
|
|
}
|
|
|
|
type ToggleBreakpointIn struct {
|
|
Id int
|
|
Name string
|
|
}
|
|
|
|
type ToggleBreakpointOut struct {
|
|
Breakpoint *api.Breakpoint
|
|
}
|
|
|
|
// ToggleBreakpoint toggles on or off a breakpoint by Name (if Name is not an
|
|
// empty string) or by ID.
|
|
func (s *RPCServer) ToggleBreakpoint(arg ToggleBreakpointIn, out *ToggleBreakpointOut) error {
|
|
var bp *api.Breakpoint
|
|
if arg.Name != "" {
|
|
bp = s.debugger.FindBreakpointByName(arg.Name)
|
|
if bp == nil {
|
|
return fmt.Errorf("no breakpoint with name %s", arg.Name)
|
|
}
|
|
} else {
|
|
bp = s.debugger.FindBreakpoint(arg.Id)
|
|
if bp == nil {
|
|
return fmt.Errorf("no breakpoint with id %d", arg.Id)
|
|
}
|
|
}
|
|
bp.Disabled = !bp.Disabled
|
|
if err := api.ValidBreakpointName(bp.Name); err != nil {
|
|
return err
|
|
}
|
|
if err := s.debugger.AmendBreakpoint(bp); err != nil {
|
|
return err
|
|
}
|
|
out.Breakpoint = bp
|
|
return nil
|
|
}
|
|
|
|
type AmendBreakpointIn struct {
|
|
Breakpoint api.Breakpoint
|
|
}
|
|
|
|
type AmendBreakpointOut struct {
|
|
}
|
|
|
|
// AmendBreakpoint 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.
|
|
//
|
|
// arg.Breakpoint.ID must be a valid breakpoint ID
|
|
func (s *RPCServer) AmendBreakpoint(arg AmendBreakpointIn, out *AmendBreakpointOut) error {
|
|
if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil {
|
|
return err
|
|
}
|
|
return s.debugger.AmendBreakpoint(&arg.Breakpoint)
|
|
}
|
|
|
|
type CancelNextIn struct {
|
|
}
|
|
|
|
type CancelNextOut struct {
|
|
}
|
|
|
|
func (s *RPCServer) CancelNext(arg CancelNextIn, out *CancelNextOut) error {
|
|
return s.debugger.CancelNext()
|
|
}
|
|
|
|
type ListThreadsIn struct {
|
|
}
|
|
|
|
type ListThreadsOut struct {
|
|
Threads []*api.Thread
|
|
}
|
|
|
|
// ListThreads lists all threads.
|
|
func (s *RPCServer) ListThreads(arg ListThreadsIn, out *ListThreadsOut) (err error) {
|
|
threads, err := s.debugger.Threads()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.debugger.LockTarget()
|
|
defer s.debugger.UnlockTarget()
|
|
out.Threads = api.ConvertThreads(threads, s.debugger.ConvertThreadBreakpoint)
|
|
return nil
|
|
}
|
|
|
|
type GetThreadIn struct {
|
|
Id int
|
|
}
|
|
|
|
type GetThreadOut struct {
|
|
Thread *api.Thread
|
|
}
|
|
|
|
// GetThread gets a thread by its ID.
|
|
func (s *RPCServer) GetThread(arg GetThreadIn, out *GetThreadOut) error {
|
|
t, err := s.debugger.FindThread(arg.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if t == nil {
|
|
return fmt.Errorf("no thread with id %d", arg.Id)
|
|
}
|
|
s.debugger.LockTarget()
|
|
defer s.debugger.UnlockTarget()
|
|
out.Thread = api.ConvertThread(t, s.debugger.ConvertThreadBreakpoint(t))
|
|
return nil
|
|
}
|
|
|
|
type ListPackageVarsIn struct {
|
|
Filter string
|
|
Cfg api.LoadConfig
|
|
}
|
|
|
|
type ListPackageVarsOut struct {
|
|
Variables []api.Variable
|
|
}
|
|
|
|
// ListPackageVars lists all package variables in the context of the current thread.
|
|
func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsOut) error {
|
|
vars, err := s.debugger.PackageVariables(arg.Filter, *api.LoadConfigToProc(&arg.Cfg))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Variables = api.ConvertVars(vars)
|
|
return nil
|
|
}
|
|
|
|
type ListRegistersIn struct {
|
|
ThreadID int
|
|
IncludeFp bool
|
|
Scope *api.EvalScope
|
|
}
|
|
|
|
type ListRegistersOut struct {
|
|
Registers string
|
|
Regs api.Registers
|
|
}
|
|
|
|
// ListRegisters lists registers and their values.
|
|
// If ListRegistersIn.Scope is not nil the registers of that eval scope will
|
|
// be returned, otherwise ListRegistersIn.ThreadID will be used.
|
|
func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) error {
|
|
if arg.ThreadID == 0 && arg.Scope == nil {
|
|
state, err := s.debugger.State(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
arg.ThreadID = state.CurrentThread.ID
|
|
}
|
|
|
|
var regs *op.DwarfRegisters
|
|
var err error
|
|
|
|
if arg.Scope != nil {
|
|
regs, err = s.debugger.ScopeRegisters(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall)
|
|
} else {
|
|
regs, err = s.debugger.ThreadRegisters(arg.ThreadID)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Regs = api.ConvertRegisters(regs, s.debugger.DwarfRegisterToString, arg.IncludeFp)
|
|
out.Registers = out.Regs.String()
|
|
|
|
return nil
|
|
}
|
|
|
|
type ListLocalVarsIn struct {
|
|
Scope api.EvalScope
|
|
Cfg api.LoadConfig
|
|
}
|
|
|
|
type ListLocalVarsOut struct {
|
|
Variables []api.Variable
|
|
}
|
|
|
|
// ListLocalVars lists all local variables in scope.
|
|
func (s *RPCServer) ListLocalVars(arg ListLocalVarsIn, out *ListLocalVarsOut) error {
|
|
vars, err := s.debugger.LocalVariables(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, *api.LoadConfigToProc(&arg.Cfg))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Variables = api.ConvertVars(vars)
|
|
return nil
|
|
}
|
|
|
|
type ListFunctionArgsIn struct {
|
|
Scope api.EvalScope
|
|
Cfg api.LoadConfig
|
|
}
|
|
|
|
type ListFunctionArgsOut struct {
|
|
Args []api.Variable
|
|
}
|
|
|
|
// ListFunctionArgs lists all arguments to the current function
|
|
func (s *RPCServer) ListFunctionArgs(arg ListFunctionArgsIn, out *ListFunctionArgsOut) error {
|
|
vars, err := s.debugger.FunctionArguments(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, *api.LoadConfigToProc(&arg.Cfg))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Args = api.ConvertVars(vars)
|
|
return nil
|
|
}
|
|
|
|
type EvalIn struct {
|
|
Scope api.EvalScope
|
|
Expr string
|
|
Cfg *api.LoadConfig
|
|
}
|
|
|
|
type EvalOut struct {
|
|
Variable *api.Variable
|
|
}
|
|
|
|
// Eval returns a variable in the specified context.
|
|
//
|
|
// See https://github.com/go-delve/delve/blob/master/Documentation/cli/expr.md
|
|
// for a description of acceptable values of arg.Expr.
|
|
func (s *RPCServer) Eval(arg EvalIn, out *EvalOut) error {
|
|
cfg := arg.Cfg
|
|
if cfg == nil {
|
|
cfg = &api.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
|
|
}
|
|
pcfg := *api.LoadConfigToProc(cfg)
|
|
v, err := s.debugger.EvalVariableInScope(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Expr, pcfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Variable = api.ConvertVar(v)
|
|
return nil
|
|
}
|
|
|
|
type SetIn struct {
|
|
Scope api.EvalScope
|
|
Symbol string
|
|
Value string
|
|
}
|
|
|
|
type SetOut struct {
|
|
}
|
|
|
|
// Set sets the value of a variable. Only numerical types and
|
|
// pointers are currently supported.
|
|
func (s *RPCServer) Set(arg SetIn, out *SetOut) error {
|
|
return s.debugger.SetVariableInScope(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Symbol, arg.Value)
|
|
}
|
|
|
|
type ListSourcesIn struct {
|
|
Filter string
|
|
}
|
|
|
|
type ListSourcesOut struct {
|
|
Sources []string
|
|
}
|
|
|
|
// ListSources lists all source files in the process matching filter.
|
|
func (s *RPCServer) ListSources(arg ListSourcesIn, out *ListSourcesOut) error {
|
|
ss, err := s.debugger.Sources(arg.Filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Sources = ss
|
|
return nil
|
|
}
|
|
|
|
type ListFunctionsIn struct {
|
|
Filter string
|
|
FollowCalls int
|
|
}
|
|
|
|
type ListFunctionsOut struct {
|
|
Funcs []string
|
|
}
|
|
|
|
// ListFunctions lists all functions in the process matching filter.
|
|
func (s *RPCServer) ListFunctions(arg ListFunctionsIn, out *ListFunctionsOut) error {
|
|
fns, err := s.debugger.Functions(arg.Filter, arg.FollowCalls)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Funcs = fns
|
|
return nil
|
|
}
|
|
|
|
type ListTypesIn struct {
|
|
Filter string
|
|
}
|
|
|
|
type ListTypesOut struct {
|
|
Types []string
|
|
}
|
|
|
|
// ListTypes lists all types in the process matching filter.
|
|
func (s *RPCServer) ListTypes(arg ListTypesIn, out *ListTypesOut) error {
|
|
tps, err := s.debugger.Types(arg.Filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Types = tps
|
|
return nil
|
|
}
|
|
|
|
type ListGoroutinesIn struct {
|
|
Start int
|
|
Count int
|
|
|
|
Filters []api.ListGoroutinesFilter
|
|
api.GoroutineGroupingOptions
|
|
|
|
EvalScope *api.EvalScope
|
|
}
|
|
|
|
type ListGoroutinesOut struct {
|
|
Goroutines []*api.Goroutine
|
|
Nextg int
|
|
Groups []api.GoroutineGroup
|
|
TooManyGroups bool
|
|
}
|
|
|
|
// ListGoroutines lists all goroutines.
|
|
// If Count is specified ListGoroutines will return at the first Count
|
|
// goroutines and an index in Nextg, that can be passed as the Start
|
|
// parameter, to get more goroutines from ListGoroutines.
|
|
// Passing a value of Start that wasn't returned by ListGoroutines will skip
|
|
// an undefined number of goroutines.
|
|
//
|
|
// If arg.Filters are specified the list of returned goroutines is filtered
|
|
// applying the specified filters.
|
|
// For example:
|
|
//
|
|
// ListGoroutinesFilter{ Kind: ListGoroutinesFilterUserLoc, Negated: false, Arg: "afile.go" }
|
|
//
|
|
// will only return goroutines whose UserLoc contains "afile.go" as a substring.
|
|
// More specifically a goroutine matches a location filter if the specified
|
|
// location, formatted like this:
|
|
//
|
|
// filename:lineno in function
|
|
//
|
|
// contains Arg[0] as a substring.
|
|
//
|
|
// Filters can also be applied to goroutine labels:
|
|
//
|
|
// ListGoroutineFilter{ Kind: ListGoroutinesFilterLabel, Negated: false, Arg: "key=value" }
|
|
//
|
|
// this filter will only return goroutines that have a key=value label.
|
|
//
|
|
// If arg.GroupBy is not GoroutineFieldNone then the goroutines will
|
|
// be grouped with the specified criterion.
|
|
// If the value of arg.GroupBy is GoroutineLabel goroutines will
|
|
// be grouped by the value of the label with key GroupByKey.
|
|
// For each group a maximum of MaxGroupMembers example goroutines are
|
|
// returned, as well as the total number of goroutines in the group.
|
|
func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut) error {
|
|
//TODO(aarzilli): if arg contains a running goroutines filter (not negated)
|
|
// and start == 0 and count == 0 then we can optimize this by just looking
|
|
// at threads directly.
|
|
|
|
var gs []*proc.G
|
|
var nextg int
|
|
var err error
|
|
var gsLoaded bool
|
|
|
|
for _, filter := range arg.Filters {
|
|
if filter.Kind == api.GoroutineWaitingOnChannel {
|
|
if filter.Negated {
|
|
return errors.New("channel filter can not be negated")
|
|
}
|
|
if arg.Count == 0 {
|
|
return errors.New("count == 0 not allowed with a channel filter")
|
|
}
|
|
if arg.EvalScope == nil {
|
|
return errors.New("channel filter without eval scope")
|
|
}
|
|
gs, err = s.debugger.ChanGoroutines(arg.EvalScope.GoroutineID, arg.EvalScope.Frame, arg.EvalScope.DeferredCall, filter.Arg, arg.Start, arg.Count)
|
|
if len(gs) == arg.Count {
|
|
nextg = arg.Start + len(gs)
|
|
} else {
|
|
nextg = -1
|
|
}
|
|
gsLoaded = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !gsLoaded {
|
|
gs, nextg, err = s.debugger.Goroutines(arg.Start, arg.Count)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gs = s.debugger.FilterGoroutines(gs, arg.Filters)
|
|
gs, out.Groups, out.TooManyGroups = s.debugger.GroupGoroutines(gs, &arg.GoroutineGroupingOptions)
|
|
s.debugger.LockTarget()
|
|
defer s.debugger.UnlockTarget()
|
|
out.Goroutines = api.ConvertGoroutines(s.debugger.Target(), gs)
|
|
out.Nextg = nextg
|
|
return nil
|
|
}
|
|
|
|
type AttachedToExistingProcessIn struct {
|
|
}
|
|
|
|
type AttachedToExistingProcessOut struct {
|
|
Answer bool
|
|
}
|
|
|
|
// AttachedToExistingProcess returns whether we attached to a running process or not
|
|
func (s *RPCServer) AttachedToExistingProcess(arg AttachedToExistingProcessIn, out *AttachedToExistingProcessOut) error {
|
|
if s.config.Debugger.AttachPid != 0 {
|
|
out.Answer = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type FindLocationIn struct {
|
|
Scope api.EvalScope
|
|
Loc string
|
|
IncludeNonExecutableLines bool
|
|
|
|
// SubstitutePathRules is a slice of source code path substitution rules,
|
|
// the first entry of each pair is the path of a directory as it appears in
|
|
// the executable file (i.e. the location of a source file when the program
|
|
// was compiled), the second entry of each pair is the location of the same
|
|
// directory on the client system.
|
|
SubstitutePathRules [][2]string
|
|
}
|
|
|
|
type FindLocationOut struct {
|
|
Locations []api.Location
|
|
SubstituteLocExpr string // if this isn't an empty string it should be passed as the location expression for CreateBreakpoint instead of the original location expression
|
|
}
|
|
|
|
// FindLocation returns concrete location information described by a location expression.
|
|
//
|
|
// loc ::= <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
|
|
//
|
|
// NOTE: this function does not actually set breakpoints.
|
|
func (s *RPCServer) FindLocation(arg FindLocationIn, out *FindLocationOut) error {
|
|
var err error
|
|
out.Locations, out.SubstituteLocExpr, err = s.debugger.FindLocation(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Loc, arg.IncludeNonExecutableLines, arg.SubstitutePathRules)
|
|
return err
|
|
}
|
|
|
|
type DisassembleIn struct {
|
|
Scope api.EvalScope
|
|
StartPC, EndPC uint64
|
|
Flavour api.AssemblyFlavour
|
|
}
|
|
|
|
type DisassembleOut struct {
|
|
Disassemble api.AsmInstructions
|
|
}
|
|
|
|
// Disassemble code.
|
|
//
|
|
// If both StartPC and EndPC are non-zero the specified range will be disassembled, otherwise the function containing StartPC will be disassembled.
|
|
//
|
|
// Scope is used to mark the instruction the specified goroutine is stopped at.
|
|
//
|
|
// Disassemble will also try to calculate the destination address of an absolute indirect CALL if it happens to be the instruction the selected goroutine is stopped at.
|
|
func (s *RPCServer) Disassemble(arg DisassembleIn, out *DisassembleOut) error {
|
|
var err error
|
|
insts, err := s.debugger.Disassemble(arg.Scope.GoroutineID, arg.StartPC, arg.EndPC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Disassemble = make(api.AsmInstructions, len(insts))
|
|
for i := range insts {
|
|
out.Disassemble[i] = api.ConvertAsmInstruction(insts[i], s.debugger.AsmInstructionText(&insts[i], proc.AssemblyFlavour(arg.Flavour)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RecordedIn struct {
|
|
}
|
|
|
|
type RecordedOut struct {
|
|
Recorded bool
|
|
TraceDirectory string
|
|
}
|
|
|
|
func (s *RPCServer) Recorded(arg RecordedIn, out *RecordedOut) error {
|
|
out.Recorded, out.TraceDirectory = s.debugger.Recorded()
|
|
return nil
|
|
}
|
|
|
|
type CheckpointIn struct {
|
|
Where string
|
|
}
|
|
|
|
type CheckpointOut struct {
|
|
ID int
|
|
}
|
|
|
|
func (s *RPCServer) Checkpoint(arg CheckpointIn, out *CheckpointOut) error {
|
|
var err error
|
|
out.ID, err = s.debugger.Checkpoint(arg.Where)
|
|
return err
|
|
}
|
|
|
|
type ListCheckpointsIn struct {
|
|
}
|
|
|
|
type ListCheckpointsOut struct {
|
|
Checkpoints []api.Checkpoint
|
|
}
|
|
|
|
func (s *RPCServer) ListCheckpoints(arg ListCheckpointsIn, out *ListCheckpointsOut) error {
|
|
var err error
|
|
cps, err := s.debugger.Checkpoints()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Checkpoints = make([]api.Checkpoint, len(cps))
|
|
for i := range cps {
|
|
out.Checkpoints[i] = api.Checkpoint(cps[i])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ClearCheckpointIn struct {
|
|
ID int
|
|
}
|
|
|
|
type ClearCheckpointOut struct {
|
|
}
|
|
|
|
func (s *RPCServer) ClearCheckpoint(arg ClearCheckpointIn, out *ClearCheckpointOut) error {
|
|
return s.debugger.ClearCheckpoint(arg.ID)
|
|
}
|
|
|
|
type IsMulticlientIn struct {
|
|
}
|
|
|
|
type IsMulticlientOut struct {
|
|
// IsMulticlient returns true if the headless instance was started with --accept-multiclient
|
|
IsMulticlient bool
|
|
}
|
|
|
|
func (s *RPCServer) IsMulticlient(arg IsMulticlientIn, out *IsMulticlientOut) error {
|
|
*out = IsMulticlientOut{
|
|
IsMulticlient: s.config.AcceptMulti,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FunctionReturnLocationsIn holds arguments for the
|
|
// FunctionReturnLocationsRPC call. It holds the name of
|
|
// the function for which all return locations should be
|
|
// given.
|
|
type FunctionReturnLocationsIn struct {
|
|
// FnName is the name of the function for which all
|
|
// return locations should be given.
|
|
FnName string
|
|
}
|
|
|
|
// FunctionReturnLocationsOut holds the result of the FunctionReturnLocations
|
|
// RPC call. It provides the list of addresses that the given function returns,
|
|
// for example with a `RET` instruction or `CALL runtime.deferreturn`.
|
|
type FunctionReturnLocationsOut struct {
|
|
// Addrs is the list of all locations where the given function returns.
|
|
Addrs []uint64
|
|
}
|
|
|
|
// FunctionReturnLocations is the implements the client call of the same name. Look at client documentation for more information.
|
|
func (s *RPCServer) FunctionReturnLocations(in FunctionReturnLocationsIn, out *FunctionReturnLocationsOut) error {
|
|
addrs, err := s.debugger.FunctionReturnLocations(in.FnName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*out = FunctionReturnLocationsOut{
|
|
Addrs: addrs,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListDynamicLibrariesIn holds the arguments of ListDynamicLibraries
|
|
type ListDynamicLibrariesIn struct {
|
|
}
|
|
|
|
// ListDynamicLibrariesOut holds the return values of ListDynamicLibraries
|
|
type ListDynamicLibrariesOut struct {
|
|
List []api.Image
|
|
}
|
|
|
|
func (s *RPCServer) ListDynamicLibraries(in ListDynamicLibrariesIn, out *ListDynamicLibrariesOut) error {
|
|
imgs := s.debugger.ListDynamicLibraries()
|
|
out.List = make([]api.Image, 0, len(imgs))
|
|
for i := range imgs {
|
|
out.List = append(out.List, api.ConvertImage(imgs[i]))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListPackagesBuildInfoIn holds the arguments of ListPackagesBuildInfo.
|
|
type ListPackagesBuildInfoIn struct {
|
|
IncludeFiles bool
|
|
Filter string // if not empty, returns only packages matching the regexp.
|
|
}
|
|
|
|
// ListPackagesBuildInfoOut holds the return values of ListPackagesBuildInfo.
|
|
type ListPackagesBuildInfoOut struct {
|
|
List []api.PackageBuildInfo
|
|
}
|
|
|
|
// ListPackagesBuildInfo returns the list of packages used by the program along with
|
|
// the directory where each package was compiled and optionally the list of
|
|
// files constituting the package.
|
|
// Note that the directory path is a best guess and may be wrong is a tool
|
|
// other than cmd/go is used to perform the build.
|
|
func (s *RPCServer) ListPackagesBuildInfo(in ListPackagesBuildInfoIn, out *ListPackagesBuildInfoOut) error {
|
|
var pattern *regexp.Regexp
|
|
if in.Filter != "" {
|
|
p, err := regexp.Compile(in.Filter)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid Filter pattern: %v", err)
|
|
}
|
|
pattern = p
|
|
}
|
|
pkgs := s.debugger.ListPackagesBuildInfo(in.IncludeFiles)
|
|
out.List = make([]api.PackageBuildInfo, 0, len(pkgs))
|
|
for _, pkg := range pkgs {
|
|
if pattern != nil && !pattern.MatchString(pkg.ImportPath) {
|
|
continue
|
|
}
|
|
var files []string
|
|
|
|
if len(pkg.Files) > 0 {
|
|
files = make([]string, 0, len(pkg.Files))
|
|
for file := range pkg.Files {
|
|
files = append(files, file)
|
|
}
|
|
}
|
|
|
|
sort.Strings(files)
|
|
|
|
out.List = append(out.List, api.PackageBuildInfo{
|
|
ImportPath: pkg.ImportPath,
|
|
DirectoryPath: pkg.DirectoryPath,
|
|
Files: files,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExamineMemoryIn holds the arguments of ExamineMemory
|
|
type ExamineMemoryIn struct {
|
|
Address uint64
|
|
Length int
|
|
}
|
|
|
|
// ExaminedMemoryOut holds the return values of ExamineMemory
|
|
type ExaminedMemoryOut struct {
|
|
Mem []byte
|
|
IsLittleEndian bool
|
|
}
|
|
|
|
const ExamineMemoryLengthLimit = 1 << 16
|
|
|
|
func (s *RPCServer) ExamineMemory(arg ExamineMemoryIn, out *ExaminedMemoryOut) error {
|
|
if arg.Length > ExamineMemoryLengthLimit {
|
|
return errors.New("len must be less than or equal to 1000")
|
|
}
|
|
Mem, err := s.debugger.ExamineMemory(arg.Address, arg.Length)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
out.Mem = Mem
|
|
out.IsLittleEndian = true //TODO: get byte order from debugger.target.BinInfo().Arch
|
|
|
|
return nil
|
|
}
|
|
|
|
type StopRecordingIn struct {
|
|
}
|
|
|
|
type StopRecordingOut struct {
|
|
}
|
|
|
|
func (s *RPCServer) StopRecording(arg StopRecordingIn, cb service.RPCCallback) {
|
|
close(cb.SetupDoneChan())
|
|
var out StopRecordingOut
|
|
err := s.debugger.StopRecording()
|
|
if err != nil {
|
|
cb.Return(nil, err)
|
|
return
|
|
}
|
|
cb.Return(out, nil)
|
|
}
|
|
|
|
type DumpStartIn struct {
|
|
Destination string
|
|
}
|
|
|
|
type DumpStartOut struct {
|
|
State api.DumpState
|
|
}
|
|
|
|
// DumpStart starts a core dump to arg.Destination.
|
|
func (s *RPCServer) DumpStart(arg DumpStartIn, out *DumpStartOut) error {
|
|
err := s.debugger.DumpStart(arg.Destination)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.State = *api.ConvertDumpState(s.debugger.DumpWait(0))
|
|
return nil
|
|
}
|
|
|
|
type DumpWaitIn struct {
|
|
Wait int
|
|
}
|
|
|
|
type DumpWaitOut struct {
|
|
State api.DumpState
|
|
}
|
|
|
|
// DumpWait waits for the core dump to finish or for arg.Wait milliseconds.
|
|
// Wait == 0 means return immediately.
|
|
// Returns the core dump status
|
|
func (s *RPCServer) DumpWait(arg DumpWaitIn, out *DumpWaitOut) error {
|
|
out.State = *api.ConvertDumpState(s.debugger.DumpWait(time.Duration(arg.Wait) * time.Millisecond))
|
|
return nil
|
|
}
|
|
|
|
type DumpCancelIn struct {
|
|
}
|
|
|
|
type DumpCancelOut struct {
|
|
}
|
|
|
|
// DumpCancel cancels the core dump.
|
|
func (s *RPCServer) DumpCancel(arg DumpCancelIn, out *DumpCancelOut) error {
|
|
return s.debugger.DumpCancel()
|
|
}
|
|
|
|
type CreateWatchpointIn struct {
|
|
Scope api.EvalScope
|
|
Expr string
|
|
Type api.WatchType
|
|
}
|
|
|
|
type CreateWatchpointOut struct {
|
|
*api.Breakpoint
|
|
}
|
|
|
|
func (s *RPCServer) CreateWatchpoint(arg CreateWatchpointIn, out *CreateWatchpointOut) error {
|
|
var err error
|
|
out.Breakpoint, err = s.debugger.CreateWatchpoint(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Expr, arg.Type)
|
|
return err
|
|
}
|
|
|
|
type BuildIDIn struct {
|
|
}
|
|
|
|
type BuildIDOut struct {
|
|
BuildID string
|
|
}
|
|
|
|
func (s *RPCServer) BuildID(arg BuildIDIn, out *BuildIDOut) error {
|
|
out.BuildID = s.debugger.BuildID()
|
|
return nil
|
|
}
|
|
|
|
type ListTargetsIn struct {
|
|
}
|
|
|
|
type ListTargetsOut struct {
|
|
Targets []api.Target
|
|
}
|
|
|
|
// ListTargets returns the list of targets we are currently attached to.
|
|
func (s *RPCServer) ListTargets(arg ListTargetsIn, out *ListTargetsOut) error {
|
|
s.debugger.LockTarget()
|
|
defer s.debugger.UnlockTarget()
|
|
out.Targets = []api.Target{}
|
|
for _, tgt := range s.debugger.TargetGroup().Targets() {
|
|
if _, err := tgt.Valid(); err == nil {
|
|
out.Targets = append(out.Targets, *api.ConvertTarget(tgt, s.debugger.ConvertThreadBreakpoint))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type FollowExecIn struct {
|
|
Enable bool
|
|
Regex string
|
|
}
|
|
|
|
type FollowExecOut struct {
|
|
}
|
|
|
|
// FollowExec enables or disables follow exec mode.
|
|
func (s *RPCServer) FollowExec(arg FollowExecIn, out *FollowExecOut) error {
|
|
return s.debugger.FollowExec(arg.Enable, arg.Regex)
|
|
}
|
|
|
|
type FollowExecEnabledIn struct {
|
|
}
|
|
|
|
type FollowExecEnabledOut struct {
|
|
Enabled bool
|
|
}
|
|
|
|
// FollowExecEnabled returns true if follow exec mode is enabled.
|
|
func (s *RPCServer) FollowExecEnabled(arg FollowExecEnabledIn, out *FollowExecEnabledOut) error {
|
|
out.Enabled = s.debugger.FollowExecEnabled()
|
|
return nil
|
|
}
|
|
|
|
type DebugInfoDirectoriesIn struct {
|
|
Set bool
|
|
List []string
|
|
}
|
|
|
|
type DebugInfoDirectoriesOut struct {
|
|
List []string
|
|
}
|
|
|
|
func (s *RPCServer) DebugInfoDirectories(arg DebugInfoDirectoriesIn, out *DebugInfoDirectoriesOut) error {
|
|
if arg.Set {
|
|
s.debugger.SetDebugInfoDirectories(arg.List)
|
|
}
|
|
out.List = s.debugger.DebugInfoDirectories()
|
|
return nil
|
|
}
|