delve/pkg/proc/native/threads_linux.go
Alessandro Arzilli b8a9ae26f6
proc: fix signal handling during stepping (#2803)
Fix signal handling during thread single stepping so that signals that
are generated by executing the current instruction are immediately
propagated to the inferior, while signals other signals sent to the
thread are delayed until the full resume happens.

Fixes a bug where a breakpoint set on an instruction that causes a
SIGSEGV would make Delve hang and a bug where signals received during
single step would make it look like an instruction is executed twice.

Fixes #2801
Fixes #2792
2021-12-07 09:21:53 -08:00

118 lines
2.8 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 {
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
}