proc,terminal: allow setting suspended breakpoints (#3154)

Allows setting suspended breakpoints and try to enable them
automatically after every time a plugin is loaded.

Fixes #1653
Updates #2551
This commit is contained in:
Alessandro Arzilli 2022-10-04 17:07:05 +02:00 committed by GitHub
parent aa03666596
commit 6e7e1d8830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 223 additions and 53 deletions

@ -26,7 +26,7 @@ checkpoint(Where) | Equivalent to API call [Checkpoint](https://godoc.org/github
clear_breakpoint(Id, Name) | Equivalent to API call [ClearBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearBreakpoint) clear_breakpoint(Id, Name) | Equivalent to API call [ClearBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearBreakpoint)
clear_checkpoint(ID) | Equivalent to API call [ClearCheckpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearCheckpoint) clear_checkpoint(ID) | Equivalent to API call [ClearCheckpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearCheckpoint)
raw_command(Name, ThreadID, GoroutineID, ReturnInfoLoadConfig, Expr, UnsafeCall) | Equivalent to API call [Command](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Command) raw_command(Name, ThreadID, GoroutineID, ReturnInfoLoadConfig, Expr, UnsafeCall) | Equivalent to API call [Command](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Command)
create_breakpoint(Breakpoint, LocExpr, SubstitutePathRules) | Equivalent to API call [CreateBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateBreakpoint) create_breakpoint(Breakpoint, LocExpr, SubstitutePathRules, Suspended) | Equivalent to API call [CreateBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateBreakpoint)
create_ebpf_tracepoint(FunctionName) | Equivalent to API call [CreateEBPFTracepoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateEBPFTracepoint) create_ebpf_tracepoint(FunctionName) | Equivalent to API call [CreateEBPFTracepoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateEBPFTracepoint)
create_watchpoint(Scope, Expr, Type) | Equivalent to API call [CreateWatchpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateWatchpoint) create_watchpoint(Scope, Expr, Type) | Equivalent to API call [CreateWatchpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateWatchpoint)
detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach) detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach)

