pkg/proc: Introduce Target and remove CommonProcess (#1834)

* pkg/proc: Introduce Target

* pkg/proc: Remove Common.fncallEnabled

Realistically we only block it on recorded backends.

* pkg/proc: Move fncallForG to Target

* pkg/proc: Remove CommonProcess

Remove final bit of functionality stored in CommonProcess and move it to
*Target.

* pkg/proc: Add SupportsFunctionCall to Target
This commit is contained in:
Derek Parker 2020-01-21 12:41:24 -08:00 committed by GitHub
parent 3f7571ec30
commit 94a20d57da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 452 additions and 492 deletions

@ -159,7 +159,6 @@ type Process struct {
breakpoints proc.BreakpointMap breakpoints proc.BreakpointMap
currentThread *Thread currentThread *Thread
selectedGoroutine *proc.G selectedGoroutine *proc.G
common proc.CommonProcess
} }
// Thread represents a thread in the core file being debugged. // Thread represents a thread in the core file being debugged.
@ -200,7 +199,7 @@ var ErrUnrecognizedFormat = errors.New("unrecognized core format")
// OpenCore will open the core file and return a Process struct. // OpenCore will open the core file and return a Process struct.
// If the DWARF information cannot be found in the binary, Delve will look // If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // for external debug files in the directories passed in.
func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error) { func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, error) {
var p *Process var p *Process
var err error var err error
for _, openFn := range openFns { for _, openFn := range openFns {
@ -217,7 +216,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error
return nil, err return nil, err
} }
return p, nil return proc.NewTarget(p), nil
} }
// initialize for core files doesn't do much // initialize for core files doesn't do much
@ -302,8 +301,8 @@ func (t *Thread) Location() (*proc.Location, error) {
// Breakpoint returns the current breakpoint this thread is stopped at. // Breakpoint returns the current breakpoint this thread is stopped at.
// For core files this always returns an empty BreakpointState struct, as // For core files this always returns an empty BreakpointState struct, as
// there are no breakpoints when debugging core files. // there are no breakpoints when debugging core files.
func (t *Thread) Breakpoint() proc.BreakpointState { func (t *Thread) Breakpoint() *proc.BreakpointState {
return proc.BreakpointState{} return &proc.BreakpointState{}
} }
// ThreadID returns the ID for this thread. // ThreadID returns the ID for this thread.
@ -434,12 +433,6 @@ func (p *Process) Valid() (bool, error) {
return true, nil return true, nil
} }
// Common returns common information across Process
// implementations.
func (p *Process) Common() *proc.CommonProcess {
return &p.common
}
// Pid returns the process ID of this process. // Pid returns the process ID of this process.
func (p *Process) Pid() int { func (p *Process) Pid() int {
return p.pid return p.pid
@ -462,11 +455,7 @@ func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.
} }
// SwitchGoroutine will change the selected and active goroutine. // SwitchGoroutine will change the selected and active goroutine.
func (p *Process) SwitchGoroutine(gid int) error { func (p *Process) SwitchGoroutine(g *proc.G) error {
g, err := proc.FindGoroutine(p, gid)
if err != nil {
return err
}
if g == nil { if g == nil {
// user specified -1 and selectedGoroutine is nil // user specified -1 and selectedGoroutine is nil
return nil return nil

@ -148,7 +148,7 @@ func TestSplicedReader(t *testing.T) {
} }
} }
func withCoreFile(t *testing.T, name, args string) *Process { func withCoreFile(t *testing.T, name, args string) *proc.Target {
// This is all very fragile and won't work on hosts with non-default core patterns. // This is all very fragile and won't work on hosts with non-default core patterns.
// Might be better to check in the binary and core? // Might be better to check in the binary and core?
tempDir, err := ioutil.TempDir("", "") tempDir, err := ioutil.TempDir("", "")

@ -90,7 +90,7 @@ type functionCallState struct {
} }
type callContext struct { type callContext struct {
p Process p *Target
// checkEscape is true if the escape check should be performed. // checkEscape is true if the escape check should be performed.
// See service/api.DebuggerCommand.UnsafeCall in service/api/types.go. // See service/api.DebuggerCommand.UnsafeCall in service/api/types.go.
@ -115,6 +115,14 @@ type continueRequest struct {
ret *Variable ret *Variable
} }
type callInjection struct {
// if continueCompleted is not nil it means we are in the process of
// executing an injected function call, see comments throughout
// pkg/proc/fncall.go for a description of how this works.
continueCompleted chan<- *G
continueRequest <-chan continueRequest
}
func (callCtx *callContext) doContinue() *G { func (callCtx *callContext) doContinue() *G {
callCtx.continueRequest <- continueRequest{cont: true} callCtx.continueRequest <- continueRequest{cont: true}
return <-callCtx.continueCompleted return <-callCtx.continueCompleted
@ -130,9 +138,9 @@ func (callCtx *callContext) doReturn(ret *Variable, err error) {
// EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'. // EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
// Because this can only be done in the current goroutine, unlike // Because this can only be done in the current goroutine, unlike
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope. // EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error { func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
bi := p.BinInfo() bi := t.BinInfo()
if !p.Common().fncallEnabled { if !t.SupportsFunctionCalls() {
return errFuncCallUnsupportedBackend return errFuncCallUnsupportedBackend
} }
@ -144,7 +152,7 @@ func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig
return errGoroutineNotRunning return errGoroutineNotRunning
} }
if callinj := p.Common().fncallForG[g.ID]; callinj != nil && callinj.continueCompleted != nil { if callinj := t.fncallForG[g.ID]; callinj != nil && callinj.continueCompleted != nil {
return errFuncCallInProgress return errFuncCallInProgress
} }
@ -162,14 +170,14 @@ func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig
continueCompleted := make(chan *G) continueCompleted := make(chan *G)
scope.callCtx = &callContext{ scope.callCtx = &callContext{
p: p, p: t,
checkEscape: checkEscape, checkEscape: checkEscape,
retLoadCfg: retLoadCfg, retLoadCfg: retLoadCfg,
continueRequest: continueRequest, continueRequest: continueRequest,
continueCompleted: continueCompleted, continueCompleted: continueCompleted,
} }
p.Common().fncallForG[g.ID] = &callInjection{ t.fncallForG[g.ID] = &callInjection{
continueCompleted, continueCompleted,
continueRequest, continueRequest,
} }
@ -178,13 +186,13 @@ func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig
contReq, ok := <-continueRequest contReq, ok := <-continueRequest
if contReq.cont { if contReq.cont {
return Continue(p) return Continue(t)
} }
return finishEvalExpressionWithCalls(p, g, contReq, ok) return finishEvalExpressionWithCalls(t, g, contReq, ok)
} }
func finishEvalExpressionWithCalls(p Process, g *G, contReq continueRequest, ok bool) error { func finishEvalExpressionWithCalls(t *Target, g *G, contReq continueRequest, ok bool) error {
fncallLog("stashing return values for %d in thread=%d\n", g.ID, g.Thread.ThreadID()) fncallLog("stashing return values for %d in thread=%d\n", g.ID, g.Thread.ThreadID())
var err error var err error
if !ok { if !ok {
@ -208,8 +216,8 @@ func finishEvalExpressionWithCalls(p Process, g *G, contReq continueRequest, ok
g.Thread.Common().returnValues = []*Variable{contReq.ret} g.Thread.Common().returnValues = []*Variable{contReq.ret}
} }
close(p.Common().fncallForG[g.ID].continueCompleted) close(t.fncallForG[g.ID].continueCompleted)
delete(p.Common().fncallForG, g.ID) delete(t.fncallForG, g.ID)
return err return err
} }
@ -234,7 +242,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
p := scope.callCtx.p p := scope.callCtx.p
bi := scope.BinInfo bi := scope.BinInfo
if !p.Common().fncallEnabled { if !p.SupportsFunctionCalls() {
return nil, errFuncCallUnsupportedBackend return nil, errFuncCallUnsupportedBackend
} }
@ -879,8 +887,8 @@ func isCallInjectionStop(loc *Location) bool {
// callInjectionProtocol is the function called from Continue to progress // callInjectionProtocol is the function called from Continue to progress
// the injection protocol for all threads. // the injection protocol for all threads.
// Returns true if a call injection terminated // Returns true if a call injection terminated
func callInjectionProtocol(p Process, threads []Thread) (done bool, err error) { func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
if len(p.Common().fncallForG) == 0 { if len(t.fncallForG) == 0 {
// we aren't injecting any calls, no need to check the threads. // we aren't injecting any calls, no need to check the threads.
return false, nil return false, nil
} }
@ -897,7 +905,7 @@ func callInjectionProtocol(p Process, threads []Thread) (done bool, err error) {
if err != nil { if err != nil {
return done, fmt.Errorf("could not determine running goroutine for thread %#x currently executing the function call injection protocol: %v", thread.ThreadID(), err) return done, fmt.Errorf("could not determine running goroutine for thread %#x currently executing the function call injection protocol: %v", thread.ThreadID(), err)
} }
callinj := p.Common().fncallForG[g.ID] callinj := t.fncallForG[g.ID]
if callinj == nil || callinj.continueCompleted == nil { if callinj == nil || callinj.continueCompleted == nil {
return false, fmt.Errorf("could not recover call injection state for goroutine %d", g.ID) return false, fmt.Errorf("could not recover call injection state for goroutine %d", g.ID)
} }
@ -905,7 +913,7 @@ func callInjectionProtocol(p Process, threads []Thread) (done bool, err error) {
callinj.continueCompleted <- g callinj.continueCompleted <- g
contReq, ok := <-callinj.continueRequest contReq, ok := <-callinj.continueRequest
if !contReq.cont { if !contReq.cont {
err := finishEvalExpressionWithCalls(p, g, contReq, ok) err := finishEvalExpressionWithCalls(t, g, contReq, ok)
if err != nil { if err != nil {
return done, err return done, err
} }

@ -126,7 +126,6 @@ type Process struct {
onDetach func() // called after a successful detach onDetach func() // called after a successful detach
common proc.CommonProcess
} }
// Thread represents an operating system thread. // Thread represents an operating system thread.
@ -184,7 +183,6 @@ func New(process *os.Process) *Process {
gcmdok: true, gcmdok: true,
threadStopInfo: true, threadStopInfo: true,
process: process, process: process,
common: proc.NewCommonProcess(true),
} }
if process != nil { if process != nil {
@ -320,7 +318,7 @@ func getLdEnvVars() []string {
// LLDBLaunch starts an instance of lldb-server and connects to it, asking // LLDBLaunch starts an instance of lldb-server and connects to it, asking
// it to launch the specified target program with the specified arguments // it to launch the specified target program with the specified arguments
// (cmd) on the specified directory wd. // (cmd) on the specified directory wd.
func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) { func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*proc.Target, error) {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
return nil, ErrUnsupportedOS return nil, ErrUnsupportedOS
@ -343,7 +341,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
var listener net.Listener var listener net.Listener
var port string var port string
var proc *exec.Cmd var process *exec.Cmd
if _, err := os.Stat(debugserverExecutable); err == nil { if _, err := os.Stat(debugserverExecutable); err == nil {
listener, err = net.Listen("tcp", "127.0.0.1:0") listener, err = net.Listen("tcp", "127.0.0.1:0")
if err != nil { if err != nil {
@ -363,7 +361,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
isDebugserver = true isDebugserver = true
proc = exec.Command(debugserverExecutable, args...) process = exec.Command(debugserverExecutable, args...)
} else { } else {
if _, err := exec.LookPath("lldb-server"); err != nil { if _, err := exec.LookPath("lldb-server"); err != nil {
return nil, &ErrBackendUnavailable{} return nil, &ErrBackendUnavailable{}
@ -374,29 +372,29 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
args = append(args, port, "--") args = append(args, port, "--")
args = append(args, cmd...) args = append(args, cmd...)
proc = exec.Command("lldb-server", args...) process = exec.Command("lldb-server", args...)
} }
if logflags.LLDBServerOutput() || logflags.GdbWire() || foreground { if logflags.LLDBServerOutput() || logflags.GdbWire() || foreground {
proc.Stdout = os.Stdout process.Stdout = os.Stdout
proc.Stderr = os.Stderr process.Stderr = os.Stderr
} }
if foreground { if foreground {
foregroundSignalsIgnore() foregroundSignalsIgnore()
proc.Stdin = os.Stdin process.Stdin = os.Stdin
} }
if wd != "" { if wd != "" {
proc.Dir = wd process.Dir = wd
} }
proc.SysProcAttr = sysProcAttr(foreground) process.SysProcAttr = sysProcAttr(foreground)
err := proc.Start() err := process.Start()
if err != nil { if err != nil {
return nil, err return nil, err
} }
p := New(proc.Process) p := New(process.Process)
p.conn.isDebugserver = isDebugserver p.conn.isDebugserver = isDebugserver
if listener != nil { if listener != nil {
@ -407,7 +405,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
if err != nil { if err != nil {
return nil, err return nil, err
} }
return p, nil return proc.NewTarget(p), nil
} }
// LLDBAttach starts an instance of lldb-server and connects to it, asking // LLDBAttach starts an instance of lldb-server and connects to it, asking
@ -415,7 +413,7 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
// Path is path to the target's executable, path only needs to be specified // Path is path to the target's executable, path only needs to be specified
// for some stubs that do not provide an automated way of determining it // for some stubs that do not provide an automated way of determining it
// (for example debugserver). // (for example debugserver).
func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error) { func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, error) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return nil, ErrUnsupportedOS return nil, ErrUnsupportedOS
} }
@ -459,7 +457,7 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*Process, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return p, nil return proc.NewTarget(p), nil
} }
// EntryPoint will return the process entry point address, useful for // EntryPoint will return the process entry point address, useful for
@ -602,11 +600,6 @@ func (p *Process) CurrentThread() proc.Thread {
return p.currentThread return p.currentThread
} }
// Common returns common information across Process implementations.
func (p *Process) Common() *proc.CommonProcess {
return &p.common
}
// SelectedGoroutine returns the current actuve selected goroutine. // SelectedGoroutine returns the current actuve selected goroutine.
func (p *Process) SelectedGoroutine() *proc.G { func (p *Process) SelectedGoroutine() *proc.G {
return p.selectedGoroutine return p.selectedGoroutine
@ -645,7 +638,6 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
} }
} }
p.common.ClearAllGCache()
for _, th := range p.threads { for _, th := range p.threads {
th.clearBreakpointState() th.clearBreakpointState()
} }
@ -751,37 +743,6 @@ func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g p.selectedGoroutine = g
} }
// StepInstruction will step exactly one CPU instruction.
func (p *Process) StepInstruction() error {
thread := p.currentThread
if p.selectedGoroutine != nil {
if p.selectedGoroutine.Thread == nil {
if _, err := p.SetBreakpoint(p.selectedGoroutine.PC, proc.NextBreakpoint, proc.SameGoroutineCondition(p.selectedGoroutine)); err != nil {
return err
}
return proc.Continue(p)
}
thread = p.selectedGoroutine.Thread.(*Thread)
}
p.common.ClearAllGCache()
if p.exited {
return &proc.ErrProcessExited{Pid: p.conn.pid}
}
thread.clearBreakpointState()
err := thread.StepInstruction()
if err != nil {
return err
}
err = thread.SetCurrentBreakpoint(true)
if err != nil {
return err
}
if g, _ := proc.GetG(thread); g != nil {
p.selectedGoroutine = g
}
return nil
}
// SwitchThread will change the internal selected thread. // SwitchThread will change the internal selected thread.
func (p *Process) SwitchThread(tid int) error { func (p *Process) SwitchThread(tid int) error {
if p.exited { if p.exited {
@ -796,13 +757,8 @@ func (p *Process) SwitchThread(tid int) error {
} }
// SwitchGoroutine will change the internal selected goroutine. // SwitchGoroutine will change the internal selected goroutine.
func (p *Process) SwitchGoroutine(gid int) error { func (p *Process) SwitchGoroutine(g *proc.G) error {
g, err := proc.FindGoroutine(p, gid)
if err != nil {
return err
}
if g == nil { if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil return nil
} }
if g.Thread != nil { if g.Thread != nil {
@ -885,7 +841,6 @@ func (p *Process) Restart(pos string) error {
p.exited = false p.exited = false
p.common.ClearAllGCache()
for _, th := range p.threads { for _, th := range p.threads {
th.clearBreakpointState() th.clearBreakpointState()
} }
@ -1243,8 +1198,8 @@ func (t *Thread) Location() (*proc.Location, error) {
} }
// Breakpoint returns the current active breakpoint for this thread. // Breakpoint returns the current active breakpoint for this thread.
func (t *Thread) Breakpoint() proc.BreakpointState { func (t *Thread) Breakpoint() *proc.BreakpointState {
return t.CurrentBreakpoint return &t.CurrentBreakpoint
} }
// ThreadID returns this threads ID. // ThreadID returns this threads ID.

@ -12,6 +12,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
"github.com/go-delve/delve/pkg/proc"
) )
// Record uses rr to record the execution of the specified program and // Record uses rr to record the execution of the specified program and
@ -54,7 +56,7 @@ func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
// Replay starts an instance of rr in replay mode, with the specified trace // Replay starts an instance of rr in replay mode, with the specified trace
// directory, and connects to it. // directory, and connects to it.
func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*Process, error) { func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.Target, error) {
if err := checkRRAvailabe(); err != nil { if err := checkRRAvailabe(); err != nil {
return nil, err return nil, err
} }
@ -94,7 +96,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
return nil, err return nil, err
} }
return p, nil return proc.NewTarget(p), nil
} }
// ErrPerfEventParanoid is the error returned by Reply and Record if // ErrPerfEventParanoid is the error returned by Reply and Record if
@ -263,13 +265,13 @@ func splitQuotedFields(in string) []string {
} }
// RecordAndReplay acts like calling Record and then Replay. // RecordAndReplay acts like calling Record and then Replay.
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (p *Process, tracedir string, err error) { func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (*proc.Target, string, error) {
tracedir, err = Record(cmd, wd, quiet) tracedir, err := Record(cmd, wd, quiet)
if tracedir == "" { if tracedir == "" {
return nil, "", err return nil, "", err
} }
p, err = Replay(tracedir, quiet, true, debugInfoDirs) t, err := Replay(tracedir, quiet, true, debugInfoDirs)
return p, tracedir, err return t, tracedir, err
} }
// safeRemoveAll removes dir and its contents but only as long as dir does // safeRemoveAll removes dir and its contents but only as long as dir does

@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
os.Exit(protest.RunTestsWithFixtures(m)) os.Exit(protest.RunTestsWithFixtures(m))
} }
func withTestRecording(name string, t testing.TB, fn func(p *gdbserial.Process, fixture protest.Fixture)) { func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixture protest.Fixture)) {
fixture := protest.BuildFixture(name, 0) fixture := protest.BuildFixture(name, 0)
protest.MustHaveRecordingAllowed(t) protest.MustHaveRecordingAllowed(t)
if path, _ := exec.LookPath("rr"); path == "" { if path, _ := exec.LookPath("rr"); path == "" {
@ -36,9 +36,7 @@ func withTestRecording(name string, t testing.TB, fn func(p *gdbserial.Process,
} }
t.Logf("replaying %q", tracedir) t.Logf("replaying %q", tracedir)
defer func() { defer p.Detach(true)
p.Detach(true)
}()
fn(p, fixture) fn(p, fixture)
} }
@ -71,7 +69,7 @@ func setFunctionBreakpoint(p proc.Process, t *testing.T, fname string) *proc.Bre
func TestRestartAfterExit(t *testing.T) { func TestRestartAfterExit(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestRecording("testnextprog", t, func(p *gdbserial.Process, fixture protest.Fixture) { withTestRecording("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "main.main") setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue") assertNoError(proc.Continue(p), t, "Continue")
loc, err := p.CurrentThread().Location() loc, err := p.CurrentThread().Location()
@ -98,7 +96,7 @@ func TestRestartAfterExit(t *testing.T) {
func TestRestartDuringStop(t *testing.T) { func TestRestartDuringStop(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestRecording("testnextprog", t, func(p *gdbserial.Process, fixture protest.Fixture) { withTestRecording("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "main.main") setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue") assertNoError(proc.Continue(p), t, "Continue")
loc, err := p.CurrentThread().Location() loc, err := p.CurrentThread().Location()
@ -139,7 +137,7 @@ func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, li
func TestReverseBreakpointCounts(t *testing.T) { func TestReverseBreakpointCounts(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestRecording("bpcountstest", t, func(p *gdbserial.Process, fixture protest.Fixture) { withTestRecording("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) {
endbp := setFileBreakpoint(p, t, fixture, 28) endbp := setFileBreakpoint(p, t, fixture, 28)
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
loc, _ := p.CurrentThread().Location() loc, _ := p.CurrentThread().Location()
@ -183,7 +181,7 @@ func TestReverseBreakpointCounts(t *testing.T) {
}) })
} }
func getPosition(p *gdbserial.Process, t *testing.T) (when string, loc *proc.Location) { func getPosition(p *proc.Target, t *testing.T) (when string, loc *proc.Location) {
var err error var err error
when, err = p.When() when, err = p.When()
assertNoError(err, t, "When") assertNoError(err, t, "When")
@ -194,7 +192,7 @@ func getPosition(p *gdbserial.Process, t *testing.T) (when string, loc *proc.Loc
func TestCheckpoints(t *testing.T) { func TestCheckpoints(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestRecording("continuetestprog", t, func(p *gdbserial.Process, fixture protest.Fixture) { withTestRecording("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
// Continues until start of main.main, record output of 'when' // Continues until start of main.main, record output of 'when'
bp := setFunctionBreakpoint(p, t, "main.main") bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue") assertNoError(proc.Continue(p), t, "Continue")
@ -278,7 +276,7 @@ func TestCheckpoints(t *testing.T) {
func TestIssue1376(t *testing.T) { func TestIssue1376(t *testing.T) {
// Backward Continue should terminate when it encounters the start of the process. // Backward Continue should terminate when it encounters the start of the process.
protest.AllowRecording(t) protest.AllowRecording(t)
withTestRecording("continuetestprog", t, func(p *gdbserial.Process, fixture protest.Fixture) { withTestRecording("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFunctionBreakpoint(p, t, "main.main") bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue (forward)") assertNoError(proc.Continue(p), t, "Continue (forward)")
_, err := p.ClearBreakpoint(bp.Addr) _, err := p.ClearBreakpoint(bp.Addr)

@ -0,0 +1,60 @@
package proc
import "encoding/binary"
type goroutineCache struct {
partialGCache map[int]*G
allGCache []*G
allgentryAddr, allglenAddr uint64
}
func (gcache *goroutineCache) init(bi *BinaryInfo) {
var err error
exeimage := bi.Images[0]
rdr := exeimage.DwarfReader()
gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
rdr.Seek(0)
gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
if err != nil {
// try old name (pre Go 1.6)
gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
}
}
func (gcache *goroutineCache) getRuntimeAllg(bi *BinaryInfo, mem MemoryReadWriter) (uint64, uint64, error) {
if gcache.allglenAddr == 0 || gcache.allgentryAddr == 0 {
return 0, 0, ErrNoRuntimeAllG
}
allglenBytes := make([]byte, 8)
_, err := mem.ReadMemory(allglenBytes, uintptr(gcache.allglenAddr))
if err != nil {
return 0, 0, err
}
allglen := binary.LittleEndian.Uint64(allglenBytes)
faddr := make([]byte, bi.Arch.PtrSize())
_, err = mem.ReadMemory(faddr, uintptr(gcache.allgentryAddr))
if err != nil {
return 0, 0, err
}
allgptr := binary.LittleEndian.Uint64(faddr)
return allgptr, allglen, nil
}
func (gcache *goroutineCache) addGoroutine(g *G) {
if gcache.partialGCache == nil {
gcache.partialGCache = make(map[int]*G)
}
gcache.partialGCache[g.ID] = g
}
// Clear clears the cached contents of the cache for runtime.allgs.
func (gcache *goroutineCache) Clear() {
gcache.partialGCache = nil
gcache.allGCache = nil
}

@ -69,8 +69,6 @@ type Info interface {
Valid() (bool, error) Valid() (bool, error)
BinInfo() *BinaryInfo BinInfo() *BinaryInfo
EntryPoint() (uint64, error) EntryPoint() (uint64, error)
// Common returns a struct with fields common to all backends
Common() *CommonProcess
ThreadInfo ThreadInfo
GoroutineInfo GoroutineInfo
@ -93,9 +91,8 @@ type GoroutineInfo interface {
// ProcessManipulation is an interface for changing the execution state of a process. // ProcessManipulation is an interface for changing the execution state of a process.
type ProcessManipulation interface { type ProcessManipulation interface {
ContinueOnce() (trapthread Thread, err error) ContinueOnce() (trapthread Thread, err error)
StepInstruction() error
SwitchThread(int) error SwitchThread(int) error
SwitchGoroutine(int) error SwitchGoroutine(*G) error
RequestManualStop() error RequestManualStop() error
// CheckAndClearManualStopRequest returns true the first time it's called // CheckAndClearManualStopRequest returns true the first time it's called
// after a call to RequestManualStop. // after a call to RequestManualStop.
@ -110,34 +107,3 @@ type BreakpointManipulation interface {
ClearBreakpoint(addr uint64) (*Breakpoint, error) ClearBreakpoint(addr uint64) (*Breakpoint, error)
ClearInternalBreakpoints() error ClearInternalBreakpoints() error
} }
// CommonProcess contains fields used by this package, common to all
// implementations of the Process interface.
type CommonProcess struct {
goroutineCache
fncallEnabled bool
fncallForG map[int]*callInjection
}
type goroutineCache struct {
partialGCache map[int]*G
allGCache []*G
allgentryAddr, allglenAddr uint64
}
type callInjection struct {
// if continueCompleted is not nil it means we are in the process of
// executing an injected function call, see comments throughout
// pkg/proc/fncall.go for a description of how this works.
continueCompleted chan<- *G
continueRequest <-chan continueRequest
}
// NewCommonProcess returns a struct with fields common across
// all process implementations.
func NewCommonProcess(fncallEnabled bool) CommonProcess {
return CommonProcess{fncallEnabled: fncallEnabled, fncallForG: make(map[int]*callInjection)}
}

@ -12,12 +12,12 @@ import (
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation") var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
// Launch returns ErrNativeBackendDisabled. // Launch returns ErrNativeBackendDisabled.
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, error) {
return nil, ErrNativeBackendDisabled return nil, ErrNativeBackendDisabled
} }
// Attach returns ErrNativeBackendDisabled. // Attach returns ErrNativeBackendDisabled.
func Attach(pid int, _ []string) (*Process, error) { func Attach(pid int, _ []string) (*proc.Target, error) {
return nil, ErrNativeBackendDisabled return nil, ErrNativeBackendDisabled
} }

@ -30,7 +30,6 @@ type Process struct {
// Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread // Normally selectedGoroutine is currentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread
selectedGoroutine *proc.G selectedGoroutine *proc.G
common proc.CommonProcess
os *OSProcessDetails os *OSProcessDetails
firstStart bool firstStart bool
stopMu sync.Mutex stopMu sync.Mutex
@ -247,7 +246,6 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
return nil, err return nil, err
} }
dbp.common.ClearAllGCache()
for _, th := range dbp.threads { for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear() th.CurrentBreakpoint.Clear()
} }
@ -267,41 +265,6 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
return trapthread, err return trapthread, err
} }
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// associated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Process) StepInstruction() (err error) {
thread := dbp.currentThread
if dbp.selectedGoroutine != nil {
if dbp.selectedGoroutine.Thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(dbp.selectedGoroutine.PC, proc.NextBreakpoint, proc.SameGoroutineCondition(dbp.selectedGoroutine)); err != nil {
return err
}
return proc.Continue(dbp)
}
thread = dbp.selectedGoroutine.Thread.(*Thread)
}
dbp.common.ClearAllGCache()
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
thread.CurrentBreakpoint.Clear()
err = thread.StepInstruction()
if err != nil {
return err
}
err = thread.SetCurrentBreakpoint(true)
if err != nil {
return err
}
if g, _ := proc.GetG(thread); g != nil {
dbp.selectedGoroutine = g
}
return nil
}
// SwitchThread changes from current thread to the thread specified by `tid`. // SwitchThread changes from current thread to the thread specified by `tid`.
func (dbp *Process) SwitchThread(tid int) error { func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited { if dbp.exited {
@ -317,14 +280,10 @@ func (dbp *Process) SwitchThread(tid int) error {
// SwitchGoroutine changes from current thread to the thread // SwitchGoroutine changes from current thread to the thread
// running the specified goroutine. // running the specified goroutine.
func (dbp *Process) SwitchGoroutine(gid int) error { func (dbp *Process) SwitchGoroutine(g *proc.G) error {
if dbp.exited { if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()} return &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
g, err := proc.FindGoroutine(dbp, gid)
if err != nil {
return err
}
if g == nil { if g == nil {
// user specified -1 and selectedGoroutine is nil // user specified -1 and selectedGoroutine is nil
return nil return nil
@ -414,9 +373,3 @@ func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
_, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction()) _, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction())
return err return err
} }
// Common returns common information across Process
// implementations
func (dbp *Process) Common() *proc.CommonProcess {
return &dbp.common
}

@ -37,7 +37,7 @@ type OSProcessDetails struct {
// custom fork/exec process in order to take advantage of // custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin which will turn Unix signals into // PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions. // Mach exceptions.
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, error) {
// check that the argument to Launch is an executable file // check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, proc.ErrNotExecutable return nil, proc.ErrNotExecutable
@ -104,7 +104,6 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
return nil, err return nil, err
} }
dbp.common.ClearAllGCache()
for _, th := range dbp.threads { for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear() th.CurrentBreakpoint.Clear()
} }
@ -127,11 +126,11 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
return nil, err return nil, err
} }
return dbp, err return proc.NewTarget(dbp), err
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int, _ []string) (*Process, error) { func Attach(pid int, _ []string) (*proc.Target, error) {
dbp := New(pid) dbp := New(pid)
kret := C.acquire_mach_task(C.int(pid), kret := C.acquire_mach_task(C.int(pid),
@ -159,7 +158,7 @@ func Attach(pid int, _ []string) (*Process, error) {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
// Kill kills the process. // Kill kills the process.

@ -43,7 +43,7 @@ type OSProcessDetails struct {
// to be supplied to that process. `wd` is working directory of the program. // to be supplied to that process. `wd` is working directory of the program.
// If the DWARF information cannot be found in the binary, Delve will look // If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // for external debug files in the directories passed in.
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*proc.Target, error) {
var ( var (
process *exec.Cmd process *exec.Cmd
err error err error
@ -60,7 +60,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
} }
dbp := New(0) dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
process = exec.Command(cmd[0]) process = exec.Command(cmd[0])
process.Args = cmd process.Args = cmd
@ -88,15 +87,14 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
// Attach to an existing process with the given PID. Once attached, if // Attach to an existing process with the given PID. Once attached, if
// the DWARF information cannot be found in the binary, Delve will look // the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // for external debug files in the directories passed in.
func Attach(pid int, debugInfoDirs []string) (*Process, error) { func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
dbp := New(pid) dbp := New(pid)
dbp.common = proc.NewCommonProcess(true)
var err error var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) }) dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
@ -113,7 +111,7 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {

@ -48,7 +48,7 @@ type OSProcessDetails struct {
// to be supplied to that process. `wd` is working directory of the program. // to be supplied to that process. `wd` is working directory of the program.
// If the DWARF information cannot be found in the binary, Delve will look // If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // for external debug files in the directories passed in.
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*proc.Target, error) {
var ( var (
process *exec.Cmd process *exec.Cmd
err error err error
@ -65,7 +65,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
} }
dbp := New(0) dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
process = exec.Command(cmd[0]) process = exec.Command(cmd[0])
process.Args = cmd process.Args = cmd
@ -93,15 +92,14 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil {
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
// Attach to an existing process with the given PID. Once attached, if // Attach to an existing process with the given PID. Once attached, if
// the DWARF information cannot be found in the binary, Delve will look // the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // for external debug files in the directories passed in.
func Attach(pid int, debugInfoDirs []string) (*Process, error) { func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
dbp := New(pid) dbp := New(pid)
dbp.common = proc.NewCommonProcess(true)
var err error var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) }) dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
@ -125,7 +123,7 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {

@ -36,7 +36,7 @@ func openExecutablePathPE(path string) (*pe.File, io.Closer, error) {
} }
// Launch creates and begins debugging a new process. // Launch creates and begins debugging a new process.
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) { func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, error) {
argv0Go, err := filepath.Abs(cmd[0]) argv0Go, err := filepath.Abs(cmd[0])
if err != nil { if err != nil {
return nil, err return nil, err
@ -57,7 +57,6 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
var p *os.Process var p *os.Process
dbp := New(0) dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
attr := &os.ProcAttr{ attr := &os.ProcAttr{
Dir: wd, Dir: wd,
@ -80,7 +79,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
dbp.Detach(true) dbp.Detach(true)
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {
@ -151,7 +150,7 @@ func findExePath(pid int) (string, error) {
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int, _ []string) (*Process, error) { func Attach(pid int, _ []string) (*proc.Target, error) {
dbp := New(pid) dbp := New(pid)
var err error var err error
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
@ -169,7 +168,7 @@ func Attach(pid int, _ []string) (*Process, error) {
dbp.Detach(true) dbp.Detach(true)
return nil, err return nil, err
} }
return dbp, nil return proc.NewTarget(dbp), nil
} }
// kill kills the process. // kill kills the process.

@ -145,8 +145,8 @@ func (t *Thread) SetCurrentBreakpoint(adjustPC bool) error {
// Breakpoint returns the current breakpoint that is active // Breakpoint returns the current breakpoint that is active
// on this thread. // on this thread.
func (t *Thread) Breakpoint() proc.BreakpointState { func (t *Thread) Breakpoint() *proc.BreakpointState {
return t.CurrentBreakpoint return &t.CurrentBreakpoint
} }
// ThreadID returns the ID of this thread. // ThreadID returns the ID of this thread.

@ -2,7 +2,6 @@ package proc
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
@ -75,8 +74,6 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri
createUnrecoveredPanicBreakpoint(p, writeBreakpoint) createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
createFatalThrowBreakpoint(p, writeBreakpoint) createFatalThrowBreakpoint(p, writeBreakpoint)
p.Common().goroutineCache.init(p.BinInfo())
return nil return nil
} }
@ -142,7 +139,7 @@ func FindFunctionLocation(p Process, funcName string, lineOffset int) ([]uint64,
} }
// Next continues execution until the next source line. // Next continues execution until the next source line.
func Next(dbp Process) (err error) { func Next(dbp *Target) (err error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return err return err
} }
@ -161,7 +158,7 @@ func Next(dbp Process) (err error) {
// Continue continues execution of the debugged // Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint // process. It will continue until it hits a breakpoint
// or is otherwise stopped. // or is otherwise stopped.
func Continue(dbp Process) error { func Continue(dbp *Target) error {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return err return err
} }
@ -181,6 +178,7 @@ func Continue(dbp Process) error {
dbp.ClearInternalBreakpoints() dbp.ClearInternalBreakpoints()
return nil return nil
} }
dbp.ClearAllGCache()
trapthread, err := dbp.ContinueOnce() trapthread, err := dbp.ContinueOnce()
if err != nil { if err != nil {
return err return err
@ -230,7 +228,7 @@ func Continue(dbp Process) error {
return err return err
} }
return conditionErrors(threads) return conditionErrors(threads)
case g == nil || dbp.Common().fncallForG[g.ID] == nil: case g == nil || dbp.fncallForG[g.ID] == nil:
// a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo // a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo
if !arch.BreakInstrMovesPC() { if !arch.BreakInstrMovesPC() {
bpsize := arch.BreakpointSize() bpsize := arch.BreakpointSize()
@ -355,7 +353,7 @@ func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string)
// Step will continue until another source line is reached. // Step will continue until another source line is reached.
// Will step into functions. // Will step into functions.
func Step(dbp Process) (err error) { func Step(dbp *Target) (err error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return err return err
} }
@ -418,7 +416,7 @@ func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
// StepOut will continue until the current goroutine exits the // StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed // function currently being executed or a deferred function is executed
func StepOut(dbp Process) error { func StepOut(dbp *Target) error {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return err return err
} }
@ -503,6 +501,43 @@ func StepOut(dbp Process) error {
return Continue(dbp) return Continue(dbp)
} }
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// associated with the selected goroutine. All other
// threads will remain stopped.
func StepInstruction(dbp *Target) (err error) {
thread := dbp.CurrentThread()
g := dbp.SelectedGoroutine()
if g != nil {
if g.Thread == nil {
// Step called on parked goroutine
if _, err := dbp.SetBreakpoint(g.PC, NextBreakpoint,
SameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err
}
return Continue(dbp)
}
thread = g.Thread
}
dbp.ClearAllGCache()
if ok, err := dbp.Valid(); !ok {
return err
}
thread.Breakpoint().Clear()
err = thread.StepInstruction()
if err != nil {
return err
}
err = thread.SetCurrentBreakpoint(true)
if err != nil {
return err
}
if tg, _ := GetG(thread); tg != nil {
dbp.SetSelectedGoroutine(tg)
}
return nil
}
// GoroutinesInfo searches for goroutines starting at index 'start', and // GoroutinesInfo searches for goroutines starting at index 'start', and
// returns an array of up to 'count' (or all found elements, if 'count' is 0) // returns an array of up to 'count' (or all found elements, if 'count' is 0)
// G structures representing the information Delve care about from the internal // G structures representing the information Delve care about from the internal
@ -510,14 +545,14 @@ func StepOut(dbp Process) error {
// GoroutinesInfo also returns the next index to be used as 'start' argument // GoroutinesInfo also returns the next index to be used as 'start' argument
// while scanning for all available goroutines, or -1 if there was an error // while scanning for all available goroutines, or -1 if there was an error
// or if the index already reached the last possible value. // or if the index already reached the last possible value.
func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) { func GoroutinesInfo(dbp *Target, start, count int) ([]*G, int, error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return nil, -1, err return nil, -1, err
} }
if dbp.Common().allGCache != nil { if dbp.gcache.allGCache != nil {
// We can't use the cached array to fulfill a subrange request // We can't use the cached array to fulfill a subrange request
if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) { if start == 0 && (count == 0 || count >= len(dbp.gcache.allGCache)) {
return dbp.Common().allGCache, -1, nil return dbp.gcache.allGCache, -1, nil
} }
} }
@ -537,7 +572,7 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
} }
} }
allgptr, allglen, err := dbp.Common().getRuntimeAllg(dbp.BinInfo(), dbp.CurrentThread()) allgptr, allglen, err := dbp.gcache.getRuntimeAllg(dbp.BinInfo(), dbp.CurrentThread())
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
@ -569,68 +604,18 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
if g.Status != Gdead { if g.Status != Gdead {
allg = append(allg, g) allg = append(allg, g)
} }
dbp.Common().addGoroutine(g) dbp.gcache.addGoroutine(g)
} }
if start == 0 { if start == 0 {
dbp.Common().allGCache = allg dbp.gcache.allGCache = allg
} }
return allg, -1, nil return allg, -1, nil
} }
func (gcache *goroutineCache) init(bi *BinaryInfo) {
var err error
exeimage := bi.Images[0]
rdr := exeimage.DwarfReader()
gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
rdr.Seek(0)
gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
if err != nil {
// try old name (pre Go 1.6)
gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
}
}
func (gcache *goroutineCache) getRuntimeAllg(bi *BinaryInfo, mem MemoryReadWriter) (uint64, uint64, error) {
if gcache.allglenAddr == 0 || gcache.allgentryAddr == 0 {
return 0, 0, ErrNoRuntimeAllG
}
allglenBytes := make([]byte, 8)
_, err := mem.ReadMemory(allglenBytes, uintptr(gcache.allglenAddr))
if err != nil {
return 0, 0, err
}
allglen := binary.LittleEndian.Uint64(allglenBytes)
faddr := make([]byte, bi.Arch.PtrSize())
_, err = mem.ReadMemory(faddr, uintptr(gcache.allgentryAddr))
if err != nil {
return 0, 0, err
}
allgptr := binary.LittleEndian.Uint64(faddr)
return allgptr, allglen, nil
}
func (gcache *goroutineCache) addGoroutine(g *G) {
if gcache.partialGCache == nil {
gcache.partialGCache = make(map[int]*G)
}
gcache.partialGCache[g.ID] = g
}
// ClearAllGCache clears the cached contents of the cache for runtime.allgs.
func (gcache *goroutineCache) ClearAllGCache() {
gcache.partialGCache = nil
gcache.allGCache = nil
}
// FindGoroutine returns a G struct representing the goroutine // FindGoroutine returns a G struct representing the goroutine
// specified by `gid`. // specified by `gid`.
func FindGoroutine(dbp Process, gid int) (*G, error) { func FindGoroutine(dbp *Target, gid int) (*G, error) {
if selg := dbp.SelectedGoroutine(); (gid == -1) || (selg != nil && selg.ID == gid) || (selg == nil && gid == 0) { if selg := dbp.SelectedGoroutine(); (gid == -1) || (selg != nil && selg.ID == gid) || (selg == nil && gid == 0) {
// Return the currently selected goroutine in the following circumstances: // Return the currently selected goroutine in the following circumstances:
// //
@ -667,7 +652,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) {
} }
} }
if g := dbp.Common().partialGCache[gid]; g != nil { if g := dbp.gcache.partialGCache[gid]; g != nil {
return g, nil return g, nil
} }
@ -696,7 +681,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) {
// ConvertEvalScope returns a new EvalScope in the context of the // ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame. // specified goroutine ID and stack frame.
// If deferCall is > 0 the eval scope will be relative to the specified deferred call. // If deferCall is > 0 the eval scope will be relative to the specified deferred call.
func ConvertEvalScope(dbp Process, gid, frame, deferCall int) (*EvalScope, error) { func ConvertEvalScope(dbp *Target, gid, frame, deferCall int) (*EvalScope, error) {
if _, err := dbp.Valid(); err != nil { if _, err := dbp.Valid(); err != nil {
return nil, err return nil, err
} }

File diff suppressed because it is too large Load Diff

@ -29,7 +29,7 @@ func TestIssue419(t *testing.T) {
errChan := make(chan error, 2) errChan := make(chan error, 2)
// SIGINT directed at the inferior should be passed along not swallowed by delve // SIGINT directed at the inferior should be passed along not swallowed by delve
withTestProcess("issue419", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) {
defer close(errChan) defer close(errChan)
setFunctionBreakpoint(p, t, "main.main") setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")

@ -23,7 +23,7 @@ func TestScopeWithEscapedVariable(t *testing.T) {
return return
} }
withTestProcess("scopeescapevareval", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("scopeescapevareval", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue") assertNoError(proc.Continue(p), t, "Continue")
// On the breakpoint there are two 'a' variables in scope, the one that // On the breakpoint there are two 'a' variables in scope, the one that
@ -72,7 +72,7 @@ func TestScope(t *testing.T) {
scopeChecks := getScopeChecks(scopetestPath, t) scopeChecks := getScopeChecks(scopetestPath, t)
withTestProcess("scopetest", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) {
for i := range scopeChecks { for i := range scopeChecks {
setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line) setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line)
} }
@ -237,7 +237,7 @@ func (check *scopeCheck) Parse(descr string, t *testing.T) {
} }
} }
func (scopeCheck *scopeCheck) checkLocalsAndArgs(p proc.Process, t *testing.T) (*proc.EvalScope, bool) { func (scopeCheck *scopeCheck) checkLocalsAndArgs(p *proc.Target, t *testing.T) (*proc.EvalScope, bool) {
scope, err := proc.GoroutineScope(p.CurrentThread()) scope, err := proc.GoroutineScope(p.CurrentThread())
assertNoError(err, t, "GoroutineScope()") assertNoError(err, t, "GoroutineScope()")

47
pkg/proc/target.go Normal file

@ -0,0 +1,47 @@
package proc
// Target represents the process being debugged.
type Target struct {
Process
// fncallForG stores a mapping of current active function calls.
fncallForG map[int]*callInjection
// gcache is a cache for Goroutines that we
// have read and parsed from the targets memory.
// This must be cleared whenever the target is resumed.
gcache goroutineCache
}
// NewTarget returns an initialized Target object.
func NewTarget(p Process) *Target {
t := &Target{
Process: p,
fncallForG: make(map[int]*callInjection),
}
t.gcache.init(p.BinInfo())
return t
}
// SupportsFunctionCalls returns whether or not the backend supports
// calling functions during a debug session.
// Currently only non-recorded processes running on AMD64 support
// function calls.
func (t *Target) SupportsFunctionCalls() bool {
if ok, _ := t.Process.Recorded(); ok {
return false
}
_, ok := t.Process.BinInfo().Arch.(*AMD64)
return ok
}
// ClearAllGCache clears the internal Goroutine cache.
// This should be called anytime the target process executes instructions.
func (t *Target) ClearAllGCache() {
t.gcache.Clear()
}
func (t *Target) Restart(from string) error {
t.ClearAllGCache()
return t.Process.Restart(from)
}

@ -20,7 +20,7 @@ type Thread interface {
Location() (*Location, error) Location() (*Location, error)
// Breakpoint will return the breakpoint that this thread is stopped at or // Breakpoint will return the breakpoint that this thread is stopped at or
// nil if the thread is not stopped at any breakpoint. // nil if the thread is not stopped at any breakpoint.
Breakpoint() BreakpointState Breakpoint() *BreakpointState
ThreadID() int ThreadID() int
// Registers returns the CPU registers of this thread. The contents of the // Registers returns the CPU registers of this thread. The contents of the

@ -10,7 +10,7 @@ import (
func TestGoroutineCreationLocation(t *testing.T) { func TestGoroutineCreationLocation(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("goroutinestackprog", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFunctionBreakpoint(p, t, "main.agoroutine") bp := setFunctionBreakpoint(p, t, "main.agoroutine")
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")

@ -37,7 +37,7 @@ type Debugger struct {
processArgs []string processArgs []string
// TODO(DO NOT MERGE WITHOUT) rename to targetMutex // TODO(DO NOT MERGE WITHOUT) rename to targetMutex
processMutex sync.Mutex processMutex sync.Mutex
target proc.Process target *proc.Target
log *logrus.Entry log *logrus.Entry
running bool running bool
@ -102,7 +102,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
d.target = p d.target = p
case d.config.CoreFile != "": case d.config.CoreFile != "":
var p proc.Process var p *proc.Target
var err error var err error
switch d.config.Backend { switch d.config.Backend {
case "rr": case "rr":
@ -165,7 +165,7 @@ func (d *Debugger) checkGoVersion() error {
} }
// Launch will start a process with the given args and working directory. // Launch will start a process with the given args and working directory.
func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error) { func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) {
switch d.config.Backend { switch d.config.Backend {
case "native": case "native":
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories) return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories)
@ -190,7 +190,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (proc.Process, error)
var ErrNoAttachPath = errors.New("must specify executable path on macOS") var ErrNoAttachPath = errors.New("must specify executable path on macOS")
// Attach will attach to the process specified by 'pid'. // Attach will attach to the process specified by 'pid'.
func (d *Debugger) Attach(pid int, path string) (proc.Process, error) { func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) {
switch d.config.Backend { switch d.config.Backend {
case "native": case "native":
return native.Attach(pid, d.config.DebugInfoDirectories) return native.Attach(pid, d.config.DebugInfoDirectories)
@ -208,7 +208,7 @@ func (d *Debugger) Attach(pid int, path string) (proc.Process, error) {
var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install XCode's command line tools or lldb-server") var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install XCode's command line tools or lldb-server")
func betterGdbserialLaunchError(p proc.Process, err error) (proc.Process, error) { func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error) {
if runtime.GOOS != "darwin" { if runtime.GOOS != "darwin" {
return p, err return p, err
} }
@ -717,7 +717,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
err = proc.Step(d.target) err = proc.Step(d.target)
case api.StepInstruction: case api.StepInstruction:
d.log.Debug("single stepping") d.log.Debug("single stepping")
err = d.target.StepInstruction() err = proc.StepInstruction(d.target)
case api.ReverseStepInstruction: case api.ReverseStepInstruction:
d.log.Debug("reverse single stepping") d.log.Debug("reverse single stepping")
if err := d.target.Direction(proc.Backward); err != nil { if err := d.target.Direction(proc.Backward); err != nil {
@ -726,7 +726,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
defer func() { defer func() {
d.target.Direction(proc.Forward) d.target.Direction(proc.Forward)
}() }()
err = d.target.StepInstruction() err = proc.StepInstruction(d.target)
case api.StepOut: case api.StepOut:
d.log.Debug("step out") d.log.Debug("step out")
err = proc.StepOut(d.target) err = proc.StepOut(d.target)
@ -736,7 +736,10 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
withBreakpointInfo = false withBreakpointInfo = false
case api.SwitchGoroutine: case api.SwitchGoroutine:
d.log.Debugf("switching to goroutine %d", command.GoroutineID) d.log.Debugf("switching to goroutine %d", command.GoroutineID)
err = d.target.SwitchGoroutine(command.GoroutineID) g, err := proc.FindGoroutine(d.target, command.GoroutineID)
if err == nil {
err = d.target.SwitchGoroutine(g)
}
withBreakpointInfo = false withBreakpointInfo = false
case api.Halt: case api.Halt:
// RequestManualStop already called // RequestManualStop already called

@ -59,7 +59,7 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) {
} }
} }
func findFirstNonRuntimeFrame(p proc.Process) (proc.Stackframe, error) { func findFirstNonRuntimeFrame(p *proc.Target) (proc.Stackframe, error) {
frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10) frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10)
if err != nil { if err != nil {
return proc.Stackframe{}, err return proc.Stackframe{}, err
@ -73,7 +73,7 @@ func findFirstNonRuntimeFrame(p proc.Process) (proc.Stackframe, error) {
return proc.Stackframe{}, fmt.Errorf("non-runtime frame not found") return proc.Stackframe{}, fmt.Errorf("non-runtime frame not found")
} }
func evalScope(p proc.Process) (*proc.EvalScope, error) { func evalScope(p *proc.Target) (*proc.EvalScope, error) {
if testBackend != "rr" { if testBackend != "rr" {
return proc.GoroutineScope(p.CurrentThread()) return proc.GoroutineScope(p.CurrentThread())
} }
@ -84,7 +84,7 @@ func evalScope(p proc.Process) (*proc.EvalScope, error) {
return proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame), nil return proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame), nil
} }
func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { func evalVariable(p *proc.Target, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
scope, err := evalScope(p) scope, err := evalScope(p)
if err != nil { if err != nil {
return nil, err return nil, err
@ -99,7 +99,7 @@ func (tc *varTest) alternateVarTest() varTest {
return r return r
} }
func setVariable(p proc.Process, symbol, value string) error { func setVariable(p *proc.Target, symbol, value string) error {
scope, err := proc.GoroutineScope(p.CurrentThread()) scope, err := proc.GoroutineScope(p.CurrentThread())
if err != nil { if err != nil {
return err return err
@ -107,16 +107,16 @@ func setVariable(p proc.Process, symbol, value string) error {
return scope.SetVariable(symbol, value) return scope.SetVariable(symbol, value)
} }
func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture protest.Fixture)) { func withTestProcess(name string, t *testing.T, fn func(p *proc.Target, fixture protest.Fixture)) {
withTestProcessArgs(name, t, ".", []string{}, 0, fn) withTestProcessArgs(name, t, ".", []string{}, 0, fn)
} }
func withTestProcessArgs(name string, t *testing.T, wd string, args []string, buildFlags protest.BuildFlags, fn func(p proc.Process, fixture protest.Fixture)) { func withTestProcessArgs(name string, t *testing.T, wd string, args []string, buildFlags protest.BuildFlags, fn func(p *proc.Target, fixture protest.Fixture)) {
if buildMode == "pie" { if buildMode == "pie" {
buildFlags |= protest.BuildModePIE buildFlags |= protest.BuildModePIE
} }
fixture := protest.BuildFixture(name, buildFlags) fixture := protest.BuildFixture(name, buildFlags)
var p proc.Process var p *proc.Target
var err error var err error
var tracedir string var tracedir string
switch testBackend { switch testBackend {
@ -189,7 +189,7 @@ func TestVariableEvaluation(t *testing.T) {
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
err := proc.Continue(p) err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
@ -247,7 +247,7 @@ func TestSetVariable(t *testing.T) {
{"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"}, {"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"},
} }
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
for _, tc := range testcases { for _, tc := range testcases {
@ -316,7 +316,7 @@ func TestVariableEvaluationShort(t *testing.T) {
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
err := proc.Continue(p) err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
@ -372,7 +372,7 @@ func TestMultilineVariableEvaluation(t *testing.T) {
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
err := proc.Continue(p) err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
@ -446,7 +446,7 @@ func TestLocalVariables(t *testing.T) {
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
err := proc.Continue(p) err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
@ -483,7 +483,7 @@ func TestLocalVariables(t *testing.T) {
func TestEmbeddedStruct(t *testing.T) { func TestEmbeddedStruct(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
testcases := []varTest{ testcases := []varTest{
{"b.val", true, "-314", "-314", "int", nil}, {"b.val", true, "-314", "-314", "int", nil},
{"b.A.val", true, "-314", "-314", "int", nil}, {"b.A.val", true, "-314", "-314", "int", nil},
@ -524,7 +524,7 @@ func TestEmbeddedStruct(t *testing.T) {
} }
func TestComplexSetting(t *testing.T) { func TestComplexSetting(t *testing.T) {
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
err := proc.Continue(p) err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
@ -822,7 +822,7 @@ func TestEvalExpression(t *testing.T) {
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error") assertNoError(proc.Continue(p), t, "Continue() returned an error")
for _, tc := range testcases { for _, tc := range testcases {
variable, err := evalVariable(p, tc.name, pnormalLoadConfig) variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
@ -851,7 +851,7 @@ func TestEvalExpression(t *testing.T) {
func TestEvalAddrAndCast(t *testing.T) { func TestEvalAddrAndCast(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error") assertNoError(proc.Continue(p), t, "Continue() returned an error")
c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig) c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig)
assertNoError(err, t, "EvalExpression(&c1)") assertNoError(err, t, "EvalExpression(&c1)")
@ -878,7 +878,7 @@ func TestEvalAddrAndCast(t *testing.T) {
func TestMapEvaluation(t *testing.T) { func TestMapEvaluation(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error") assertNoError(proc.Continue(p), t, "Continue() returned an error")
m1v, err := evalVariable(p, "m1", pnormalLoadConfig) m1v, err := evalVariable(p, "m1", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()") assertNoError(err, t, "EvalVariable()")
@ -920,7 +920,7 @@ func TestMapEvaluation(t *testing.T) {
func TestUnsafePointer(t *testing.T) { func TestUnsafePointer(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error") assertNoError(proc.Continue(p), t, "Continue() returned an error")
up1v, err := evalVariable(p, "up1", pnormalLoadConfig) up1v, err := evalVariable(p, "up1", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable(up1)") assertNoError(err, t, "EvalVariable(up1)")
@ -958,7 +958,7 @@ func TestIssue426(t *testing.T) {
// Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces // Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces
// differs from the serialization used by the linker to produce DWARF type information // differs from the serialization used by the linker to produce DWARF type information
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testvariables2", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error") assertNoError(proc.Continue(p), t, "Continue() returned an error")
for _, testcase := range testcases { for _, testcase := range testcases {
v, err := evalVariable(p, testcase.name, pnormalLoadConfig) v, err := evalVariable(p, testcase.name, pnormalLoadConfig)
@ -971,7 +971,7 @@ func TestIssue426(t *testing.T) {
}) })
} }
func testPackageRenamesHelper(t *testing.T, p proc.Process, testcases []varTest) { func testPackageRenamesHelper(t *testing.T, p *proc.Target, testcases []varTest) {
for _, tc := range testcases { for _, tc := range testcases {
variable, err := evalVariable(p, tc.name, pnormalLoadConfig) variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
if tc.err == nil { if tc.err == nil {
@ -1041,7 +1041,7 @@ func TestPackageRenames(t *testing.T) {
} }
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("pkgrenames", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue() returned an error") assertNoError(proc.Continue(p), t, "Continue() returned an error")
testPackageRenamesHelper(t, p, testcases) testPackageRenamesHelper(t, p, testcases)
@ -1075,7 +1075,7 @@ func TestConstants(t *testing.T) {
// Not supported on 1.9 or earlier // Not supported on 1.9 or earlier
t.Skip("constants added in go 1.10") t.Skip("constants added in go 1.10")
} }
withTestProcess("consts", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("consts", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue") assertNoError(proc.Continue(p), t, "Continue")
for _, testcase := range testcases { for _, testcase := range testcases {
variable, err := evalVariable(p, testcase.name, pnormalLoadConfig) variable, err := evalVariable(p, testcase.name, pnormalLoadConfig)
@ -1085,7 +1085,7 @@ func TestConstants(t *testing.T) {
}) })
} }
func setFunctionBreakpoint(p proc.Process, t testing.TB, fname string) *proc.Breakpoint { func setFunctionBreakpoint(p *proc.Target, t testing.TB, fname string) *proc.Breakpoint {
_, f, l, _ := runtime.Caller(1) _, f, l, _ := runtime.Caller(1)
f = filepath.Base(f) f = filepath.Base(f)
@ -1104,7 +1104,7 @@ func setFunctionBreakpoint(p proc.Process, t testing.TB, fname string) *proc.Bre
} }
func TestIssue1075(t *testing.T) { func TestIssue1075(t *testing.T) {
withTestProcess("clientdo", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("clientdo", t, func(p *proc.Target, fixture protest.Fixture) {
setFunctionBreakpoint(p, t, "net/http.(*Client).Do") setFunctionBreakpoint(p, t, "net/http.(*Client).Do")
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
@ -1230,7 +1230,7 @@ func TestCallFunction(t *testing.T) {
{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method {`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
} }
withTestProcess("fncall", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("fncall", t, func(p *proc.Target, fixture protest.Fixture) {
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", 0) _, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", 0)
if err != nil { if err != nil {
t.Skip("function calls not supported on this version of go") t.Skip("function calls not supported on this version of go")
@ -1257,7 +1257,7 @@ func TestCallFunction(t *testing.T) {
}) })
} }
func testCallFunction(t *testing.T, p proc.Process, tc testCaseCallFunction) { func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
const unsafePrefix = "-unsafe " const unsafePrefix = "-unsafe "
var callExpr, varExpr string var callExpr, varExpr string
@ -1333,7 +1333,7 @@ func testCallFunction(t *testing.T, p proc.Process, tc testCaseCallFunction) {
func TestIssue1531(t *testing.T) { func TestIssue1531(t *testing.T) {
// Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0. // Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0.
withTestProcess("issue1531", t, func(p proc.Process, fixture protest.Fixture) { withTestProcess("issue1531", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
hasKeys := func(mv *proc.Variable, keys ...string) { hasKeys := func(mv *proc.Variable, keys ...string) {
@ -1373,7 +1373,7 @@ func TestIssue1531(t *testing.T) {
}) })
} }
func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, lineno int) *proc.Breakpoint { func setFileBreakpoint(p *proc.Target, t *testing.T, fixture protest.Fixture, lineno int) *proc.Breakpoint {
_, f, l, _ := runtime.Caller(1) _, f, l, _ := runtime.Caller(1)
f = filepath.Base(f) f = filepath.Base(f)
@ -1391,7 +1391,7 @@ func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, li
return bp return bp
} }
func currentLocation(p proc.Process, t *testing.T) (pc uint64, f string, ln int, fn *proc.Function) { func currentLocation(p *proc.Target, t *testing.T) (pc uint64, f string, ln int, fn *proc.Function) {
regs, err := p.CurrentThread().Registers(false) regs, err := p.CurrentThread().Registers(false)
if err != nil { if err != nil {
t.Fatalf("Registers error: %v", err) t.Fatalf("Registers error: %v", err)
@ -1401,7 +1401,7 @@ func currentLocation(p proc.Process, t *testing.T) (pc uint64, f string, ln int,
return regs.PC(), f, l, fn return regs.PC(), f, l, fn
} }
func assertCurrentLocationFunction(p proc.Process, t *testing.T, fnname string) { func assertCurrentLocationFunction(p *proc.Target, t *testing.T, fnname string) {
_, _, _, fn := currentLocation(p, t) _, _, _, fn := currentLocation(p, t)
if fn == nil { if fn == nil {
t.Fatalf("Not in a function") t.Fatalf("Not in a function")
@ -1414,7 +1414,7 @@ func assertCurrentLocationFunction(p proc.Process, t *testing.T, fnname string)
func TestPluginVariables(t *testing.T) { func TestPluginVariables(t *testing.T) {
pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/") pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/")
withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p proc.Process, fixture protest.Fixture) { withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture, 41) setFileBreakpoint(p, t, fixture, 41)
assertNoError(proc.Continue(p), t, "Continue 1") assertNoError(proc.Continue(p), t, "Continue 1")