delve/pkg/proc/target.go
Alessandro Arzilli 134fcb186e
proc: cache result of GetG (#1921)
Benchmark before:

BenchmarkConditionalBreakpoints-4   	       1	7031242832 ns/op

Benchmark after:

BenchmarkConditionalBreakpoints-4   	       1	5282482841 ns/op

Conditional breakpoint evaluation latency: 0.70ms -> 0.52ms

Updates #1549
2020-03-10 12:48:46 -07:00

182 lines
5.5 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()
for _, thread := range t.ThreadList() {
thread.Common().g = nil
}
}
// 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)
}