@ -270,6 +270,10 @@ func packageMatch(specPkg, symPkg string, packageMap map[string][]string) bool {
// Find will search all functions in the target program and filter them via the // Find will search all functions in the target program and filter them via the
// regex location spec. Only functions matching the regex will be returned. // regex location spec. Only functions matching the regex will be returned.
func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) { func (loc *RegexLocationSpec) Find(t *proc.Target, _ []string, scope *proc.EvalScope, locStr string, includeNonExecutableLines bool, _ [][2]string) ([]api.Location, error) {
if scope == nil {
//TODO(aarzilli): this needs only the list of function we should make it work
return nil, fmt.Errorf("could not determine location (scope is nil)")
}
funcs := scope.BinInfo.Functions funcs := scope.BinInfo.Functions
matches, err := regexFilterFuncs(loc.FuncRegex, funcs) matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil { if err != nil {
@ -390,7 +394,10 @@ func (loc *NormalLocationSpec) Find(t *proc.Target, processArgs []string, scope
candidateFuncs = loc.findFuncCandidates(t.BinInfo(), limit) candidateFuncs = loc.findFuncCandidates(t.BinInfo(), limit)
} }
if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 && scope != nil { if matching := len(candidateFiles) + len(candidateFuncs); matching == 0 {
if scope == nil {
return nil, fmt.Errorf("location \"%s\" not found", locStr)
}
// if no result was found this locations string could be an // if no result was found this locations string could be an
// expression that the user forgot to prefix with '*', try treating it as // expression that the user forgot to prefix with '*', try treating it as
// such. // such.

@ -392,6 +392,27 @@ func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error
return pc, nil return pc, nil
} }
func findRetPC(t *Target, name string) ([]uint64, error) {
fn := t.BinInfo().LookupFunc[name]
if fn == nil {
return nil, fmt.Errorf("could not find %s", name)
}
text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End)
if err != nil {
return nil, err
}
r := []uint64{}
for _, instr := range text {
if instr.IsRet() {
r = append(r, instr.Loc.PC)
}
}
if len(r) == 0 {
return nil, fmt.Errorf("could not find return instruction in %s", name)
}
return r, nil
}
// cpuArch is a stringer interface representing CPU architectures. // cpuArch is a stringer interface representing CPU architectures.
type cpuArch interface { type cpuArch interface {
String() string String() string

@ -131,6 +131,10 @@ const (
// adjust the watchpoint of stack variables. // adjust the watchpoint of stack variables.
StackResizeBreakpoint StackResizeBreakpoint
// PluginOpenBreakpoint is a breakpoint used to detect that a plugin has
// been loaded and we should try to enable suspended breakpoints.
PluginOpenBreakpoint
steppingMask = NextBreakpoint | NextDeferBreakpoint | StepBreakpoint steppingMask = NextBreakpoint | NextDeferBreakpoint | StepBreakpoint
) )
@ -204,6 +208,8 @@ func (bp *Breakpoint) VerboseDescr() []string {
r = append(r, fmt.Sprintf("WatchOutOfScope Cond=%q checkPanicCall=%v", exprToString(breaklet.Cond), breaklet.checkPanicCall)) r = append(r, fmt.Sprintf("WatchOutOfScope Cond=%q checkPanicCall=%v", exprToString(breaklet.Cond), breaklet.checkPanicCall))
case StackResizeBreakpoint: case StackResizeBreakpoint:
r = append(r, fmt.Sprintf("StackResizeBreakpoint Cond=%q", exprToString(breaklet.Cond))) r = append(r, fmt.Sprintf("StackResizeBreakpoint Cond=%q", exprToString(breaklet.Cond)))
case PluginOpenBreakpoint:
r = append(r, "PluginOpenBreakpoint")
default: default:
r = append(r, fmt.Sprintf("Unknown %d", breaklet.Kind)) r = append(r, fmt.Sprintf("Unknown %d", breaklet.Kind))
} }
@ -304,7 +310,7 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
} }
} }
case StackResizeBreakpoint: case StackResizeBreakpoint, PluginOpenBreakpoint:
// no further checks // no further checks
default: default:

@ -96,27 +96,15 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi
// Stack Resize Sentinel // Stack Resize Sentinel
fn := t.BinInfo().LookupFunc["runtime.copystack"] retpcs, err := findRetPC(t, "runtime.copystack")
if fn == nil {
return errors.New("could not find runtime.copystack")
}
text, err := Disassemble(t.Memory(), nil, t.Breakpoints(), t.BinInfo(), fn.Entry, fn.End)
if err != nil { if err != nil {
return err return err
} }
var retpc uint64 if len(retpcs) > 1 {
for _, instr := range text { return errors.New("runtime.copystack has too many return instructions")
if instr.IsRet() {
if retpc != 0 {
return errors.New("runtime.copystack has too many return instructions")
}
retpc = instr.Loc.PC
}
} }
if retpc == 0 {
return errors.New("could not find return instruction in runtime.copystack") rszbp, err := t.SetBreakpoint(0, retpcs[0], StackResizeBreakpoint, sameGCond)
}
rszbp, err := t.SetBreakpoint(0, retpc, StackResizeBreakpoint, sameGCond)
if err != nil { if err != nil {
return err return err
} }

