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
This commit is contained in:
Alessandro Arzilli 2020-03-10 20:27:38 +01:00 committed by GitHub
parent f3a191cd73
commit 9f97edb0bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 265 additions and 270 deletions

@ -155,12 +155,13 @@ type Process struct {
entryPoint uint64 entryPoint uint64
bi *proc.BinaryInfo bi *proc.BinaryInfo
breakpoints proc.BreakpointMap breakpoints proc.BreakpointMap
currentThread *Thread currentThread *Thread
selectedGoroutine *proc.G
} }
var _ proc.ProcessInternal = &Process{}
// Thread represents a thread in the core file being debugged. // Thread represents a thread in the core file being debugged.
type Thread struct { type Thread struct {
th osThread th osThread
@ -212,17 +213,12 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
return nil, err return nil, err
} }
if err := p.initialize(exePath, debugInfoDirs); err != nil { return proc.NewTarget(p, proc.NewTargetConfig{
return nil, err Path: exePath,
} DebugInfoDirs: debugInfoDirs,
WriteBreakpoint: p.writeBreakpoint,
return proc.NewTarget(p, false), nil DisableAsyncPreempt: false,
} StopReason: proc.StopAttached})
// initialize for core files doesn't do much
// aside from call the post initialization setup.
func (p *Process) initialize(path string, debugInfoDirs []string) error {
return proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint)
} }
// BinInfo will return the binary info. // BinInfo will return the binary info.
@ -230,13 +226,6 @@ func (p *Process) BinInfo() *proc.BinaryInfo {
return p.bi return p.bi
} }
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// EntryPoint will return the entry point address for this core file. // EntryPoint will return the entry point address for this core file.
func (p *Process) EntryPoint() (uint64, error) { func (p *Process) EntryPoint() (uint64, error) {
return p.entryPoint, nil return p.entryPoint, nil
@ -392,8 +381,8 @@ func (p *Process) ClearInternalBreakpoints() error {
// ContinueOnce will always return an error because you // ContinueOnce will always return an error because you
// cannot control execution of a core file. // cannot control execution of a core file.
func (p *Process) ContinueOnce() (proc.Thread, error) { func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
return nil, ErrContinueCore return nil, proc.StopUnknown, ErrContinueCore
} }
// StepInstruction will always return an error // StepInstruction will always return an error
@ -443,40 +432,11 @@ func (p *Process) Pid() int {
func (p *Process) ResumeNotify(chan<- struct{}) { func (p *Process) ResumeNotify(chan<- struct{}) {
} }
// SelectedGoroutine returns the current active and selected
// goroutine.
func (p *Process) SelectedGoroutine() *proc.G {
return p.selectedGoroutine
}
// SetBreakpoint will always return an error for core files as you cannot write memory or control execution. // SetBreakpoint will always return an error for core files as you cannot write memory or control execution.
func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) { func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
return nil, ErrWriteCore return nil, ErrWriteCore
} }
// SwitchGoroutine will change the selected and active goroutine.
func (p *Process) SwitchGoroutine(g *proc.G) error {
if g == nil {
// user specified -1 and selectedGoroutine is 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 *Process) SwitchThread(tid int) error {
if th, ok := p.Threads[tid]; ok {
p.currentThread = th
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// ThreadList will return a list of all threads currently in the process. // ThreadList will return a list of all threads currently in the process.
func (p *Process) ThreadList() []proc.Thread { func (p *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(p.Threads)) r := make([]proc.Thread, 0, len(p.Threads))
@ -491,3 +451,8 @@ func (p *Process) FindThread(threadID int) (proc.Thread, bool) {
t, ok := p.Threads[threadID] t, ok := p.Threads[threadID]
return t, ok return t, ok
} }
// SetCurrentThread is used internally by proc.Target to change the current thread.
func (p *Process) SetCurrentThread(th proc.Thread) {
p.currentThread = th.(*Thread)
}

@ -104,9 +104,8 @@ type Process struct {
bi *proc.BinaryInfo bi *proc.BinaryInfo
conn gdbConn conn gdbConn
threads map[int]*Thread threads map[int]*Thread
currentThread *Thread currentThread *Thread
selectedGoroutine *proc.G
exited, detached bool exited, detached bool
ctrlC bool // ctrl-c was sent to stop inferior ctrlC bool // ctrl-c was sent to stop inferior
@ -125,9 +124,10 @@ type Process struct {
waitChan chan *os.ProcessState waitChan chan *os.ProcessState
onDetach func() // called after a successful detach onDetach func() // called after a successful detach
} }
var _ proc.ProcessInternal = &Process{}
// Thread represents an operating system thread. // Thread represents an operating system thread.
type Thread struct { type Thread struct {
ID int ID int
@ -198,7 +198,7 @@ func New(process *os.Process) *Process {
} }
// Listen waits for a connection from the stub. // Listen waits for a connection from the stub.
func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string) error { func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
acceptChan := make(chan net.Conn) acceptChan := make(chan net.Conn)
go func() { go func() {
@ -210,25 +210,25 @@ func (p *Process) Listen(listener net.Listener, path string, pid int, debugInfoD
case conn := <-acceptChan: case conn := <-acceptChan:
listener.Close() listener.Close()
if conn == nil { if conn == nil {
return errors.New("could not connect") return nil, errors.New("could not connect")
} }
return p.Connect(conn, path, pid, debugInfoDirs) return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
case status := <-p.waitChan: case status := <-p.waitChan:
listener.Close() listener.Close()
return fmt.Errorf("stub exited while waiting for connection: %v", status) return nil, fmt.Errorf("stub exited while waiting for connection: %v", status)
} }
} }
// Dial attempts to connect to the stub. // Dial attempts to connect to the stub.
func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string) error { func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
for { for {
conn, err := net.Dial("tcp", addr) conn, err := net.Dial("tcp", addr)
if err == nil { if err == nil {
return p.Connect(conn, path, pid, debugInfoDirs) return p.Connect(conn, path, pid, debugInfoDirs, stopReason)
} }
select { select {
case status := <-p.waitChan: case status := <-p.waitChan:
return fmt.Errorf("stub exited while attempting to connect: %v", status) return nil, fmt.Errorf("stub exited while attempting to connect: %v", status)
default: default:
} }
time.Sleep(time.Second) time.Sleep(time.Second)
@ -241,13 +241,13 @@ func (p *Process) Dial(addr string, path string, pid int, debugInfoDirs []string
// program and the PID of the target process, both are optional, however // program and the PID of the target process, both are optional, however
// some stubs do not provide ways to determine path and pid automatically // some stubs do not provide ways to determine path and pid automatically
// and Connect will be unable to function without knowing them. // and Connect will be unable to function without knowing them.
func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string) error { func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
p.conn.conn = conn p.conn.conn = conn
p.conn.pid = pid p.conn.pid = pid
err := p.conn.handshake() err := p.conn.handshake()
if err != nil { if err != nil {
conn.Close() conn.Close()
return err return nil, err
} }
if verbuf, err := p.conn.exec([]byte("$qGDBServerVersion"), "init"); err == nil { if verbuf, err := p.conn.exec([]byte("$qGDBServerVersion"), "init"); err == nil {
@ -262,8 +262,9 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
} }
} }
if err := p.initialize(path, debugInfoDirs); err != nil { tgt, err := p.initialize(path, debugInfoDirs, stopReason)
return err if err != nil {
return nil, err
} }
// None of the stubs we support returns the value of fs_base or gs_base // None of the stubs we support returns the value of fs_base or gs_base
@ -279,7 +280,8 @@ func (p *Process) Connect(conn net.Conn, path string, pid int, debugInfoDirs []s
p.loadGInstrAddr = addr p.loadGInstrAddr = addr
} }
} }
return nil
return tgt, nil
} }
// unusedPort returns an unused tcp port // unusedPort returns an unused tcp port
@ -402,15 +404,13 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
p := New(process.Process) p := New(process.Process)
p.conn.isDebugserver = isDebugserver p.conn.isDebugserver = isDebugserver
var tgt *proc.Target
if listener != nil { if listener != nil {
err = p.Listen(listener, cmd[0], 0, debugInfoDirs) tgt, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
} else { } else {
err = p.Dial(port, cmd[0], 0, debugInfoDirs) tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched)
} }
if err != nil { return tgt, err
return nil, err
}
return proc.NewTarget(p, runtime.GOOS == "darwin"), nil
} }
// LLDBAttach starts an instance of lldb-server and connects to it, asking // LLDBAttach starts an instance of lldb-server and connects to it, asking
@ -454,15 +454,13 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err
p := New(process.Process) p := New(process.Process)
p.conn.isDebugserver = isDebugserver p.conn.isDebugserver = isDebugserver
var tgt *proc.Target
if listener != nil { if listener != nil {
err = p.Listen(listener, path, pid, debugInfoDirs) tgt, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached)
} else { } else {
err = p.Dial(port, path, pid, debugInfoDirs) tgt, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached)
} }
if err != nil { return tgt, err
return nil, err
}
return proc.NewTarget(p, false), nil
} }
// EntryPoint will return the process entry point address, useful for // EntryPoint will return the process entry point address, useful for
@ -481,7 +479,7 @@ func (p *Process) EntryPoint() (uint64, error) {
// initialize uses qProcessInfo to load the inferior's PID and // initialize uses qProcessInfo to load the inferior's PID and
// executable path. This command is not supported by all stubs and not all // executable path. This command is not supported by all stubs and not all
// stubs will report both the PID and executable path. // stubs will report both the PID and executable path.
func (p *Process) initialize(path string, debugInfoDirs []string) error { func (p *Process) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) {
var err error var err error
if path == "" { if path == "" {
// If we are attaching to a running process and the user didn't specify // If we are attaching to a running process and the user didn't specify
@ -495,11 +493,11 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
_, path, err = queryProcessInfo(p, p.Pid()) _, path, err = queryProcessInfo(p, p.Pid())
if err != nil { if err != nil {
p.conn.conn.Close() p.conn.conn.Close()
return err return nil, err
} }
} else { } else {
p.conn.conn.Close() p.conn.conn.Close()
return fmt.Errorf("could not determine executable path: %v", err) return nil, fmt.Errorf("could not determine executable path: %v", err)
} }
} }
} }
@ -520,7 +518,7 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
if err != nil { if err != nil {
p.conn.conn.Close() p.conn.conn.Close()
p.bi.Close() p.bi.Close()
return err return nil, err
} }
p.clearThreadSignals() p.clearThreadSignals()
@ -529,14 +527,20 @@ func (p *Process) initialize(path string, debugInfoDirs []string) error {
if err != nil && !isProtocolErrorUnsupported(err) { if err != nil && !isProtocolErrorUnsupported(err) {
p.conn.conn.Close() p.conn.conn.Close()
p.bi.Close() p.bi.Close()
return err return nil, err
} }
} }
if err = proc.PostInitializationSetup(p, path, debugInfoDirs, p.writeBreakpoint); err != nil { tgt, err := proc.NewTarget(p, proc.NewTargetConfig{
Path: path,
DebugInfoDirs: debugInfoDirs,
WriteBreakpoint: p.writeBreakpoint,
DisableAsyncPreempt: runtime.GOOS == "darwin",
StopReason: stopReason})
if err != nil {
p.conn.conn.Close() p.conn.conn.Close()
return err return nil, err
} }
return nil return tgt, nil
} }
func queryProcessInfo(p *Process, pid int) (int, string, error) { func queryProcessInfo(p *Process, pid int) (int, string, error) {
@ -606,9 +610,9 @@ func (p *Process) CurrentThread() proc.Thread {
return p.currentThread return p.currentThread
} }
// SelectedGoroutine returns the current actuve selected goroutine. // SetCurrentThread is used internally by proc.Target to change the current thread.
func (p *Process) SelectedGoroutine() *proc.G { func (p *Process) SetCurrentThread(th proc.Thread) {
return p.selectedGoroutine p.currentThread = th.(*Thread)
} }
const ( const (
@ -628,9 +632,9 @@ const (
// ContinueOnce will continue execution of the process until // ContinueOnce will continue execution of the process until
// a breakpoint is hit or signal is received. // a breakpoint is hit or signal is received.
func (p *Process) ContinueOnce() (proc.Thread, error) { func (p *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
if p.exited { if p.exited {
return nil, &proc.ErrProcessExited{Pid: p.conn.pid} return nil, proc.StopExited, &proc.ErrProcessExited{Pid: p.conn.pid}
} }
if p.conn.direction == proc.Forward { if p.conn.direction == proc.Forward {
@ -638,7 +642,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
for _, thread := range p.threads { for _, thread := range p.threads {
if thread.CurrentBreakpoint.Breakpoint != nil { if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil { if err := thread.stepInstruction(&threadUpdater{p: p}); err != nil {
return nil, err return nil, proc.StopUnknown, err
} }
} }
} }
@ -664,7 +668,7 @@ continueLoop:
if _, exited := err.(proc.ErrProcessExited); exited { if _, exited := err.(proc.ErrProcessExited); exited {
p.exited = true p.exited = true
} }
return nil, err return nil, proc.StopExited, err
} }
// For stubs that support qThreadStopInfo updateThreadList will // For stubs that support qThreadStopInfo updateThreadList will
@ -689,16 +693,16 @@ continueLoop:
if p.BinInfo().GOOS == "linux" { if p.BinInfo().GOOS == "linux" {
if err := linutil.ElfUpdateSharedObjects(p); err != nil { if err := linutil.ElfUpdateSharedObjects(p); err != nil {
return nil, err return nil, proc.StopUnknown, err
} }
} }
if err := p.setCurrentBreakpoints(); err != nil { if err := p.setCurrentBreakpoints(); err != nil {
return nil, err return nil, proc.StopUnknown, err
} }
if trapthread == nil { if trapthread == nil {
return nil, fmt.Errorf("could not find thread %s", threadID) return nil, proc.StopUnknown, fmt.Errorf("could not find thread %s", threadID)
} }
var err error var err error
@ -720,7 +724,7 @@ continueLoop:
// the signals that are reported here can not be propagated back to the target process. // the signals that are reported here can not be propagated back to the target process.
trapthread.sig = 0 trapthread.sig = 0
} }
return trapthread, err return trapthread, proc.StopUnknown, err
} }
func (p *Process) findThreadByStrID(threadID string) *Thread { func (p *Process) findThreadByStrID(threadID string) *Thread {
@ -808,38 +812,6 @@ func (p *Process) handleThreadSignals(trapthread *Thread) (trapthreadOut *Thread
return trapthread, shouldStop return trapthread, shouldStop
} }
// SetSelectedGoroutine will set internally the goroutine that should be
// the default for any command executed, the goroutine being actively
// followed.
func (p *Process) SetSelectedGoroutine(g *proc.G) {
p.selectedGoroutine = g
}
// SwitchThread will change the internal selected thread.
func (p *Process) SwitchThread(tid int) error {
if p.exited {
return proc.ErrProcessExited{Pid: p.conn.pid}
}
if th, ok := p.threads[tid]; ok {
p.currentThread = th
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// SwitchGoroutine will change the internal selected goroutine.
func (p *Process) SwitchGoroutine(g *proc.G) error {
if g == nil {
return nil
}
if g.Thread != nil {
return p.SwitchThread(g.Thread.ThreadID())
}
p.selectedGoroutine = g
return nil
}
// RequestManualStop will attempt to stop the process // RequestManualStop will attempt to stop the process
// without a breakpoint or signal having been recieved. // without a breakpoint or signal having been recieved.
func (p *Process) RequestManualStop() error { func (p *Process) RequestManualStop() error {
@ -944,7 +916,6 @@ func (p *Process) Restart(pos string) error {
} }
p.clearThreadSignals() p.clearThreadSignals()
p.clearThreadRegisters() p.clearThreadRegisters()
p.selectedGoroutine, _ = proc.GetG(p.CurrentThread())
for addr := range p.breakpoints.M { for addr := range p.breakpoints.M {
p.conn.setBreakpoint(addr) p.conn.setBreakpoint(addr)

@ -90,13 +90,13 @@ func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string)
safeRemoveAll(p.tracedir) safeRemoveAll(p.tracedir)
} }
} }
err = p.Dial(init.port, init.exe, 0, debugInfoDirs) tgt, err := p.Dial(init.port, init.exe, 0, debugInfoDirs, proc.StopLaunched)
if err != nil { if err != nil {
rrcmd.Process.Kill() rrcmd.Process.Kill()
return nil, err return nil, err
} }
return proc.NewTarget(p, false), nil return tgt, nil
} }
// ErrPerfEventParanoid is the error returned by Reply and Record if // ErrPerfEventParanoid is the error returned by Reply and Record if

