
This pull request makes several changes to delve to allow headless instancess that are started with the --accept-multiclient flag to keep running even if there is no connected client. Specifically: 1. Makes a headless instance started with --accept-multiclient quit after one of the clients sends a Detach request (previously they would never ever quit, which was a bug). 2. Changes proc/gdbserial and proc/native so that they mark the Process as exited after they detach, even if they did not kill the process during detach. This prevents bugs such as #1231 where we attempt to manipulate a target process after we detached from it. 3. On non --accept-multiclient instances do not kill the target process unless we started it or the client specifically requests it (previously if the client did not Detach before closing the connection we would kill the target process unconditionally) 4. Add a -c option to the quit command that detaches from the headless server after restarting the target. 5. Change terminal so that, when attached to --accept-multiclient, pressing ^C will prompt the user to either disconnect from the server or pause the target process. Also extend the exit prompt to ask if the user wants to keep the headless server running. Implements #245, #952, #1159, #1231
409 lines
11 KiB
Go
409 lines
11 KiB
Go
package native
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"runtime"
|
|
"sync"
|
|
|
|
"github.com/derekparker/delve/pkg/proc"
|
|
)
|
|
|
|
// Process represents all of the information the debugger
|
|
// is holding onto regarding the process we are debugging.
|
|
type Process struct {
|
|
bi proc.BinaryInfo
|
|
pid int // Process Pid
|
|
|
|
// Breakpoint table, holds information on breakpoints.
|
|
// Maps instruction address to Breakpoint struct.
|
|
breakpoints proc.BreakpointMap
|
|
|
|
// List of threads mapped as such: pid -> *Thread
|
|
threads map[int]*Thread
|
|
|
|
// Active 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
|
|
|
|
common proc.CommonProcess
|
|
os *OSProcessDetails
|
|
firstStart bool
|
|
stopMu sync.Mutex
|
|
resumeChan chan<- struct{}
|
|
ptraceChan chan func()
|
|
ptraceDoneChan chan interface{}
|
|
childProcess bool // this process was launched, not attached to
|
|
manualStopRequested bool
|
|
|
|
exited, detached bool
|
|
}
|
|
|
|
// New returns an initialized Process struct. Before returning,
|
|
// it will also launch a goroutine in order to handle ptrace(2)
|
|
// functions. For more information, see the documentation on
|
|
// `handlePtraceFuncs`.
|
|
func New(pid int) *Process {
|
|
dbp := &Process{
|
|
pid: pid,
|
|
threads: make(map[int]*Thread),
|
|
breakpoints: proc.NewBreakpointMap(),
|
|
firstStart: true,
|
|
os: new(OSProcessDetails),
|
|
ptraceChan: make(chan func()),
|
|
ptraceDoneChan: make(chan interface{}),
|
|
bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
|
|
}
|
|
go dbp.handlePtraceFuncs()
|
|
return dbp
|
|
}
|
|
|
|
func (dbp *Process) BinInfo() *proc.BinaryInfo {
|
|
return &dbp.bi
|
|
}
|
|
|
|
func (dbp *Process) Recorded() (bool, string) { return false, "" }
|
|
func (dbp *Process) Restart(string) error { return proc.NotRecordedErr }
|
|
func (dbp *Process) Direction(proc.Direction) error { return proc.NotRecordedErr }
|
|
func (dbp *Process) When() (string, error) { return "", nil }
|
|
func (dbp *Process) Checkpoint(string) (int, error) { return -1, proc.NotRecordedErr }
|
|
func (dbp *Process) Checkpoints() ([]proc.Checkpoint, error) { return nil, proc.NotRecordedErr }
|
|
func (dbp *Process) ClearCheckpoint(int) error { return proc.NotRecordedErr }
|
|
|
|
// Detach from the process being debugged, optionally killing it.
|
|
func (dbp *Process) Detach(kill bool) (err error) {
|
|
if dbp.exited {
|
|
return nil
|
|
}
|
|
if kill && dbp.childProcess {
|
|
err := dbp.kill()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dbp.bi.Close()
|
|
return nil
|
|
}
|
|
if !kill {
|
|
// Clean up any breakpoints we've set.
|
|
for _, bp := range dbp.breakpoints.M {
|
|
if bp != nil {
|
|
_, err := dbp.ClearBreakpoint(bp.Addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dbp.execPtraceFunc(func() {
|
|
err = dbp.detach(kill)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if kill {
|
|
err = killProcess(dbp.pid)
|
|
}
|
|
})
|
|
dbp.detached = true
|
|
dbp.postExit()
|
|
return
|
|
}
|
|
|
|
func (dbp *Process) Valid() (bool, error) {
|
|
if dbp.detached {
|
|
return false, &proc.ProcessDetachedError{}
|
|
}
|
|
if dbp.exited {
|
|
return false, &proc.ProcessExitedError{Pid: dbp.Pid()}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (dbp *Process) ResumeNotify(ch chan<- struct{}) {
|
|
dbp.resumeChan = ch
|
|
}
|
|
|
|
func (dbp *Process) Pid() int {
|
|
return dbp.pid
|
|
}
|
|
|
|
func (dbp *Process) SelectedGoroutine() *proc.G {
|
|
return dbp.selectedGoroutine
|
|
}
|
|
|
|
func (dbp *Process) ThreadList() []proc.Thread {
|
|
r := make([]proc.Thread, 0, len(dbp.threads))
|
|
for _, v := range dbp.threads {
|
|
r = append(r, v)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (dbp *Process) FindThread(threadID int) (proc.Thread, bool) {
|
|
th, ok := dbp.threads[threadID]
|
|
return th, ok
|
|
}
|
|
|
|
func (dbp *Process) CurrentThread() proc.Thread {
|
|
return dbp.currentThread
|
|
}
|
|
|
|
func (dbp *Process) Breakpoints() *proc.BreakpointMap {
|
|
return &dbp.breakpoints
|
|
}
|
|
|
|
// LoadInformation finds the executable and then uses it
|
|
// to parse the following information:
|
|
// * Dwarf .debug_frame section
|
|
// * Dwarf .debug_line section
|
|
// * Go symbol table.
|
|
func (dbp *Process) LoadInformation(path string) error {
|
|
var wg sync.WaitGroup
|
|
|
|
path = findExecutable(path, dbp.pid)
|
|
|
|
wg.Add(1)
|
|
go dbp.loadProcessInformation(&wg)
|
|
err := dbp.bi.LoadBinaryInfo(path, &wg)
|
|
wg.Wait()
|
|
if err == nil {
|
|
err = dbp.bi.LoadError()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// RequestManualStop sets the `halt` flag and
|
|
// sends SIGSTOP to all threads.
|
|
func (dbp *Process) RequestManualStop() error {
|
|
if dbp.exited {
|
|
return &proc.ProcessExitedError{Pid: dbp.Pid()}
|
|
}
|
|
dbp.stopMu.Lock()
|
|
defer dbp.stopMu.Unlock()
|
|
dbp.manualStopRequested = true
|
|
return dbp.requestManualStop()
|
|
}
|
|
|
|
func (dbp *Process) CheckAndClearManualStopRequest() bool {
|
|
dbp.stopMu.Lock()
|
|
msr := dbp.manualStopRequested
|
|
dbp.manualStopRequested = false
|
|
dbp.stopMu.Unlock()
|
|
return msr
|
|
}
|
|
|
|
func (dbp *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []byte, error) {
|
|
f, l, fn := dbp.bi.PCToLine(uint64(addr))
|
|
if fn == nil {
|
|
return "", 0, nil, nil, proc.InvalidAddressError{Address: addr}
|
|
}
|
|
|
|
originalData := make([]byte, dbp.bi.Arch.BreakpointSize())
|
|
_, err := dbp.currentThread.ReadMemory(originalData, uintptr(addr))
|
|
if err != nil {
|
|
return "", 0, nil, nil, err
|
|
}
|
|
if err := dbp.writeSoftwareBreakpoint(dbp.currentThread, addr); err != nil {
|
|
return "", 0, nil, nil, err
|
|
}
|
|
|
|
return f, l, fn, originalData, nil
|
|
}
|
|
|
|
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
|
|
// break point table.
|
|
func (dbp *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) {
|
|
return dbp.breakpoints.Set(addr, kind, cond, dbp.writeBreakpoint)
|
|
}
|
|
|
|
// ClearBreakpoint clears the breakpoint at addr.
|
|
func (dbp *Process) ClearBreakpoint(addr uint64) (*proc.Breakpoint, error) {
|
|
if dbp.exited {
|
|
return nil, &proc.ProcessExitedError{Pid: dbp.Pid()}
|
|
}
|
|
return dbp.breakpoints.Clear(addr, dbp.currentThread.ClearBreakpoint)
|
|
}
|
|
|
|
func (dbp *Process) ContinueOnce() (proc.Thread, error) {
|
|
if dbp.exited {
|
|
return nil, &proc.ProcessExitedError{Pid: dbp.Pid()}
|
|
}
|
|
|
|
if err := dbp.resume(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dbp.common.ClearAllGCache()
|
|
for _, th := range dbp.threads {
|
|
th.CurrentBreakpoint.Clear()
|
|
}
|
|
|
|
if dbp.resumeChan != nil {
|
|
close(dbp.resumeChan)
|
|
dbp.resumeChan = nil
|
|
}
|
|
|
|
trapthread, err := dbp.trapWait(-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dbp.stop(trapthread); err != nil {
|
|
return nil, err
|
|
}
|
|
return trapthread, err
|
|
}
|
|
|
|
// StepInstruction will continue the current thread for exactly
|
|
// one instruction. This method affects only the thread
|
|
// associated with the selected goroutine. All other
|
|
// threads will remain stopped.
|
|
func (dbp *Process) StepInstruction() (err error) {
|
|
thread := dbp.currentThread
|
|
if dbp.selectedGoroutine != nil {
|
|
if dbp.selectedGoroutine.Thread == nil {
|
|
// Step called on parked goroutine
|
|
if _, err := dbp.SetBreakpoint(dbp.selectedGoroutine.PC, proc.NextBreakpoint, proc.SameGoroutineCondition(dbp.selectedGoroutine)); err != nil {
|
|
return err
|
|
}
|
|
return proc.Continue(dbp)
|
|
}
|
|
thread = dbp.selectedGoroutine.Thread.(*Thread)
|
|
}
|
|
dbp.common.ClearAllGCache()
|
|
if dbp.exited {
|
|
return &proc.ProcessExitedError{Pid: dbp.Pid()}
|
|
}
|
|
thread.CurrentBreakpoint.Clear()
|
|
err = thread.StepInstruction()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = thread.SetCurrentBreakpoint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if g, _ := proc.GetG(thread); g != nil {
|
|
dbp.selectedGoroutine = g
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SwitchThread changes from current thread to the thread specified by `tid`.
|
|
func (dbp *Process) SwitchThread(tid int) error {
|
|
if dbp.exited {
|
|
return &proc.ProcessExitedError{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(gid int) error {
|
|
if dbp.exited {
|
|
return &proc.ProcessExitedError{Pid: dbp.Pid()}
|
|
}
|
|
g, err := proc.FindGoroutine(dbp, gid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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.
|
|
func (dbp *Process) FindBreakpoint(pc uint64) (*proc.Breakpoint, bool) {
|
|
// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
|
|
if bp, ok := dbp.breakpoints.M[pc-uint64(dbp.bi.Arch.BreakpointSize())]; ok {
|
|
return bp, true
|
|
}
|
|
// Directly use addr to lookup breakpoint.
|
|
if bp, ok := dbp.breakpoints.M[pc]; ok {
|
|
return bp, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Returns a new Process struct.
|
|
func initializeDebugProcess(dbp *Process, path string) (*Process, error) {
|
|
err := dbp.LoadInformation(path)
|
|
if err != nil {
|
|
return dbp, err
|
|
}
|
|
|
|
if err := dbp.updateThreadList(); err != nil {
|
|
return dbp, err
|
|
}
|
|
|
|
// selectedGoroutine can not be set correctly by the call to updateThreadList
|
|
// because without calling SetGStructOffset we can not read the G struct of currentThread
|
|
// but without calling updateThreadList we can not examine memory to determine
|
|
// the offset of g struct inside TLS
|
|
dbp.selectedGoroutine, _ = proc.GetG(dbp.currentThread)
|
|
|
|
proc.CreateUnrecoveredPanicBreakpoint(dbp, dbp.writeBreakpoint, &dbp.breakpoints)
|
|
|
|
return dbp, nil
|
|
}
|
|
|
|
func (dbp *Process) ClearInternalBreakpoints() error {
|
|
return dbp.breakpoints.ClearInternalBreakpoints(func(bp *proc.Breakpoint) error {
|
|
if err := dbp.currentThread.ClearBreakpoint(bp); err != nil {
|
|
return err
|
|
}
|
|
for _, thread := range dbp.threads {
|
|
if thread.CurrentBreakpoint.Breakpoint == bp {
|
|
thread.CurrentBreakpoint.Clear()
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (dbp *Process) handlePtraceFuncs() {
|
|
// We must ensure here that we are running on the same thread during
|
|
// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
|
|
// all commands after PTRACE_ATTACH to come from the same thread.
|
|
runtime.LockOSThread()
|
|
|
|
for fn := range dbp.ptraceChan {
|
|
fn()
|
|
dbp.ptraceDoneChan <- nil
|
|
}
|
|
}
|
|
|
|
func (dbp *Process) execPtraceFunc(fn func()) {
|
|
dbp.ptraceChan <- fn
|
|
<-dbp.ptraceDoneChan
|
|
}
|
|
|
|
func (dbp *Process) postExit() {
|
|
dbp.exited = true
|
|
close(dbp.ptraceChan)
|
|
close(dbp.ptraceDoneChan)
|
|
dbp.bi.Close()
|
|
}
|
|
|
|
func (dbp *Process) writeSoftwareBreakpoint(thread *Thread, addr uint64) error {
|
|
_, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction())
|
|
return err
|
|
}
|
|
|
|
func (dbp *Process) Common() *proc.CommonProcess {
|
|
return &dbp.common
|
|
}
|