@ -10,6 +10,7 @@ import (
"github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc/internal/ebpf" "github.com/go-delve/delve/pkg/proc/internal/ebpf"
) )
@ -213,6 +214,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
t.createUnrecoveredPanicBreakpoint() t.createUnrecoveredPanicBreakpoint()
t.createFatalThrowBreakpoint() t.createFatalThrowBreakpoint()
t.createPluginOpenBreakpoint()
t.gcache.init(p.BinInfo()) t.gcache.init(p.BinInfo())
t.fakeMemoryRegistryMap = make(map[string]*compositeMemory) t.fakeMemoryRegistryMap = make(map[string]*compositeMemory)
@ -426,6 +428,21 @@ func (t *Target) createFatalThrowBreakpoint() {
} }
} }
// createPluginOpenBreakpoint creates a breakpoint at the return instruction
// of plugin.Open (if it exists) that will try to enable suspended
// breakpoints.
func (t *Target) createPluginOpenBreakpoint() {
retpcs, _ := findRetPC(t, "plugin.Open")
for _, retpc := range retpcs {
bp, err := t.SetBreakpoint(0, retpc, PluginOpenBreakpoint, nil)
if err != nil {
t.BinInfo().logger.Errorf("could not set plugin.Open breakpoint: %v", err)
} else {
bp.Breaklets[len(bp.Breaklets)-1].callback = t.pluginOpenCallback
}
}
}
// CurrentThread returns the currently selected thread which will be used // CurrentThread returns the currently selected thread which will be used
// for next/step/stepout and for reading variables, unless a goroutine is // for next/step/stepout and for reading variables, unless a goroutine is
// selected. // selected.
@ -588,6 +605,30 @@ func (t *Target) dwrapUnwrap(fn *Function) *Function {
return fn return fn
} }
func (t *Target) pluginOpenCallback(Thread) bool {
logger := logflags.DebuggerLogger()
for _, lbp := range t.Breakpoints().Logical {
if isSuspended(t, lbp) {
err := enableBreakpointOnTarget(t, lbp)
if err != nil {
logger.Debugf("could not enable breakpoint %d: %v", lbp.LogicalID, err)
} else {
logger.Debugf("suspended breakpoint %d enabled", lbp.LogicalID)
}
}
}
return false
}
func isSuspended(t *Target, lbp *LogicalBreakpoint) bool {
for _, bp := range t.Breakpoints().M {
if bp.LogicalID() == lbp.LogicalID {
return false
}
}
return true
}
type dummyRecordingManipulation struct { type dummyRecordingManipulation struct {
} }

@ -1741,19 +1741,37 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
} }
requestedBp.Tracepoint = tracepoint requestedBp.Tracepoint = tracepoint
locs, err := t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) locs, findLocErr := t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules())
if err != nil { if findLocErr != nil && requestedBp.Name != "" {
if requestedBp.Name == "" {
return nil, err
}
requestedBp.Name = "" requestedBp.Name = ""
spec = argstr spec = argstr
var err2 error var err2 error
locs, err2 = t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules()) locs, err2 = t.client.FindLocation(ctx.Scope, spec, true, t.substitutePathRules())
if err2 != nil { if err2 == nil {
return nil, err findLocErr = nil
} }
} }
if findLocErr != nil && shouldAskToSuspendBreakpoint(t) {
fmt.Fprintf(os.Stderr, "Command failed: %s\n", findLocErr.Error())
findLocErr = nil
answer, err := yesno(t.line, "Set a suspended breakpoint (Delve will try to set this breakpoint when a plugin is loaded) [Y/n]?")
if err != nil {
return nil, err
}
if !answer {
return nil, nil
}
bp, err := t.client.CreateBreakpointWithExpr(requestedBp, spec, t.substitutePathRules(), true)
if err != nil {
return nil, err
}
fmt.Fprintf(t.stdout, "%s set at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp))
return nil, nil
}
if findLocErr != nil {
return nil, findLocErr
}
created := []*api.Breakpoint{} created := []*api.Breakpoint{}
for _, loc := range locs { for _, loc := range locs {
requestedBp.Addr = loc.PC requestedBp.Addr = loc.PC
@ -1763,7 +1781,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
requestedBp.LoadArgs = &ShortLoadConfig requestedBp.LoadArgs = &ShortLoadConfig
} }
bp, err := t.client.CreateBreakpointWithExpr(requestedBp, spec, t.substitutePathRules()) bp, err := t.client.CreateBreakpointWithExpr(requestedBp, spec, t.substitutePathRules(), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3199,3 +3217,8 @@ func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string {
} }
return out.String() return out.String()
} }
func shouldAskToSuspendBreakpoint(t *Term) bool {
fns, _ := t.client.ListFunctions(`^plugin\.Open$`)
return len(fns) > 0
}

