2020-01-21 20:41:24 +00:00
|
|
|
package proc
|
|
|
|
|
2020-03-10 19:27:38 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2020-01-21 20:41:24 +00:00
|
|
|
// Target represents the process being debugged.
|
|
|
|
type Target struct {
|
|
|
|
Process
|
|
|
|
|
2020-03-10 19:27:38 +00:00
|
|
|
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
|
|
|
|
|
2020-01-21 20:41:24 +00:00
|
|
|
// fncallForG stores a mapping of current active function calls.
|
|
|
|
fncallForG map[int]*callInjection
|
|
|
|
|
2020-02-11 01:31:54 +00:00
|
|
|
asyncPreemptChanged bool // runtime/debug.asyncpreemptoff was changed
|
|
|
|
asyncPreemptOff int64 // cached value of runtime/debug.asyncpreemptoff
|
|
|
|
|
2020-01-21 20:41:24 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-03-10 19:27:38 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-01-21 20:41:24 +00:00
|
|
|
// NewTarget returns an initialized Target object.
|
2020-03-10 19:27:38 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-21 20:41:24 +00:00
|
|
|
t := &Target{
|
|
|
|
Process: p,
|
2020-03-10 19:27:38 +00:00
|
|
|
proc: p.(ProcessInternal),
|
2020-01-21 20:41:24 +00:00
|
|
|
fncallForG: make(map[int]*callInjection),
|
2020-03-10 19:27:38 +00:00
|
|
|
StopReason: cfg.StopReason,
|
2020-01-21 20:41:24 +00:00
|
|
|
}
|
2020-03-10 19:27:38 +00:00
|
|
|
|
|
|
|
g, _ := GetG(p.CurrentThread())
|
|
|
|
t.selectedGoroutine = g
|
|
|
|
|
|
|
|
createUnrecoveredPanicBreakpoint(p, cfg.WriteBreakpoint)
|
|
|
|
createFatalThrowBreakpoint(p, cfg.WriteBreakpoint)
|
|
|
|
|
2020-01-21 20:41:24 +00:00
|
|
|
t.gcache.init(p.BinInfo())
|
2020-02-11 01:31:54 +00:00
|
|
|
|
2020-03-10 19:27:38 +00:00
|
|
|
if cfg.DisableAsyncPreempt {
|
2020-02-11 01:31:54 +00:00
|
|
|
setAsyncPreemptOff(t, 1)
|
|
|
|
}
|
|
|
|
|
2020-03-10 19:27:38 +00:00
|
|
|
return t, nil
|
2020-01-21 20:41:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|
|
|
|
|
2020-03-04 18:21:29 +00:00
|
|
|
// 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).
|
2020-01-21 20:41:24 +00:00
|
|
|
func (t *Target) Restart(from string) error {
|
|
|
|
t.ClearAllGCache()
|
2020-03-10 19:27:38 +00:00
|
|
|
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)
|
2020-01-21 20:41:24 +00:00
|
|
|
}
|
2020-02-11 01:31:54 +00:00
|
|
|
|
2020-03-04 18:21:29 +00:00
|
|
|
// 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.
|
2020-02-11 01:31:54 +00:00
|
|
|
func (t *Target) Detach(kill bool) error {
|
|
|
|
if !kill && t.asyncPreemptChanged {
|
|
|
|
setAsyncPreemptOff(t, t.asyncPreemptOff)
|
|
|
|
}
|
2020-03-10 19:27:38 +00:00
|
|
|
t.StopReason = StopUnknown
|
|
|
|
return t.proc.Detach(kill)
|
2020-02-11 01:31:54 +00:00
|
|
|
}
|