
* 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
179 lines
5.4 KiB
Go
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)
|
|
}
|