@ -18,16 +18,27 @@ type Process interface {
RecordingManipulation RecordingManipulation
} }
// RecordingManipulation is an interface for manipulating process recordings. // ProcessInternal holds a set of methods that are not meant to be called by
type RecordingManipulation interface { // anyone except for an instance of `proc.Target`. These methods are not
// Recorded returns true if the current process is a recording and the path // safe to use by themselves and should never be called directly outside of
// to the trace directory. // the `proc` package.
Recorded() (recorded bool, tracedir string) // This is temporary and in support of an ongoing refactor.
type ProcessInternal interface {
SetCurrentThread(Thread)
// Restart restarts the recording from the specified position, or from the // Restart restarts the recording from the specified position, or from the
// last checkpoint if pos == "". // last checkpoint if pos == "".
// If pos starts with 'c' it's a checkpoint ID, otherwise it's an event // If pos starts with 'c' it's a checkpoint ID, otherwise it's an event
// number. // number.
Restart(pos string) error Restart(pos string) error
Detach(bool) error
ContinueOnce() (trapthread Thread, stopReason StopReason, err error)
}
// RecordingManipulation is an interface for manipulating process recordings.
type RecordingManipulation interface {
// Recorded returns true if the current process is a recording and the path
// to the trace directory.
Recorded() (recorded bool, tracedir string)
// Direction changes execution direction. // Direction changes execution direction.
Direction(Direction) error Direction(Direction) error
// When returns current recording position. // When returns current recording position.
@ -71,7 +82,6 @@ type Info interface {
EntryPoint() (uint64, error) EntryPoint() (uint64, error)
ThreadInfo ThreadInfo
GoroutineInfo
} }
// ThreadInfo is an interface for getting information on active threads // ThreadInfo is an interface for getting information on active threads
@ -82,22 +92,12 @@ type ThreadInfo interface {
CurrentThread() Thread CurrentThread() Thread
} }
// GoroutineInfo is an interface for getting information on running goroutines.
type GoroutineInfo interface {
SelectedGoroutine() *G
SetSelectedGoroutine(*G)
}
// ProcessManipulation is an interface for changing the execution state of a process. // ProcessManipulation is an interface for changing the execution state of a process.
type ProcessManipulation interface { type ProcessManipulation interface {
ContinueOnce() (trapthread Thread, err error)
SwitchThread(int) error
SwitchGoroutine(*G) error
RequestManualStop() error RequestManualStop() error
// CheckAndClearManualStopRequest returns true the first time it's called // CheckAndClearManualStopRequest returns true the first time it's called
// after a call to RequestManualStop. // after a call to RequestManualStop.
CheckAndClearManualStopRequest() bool CheckAndClearManualStopRequest() bool
Detach(bool) error
} }
// BreakpointManipulation is an interface for managing breakpoints. // BreakpointManipulation is an interface for managing breakpoints.

