proc/*: minor miscellaneous code cleanups (#2790)

* made Pid a method of Target instead of a method of Process
* changed argument of NewTarget to ProcessInternal, since that's the
  interface that backends have to implement
* removed warnings about ProcessInternal since there is no way for
  users of pkg/proc to access those methods anyway
* made RecordingManipulation an optional interface for backends, Target
  supplies its own dummy implementation when the backend doesn't
* inlined small interfaces that only existed to be inlined in
  proc.Process anyway
* removed unused function findExecutable in the Windows and no-native
  darwin backends
* removed (*EvalScope).EvalVariable, an old synonym for EvalExpression
This commit is contained in:
Alessandro Arzilli 2021-11-26 17:06:23 +01:00 committed by GitHub
parent 88dca53e5c
commit 01b01423ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 124 additions and 141 deletions

@ -164,8 +164,6 @@ type process struct {
breakpoints proc.BreakpointMap breakpoints proc.BreakpointMap
} }
var _ proc.ProcessInternal = &process{}
// thread represents a thread in the core file being debugged. // thread represents a thread in the core file being debugged.
type thread struct { type thread struct {
th osThread th osThread
@ -222,7 +220,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
return nil, ErrNoThreads return nil, ErrNoThreads
} }
return proc.NewTarget(p, currentThread, proc.NewTargetConfig{ return proc.NewTarget(p, p.pid, currentThread, proc.NewTargetConfig{
Path: exePath, Path: exePath,
DebugInfoDirs: debugInfoDirs, DebugInfoDirs: debugInfoDirs,
DisableAsyncPreempt: false, DisableAsyncPreempt: false,
@ -459,11 +457,6 @@ func (p *process) Valid() (bool, error) {
return true, nil return true, nil
} }
// Pid returns the process ID of this process.
func (p *process) Pid() int {
return p.pid
}
// ResumeNotify is a no-op on core files as we cannot // ResumeNotify is a no-op on core files as we cannot
// control execution. // control execution.
func (p *process) ResumeNotify(chan<- struct{}) { func (p *process) ResumeNotify(chan<- struct{}) {

@ -297,7 +297,7 @@ func TestCore(t *testing.T) {
if mainFrame == nil { if mainFrame == nil {
t.Fatalf("Couldn't find main in stack %v", panickingStack) t.Fatalf("Couldn't find main in stack %v", panickingStack)
} }
msg, err := proc.FrameToScope(p, p.Memory(), nil, *mainFrame).EvalVariable("msg", proc.LoadConfig{MaxStringLen: 64}) msg, err := proc.FrameToScope(p, p.Memory(), nil, *mainFrame).EvalExpression("msg", proc.LoadConfig{MaxStringLen: 64})
if err != nil { if err != nil {
t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err) t.Fatalf("Couldn't EvalVariable(msg, ...): %v", err)
} }
@ -429,11 +429,11 @@ mainSearch:
scope := proc.FrameToScope(p, p.Memory(), nil, *mainFrame) scope := proc.FrameToScope(p, p.Memory(), nil, *mainFrame)
loadConfig := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1} loadConfig := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
v1, err := scope.EvalVariable("t", loadConfig) v1, err := scope.EvalExpression("t", loadConfig)
assertNoError(err, t, "EvalVariable(t)") assertNoError(err, t, "EvalVariable(t)")
assertNoError(v1.Unreadable, t, "unreadable variable 't'") assertNoError(v1.Unreadable, t, "unreadable variable 't'")
t.Logf("t = %#v\n", v1) t.Logf("t = %#v\n", v1)
v2, err := scope.EvalVariable("s", loadConfig) v2, err := scope.EvalExpression("s", loadConfig)
assertNoError(err, t, "EvalVariable(s)") assertNoError(err, t, "EvalVariable(s)")
assertNoError(v2.Unreadable, t, "unreadable variable 's'") assertNoError(v2.Unreadable, t, "unreadable variable 's'")
t.Logf("s = %#v\n", v2) t.Logf("s = %#v\n", v2)

@ -155,7 +155,7 @@ func (t *Target) Dump(out elfwriter.WriteCloserSeeker, flags DumpFlags, state *D
notes = append(notes, elfwriter.Note{ notes = append(notes, elfwriter.Note{
Type: elfwriter.DelveHeaderNoteType, Type: elfwriter.DelveHeaderNoteType,
Name: "Delve Header", Name: "Delve Header",
Data: []byte(fmt.Sprintf("%s/%s\n%s\n%s%d\n%s%#x\n", bi.GOOS, bi.Arch.Name, version.DelveVersion.String(), elfwriter.DelveHeaderTargetPidPrefix, t.Pid(), elfwriter.DelveHeaderEntryPointPrefix, entryPoint)), Data: []byte(fmt.Sprintf("%s/%s\n%s\n%s%d\n%s%#x\n", bi.GOOS, bi.Arch.Name, version.DelveVersion.String(), elfwriter.DelveHeaderTargetPidPrefix, t.pid, elfwriter.DelveHeaderEntryPointPrefix, entryPoint)),
}) })
threads := t.ThreadList() threads := t.ThreadList()

@ -423,11 +423,6 @@ func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error {
return fmt.Errorf("can not set variables of type %s (not implemented)", dstv.Kind.String()) return fmt.Errorf("can not set variables of type %s (not implemented)", dstv.Kind.String())
} }
// EvalVariable returns the value of the given expression (backwards compatibility).
func (scope *EvalScope) EvalVariable(name string, cfg LoadConfig) (*Variable, error) {
return scope.EvalExpression(name, cfg)
}
// SetVariable sets the value of the named variable // SetVariable sets the value of the named variable
func (scope *EvalScope) SetVariable(name, value string) error { func (scope *EvalScope) SetVariable(name, value string) error {
t, err := parser.ParseExpr(name) t, err := parser.ParseExpr(name)

@ -170,7 +170,7 @@ type gdbProcess struct {
onDetach func() // called after a successful detach onDetach func() // called after a successful detach
} }
var _ proc.ProcessInternal = &gdbProcess{} var _ proc.RecordingManipulationInternal = &gdbProcess{}
// gdbThread represents an operating system thread. // gdbThread represents an operating system thread.
type gdbThread struct { type gdbThread struct {
@ -704,7 +704,7 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
return nil, err return nil, err
} }
} }
tgt, err := proc.NewTarget(p, p.currentThread, proc.NewTargetConfig{ tgt, err := proc.NewTarget(p, p.conn.pid, p.currentThread, proc.NewTargetConfig{
Path: path, Path: path,
DebugInfoDirs: debugInfoDirs, DebugInfoDirs: debugInfoDirs,
DisableAsyncPreempt: runtime.GOOS == "darwin", DisableAsyncPreempt: runtime.GOOS == "darwin",

@ -13,9 +13,20 @@ import (
// There is one exception to this rule: it is safe to call RequestManualStop // There is one exception to this rule: it is safe to call RequestManualStop
// concurrently with ContinueOnce. // concurrently with ContinueOnce.
type Process interface { type Process interface {
Info // ResumeNotify specifies a channel that will be closed the next time
ProcessManipulation // ContinueOnce finishes resuming the target.
RecordingManipulation ResumeNotify(chan<- struct{})
BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
// RequestManualStop attempts to stop all the process' threads.
RequestManualStop() error
// CheckAndClearManualStopRequest returns true the first time it's called
// after a call to RequestManualStop.
CheckAndClearManualStopRequest() bool
FindThread(threadID int) (Thread, bool)
ThreadList() []Thread
Breakpoints() *BreakpointMap Breakpoints() *BreakpointMap
@ -23,22 +34,16 @@ type Process interface {
Memory() MemoryReadWriter Memory() MemoryReadWriter
} }
// ProcessInternal holds a set of methods that are not meant to be called by // ProcessInternal holds a set of methods that need to be implemented by a
// anyone except for an instance of `proc.Target`. These methods are not // Delve backend. Methods in the Process interface are safe to be called by
// safe to use by themselves and should never be called directly outside of // clients of the 'proc' library, while all other methods are only called
// the `proc` package. // directly within 'proc'.
// This is temporary and in support of an ongoing refactor.
type ProcessInternal interface { type ProcessInternal interface {
Process
// Valid returns true if this Process can be used. When it returns false it // Valid returns true if this Process can be used. When it returns false it
// also returns an error describing why the Process is invalid (either // also returns an error describing why the Process is invalid (either
// ErrProcessExited or ErrProcessDetached). // ErrProcessExited or ErrProcessDetached).
Valid() (bool, error) Valid() (bool, error)
// Restart restarts the recording from the specified position, or from the
// last checkpoint if pos == "".
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
// number.
// Returns the new current thread after the restart has completed.
Restart(pos string) (Thread, error)
Detach(bool) error Detach(bool) error
ContinueOnce() (trapthread Thread, stopReason StopReason, err error) ContinueOnce() (trapthread Thread, stopReason StopReason, err error)
@ -47,6 +52,7 @@ type ProcessInternal interface {
SupportsBPF() bool SupportsBPF() bool
SetUProbe(string, int64, []ebpf.UProbeArgMap) error SetUProbe(string, int64, []ebpf.UProbeArgMap) error
GetBufferedTracepoints() []ebpf.RawUProbeParams
// DumpProcessNotes returns ELF core notes describing the process and its threads. // DumpProcessNotes returns ELF core notes describing the process and its threads.
// Implementing this method is optional. // Implementing this method is optional.
@ -54,8 +60,6 @@ type ProcessInternal interface {
// MemoryMap returns the memory map of the target process. This method must be implemented if CanDump is true. // MemoryMap returns the memory map of the target process. This method must be implemented if CanDump is true.
MemoryMap() ([]MemoryMapEntry, error) MemoryMap() ([]MemoryMapEntry, error)
GetBufferedTracepoints() []ebpf.RawUProbeParams
// StartCallInjection notifies the backend that we are about to inject a function call. // StartCallInjection notifies the backend that we are about to inject a function call.
StartCallInjection() (func(), error) StartCallInjection() (func(), error)
} }
@ -79,6 +83,19 @@ type RecordingManipulation interface {
ClearCheckpoint(id int) error ClearCheckpoint(id int) error
} }
// RecordingManipulationInternal is an interface that a Delve backend can
// implement if it is a recording.
type RecordingManipulationInternal interface {
RecordingManipulation
// Restart restarts the recording from the specified position, or from the
// last checkpoint if pos == "".
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
// number.
// Returns the new current thread after the restart has completed.
Restart(pos string) (Thread, error)
}
// Direction is the direction of execution for the target process. // Direction is the direction of execution for the target process.
type Direction int8 type Direction int8
@ -95,31 +112,3 @@ type Checkpoint struct {
When string When string
Where string Where string
} }
// Info is an interface that provides general information on the target.
type Info interface {
Pid() int
// ResumeNotify specifies a channel that will be closed the next time
// ContinueOnce finishes resuming the target.
ResumeNotify(chan<- struct{})
BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
ThreadInfo
}
// ThreadInfo is an interface for getting information on active threads
// in the process.
type ThreadInfo interface {
FindThread(threadID int) (Thread, bool)
ThreadList() []Thread
}
// ProcessManipulation is an interface for changing the execution state of a process.
type ProcessManipulation interface {
// RequestManualStop attempts to stop all the process' threads.
RequestManualStop() error
// CheckAndClearManualStopRequest returns true the first time it's called
// after a call to RequestManualStop.
CheckAndClearManualStopRequest() bool
}

@ -12,10 +12,10 @@ import (
func (p *nativeProcess) MemoryMap() ([]proc.MemoryMapEntry, error) { func (p *nativeProcess) MemoryMap() ([]proc.MemoryMapEntry, error) {
const VmFlagsPrefix = "VmFlags:" const VmFlagsPrefix = "VmFlags:"
smapsbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/smaps", p.Pid())) smapsbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/smaps", p.pid))
if err != nil { if err != nil {
// Older versions of Linux don't have smaps but have maps which is in a similar format. // Older versions of Linux don't have smaps but have maps which is in a similar format.
smapsbuf, err = ioutil.ReadFile(fmt.Sprintf("/proc/%d/maps", p.Pid())) smapsbuf, err = ioutil.ReadFile(fmt.Sprintf("/proc/%d/maps", p.pid))
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -37,10 +37,6 @@ type osProcessDetails struct{}
func (os *osProcessDetails) Close() {} func (os *osProcessDetails) Close() {}
func findExecutable(path string, pid int) string {
panic(ErrNativeBackendDisabled)
}
func killProcess(pid int) error { func killProcess(pid int) error {
panic(ErrNativeBackendDisabled) panic(ErrNativeBackendDisabled)
} }

@ -46,8 +46,6 @@ type nativeProcess struct {
exited, detached bool exited, detached bool
} }
var _ proc.ProcessInternal = &nativeProcess{}
// newProcess returns an initialized Process struct. Before returning, // newProcess returns an initialized Process struct. Before returning,
// it will also launch a goroutine in order to handle ptrace(2) // it will also launch a goroutine in order to handle ptrace(2)
// functions. For more information, see the documentation on // functions. For more information, see the documentation on
@ -72,40 +70,6 @@ func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo {
return dbp.bi return dbp.bi
} }
// Recorded always returns false for the native proc backend.
func (dbp *nativeProcess) Recorded() (bool, string) { return false, "" }
// Restart will always return an error in the native proc backend, only for
// recorded traces.
func (dbp *nativeProcess) Restart(string) (proc.Thread, error) { return nil, proc.ErrNotRecorded }
// ChangeDirection will always return an error in the native proc backend, only for
// recorded traces.
func (dbp *nativeProcess) ChangeDirection(dir proc.Direction) error {
if dir != proc.Forward {
return proc.ErrNotRecorded
}
return nil
}
// GetDirection will always return Forward.
func (p *nativeProcess) GetDirection() proc.Direction { return proc.Forward }
// When will always return an empty string and nil, not supported on native proc backend.
func (dbp *nativeProcess) When() (string, error) { return "", nil }
// Checkpoint will always return an error on the native proc backend,
// only supported for recorded traces.
func (dbp *nativeProcess) Checkpoint(string) (int, error) { return -1, proc.ErrNotRecorded }
// Checkpoints will always return an error on the native proc backend,
// only supported for recorded traces.
func (dbp *nativeProcess) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.ErrNotRecorded }
// ClearCheckpoint will always return an error on the native proc backend,
// only supported in recorded traces.
func (dbp *nativeProcess) ClearCheckpoint(int) error { return proc.ErrNotRecorded }
// StartCallInjection notifies the backend that we are about to inject a function call. // StartCallInjection notifies the backend that we are about to inject a function call.
func (dbp *nativeProcess) StartCallInjection() (func(), error) { return func() {}, nil } func (dbp *nativeProcess) StartCallInjection() (func(), error) { return func() {}, nil }
@ -143,7 +107,7 @@ func (dbp *nativeProcess) Valid() (bool, error) {
return false, proc.ErrProcessDetached return false, proc.ErrProcessDetached
} }
if dbp.exited { if dbp.exited {
return false, proc.ErrProcessExited{Pid: dbp.Pid()} return false, proc.ErrProcessExited{Pid: dbp.pid}
} }
return true, nil return true, nil
} }
@ -154,11 +118,6 @@ func (dbp *nativeProcess) ResumeNotify(ch chan<- struct{}) {
dbp.resumeChan = ch dbp.resumeChan = ch
} }
// Pid returns the process ID.
func (dbp *nativeProcess) Pid() int {
return dbp.pid
}
// ThreadList returns a list of threads in the process. // ThreadList returns a list of threads in the process.
func (dbp *nativeProcess) ThreadList() []proc.Thread { func (dbp *nativeProcess) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(dbp.threads)) r := make([]proc.Thread, 0, len(dbp.threads))
@ -188,7 +147,7 @@ func (dbp *nativeProcess) Breakpoints() *proc.BreakpointMap {
// sends SIGSTOP to all threads. // sends SIGSTOP to all threads.
func (dbp *nativeProcess) RequestManualStop() error { func (dbp *nativeProcess) RequestManualStop() error {
if dbp.exited { if dbp.exited {
return proc.ErrProcessExited{Pid: dbp.Pid()} return proc.ErrProcessExited{Pid: dbp.pid}
} }
dbp.stopMu.Lock() dbp.stopMu.Lock()
defer dbp.stopMu.Unlock() defer dbp.stopMu.Unlock()
@ -245,7 +204,7 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
// This could be the result of a breakpoint or signal. // This could be the result of a breakpoint or signal.
func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) { func (dbp *nativeProcess) ContinueOnce() (proc.Thread, proc.StopReason, error) {
if dbp.exited { if dbp.exited {
return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid}
} }
for { for {
@ -306,7 +265,7 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc
if !dbp.childProcess { if !dbp.childProcess {
stopReason = proc.StopAttached stopReason = proc.StopAttached
} }
tgt, err := proc.NewTarget(dbp, dbp.memthread, proc.NewTargetConfig{ tgt, err := proc.NewTarget(dbp, dbp.pid, dbp.memthread, proc.NewTargetConfig{
Path: path, Path: path,
DebugInfoDirs: debugInfoDirs, DebugInfoDirs: debugInfoDirs,
DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd", DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd",

@ -435,7 +435,7 @@ func (dbp *nativeProcess) resume() error {
// stop stops all running threads and sets breakpoints // stop stops all running threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return nil, proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.ErrProcessExited{Pid: dbp.pid}
} }
for _, th := range dbp.threads { for _, th := range dbp.threads {
if !th.Stopped() { if !th.Stopped() {

@ -352,7 +352,7 @@ func (dbp *nativeProcess) resume() error {
// stop stops all running threads and sets breakpoints // stop stops all running threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return nil, proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.ErrProcessExited{Pid: dbp.pid}
} }
// set breakpoints on all threads // set breakpoints on all threads
for _, th := range dbp.threads { for _, th := range dbp.threads {

@ -540,7 +540,7 @@ func (dbp *nativeProcess) resume() error {
// stop stops all running threads and sets breakpoints // stop stops all running threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return nil, proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.ErrProcessExited{Pid: dbp.pid}
} }
for _, th := range dbp.threads { for _, th := range dbp.threads {
@ -772,7 +772,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
if err != nil { if err != nil {
return err return err
} }
err = dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, off) err = dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
if err != nil { if err != nil {
return err return err
} }
@ -783,7 +783,7 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf
return err return err
} }
return dbp.os.ebpf.AttachUprobe(dbp.Pid(), debugname, off) return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
} }
func killProcess(pid int) error { func killProcess(pid int) error {

@ -235,10 +235,6 @@ func (dbp *nativeProcess) addThread(hThread syscall.Handle, threadID int, attach
return thread, nil return thread, nil
} }
func findExecutable(path string, pid int) string {
return path
}
type waitForDebugEventFlags int type waitForDebugEventFlags int
const ( const (
@ -428,7 +424,7 @@ func (dbp *nativeProcess) resume() error {
// stop stops all running threads threads and sets breakpoints // stop stops all running threads threads and sets breakpoints
func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
if dbp.exited { if dbp.exited {
return nil, proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.ErrProcessExited{Pid: dbp.pid}
} }
dbp.os.running = false dbp.os.running = false

@ -1164,7 +1164,7 @@ func evalVariableOrError(p *proc.Target, symbol string) (*proc.Variable, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return scope.EvalVariable(symbol, normalLoadConfig) return scope.EvalExpression(symbol, normalLoadConfig)
} }
func evalVariable(p *proc.Target, t testing.TB, symbol string) *proc.Variable { func evalVariable(p *proc.Target, t testing.TB, symbol string) *proc.Variable {
@ -1310,7 +1310,7 @@ func TestFrameEvaluation(t *testing.T) {
scope, err := proc.ConvertEvalScope(p, g.ID, frame, 0) scope, err := proc.ConvertEvalScope(p, g.ID, frame, 0)
assertNoError(err, t, "ConvertEvalScope()") assertNoError(err, t, "ConvertEvalScope()")
t.Logf("scope = %v", scope) t.Logf("scope = %v", scope)
v, err := scope.EvalVariable("i", normalLoadConfig) v, err := scope.EvalExpression("i", normalLoadConfig)
t.Logf("v = %v", v) t.Logf("v = %v", v)
if err != nil { if err != nil {
t.Logf("Goroutine %d: %v\n", g.ID, err) t.Logf("Goroutine %d: %v\n", g.ID, err)
@ -1338,7 +1338,7 @@ func TestFrameEvaluation(t *testing.T) {
for i := 0; i <= 3; i++ { for i := 0; i <= 3; i++ {
scope, err := proc.ConvertEvalScope(p, g.ID, i+1, 0) scope, err := proc.ConvertEvalScope(p, g.ID, i+1, 0)
assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1)) assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1))
v, err := scope.EvalVariable("n", normalLoadConfig) v, err := scope.EvalExpression("n", normalLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1)) assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
n, _ := constant.Int64Val(v.Value) n, _ := constant.Int64Val(v.Value)
t.Logf("frame %d n %d\n", i+1, n) t.Logf("frame %d n %d\n", i+1, n)
@ -1367,7 +1367,7 @@ func TestThreadFrameEvaluation(t *testing.T) {
// be a thread scope. // be a thread scope.
scope, err := proc.ConvertEvalScope(p, 0, 0, 0) scope, err := proc.ConvertEvalScope(p, 0, 0, 0)
assertNoError(err, t, "ConvertEvalScope() on frame 0") assertNoError(err, t, "ConvertEvalScope() on frame 0")
_, err = scope.EvalVariable("s", normalLoadConfig) _, err = scope.EvalExpression("s", normalLoadConfig)
assertNoError(err, t, "EvalVariable(\"s\") on frame 0") assertNoError(err, t, "EvalVariable(\"s\") on frame 0")
}) })
} }
@ -1520,10 +1520,10 @@ func TestBreakpointCountsWithDetection(t *testing.T) {
} }
scope, err := proc.GoroutineScope(p, th) scope, err := proc.GoroutineScope(p, th)
assertNoError(err, t, "Scope()") assertNoError(err, t, "Scope()")
v, err := scope.EvalVariable("i", normalLoadConfig) v, err := scope.EvalExpression("i", normalLoadConfig)
assertNoError(err, t, "evalVariable") assertNoError(err, t, "evalVariable")
i, _ := constant.Int64Val(v.Value) i, _ := constant.Int64Val(v.Value)
v, err = scope.EvalVariable("id", normalLoadConfig) v, err = scope.EvalExpression("id", normalLoadConfig)
assertNoError(err, t, "evalVariable") assertNoError(err, t, "evalVariable")
id, _ := constant.Int64Val(v.Value) id, _ := constant.Int64Val(v.Value)
m[id] = i m[id] = i
@ -4253,11 +4253,11 @@ func TestReadDeferArgs(t *testing.T) {
} }
} }
avar, err := scope.EvalVariable("a", normalLoadConfig) avar, err := scope.EvalExpression("a", normalLoadConfig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
bvar, err := scope.EvalVariable("b", normalLoadConfig) bvar, err := scope.EvalExpression("b", normalLoadConfig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -285,7 +285,7 @@ func (check *scopeCheck) checkVar(v *proc.Variable, t *testing.T) {
} }
func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testing.T) { func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testing.T) {
v, err := scope.EvalVariable(varCheck.name, normalLoadConfig) v, err := scope.EvalExpression(varCheck.name, normalLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", varCheck.name)) assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", varCheck.name))
varCheck.check(line, v, t, "EvalExpression") varCheck.check(line, v, t, "EvalExpression")

@ -36,8 +36,12 @@ const (
// Target represents the process being debugged. // Target represents the process being debugged.
type Target struct { type Target struct {
Process Process
RecordingManipulation
proc ProcessInternal proc ProcessInternal
recman RecordingManipulationInternal
pid int
// StopReason describes the reason why the target process is stopped. // StopReason describes the reason why the target process is stopped.
// A process could be stopped for multiple simultaneous reasons, in which // A process could be stopped for multiple simultaneous reasons, in which
@ -169,7 +173,8 @@ func DisableAsyncPreemptEnv() []string {
} }
// NewTarget returns an initialized Target object. // NewTarget returns an initialized Target object.
func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, error) { // The p argument can optionally implement the RecordingManipulation interface.
func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetConfig) (*Target, error) {
entryPoint, err := p.EntryPoint() entryPoint, err := p.EntryPoint()
if err != nil { if err != nil {
return nil, err return nil, err
@ -187,13 +192,21 @@ func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, e
t := &Target{ t := &Target{
Process: p, Process: p,
proc: p.(ProcessInternal), proc: p,
fncallForG: make(map[int]*callInjection), fncallForG: make(map[int]*callInjection),
StopReason: cfg.StopReason, StopReason: cfg.StopReason,
currentThread: currentThread, currentThread: currentThread,
CanDump: cfg.CanDump, CanDump: cfg.CanDump,
pid: pid,
} }
if recman, ok := p.(RecordingManipulationInternal); ok {
t.recman = recman
} else {
t.recman = &dummyRecordingManipulation{}
}
t.RecordingManipulation = t.recman
g, _ := GetG(currentThread) g, _ := GetG(currentThread)
t.selectedGoroutine = g t.selectedGoroutine = g
@ -210,6 +223,11 @@ func NewTarget(p Process, currentThread Thread, cfg NewTargetConfig) (*Target, e
return t, nil return t, nil
} }
// Pid returns the pid of the target process.
func (t *Target) Pid() int {
return t.pid
}
// IsCgo returns the value of runtime.iscgo // IsCgo returns the value of runtime.iscgo
func (t *Target) IsCgo() bool { func (t *Target) IsCgo() bool {
if t.iscgo != nil { if t.iscgo != nil {
@ -265,7 +283,7 @@ func (t *Target) ClearCaches() {
// Restarting of a normal process happens at a higher level (debugger.Restart). // Restarting of a normal process happens at a higher level (debugger.Restart).
func (t *Target) Restart(from string) error { func (t *Target) Restart(from string) error {
t.ClearCaches() t.ClearCaches()
currentThread, err := t.proc.Restart(from) currentThread, err := t.recman.Restart(from)
if err != nil { if err != nil {
return err return err
} }
@ -544,3 +562,40 @@ func (t *Target) dwrapUnwrap(fn *Function) *Function {
} }
return fn return fn
} }
type dummyRecordingManipulation struct {
}
// Recorded always returns false for the native proc backend.
func (*dummyRecordingManipulation) Recorded() (bool, string) { return false, "" }
// ChangeDirection will always return an error in the native proc backend, only for
// recorded traces.
func (*dummyRecordingManipulation) ChangeDirection(dir Direction) error {
if dir != Forward {
return ErrNotRecorded
}
return nil
}
// GetDirection will always return Forward.
func (*dummyRecordingManipulation) GetDirection() Direction { return Forward }
// When will always return an empty string and nil, not supported on native proc backend.
func (*dummyRecordingManipulation) When() (string, error) { return "", nil }
// Checkpoint will always return an error on the native proc backend,
// only supported for recorded traces.
func (*dummyRecordingManipulation) Checkpoint(string) (int, error) { return -1, ErrNotRecorded }
// Checkpoints will always return an error on the native proc backend,
// only supported for recorded traces.
func (*dummyRecordingManipulation) Checkpoints() ([]Checkpoint, error) { return nil, ErrNotRecorded }
// ClearCheckpoint will always return an error on the native proc backend,
// only supported in recorded traces.
func (*dummyRecordingManipulation) ClearCheckpoint(int) error { return ErrNotRecorded }
// Restart will always return an error in the native proc backend, only for
// recorded traces.
func (*dummyRecordingManipulation) Restart(string) (Thread, error) { return nil, ErrNotRecorded }

@ -1332,7 +1332,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
bpi.Variables = make([]api.Variable, len(bp.Variables)) bpi.Variables = make([]api.Variable, len(bp.Variables))
} }
for i := range bp.Variables { for i := range bp.Variables {
v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}) v, err := s.EvalExpression(bp.Variables[i], proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1})
if err != nil { if err != nil {
bpi.Variables[i] = api.Variable{Name: bp.Variables[i], Unreadable: fmt.Sprintf("eval error: %v", err)} bpi.Variables[i] = api.Variable{Name: bp.Variables[i], Unreadable: fmt.Sprintf("eval error: %v", err)}
} else { } else {
@ -1516,7 +1516,7 @@ func (d *Debugger) Function(goid, frame, deferredCall int, cfg proc.LoadConfig)
// EvalVariableInScope will attempt to evaluate the variable represented by 'symbol' // EvalVariableInScope will attempt to evaluate the variable represented by 'symbol'
// in the scope provided. // in the scope provided.
func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, expr string, cfg proc.LoadConfig) (*proc.Variable, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
@ -1524,7 +1524,7 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol str
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s.EvalVariable(symbol, cfg) return s.EvalExpression(expr, cfg)
} }
// LoadResliced will attempt to 'reslice' a map, array or slice so that the values // LoadResliced will attempt to 'reslice' a map, array or slice so that the values

@ -101,7 +101,7 @@ func evalVariable(p *proc.Target, symbol string, cfg proc.LoadConfig) (*proc.Var
return nil, err return nil, err
} }
return scope.EvalVariable(symbol, cfg) return scope.EvalExpression(symbol, cfg)
} }
func (tc *varTest) alternateVarTest() varTest { func (tc *varTest) alternateVarTest() varTest {