delve/pkg/proc/target.go
Alessandro Arzilli 9f97edb0bb
proc,proc/*: add StopReason field to Target (#1877)
* proc,proc/*: move SelectedGoroutine to proc.Target, remove PostInitializationSetup

moves SelectedGoroutine, SwitchThread and SwitchGoroutine to
proc.Target, merges PostInitializationSetup with NewTarget.

* proc,proc/*: add StopReason field to Target

Adds a StopReason field to the Target object describing why the target
process is currently stopped. This will be useful for the DAP server
(which needs to report this reason in one of its requests) as well as
making pull request #1785 (reverse step) conformant to the new
architecture.

* proc: collect NewTarget arguments into a struct
2020-03-10 12:27:38 -07:00

179 lines
5.4 KiB
Go

package proc
import (
"fmt"
)
// Target represents the process being debugged.
type Target struct {
Process
proc ProcessInternal
// StopReason describes the reason why the target process is stopped.
// A process could be stopped for multiple simultaneous reasons, in which
// case only one will be reported.
StopReason StopReason
// Goroutine that will be used by default to set breakpoint, eval variables, etc...
// 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 *G
// fncallForG stores a mapping of current active function calls.
fncallForG map[int]*callInjection
asyncPreemptChanged bool // runtime/debug.asyncpreemptoff was changed
asyncPreemptOff int64 // cached value of runtime/debug.asyncpreemptoff
// 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
}
// StopReason describes the reason why the target process is stopped.
// A process could be stopped for multiple simultaneous reasons, in which
// case only one will be reported.
type StopReason uint8
const (
StopUnknown StopReason = iota
StopLaunched // The process was just launched
StopAttached // The debugger stopped the process after attaching
StopExited // The target proces terminated
StopBreakpoint // The target process hit one or more software breakpoints
StopHardcodedBreakpoint // The target process hit a hardcoded breakpoint (for example runtime.Breakpoint())
StopManual // A manual stop was requested
StopNextFinished // The next/step/stepout command terminated
StopCallReturned // An injected call commpleted
)
// NewTargetConfig contains the configuration for a new Target object,
type NewTargetConfig struct {
Path string // path of the main executable
DebugInfoDirs []string // Directories to search for split debug info
WriteBreakpoint WriteBreakpointFn // Function to write a breakpoint to the target process
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
StopReason StopReason // Initial stop reason
}
// NewTarget returns an initialized Target object.
func NewTarget(p Process, cfg NewTargetConfig) (*Target, error) {
entryPoint, err := p.EntryPoint()
if err != nil {
return nil, err
}
err = p.BinInfo().LoadBinaryInfo(cfg.Path, entryPoint, cfg.DebugInfoDirs)
if err != nil {
return nil, err
}
for _, image := range p.BinInfo().Images {
if image.loadErr != nil {
return nil, image.loadErr
}
}
t := &Target{
Process: p,
proc: p.(ProcessInternal),
fncallForG: make(map[int]*callInjection),
StopReason: cfg.StopReason,
}
g, _ := GetG(p.CurrentThread())
t.selectedGoroutine = g
createUnrecoveredPanicBreakpoint(p, cfg.WriteBreakpoint)
createFatalThrowBreakpoint(p, cfg.WriteBreakpoint)
t.gcache.init(p.BinInfo())
if cfg.DisableAsyncPreempt {
setAsyncPreemptOff(t, 1)
}
return t, nil
}
// 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()
}
// Restart will start the process over from the location specified by the "from" locspec.
// This is only useful for recorded targets.
// Restarting of a normal process happens at a higher level (debugger.Restart).
func (t *Target) Restart(from string) error {
t.ClearAllGCache()
err := t.proc.Restart(from)
if err != nil {
return err
}
t.selectedGoroutine, _ = GetG(t.CurrentThread())
if from != "" {
t.StopReason = StopManual
} else {
t.StopReason = StopLaunched
}
return nil
}
// SelectedGoroutine returns the currently selected goroutine.
func (t *Target) SelectedGoroutine() *G {
return t.selectedGoroutine
}
// SwitchGoroutine will change the selected and active goroutine.
func (p *Target) SwitchGoroutine(g *G) error {
if ok, err := p.Valid(); !ok {
return err
}
if g == nil {
return nil
}
if g.Thread != nil {
return p.SwitchThread(g.Thread.ThreadID())
}
p.selectedGoroutine = g
return nil
}
// SwitchThread will change the selected and active thread.
func (p *Target) SwitchThread(tid int) error {
if ok, err := p.Valid(); !ok {
return err
}
if th, ok := p.FindThread(tid); ok {
p.proc.SetCurrentThread(th)
p.selectedGoroutine, _ = GetG(p.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// Detach will detach the target from the underylying process.
// This means the debugger will no longer receive events from the process
// we were previously debugging.
// If kill is true then the process will be killed when we detach.
func (t *Target) Detach(kill bool) error {
if !kill && t.asyncPreemptChanged {
setAsyncPreemptOff(t, t.asyncPreemptOff)
}
t.StopReason = StopUnknown
return t.proc.Detach(kill)
}