@ -1,7 +1,6 @@
package native package native
import ( import (
"fmt"
"go/ast" "go/ast"
"runtime" "runtime"
"sync" "sync"
@ -26,10 +25,6 @@ type Process struct {
// Active thread // Active thread
currentThread *Thread currentThread *Thread
// 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 *proc.G
os *OSProcessDetails os *OSProcessDetails
firstStart bool firstStart bool
stopMu sync.Mutex stopMu sync.Mutex
@ -42,6 +37,8 @@ type Process struct {
exited, detached bool exited, detached bool
} }
var _ proc.ProcessInternal = &Process{}
// New returns an initialized Process struct. Before returning, // New returns an initialized Process struct. Before returning,
// it will also launch a goroutine in order to handle ptrace(2) // it will also launch a goroutine in order to handle ptrace(2)
// functions. For more information, see the documentation on // functions. For more information, see the documentation on
@ -151,12 +148,6 @@ func (dbp *Process) Pid() int {
return dbp.pid return dbp.pid
} }
// SelectedGoroutine returns the current selected,
// active goroutine.
func (dbp *Process) SelectedGoroutine() *proc.G {
return dbp.selectedGoroutine
}
// ThreadList returns a list of threads in the process. // ThreadList returns a list of threads in the process.
func (dbp *Process) ThreadList() []proc.Thread { func (dbp *Process) ThreadList() []proc.Thread {
r := make([]proc.Thread, 0, len(dbp.threads)) r := make([]proc.Thread, 0, len(dbp.threads))
@ -177,6 +168,11 @@ func (dbp *Process) CurrentThread() proc.Thread {
return dbp.currentThread return dbp.currentThread
} }
// SetCurrentThread is used internally by proc.Target to change the current thread.
func (p *Process) SetCurrentThread(th proc.Thread) {
p.currentThread = th.(*Thread)
}
// Breakpoints returns a list of breakpoints currently set. // Breakpoints returns a list of breakpoints currently set.
func (dbp *Process) Breakpoints() *proc.BreakpointMap { func (dbp *Process) Breakpoints() *proc.BreakpointMap {
return &dbp.breakpoints return &dbp.breakpoints
@ -237,13 +233,13 @@ func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
// ContinueOnce will continue the target until it stops. // ContinueOnce will continue the target until it stops.
// This could be the result of a breakpoint or signal. // This could be the result of a breakpoint or signal.
func (dbp *Process) ContinueOnce() (proc.Thread, error) { func (dbp *Process) ContinueOnce() (proc.Thread, proc.StopReason, error) {
if dbp.exited { if dbp.exited {
return nil, &proc.ErrProcessExited{Pid: dbp.Pid()} return nil, proc.StopExited, &proc.ErrProcessExited{Pid: dbp.Pid()}
} }
if err := dbp.resume(); err != nil { if err := dbp.resume(); err != nil {
return nil, err return nil, proc.StopUnknown, err
} }
for _, th := range dbp.threads { for _, th := range dbp.threads {
@ -257,42 +253,12 @@ func (dbp *Process) ContinueOnce() (proc.Thread, error) {
trapthread, err := dbp.trapWait(-1) trapthread, err := dbp.trapWait(-1)
if err != nil { if err != nil {
return nil, err return nil, proc.StopUnknown, err
} }
if err := dbp.stop(trapthread); err != nil { if err := dbp.stop(trapthread); err != nil {
return nil, err return nil, proc.StopUnknown, err
} }
return trapthread, err return trapthread, proc.StopUnknown, err
}
// SwitchThread changes from current thread to the thread specified by `tid`.
func (dbp *Process) SwitchThread(tid int) error {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
if th, ok := dbp.threads[tid]; ok {
dbp.currentThread = th
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
return nil
}
return fmt.Errorf("thread %d does not exist", tid)
}
// SwitchGoroutine changes from current thread to the thread
// running the specified goroutine.
func (dbp *Process) SwitchGoroutine(g *proc.G) error {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
if g == nil {
// user specified -1 and selectedGoroutine is nil
return nil
}
if g.Thread != nil {
return dbp.SwitchThread(g.Thread.ThreadID())
}
dbp.selectedGoroutine = g
return nil
} }
// FindBreakpoint finds the breakpoint for the given pc. // FindBreakpoint finds the breakpoint for the given pc.
@ -312,21 +278,23 @@ func (dbp *Process) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakpoint,
// initialize will ensure that all relevant information is loaded // initialize will ensure that all relevant information is loaded
// so the process is ready to be debugged. // so the process is ready to be debugged.
func (dbp *Process) initialize(path string, debugInfoDirs []string) error { func (dbp *Process) initialize(path string, debugInfoDirs []string) (*proc.Target, error) {
if err := initialize(dbp); err != nil { if err := initialize(dbp); err != nil {
return err return nil, err
} }
if err := dbp.updateThreadList(); err != nil { if err := dbp.updateThreadList(); err != nil {
return err return nil, err
} }
return proc.PostInitializationSetup(dbp, path, debugInfoDirs, dbp.writeBreakpoint) stopReason := proc.StopLaunched
} if !dbp.childProcess {
stopReason = proc.StopAttached
// SetSelectedGoroutine will set internally the goroutine that should be }
// the default for any command executed, the goroutine being actively return proc.NewTarget(dbp, proc.NewTargetConfig{
// followed. Path: path,
func (dbp *Process) SetSelectedGoroutine(g *proc.G) { DebugInfoDirs: debugInfoDirs,
dbp.selectedGoroutine = g WriteBreakpoint: dbp.writeBreakpoint,
DisableAsyncPreempt: runtime.GOOS == "windows",
StopReason: stopReason})
} }
// ClearInternalBreakpoints will clear all non-user set breakpoints. These // ClearInternalBreakpoints will clear all non-user set breakpoints. These

@ -117,16 +117,14 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
} }
dbp.os.initialized = true dbp.os.initialized = true
err = dbp.initialize(argv0Go, []string{}) dbp.currentThread = trapthread
tgt, err := dbp.initialize(argv0Go, []string{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := dbp.SwitchThread(trapthread.ID); err != nil { return tgt, err
return nil, err
}
return proc.NewTarget(dbp, false), err
} }
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
@ -153,12 +151,12 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
return nil, err return nil, err
} }
err = dbp.initialize("", []string{}) tgt, err := dbp.initialize("", []string{})
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
} }
return proc.NewTarget(dbp, false), nil return tgt, nil
} }
// Kill kills the process. // Kill kills the process.
@ -269,7 +267,7 @@ func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
dbp.threads[port] = thread dbp.threads[port] = thread
thread.os.threadAct = C.thread_act_t(port) thread.os.threadAct = C.thread_act_t(port)
if dbp.currentThread == nil { if dbp.currentThread == nil {
dbp.SwitchThread(thread.ID) dbp.currentThread = thread
} }
return thread, nil return thread, nil
} }