@ -319,6 +319,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
return starlark.None, decorateError(thread, err) return starlark.None, decorateError(thread, err)
} }
} }
if len(args) > 3 && args[3] != starlark.None {
err := unmarshalStarlarkValue(args[3], &rpcArgs.Suspended, "Suspended")
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
for _, kv := range kwargs { for _, kv := range kwargs {
var err error var err error
switch kv[0].(starlark.String) { switch kv[0].(starlark.String) {
@ -328,6 +334,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.LocExpr, "LocExpr") err = unmarshalStarlarkValue(kv[1], &rpcArgs.LocExpr, "LocExpr")
case "SubstitutePathRules": case "SubstitutePathRules":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.SubstitutePathRules, "SubstitutePathRules") err = unmarshalStarlarkValue(kv[1], &rpcArgs.SubstitutePathRules, "SubstitutePathRules")
case "Suspended":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Suspended, "Suspended")
default: default:
err = fmt.Errorf("unknown argument %q", kv[0]) err = fmt.Errorf("unknown argument %q", kv[0])
} }

@ -70,7 +70,7 @@ type Client interface {
// CreateBreakpoint creates a new breakpoint. // CreateBreakpoint creates a new breakpoint.
CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error) CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error)
// CreateBreakpointWithExpr creates a new breakpoint and sets an expression to restore it after it is disabled. // CreateBreakpointWithExpr creates a new breakpoint and sets an expression to restore it after it is disabled.
CreateBreakpointWithExpr(*api.Breakpoint, string, [][2]string) (*api.Breakpoint, error) CreateBreakpointWithExpr(*api.Breakpoint, string, [][2]string, bool) (*api.Breakpoint, error)
// CreateWatchpoint creates a new watchpoint. // CreateWatchpoint creates a new watchpoint.
CreateWatchpoint(api.EvalScope, string, api.WatchType) (*api.Breakpoint, error) CreateWatchpoint(api.EvalScope, string, api.WatchType) (*api.Breakpoint, error)
// ListBreakpoints gets all breakpoints. // ListBreakpoints gets all breakpoints.

@ -1381,7 +1381,7 @@ func (s *Session) setBreakpoints(prefix string, totalBps int, metadataFunc func(
err = setLogMessage(bp, want.logMessage) err = setLogMessage(bp, want.logMessage)
if err == nil { if err == nil {
// Create new breakpoints. // Create new breakpoints.
got, err = s.debugger.CreateBreakpoint(bp, "", nil) got, err = s.debugger.CreateBreakpoint(bp, "", nil, false)
} }
} }
} }

