
Adds the ability to automatically debug child processes executed by the target to the linux native backend. This commit does not contain user interface or API to access this functionality. Updates #2551
146 lines
3.5 KiB
Go
146 lines
3.5 KiB
Go
package native
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
sys "golang.org/x/sys/unix"
|
|
|
|
"github.com/go-delve/delve/pkg/proc"
|
|
)
|
|
|
|
type waitStatus sys.WaitStatus
|
|
|
|
// osSpecificDetails hold Linux specific
|
|
// process details.
|
|
type osSpecificDetails struct {
|
|
delayedSignal int
|
|
running bool
|
|
setbp bool
|
|
phantomBreakpointPC uint64
|
|
}
|
|
|
|
func (t *nativeThread) stop() (err error) {
|
|
err = sys.Tgkill(t.dbp.pid, t.ID, sys.SIGSTOP)
|
|
if err != nil {
|
|
if err == sys.ESRCH {
|
|
return
|
|
}
|
|
err = fmt.Errorf("stop err %s on thread %d", err, t.ID)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Stopped returns whether the thread is stopped at
|
|
// the operating system level.
|
|
func (t *nativeThread) Stopped() bool {
|
|
state := status(t.ID, t.dbp.os.comm)
|
|
return state == statusTraceStop || state == statusTraceStopT
|
|
}
|
|
|
|
func (t *nativeThread) resume() error {
|
|
sig := t.os.delayedSignal
|
|
t.os.delayedSignal = 0
|
|
return t.resumeWithSig(sig)
|
|
}
|
|
|
|
func (t *nativeThread) resumeWithSig(sig int) (err error) {
|
|
t.os.running = true
|
|
t.dbp.execPtraceFunc(func() { err = ptraceCont(t.ID, sig) })
|
|
return
|
|
}
|
|
|
|
func (t *nativeThread) singleStep() (err error) {
|
|
sig := 0
|
|
for {
|
|
t.dbp.execPtraceFunc(func() { err = ptraceSingleStep(t.ID, sig) })
|
|
sig = 0
|
|
if err != nil {
|
|
return err
|
|
}
|
|
wpid, status, err := t.dbp.waitFast(t.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (status == nil || status.Exited()) && wpid == t.dbp.pid {
|
|
t.dbp.postExit()
|
|
rs := 0
|
|
if status != nil {
|
|
rs = status.ExitStatus()
|
|
}
|
|
return proc.ErrProcessExited{Pid: t.dbp.pid, Status: rs}
|
|
}
|
|
if wpid == t.ID {
|
|
switch s := status.StopSignal(); s {
|
|
case sys.SIGTRAP:
|
|
return nil
|
|
case sys.SIGSTOP:
|
|
// delayed SIGSTOP, ignore it
|
|
case sys.SIGILL, sys.SIGBUS, sys.SIGFPE, sys.SIGSEGV, sys.SIGSTKFLT:
|
|
// propagate signals that can have been caused by the current instruction
|
|
sig = int(s)
|
|
default:
|
|
// delay propagation of all other signals
|
|
t.os.delayedSignal = int(s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *nativeThread) WriteMemory(addr uint64, data []byte) (written int, err error) {
|
|
if t.dbp.exited {
|
|
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
|
|
}
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
// ProcessVmWrite can't poke read-only memory like ptrace, so don't
|
|
// even bother for small writes -- likely breakpoints and such.
|
|
if len(data) > sys.SizeofPtr {
|
|
written, _ = processVmWrite(t.ID, uintptr(addr), data)
|
|
}
|
|
if written == 0 {
|
|
t.dbp.execPtraceFunc(func() { written, err = sys.PtracePokeData(t.ID, uintptr(addr), data) })
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t *nativeThread) ReadMemory(data []byte, addr uint64) (n int, err error) {
|
|
if t.dbp.exited {
|
|
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
|
|
}
|
|
if len(data) == 0 {
|
|
return
|
|
}
|
|
n, _ = processVmRead(t.ID, uintptr(addr), data)
|
|
if n == 0 {
|
|
t.dbp.execPtraceFunc(func() { n, err = sys.PtracePeekData(t.ID, uintptr(addr), data) })
|
|
}
|
|
return
|
|
}
|
|
|
|
// SoftExc returns true if this thread received a software exception during the last resume.
|
|
func (t *nativeThread) SoftExc() bool {
|
|
return t.os.setbp
|
|
}
|
|
|
|
// Continue the execution of this thread.
|
|
//
|
|
// If we are currently at a breakpoint, we'll clear it
|
|
// first and then resume execution. Thread will continue until
|
|
// it hits a breakpoint or is signaled.
|
|
func (t *nativeThread) Continue() error {
|
|
pc, err := t.PC()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Check whether we are stopped at a breakpoint, and
|
|
// if so, single step over it before continuing.
|
|
if _, ok := t.dbp.FindBreakpoint(pc, false); ok {
|
|
if err := t.StepInstruction(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return t.resume()
|
|
}
|