@ -84,10 +84,11 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err != nil { if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err) return nil, fmt.Errorf("waiting for target execve failed: %s", err)
} }
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
if err != nil {
return nil, err return nil, err
} }
return proc.NewTarget(dbp, false), nil return tgt, nil
} }
// Attach to an existing process with the given PID. Once attached, if // Attach to an existing process with the given PID. Once attached, if
@ -106,12 +107,12 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
return nil, err return nil, err
} }
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs) tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
} }
return proc.NewTarget(dbp, false), nil return tgt, nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {
@ -165,7 +166,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
} }
if dbp.currentThread == nil { if dbp.currentThread == nil {
dbp.SwitchThread(tid) dbp.currentThread = dbp.threads[tid]
} }
return dbp.threads[tid], nil return dbp.threads[tid], nil

@ -90,10 +90,11 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string) (*
if err != nil { if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err) return nil, fmt.Errorf("waiting for target execve failed: %s", err)
} }
if err = dbp.initialize(cmd[0], debugInfoDirs); err != nil { tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
if err != nil {
return nil, err return nil, err
} }
return proc.NewTarget(dbp, false), nil return tgt, nil
} }
// Attach to an existing process with the given PID. Once attached, if // Attach to an existing process with the given PID. Once attached, if
@ -112,7 +113,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
return nil, err return nil, err
} }
err = dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs) tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
if err != nil { if err != nil {
dbp.Detach(false) dbp.Detach(false)
return nil, err return nil, err
@ -124,7 +125,7 @@ func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return proc.NewTarget(dbp, false), nil return tgt, nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {
@ -222,7 +223,7 @@ func (dbp *Process) addThread(tid int, attach bool) (*Thread, error) {
os: new(OSSpecificDetails), os: new(OSSpecificDetails),
} }
if dbp.currentThread == nil { if dbp.currentThread == nil {
dbp.SwitchThread(tid) dbp.currentThread = dbp.threads[tid]
} }
return dbp.threads[tid], nil return dbp.threads[tid], nil
} }