@ -641,7 +641,11 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig, withBreakpointInfo bool) (
// //
// If LocExpr is specified it will be used, along with substitutePathRules, // If LocExpr is specified it will be used, along with substitutePathRules,
// to re-enable the breakpoint after it is disabled. // to re-enable the breakpoint after it is disabled.
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint, locExpr string, substitutePathRules [][2]string) (*api.Breakpoint, error) { //
// If suspended is true a logical breakpoint will be created even if the
// location can not be found, the backend will attempt to enable the
// breakpoint every time a new plugin is loaded.
func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint, locExpr string, substitutePathRules [][2]string, suspended bool) (*api.Breakpoint, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
@ -721,12 +725,13 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint, locExpr string,
setbp.Expr = func(t *proc.Target) []uint64 { setbp.Expr = func(t *proc.Target) []uint64 {
locs, err := loc.Find(t, d.processArgs, nil, locExpr, false, substitutePathRules) locs, err := loc.Find(t, d.processArgs, nil, locExpr, false, substitutePathRules)
if err != nil || len(locs) != 1 { if err != nil || len(locs) != 1 {
logflags.DebuggerLogger().Debugf("could not evaluate breakpoint expression %q: %v (number of results %d)", locExpr, err, len(locs))
return nil return nil
} }
return locs[0].PCs return locs[0].PCs
} }
} }
createdBp, err := createLogicalBreakpoint(d, requestedBp, &setbp) createdBp, err := createLogicalBreakpoint(d, requestedBp, &setbp, suspended)
if err != nil { if err != nil {
return nil, err return nil, err
@ -761,7 +766,7 @@ func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint {
// createLogicalBreakpoint creates one physical breakpoint for each address // createLogicalBreakpoint creates one physical breakpoint for each address
// in addrs and associates all of them with the same logical breakpoint. // in addrs and associates all of them with the same logical breakpoint.
func createLogicalBreakpoint(d *Debugger, requestedBp *api.Breakpoint, setbp *proc.SetBreakpoint) (*api.Breakpoint, error) { func createLogicalBreakpoint(d *Debugger, requestedBp *api.Breakpoint, setbp *proc.SetBreakpoint, suspended bool) (*api.Breakpoint, error) {
id := requestedBp.ID id := requestedBp.ID
var lbp *proc.LogicalBreakpoint var lbp *proc.LogicalBreakpoint
@ -781,8 +786,12 @@ func createLogicalBreakpoint(d *Debugger, requestedBp *api.Breakpoint, setbp *pr
err = d.target.EnableBreakpoint(lbp) err = d.target.EnableBreakpoint(lbp)
if err != nil { if err != nil {
delete(d.target.LogicalBreakpoints, lbp.LogicalID) if suspended {
return nil, err logflags.DebuggerLogger().Debugf("could not enable new breakpoint: %v (breakpoint will be suspended)", err)
} else {
delete(d.target.LogicalBreakpoints, lbp.LogicalID)
return nil, err
}
} }
return d.convertBreakpoint(lbp), nil return d.convertBreakpoint(lbp), nil

@ -106,7 +106,7 @@ func (s *RPCServer) CreateBreakpoint(bp, newBreakpoint *api.Breakpoint) error {
if err := api.ValidBreakpointName(bp.Name); err != nil { if err := api.ValidBreakpointName(bp.Name); err != nil {
return err return err
} }
createdbp, err := s.debugger.CreateBreakpoint(bp, "", nil) createdbp, err := s.debugger.CreateBreakpoint(bp, "", nil, false)
if err != nil { if err != nil {
return err return err
} }

@ -245,16 +245,16 @@ func (c *RPCClient) GetBreakpointByName(name string) (*api.Breakpoint, error) {
// https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint // https://pkg.go.dev/github.com/go-delve/delve/service/debugger#Debugger.CreateBreakpoint
func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) { func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoint, error) {
var out CreateBreakpointOut var out CreateBreakpointOut
err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, "", nil}, &out) err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, "", nil, false}, &out)
return &out.Breakpoint, err return &out.Breakpoint, err
} }
// CreateBreakpointWithExpr is like CreateBreakpoint but will also set a // CreateBreakpointWithExpr is like CreateBreakpoint but will also set a
// location expression to be used to restore the breakpoint after it is // location expression to be used to restore the breakpoint after it is
// disabled. // disabled.
func (c *RPCClient) CreateBreakpointWithExpr(breakPoint *api.Breakpoint, locExpr string, substitutePathRules [][2]string) (*api.Breakpoint, error) { func (c *RPCClient) CreateBreakpointWithExpr(breakPoint *api.Breakpoint, locExpr string, substitutePathRules [][2]string, suspended bool) (*api.Breakpoint, error) {
var out CreateBreakpointOut var out CreateBreakpointOut
err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, locExpr, substitutePathRules}, &out) err := c.call("CreateBreakpoint", CreateBreakpointIn{*breakPoint, locExpr, substitutePathRules, suspended}, &out)
return &out.Breakpoint, err return &out.Breakpoint, err
} }

