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
currentThread *Thread
selectedGoroutine *proc.G
common proc.CommonProcess
}
// 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.
// If the DWARF information cannot be found in the binary, Delve will look
// 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 err error
for _, openFn := range openFns {
@ -217,7 +216,7 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*Process, error
return nil, err
}
return p, nil
return proc.NewTarget(p), nil
}
// 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.
// For core files this always returns an empty BreakpointState struct, as
// there are no breakpoints when debugging core files.
func (t *Thread) Breakpoint() proc.BreakpointState {
return proc.BreakpointState{}
func (t *Thread) Breakpoint() *proc.BreakpointState {
return &proc.BreakpointState{}
}
// ThreadID returns the ID for this thread.
@ -434,12 +433,6 @@ func (p *Process) Valid() (bool, error) {
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.
func (p *Process) Pid() int {
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.
func (p *Process) SwitchGoroutine(gid int) error {
g, err := proc.FindGoroutine(p, gid)
if err != nil {
return err
}
func (p *Process) SwitchGoroutine(g *proc.G) error {
if g == nil {
// user specified -1 and selectedGoroutine is 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.
// Might be better to check in the binary and core?
tempDir, err := ioutil.TempDir("", "")

@ -90,7 +90,7 @@ type functionCallState struct {
}
type callContext struct {
p Process
p *Target
// checkEscape is true if the escape check should be performed.
// See service/api.DebuggerCommand.UnsafeCall in service/api/types.go.
@ -115,6 +115,14 @@ type continueRequest struct {
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 {
callCtx.continueRequest <- continueRequest{cont: true}
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'.
// Because this can only be done in the current goroutine, unlike
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
bi := p.BinInfo()
if !p.Common().fncallEnabled {
func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
bi := t.BinInfo()
if !t.SupportsFunctionCalls() {
return errFuncCallUnsupportedBackend
}
@ -144,7 +152,7 @@ func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig
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
}
@ -162,14 +170,14 @@ func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig
continueCompleted := make(chan *G)
scope.callCtx = &callContext{
p: p,
p: t,
checkEscape: checkEscape,
retLoadCfg: retLoadCfg,
continueRequest: continueRequest,
continueCompleted: continueCompleted,
}
p.Common().fncallForG[g.ID] = &callInjection{
t.fncallForG[g.ID] = &callInjection{
continueCompleted,
continueRequest,
}
@ -178,13 +186,13 @@ func EvalExpressionWithCalls(p Process, g *G, expr string, retLoadCfg LoadConfig
contReq, ok := <-continueRequest
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())
var err error
if !ok {
@ -208,8 +216,8 @@ func finishEvalExpressionWithCalls(p Process, g *G, contReq continueRequest, ok
g.Thread.Common().returnValues = []*Variable{contReq.ret}
}
close(p.Common().fncallForG[g.ID].continueCompleted)
delete(p.Common().fncallForG, g.ID)
close(t.fncallForG[g.ID].continueCompleted)
delete(t.fncallForG, g.ID)
return err
}
@ -234,7 +242,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
p := scope.callCtx.p
bi := scope.BinInfo
if !p.Common().fncallEnabled {
if !p.SupportsFunctionCalls() {
return nil, errFuncCallUnsupportedBackend
}
@ -879,8 +887,8 @@ func isCallInjectionStop(loc *Location) bool {
// callInjectionProtocol is the function called from Continue to progress
// the injection protocol for all threads.
// Returns true if a call injection terminated
func callInjectionProtocol(p Process, threads []Thread) (done bool, err error) {
if len(p.Common().fncallForG) == 0 {
func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
if len(t.fncallForG) == 0 {
// we aren't injecting any calls, no need to check the threads.
return false, nil
}
@ -897,7 +905,7 @@ func callInjectionProtocol(p Process, threads []Thread) (done bool, err error) {
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)
}
callinj := p.Common().fncallForG[g.ID]
callinj := t.fncallForG[g.ID]
if callinj == nil || callinj.continueCompleted == nil {
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
contReq, ok := <-callinj.continueRequest
if !contReq.cont {
err := finishEvalExpressionWithCalls(p, g, contReq, ok)
err := finishEvalExpressionWithCalls(t, g, contReq, ok)
if err != nil {
return done, err
}

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

@ -12,6 +12,8 @@ import (
"strconv"
"strings"
"unicode"
"github.com/go-delve/delve/pkg/proc"
)
// 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
// 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 {
return nil, err
}
@ -94,7 +96,7 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
return nil, err
}
return p, nil
return proc.NewTarget(p), nil
}
// 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.
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (p *Process, tracedir string, err error) {
tracedir, err = Record(cmd, wd, quiet)
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (*proc.Target, string, error) {
tracedir, err := Record(cmd, wd, quiet)
if tracedir == "" {
return nil, "", err
}
p, err = Replay(tracedir, quiet, true, debugInfoDirs)
return p, tracedir, err
t, err := Replay(tracedir, quiet, true, debugInfoDirs)
return t, tracedir, err
}
// 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))
}
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)
protest.MustHaveRecordingAllowed(t)
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)
defer func() {
p.Detach(true)
}()
defer p.Detach(true)
fn(p, fixture)
}
@ -71,7 +69,7 @@ func setFunctionBreakpoint(p proc.Process, t *testing.T, fname string) *proc.Bre
func TestRestartAfterExit(t *testing.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")
assertNoError(proc.Continue(p), t, "Continue")
loc, err := p.CurrentThread().Location()
@ -98,7 +96,7 @@ func TestRestartAfterExit(t *testing.T) {
func TestRestartDuringStop(t *testing.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")
assertNoError(proc.Continue(p), t, "Continue")
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) {
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)
assertNoError(proc.Continue(p), t, "Continue()")
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
when, err = p.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) {
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'
bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue")
@ -278,7 +276,7 @@ func TestCheckpoints(t *testing.T) {
func TestIssue1376(t *testing.T) {
// Backward Continue should terminate when it encounters the start of the process.
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")
assertNoError(proc.Continue(p), t, "Continue (forward)")
_, 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)
BinInfo() *BinaryInfo
EntryPoint() (uint64, error)
// Common returns a struct with fields common to all backends
Common() *CommonProcess
ThreadInfo
GoroutineInfo
@ -93,9 +91,8 @@ type GoroutineInfo interface {
// ProcessManipulation is an interface for changing the execution state of a process.
type ProcessManipulation interface {
ContinueOnce() (trapthread Thread, err error)
StepInstruction() error
SwitchThread(int) error
SwitchGoroutine(int) error
SwitchGoroutine(*G) error
RequestManualStop() error
// CheckAndClearManualStopRequest returns true the first time it's called
// after a call to RequestManualStop.
@ -110,34 +107,3 @@ type BreakpointManipulation interface {
ClearBreakpoint(addr uint64) (*Breakpoint, 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")
// 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
}
// Attach returns ErrNativeBackendDisabled.
func Attach(pid int, _ []string) (*Process, error) {
func Attach(pid int, _ []string) (*proc.Target, error) {
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
selectedGoroutine *proc.G
common proc.CommonProcess
os *OSProcessDetails
firstStart bool
stopMu sync.Mutex
@ -247,7 +246,6 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
return nil, err
}
dbp.common.ClearAllGCache()
for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear()
}
@ -267,41 +265,6 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
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`.
func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited {
@ -317,14 +280,10 @@ func (dbp *Process) SwitchThread(tid int) error {
// SwitchGoroutine changes from current thread to the thread
// running the specified goroutine.
func (dbp *Process) SwitchGoroutine(gid int) error {
func (dbp *Process) SwitchGoroutine(g *proc.G) error {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
g, err := proc.FindGoroutine(dbp, gid)
if err != nil {
return err
}
if g == nil {
// user specified -1 and selectedGoroutine is 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())
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
// PT_SIGEXC on Darwin which will turn Unix signals into
// 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
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, proc.ErrNotExecutable
@ -104,7 +104,6 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
return nil, err
}
dbp.common.ClearAllGCache()
for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear()
}
@ -127,11 +126,11 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
return nil, err
}
return dbp, err
return proc.NewTarget(dbp), err
}
// 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)
kret := C.acquire_mach_task(C.int(pid),
@ -159,7 +158,7 @@ func Attach(pid int, _ []string) (*Process, error) {
dbp.Detach(false)
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
// Kill kills the process.

@ -43,7 +43,7 @@ type OSProcessDetails struct {
// 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
// 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 (
process *exec.Cmd
err error
@ -60,7 +60,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
}
dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() {
process = exec.Command(cmd[0])
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 {
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
// Attach to an existing process with the given PID. Once attached, if
// the DWARF information cannot be found in the binary, Delve will look
// 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.common = proc.NewCommonProcess(true)
var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
@ -113,7 +111,7 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
dbp.Detach(false)
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
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.
// If the DWARF information cannot be found in the binary, Delve will look
// 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 (
process *exec.Cmd
err error
@ -65,7 +65,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
}
dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() {
process = exec.Command(cmd[0])
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 {
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
// Attach to an existing process with the given PID. Once attached, if
// the DWARF information cannot be found in the binary, Delve will look
// 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.common = proc.NewCommonProcess(true)
var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
@ -125,7 +123,7 @@ func Attach(pid int, debugInfoDirs []string) (*Process, error) {
if err != nil {
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
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.
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])
if err != nil {
return nil, err
@ -57,7 +57,6 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
var p *os.Process
dbp := New(0)
dbp.common = proc.NewCommonProcess(true)
dbp.execPtraceFunc(func() {
attr := &os.ProcAttr{
Dir: wd,
@ -80,7 +79,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, err
dbp.Detach(true)
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
func initialize(dbp *Process) error {
@ -151,7 +150,7 @@ func findExePath(pid int) (string, error) {
}
// 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)
var err error
dbp.execPtraceFunc(func() {
@ -169,7 +168,7 @@ func Attach(pid int, _ []string) (*Process, error) {
dbp.Detach(true)
return nil, err
}
return dbp, nil
return proc.NewTarget(dbp), nil
}
// kill kills the process.

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

@ -2,7 +2,6 @@ package proc
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"go/ast"
@ -75,8 +74,6 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
createFatalThrowBreakpoint(p, writeBreakpoint)
p.Common().goroutineCache.init(p.BinInfo())
return nil
}
@ -142,7 +139,7 @@ func FindFunctionLocation(p Process, funcName string, lineOffset int) ([]uint64,
}
// 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 {
return err
}
@ -161,7 +158,7 @@ func Next(dbp Process) (err error) {
// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// or is otherwise stopped.
func Continue(dbp Process) error {
func Continue(dbp *Target) error {
if _, err := dbp.Valid(); err != nil {
return err
}
@ -181,6 +178,7 @@ func Continue(dbp Process) error {
dbp.ClearInternalBreakpoints()
return nil
}
dbp.ClearAllGCache()
trapthread, err := dbp.ContinueOnce()
if err != nil {
return err
@ -230,7 +228,7 @@ func Continue(dbp Process) error {
return err
}
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
if !arch.BreakInstrMovesPC() {
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.
// Will step into functions.
func Step(dbp Process) (err error) {
func Step(dbp *Target) (err error) {
if _, err := dbp.Valid(); err != nil {
return err
}
@ -418,7 +416,7 @@ func andFrameoffCondition(cond ast.Expr, frameoff int64) ast.Expr {
// StepOut will continue until the current goroutine exits the
// 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 {
return err
}
@ -503,6 +501,43 @@ func StepOut(dbp Process) error {
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
// 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
@ -510,14 +545,14 @@ func StepOut(dbp Process) error {
// 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
// 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 {
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
if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) {
return dbp.Common().allGCache, -1, nil
if start == 0 && (count == 0 || count >= len(dbp.gcache.allGCache)) {
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 {
return nil, -1, err
}
@ -569,68 +604,18 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
if g.Status != Gdead {
allg = append(allg, g)
}
dbp.Common().addGoroutine(g)
dbp.gcache.addGoroutine(g)
}
if start == 0 {
dbp.Common().allGCache = allg
dbp.gcache.allGCache = allg
}
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
// 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) {
// 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
}
@ -696,7 +681,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) {
// ConvertEvalScope returns a new EvalScope in the context of the
// specified goroutine ID and stack frame.
// 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 {
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)
// 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)
setFunctionBreakpoint(p, t, "main.main")
assertNoError(proc.Continue(p), t, "Continue()")

@ -23,7 +23,7 @@ func TestScopeWithEscapedVariable(t *testing.T) {
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")
// 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)
withTestProcess("scopetest", t, func(p proc.Process, fixture protest.Fixture) {
withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) {
for i := range scopeChecks {
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())
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)
// Breakpoint will return the breakpoint that this thread is stopped at or
// nil if the thread is not stopped at any breakpoint.
Breakpoint() BreakpointState
Breakpoint() *BreakpointState
ThreadID() int
// Registers returns the CPU registers of this thread. The contents of the

@ -10,7 +10,7 @@ import (
func TestGoroutineCreationLocation(t *testing.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")
assertNoError(proc.Continue(p), t, "Continue()")

@ -37,7 +37,7 @@ type Debugger struct {
processArgs []string
// TODO(DO NOT MERGE WITHOUT) rename to targetMutex
processMutex sync.Mutex
target proc.Process
target *proc.Target
log *logrus.Entry
running bool
@ -102,7 +102,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
d.target = p
case d.config.CoreFile != "":
var p proc.Process
var p *proc.Target
var err error
switch d.config.Backend {
case "rr":
@ -165,7 +165,7 @@ func (d *Debugger) checkGoVersion() error {
}
// 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 {
case "native":
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")
// 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 {
case "native":
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")
func betterGdbserialLaunchError(p proc.Process, err error) (proc.Process, error) {
func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error) {
if runtime.GOOS != "darwin" {
return p, err
}
@ -717,7 +717,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
err = proc.Step(d.target)
case api.StepInstruction:
d.log.Debug("single stepping")
err = d.target.StepInstruction()
err = proc.StepInstruction(d.target)
case api.ReverseStepInstruction:
d.log.Debug("reverse single stepping")
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() {
d.target.Direction(proc.Forward)
}()
err = d.target.StepInstruction()
err = proc.StepInstruction(d.target)
case api.StepOut:
d.log.Debug("step out")
err = proc.StepOut(d.target)
@ -736,7 +736,10 @@ func (d *Debugger) Command(command *api.DebuggerCommand) (*api.DebuggerState, er
withBreakpointInfo = false
case api.SwitchGoroutine:
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
case api.Halt:
// 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)
if err != nil {
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")
}
func evalScope(p proc.Process) (*proc.EvalScope, error) {
func evalScope(p *proc.Target) (*proc.EvalScope, error) {
if testBackend != "rr" {
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
}
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)
if err != nil {
return nil, err
@ -99,7 +99,7 @@ func (tc *varTest) alternateVarTest() varTest {
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())
if err != nil {
return err
@ -107,16 +107,16 @@ func setVariable(p proc.Process, symbol, value string) error {
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)
}
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" {
buildFlags |= protest.BuildModePIE
}
fixture := protest.BuildFixture(name, buildFlags)
var p proc.Process
var p *proc.Target
var err error
var tracedir string
switch testBackend {
@ -189,7 +189,7 @@ func TestVariableEvaluation(t *testing.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)
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]"},
}
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()")
for _, tc := range testcases {
@ -316,7 +316,7 @@ func TestVariableEvaluationShort(t *testing.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)
assertNoError(err, t, "Continue() returned an error")
@ -372,7 +372,7 @@ func TestMultilineVariableEvaluation(t *testing.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)
assertNoError(err, t, "Continue() returned an error")
@ -446,7 +446,7 @@ func TestLocalVariables(t *testing.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)
assertNoError(err, t, "Continue() returned an error")
@ -483,7 +483,7 @@ func TestLocalVariables(t *testing.T) {
func TestEmbeddedStruct(t *testing.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{
{"b.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) {
withTestProcess("testvariables", t, func(p proc.Process, fixture protest.Fixture) {
withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) {
err := proc.Continue(p)
assertNoError(err, t, "Continue() returned an error")
@ -822,7 +822,7 @@ func TestEvalExpression(t *testing.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")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
@ -851,7 +851,7 @@ func TestEvalExpression(t *testing.T) {
func TestEvalAddrAndCast(t *testing.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")
c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig)
assertNoError(err, t, "EvalExpression(&c1)")
@ -878,7 +878,7 @@ func TestEvalAddrAndCast(t *testing.T) {
func TestMapEvaluation(t *testing.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")
m1v, err := evalVariable(p, "m1", pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
@ -920,7 +920,7 @@ func TestMapEvaluation(t *testing.T) {
func TestUnsafePointer(t *testing.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")
up1v, err := evalVariable(p, "up1", pnormalLoadConfig)
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
// differs from the serialization used by the linker to produce DWARF type information
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")
for _, testcase := range testcases {
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 {
variable, err := evalVariable(p, tc.name, pnormalLoadConfig)
if tc.err == nil {
@ -1041,7 +1041,7 @@ func TestPackageRenames(t *testing.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")
testPackageRenamesHelper(t, p, testcases)
@ -1075,7 +1075,7 @@ func TestConstants(t *testing.T) {
// Not supported on 1.9 or earlier
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")
for _, testcase := range testcases {
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 = filepath.Base(f)
@ -1104,7 +1104,7 @@ func setFunctionBreakpoint(p proc.Process, t testing.TB, fname string) *proc.Bre
}
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")
assertNoError(proc.Continue(p), t, "Continue()")
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
}
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)
if err != nil {
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 "
var callExpr, varExpr string
@ -1333,7 +1333,7 @@ func testCallFunction(t *testing.T, p proc.Process, tc testCaseCallFunction) {
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.
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()")
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 = filepath.Base(f)
@ -1391,7 +1391,7 @@ func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, li
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)
if err != nil {
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
}
func assertCurrentLocationFunction(p proc.Process, t *testing.T, fnname string) {
func assertCurrentLocationFunction(p *proc.Target, t *testing.T, fnname string) {
_, _, _, fn := currentLocation(p, t)
if fn == nil {
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) {
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)
assertNoError(proc.Continue(p), t, "Continue 1")