@ -78,11 +78,12 @@ func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target,
dbp.pid = p.Pid dbp.pid = p.Pid
dbp.childProcess = true dbp.childProcess = true
if err = dbp.initialize(argv0Go, []string{}); err != nil { tgt, err := dbp.initialize(argv0Go, []string{})
if err != nil {
dbp.Detach(true) dbp.Detach(true)
return nil, err return nil, err
} }
return proc.NewTarget(dbp, true), nil return tgt, nil
} }
func initialize(dbp *Process) error { func initialize(dbp *Process) error {
@ -167,11 +168,12 @@ func Attach(pid int, _ []string) (*proc.Target, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = dbp.initialize(exepath, []string{}); err != nil { tgt, err := dbp.initialize(exepath, []string{})
if err != nil {
dbp.Detach(true) dbp.Detach(true)
return nil, err return nil, err
} }
return proc.NewTarget(dbp, true), nil return tgt, nil
} }
// kill kills the process. // kill kills the process.
@ -223,7 +225,7 @@ func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, susp
thread.os.hThread = hThread thread.os.hThread = hThread
dbp.threads[threadID] = thread dbp.threads[threadID] = thread
if dbp.currentThread == nil { if dbp.currentThread == nil {
dbp.SwitchThread(thread.ID) dbp.currentThread = dbp.threads[threadID]
} }
if suspendNewThreads { if suspendNewThreads {
_, err := _SuspendThread(thread.os.hThread) _, err := _SuspendThread(thread.os.hThread)

@ -55,33 +55,6 @@ func (pe ProcessDetachedError) Error() string {
return "detached from the process" return "detached from the process"
} }
// PostInitializationSetup handles all of the initialization procedures
// that must happen after Delve creates or attaches to a process.
func PostInitializationSetup(p Process, path string, debugInfoDirs []string, writeBreakpoint WriteBreakpointFn) error {
entryPoint, err := p.EntryPoint()
if err != nil {
return err
}
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
if err != nil {
return err
}
for _, image := range p.BinInfo().Images {
if image.loadErr != nil {
return image.loadErr
}
}
g, _ := GetG(p.CurrentThread())
p.SetSelectedGoroutine(g)
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
createFatalThrowBreakpoint(p, writeBreakpoint)
return nil
}
// FindFileLocation returns the PC for a given file:line. // FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normalized to lower case and '/' on Windows. // Assumes that `file` is normalized to lower case and '/' on Windows.
func FindFileLocation(p Process, fileName string, lineno int) ([]uint64, error) { func FindFileLocation(p Process, fileName string, lineno int) ([]uint64, error) {
@ -175,16 +148,19 @@ func Continue(dbp *Target) error {
// Make sure we clear internal breakpoints if we simultaneously receive a // Make sure we clear internal breakpoints if we simultaneously receive a
// manual stop request and hit a breakpoint. // manual stop request and hit a breakpoint.
if dbp.CheckAndClearManualStopRequest() { if dbp.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual
dbp.ClearInternalBreakpoints() dbp.ClearInternalBreakpoints()
} }
}() }()
for { for {
if dbp.CheckAndClearManualStopRequest() { if dbp.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual
dbp.ClearInternalBreakpoints() dbp.ClearInternalBreakpoints()
return nil return nil
} }
dbp.ClearAllGCache() dbp.ClearAllGCache()
trapthread, err := dbp.ContinueOnce() trapthread, stopReason, err := dbp.proc.ContinueOnce()
dbp.StopReason = stopReason
if err != nil { if err != nil {
return err return err
} }
@ -232,6 +208,7 @@ func Continue(dbp *Target) error {
if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil { if err := stepInstructionOut(dbp, curthread, "runtime.breakpoint", "runtime.Breakpoint"); err != nil {
return err return err
} }
dbp.StopReason = StopHardcodedBreakpoint
return conditionErrors(threads) return conditionErrors(threads)
case g == nil || dbp.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 // a hardcoded breakpoint somewhere else in the code (probably cgo), or manual stop in cgo
@ -272,6 +249,7 @@ func Continue(dbp *Target) error {
if err := dbp.ClearInternalBreakpoints(); err != nil { if err := dbp.ClearInternalBreakpoints(); err != nil {
return err return err
} }
dbp.StopReason = StopNextFinished
return conditionErrors(threads) return conditionErrors(threads)
} }
case curbp.Active: case curbp.Active:
@ -288,6 +266,7 @@ func Continue(dbp *Target) error {
if curbp.Name == UnrecoveredPanic { if curbp.Name == UnrecoveredPanic {
dbp.ClearInternalBreakpoints() dbp.ClearInternalBreakpoints()
} }
dbp.StopReason = StopBreakpoint
return conditionErrors(threads) return conditionErrors(threads)
default: default:
// not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat // not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat
@ -295,6 +274,7 @@ func Continue(dbp *Target) error {
if callInjectionDone { if callInjectionDone {
// a call injection was finished, don't let a breakpoint with a failed // a call injection was finished, don't let a breakpoint with a failed
// condition or a step breakpoint shadow this. // condition or a step breakpoint shadow this.
dbp.StopReason = StopCallReturned
return conditionErrors(threads) return conditionErrors(threads)
} }
} }
@ -318,7 +298,7 @@ func conditionErrors(threads []Thread) error {
// - a thread with onTriggeredInternalBreakpoint() == true // - a thread with onTriggeredInternalBreakpoint() == true
// - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread) // - a thread with onTriggeredBreakpoint() == true (prioritizing trapthread)
// - trapthread // - trapthread
func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error { func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
for _, th := range threads { for _, th := range threads {
if bp := th.Breakpoint(); bp.Active && bp.Internal { if bp := th.Breakpoint(); bp.Active && bp.Internal {
return dbp.SwitchThread(th.ThreadID()) return dbp.SwitchThread(th.ThreadID())
@ -339,7 +319,7 @@ func pickCurrentThread(dbp Process, trapthread Thread, threads []Thread) error {
// function is neither fnname1 or fnname2. // function is neither fnname1 or fnname2.
// This function is used to step out of runtime.Breakpoint as well as // This function is used to step out of runtime.Breakpoint as well as
// runtime.debugCallV1. // runtime.debugCallV1.
func stepInstructionOut(dbp Process, curthread Thread, fnname1, fnname2 string) error { func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string) error {
for { for {
if err := curthread.StepInstruction(); err != nil { if err := curthread.StepInstruction(); err != nil {
return err return err
@ -538,7 +518,7 @@ func StepInstruction(dbp *Target) (err error) {
return err return err
} }
if tg, _ := GetG(thread); tg != nil { if tg, _ := GetG(thread); tg != nil {
dbp.SetSelectedGoroutine(tg) dbp.selectedGoroutine = tg
} }
return nil return nil
} }

@ -1,9 +1,24 @@
package proc package proc
import (
"fmt"
)
// Target represents the process being debugged. // Target represents the process being debugged.
type Target struct { type Target struct {
Process 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 stores a mapping of current active function calls.
fncallForG map[int]*callInjection fncallForG map[int]*callInjection
@ -16,19 +31,69 @@ type Target struct {
gcache goroutineCache 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. // NewTarget returns an initialized Target object.
func NewTarget(p Process, disableAsyncPreempt bool) *Target { 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{ t := &Target{
Process: p, Process: p,
proc: p.(ProcessInternal),
fncallForG: make(map[int]*callInjection), 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()) t.gcache.init(p.BinInfo())
if disableAsyncPreempt { if cfg.DisableAsyncPreempt {
setAsyncPreemptOff(t, 1) setAsyncPreemptOff(t, 1)
} }
return t return t, nil
} }
// SupportsFunctionCalls returns whether or not the backend supports // SupportsFunctionCalls returns whether or not the backend supports
@ -54,7 +119,50 @@ func (t *Target) ClearAllGCache() {
// Restarting of a normal process happens at a higher level (debugger.Restart). // Restarting of a normal process happens at a higher level (debugger.Restart).
func (t *Target) Restart(from string) error { func (t *Target) Restart(from string) error {
t.ClearAllGCache() t.ClearAllGCache()
return t.Process.Restart(from) 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. // Detach will detach the target from the underylying process.
@ -65,5 +173,6 @@ func (t *Target) Detach(kill bool) error {
if !kill && t.asyncPreemptChanged { if !kill && t.asyncPreemptChanged {
setAsyncPreemptOff(t, t.asyncPreemptOff) setAsyncPreemptOff(t, t.asyncPreemptOff)
} }
return t.Process.Detach(kill) t.StopReason = StopUnknown
return t.proc.Detach(kill)
} }

@ -141,7 +141,7 @@ func (err *ErrNoSourceForPC) Error() string {
// for an inlined function call. Everything works the same as normal except // for an inlined function call. Everything works the same as normal except
// when removing instructions belonging to inlined calls we also remove all // when removing instructions belonging to inlined calls we also remove all
// instructions belonging to the current inlined call. // instructions belonging to the current inlined call.
func next(dbp Process, stepInto, inlinedStepOut bool) error { func next(dbp *Target, stepInto, inlinedStepOut bool) error {
selg := dbp.SelectedGoroutine() selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread() curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread) topframe, retframe, err := topframe(selg, curthread)