@ -245,6 +245,7 @@ type CreateBreakpointIn struct {
LocExpr string LocExpr string
SubstitutePathRules [][2]string SubstitutePathRules [][2]string
Suspended bool
} }
type CreateBreakpointOut struct { type CreateBreakpointOut struct {
@ -259,7 +260,7 @@ func (s *RPCServer) CreateBreakpoint(arg CreateBreakpointIn, out *CreateBreakpoi
if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil { if err := api.ValidBreakpointName(arg.Breakpoint.Name); err != nil {
return err return err
} }
createdbp, err := s.debugger.CreateBreakpoint(&arg.Breakpoint, arg.LocExpr, arg.SubstitutePathRules) createdbp, err := s.debugger.CreateBreakpoint(&arg.Breakpoint, arg.LocExpr, arg.SubstitutePathRules, arg.Suspended)
if err != nil { if err != nil {
return err return err
} }

@ -55,12 +55,12 @@ func TestMain(m *testing.M) {
} }
func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
withTestClient2Extended(name, t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) { withTestClient2Extended(name, t, 0, [3]string{}, nil, func(c service.Client, fixture protest.Fixture) {
fn(c) fn(c)
}) })
} }
func startServer(name string, buildFlags protest.BuildFlags, t *testing.T, redirects [3]string) (clientConn net.Conn, fixture protest.Fixture) { func startServer(name string, buildFlags protest.BuildFlags, t *testing.T, redirects [3]string, args []string) (clientConn net.Conn, fixture protest.Fixture) {
if testBackend == "rr" { if testBackend == "rr" {
protest.MustHaveRecordingAllowed(t) protest.MustHaveRecordingAllowed(t)
} }
@ -77,7 +77,7 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T, redir
} }
server := rpccommon.NewServer(&service.Config{ server := rpccommon.NewServer(&service.Config{
Listener: listener, Listener: listener,
ProcessArgs: []string{fixture.Path}, ProcessArgs: append([]string{fixture.Path}, args...),
Debugger: debugger.Config{ Debugger: debugger.Config{
Backend: testBackend, Backend: testBackend,
CheckGoVersion: true, CheckGoVersion: true,
@ -93,8 +93,8 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T, redir
return clientConn, fixture return clientConn, fixture
} }
func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, redirects [3]string, fn func(c service.Client, fixture protest.Fixture)) { func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, redirects [3]string, args []string, fn func(c service.Client, fixture protest.Fixture)) {
clientConn, fixture := startServer(name, buildFlags, t, redirects) clientConn, fixture := startServer(name, buildFlags, t, redirects, args)
client := rpc2.NewClientFromConn(clientConn) client := rpc2.NewClientFromConn(clientConn)
defer func() { defer func() {
client.Detach(true) client.Detach(true)
@ -230,7 +230,7 @@ func TestRestart_rebuild(t *testing.T) {
// In the original fixture file the env var tested for is SOMEVAR. // In the original fixture file the env var tested for is SOMEVAR.
os.Setenv("SOMEVAR", "bah") os.Setenv("SOMEVAR", "bah")
withTestClient2Extended("testenv", t, 0, [3]string{}, func(c service.Client, f protest.Fixture) { withTestClient2Extended("testenv", t, 0, [3]string{}, nil, func(c service.Client, f protest.Fixture) {
<-c.Continue() <-c.Continue()
var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig) var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig)
@ -1019,7 +1019,7 @@ func TestClientServer_FindLocations(t *testing.T) {
findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr)
}) })
withTestClient2Extended("locationsUpperCase", t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) { withTestClient2Extended("locationsUpperCase", t, 0, [3]string{}, nil, func(c service.Client, fixture protest.Fixture) {
// Upper case // Upper case
findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0)
@ -1724,7 +1724,7 @@ func TestClientServer_FpRegisters(t *testing.T) {
{"XMM12", "…[ZMM12hh] 0x3ff66666666666663ff4cccccccccccd"}, {"XMM12", "…[ZMM12hh] 0x3ff66666666666663ff4cccccccccccd"},
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestClient2Extended("fputest/", t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) { withTestClient2Extended("fputest/", t, 0, [3]string{}, nil, func(c service.Client, fixture protest.Fixture) {
_, err := c.CreateBreakpoint(&api.Breakpoint{File: filepath.Join(fixture.BuildDir, "fputest.go"), Line: 25}) _, err := c.CreateBreakpoint(&api.Breakpoint{File: filepath.Join(fixture.BuildDir, "fputest.go"), Line: 25})
assertNoError(err, t, "CreateBreakpoint") assertNoError(err, t, "CreateBreakpoint")
@ -2276,7 +2276,7 @@ func (c *brokenRPCClient) call(method string, args, reply interface{}) error {
} }
func TestUnknownMethodCall(t *testing.T) { func TestUnknownMethodCall(t *testing.T) {
clientConn, _ := startServer("continuetestprog", 0, t, [3]string{}) clientConn, _ := startServer("continuetestprog", 0, t, [3]string{}, nil)
client := &brokenRPCClient{jsonrpc.NewClient(clientConn)} client := &brokenRPCClient{jsonrpc.NewClient(clientConn)}
client.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{}) client.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{})
defer client.Detach(true) defer client.Detach(true)
@ -2420,7 +2420,7 @@ func TestClearLogicalBreakpoint(t *testing.T) {
// Clearing a logical breakpoint should clear all associated physical // Clearing a logical breakpoint should clear all associated physical
// breakpoints. // breakpoints.
// Issue #1955. // Issue #1955.
withTestClient2Extended("testinline", t, protest.EnableInlining, [3]string{}, func(c service.Client, fixture protest.Fixture) { withTestClient2Extended("testinline", t, protest.EnableInlining, [3]string{}, nil, func(c service.Client, fixture protest.Fixture) {
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.inlineThis"}) bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.inlineThis"})
assertNoError(err, t, "CreateBreakpoint()") assertNoError(err, t, "CreateBreakpoint()")
t.Logf("breakpoint set at %#v", bp.Addrs) t.Logf("breakpoint set at %#v", bp.Addrs)
@ -2446,7 +2446,7 @@ func TestRedirects(t *testing.T) {
outfile = "redirect-output.txt" outfile = "redirect-output.txt"
) )
protest.AllowRecording(t) protest.AllowRecording(t)
withTestClient2Extended("redirect", t, 0, [3]string{infile, outfile, ""}, func(c service.Client, fixture protest.Fixture) { withTestClient2Extended("redirect", t, 0, [3]string{infile, outfile, ""}, nil, func(c service.Client, fixture protest.Fixture) {
outpath := filepath.Join(fixture.BuildDir, outfile) outpath := filepath.Join(fixture.BuildDir, outfile)
<-c.Continue() <-c.Continue()
buf, err := ioutil.ReadFile(outpath) buf, err := ioutil.ReadFile(outpath)
@ -2774,7 +2774,7 @@ func TestClientServer_SinglelineStringFormattedWithBigInts(t *testing.T) {
if runtime.GOARCH != "amd64" { if runtime.GOARCH != "amd64" {
t.Skip("N/A") t.Skip("N/A")
} }
withTestClient2Extended("xmm0print/", t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) { withTestClient2Extended("xmm0print/", t, 0, [3]string{}, nil, func(c service.Client, fixture protest.Fixture) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.VPSLLQ36", Line: 4}) _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.VPSLLQ36", Line: 4})
assertNoError(err, t, "CreateBreakpoint") assertNoError(err, t, "CreateBreakpoint")
state := <-c.Continue() state := <-c.Continue()
@ -2872,7 +2872,7 @@ func TestRestart_PreserveFunctionBreakpoint(t *testing.T) {
copy(filepath.Join(dir, "testfnpos1.go")) copy(filepath.Join(dir, "testfnpos1.go"))
withTestClient2Extended("testfnpos", t, 0, [3]string{}, func(c service.Client, f protest.Fixture) { withTestClient2Extended("testfnpos", t, 0, [3]string{}, nil, func(c service.Client, f protest.Fixture) {
_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.f1"}) _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.f1"})
assertNoError(err, t, "CreateBreakpoint") assertNoError(err, t, "CreateBreakpoint")
state := <-c.Continue() state := <-c.Continue()
@ -2895,3 +2895,69 @@ func TestRestart_PreserveFunctionBreakpoint(t *testing.T) {
} }
}) })
} }
func assertLine(t *testing.T, state *api.DebuggerState, file string, lineno int) {
t.Helper()
t.Logf("%s:%d", state.CurrentThread.File, state.CurrentThread.Line)
if !strings.HasSuffix(state.CurrentThread.File, file) || state.CurrentThread.Line != lineno {
t.Fatalf("wrong location %s:%d", state.CurrentThread.File, state.CurrentThread.Line)
}
}
func TestPluginSuspendedBreakpoint(t *testing.T) {
// Tests that breakpoints created in a suspended state will be enabled automatically when a plugin is loaded.
pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
dir, err := filepath.Abs(protest.FindFixturesDir())
assertNoError(err, t, "filepath.Abs")
withTestClient2Extended("plugintest", t, protest.AllNonOptimized, [3]string{}, []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, func(c service.Client, f protest.Fixture) {
_, err := c.CreateBreakpointWithExpr(&api.Breakpoint{FunctionName: "github.com/go-delve/delve/_fixtures/plugin1.Fn1", Line: 1}, "", nil, true)
assertNoError(err, t, "CreateBreakpointWithExpr(Fn1) (suspended)")
_, err = c.CreateBreakpointWithExpr(&api.Breakpoint{File: filepath.Join(dir, "plugin2", "plugin2.go"), Line: 9}, "", nil, true)
assertNoError(err, t, "CreateBreakpointWithExpr(plugin2.go:9) (suspended)")
cont := func(name, file string, lineno int) {
t.Helper()
state := <-c.Continue()
assertNoError(state.Err, t, name)
assertLine(t, state, file, lineno)
}
cont("Continue 1", "plugintest.go", 22)
cont("Continue 2", "plugintest.go", 27)
cont("Continue 3", "plugin1.go", 6)
cont("Continue 4", "plugin2.go", 9)
})
withTestClient2Extended("plugintest", t, protest.AllNonOptimized, [3]string{}, []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, func(c service.Client, f protest.Fixture) {
exprbreak := func(expr string) {
t.Helper()
_, err := c.CreateBreakpointWithExpr(&api.Breakpoint{}, expr, nil, true)
assertNoError(err, t, fmt.Sprintf("CreateBreakpointWithExpr(%s) (suspended)", expr))
}
cont := func(name, file string, lineno int) {
t.Helper()
state := <-c.Continue()
assertNoError(state.Err, t, name)
assertLine(t, state, file, lineno)
}
exprbreak("plugin1.Fn1")
exprbreak("plugin2.go:9")
// The following breakpoints can never be un-suspended because the
// expression is never resolved, but this shouldn't cause problems
exprbreak("m[0]")
exprbreak("*m[0]")
exprbreak("unknownfn")
exprbreak("+2")
cont("Continue 1", "plugintest.go", 22)
cont("Continue 2", "plugintest.go", 27)
cont("Continue 3", "plugin1.go", 5)
cont("Continue 4", "plugin2.go", 9)
})
}