proc: fix interaction of RequestManualStop and conditional breakpoints (#876)
* proc: fix interaction of RequestManualStop and conditional breakpoints A conditional breakpoint that is hit but has the condition evaluate to false can block a RequestManualStop from working. If the conditional breakpoint is set on an instruction that is executed very frequently by multiple goroutines (or many conditional breakpoints are set) it could prevent all calls to RequestManualStop from working. This commit fixes the problem by changing proc.Continue to exit unconditionally after a RequestManualStop is called. * proc/gdbserial: fix ContinueOnce getting stuck on macOS Fixes #902
This commit is contained in:
parent
8276ba06cd
commit
07e53f7cbb
@ -269,6 +269,10 @@ func (p *Process) RequestManualStop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Process) ManualStopRequested() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Process) CurrentThread() proc.Thread {
|
func (p *Process) CurrentThread() proc.Thread {
|
||||||
return &Thread{p.currentThread, p}
|
return &Thread{p.currentThread, p}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,6 +108,8 @@ type Process struct {
|
|||||||
exited bool
|
exited bool
|
||||||
ctrlC bool // ctrl-c was sent to stop inferior
|
ctrlC bool // ctrl-c was sent to stop inferior
|
||||||
|
|
||||||
|
manualStopRequested bool
|
||||||
|
|
||||||
breakpoints map[uint64]*proc.Breakpoint
|
breakpoints map[uint64]*proc.Breakpoint
|
||||||
breakpointIDCounter int
|
breakpointIDCounter int
|
||||||
internalBreakpointIDCounter int
|
internalBreakpointIDCounter int
|
||||||
@ -540,7 +542,7 @@ func (p *Process) ContinueOnce() (proc.Thread, error) {
|
|||||||
th.clearBreakpointState()
|
th.clearBreakpointState()
|
||||||
}
|
}
|
||||||
|
|
||||||
p.ctrlC = false
|
p.setCtrlC(false)
|
||||||
|
|
||||||
// resume all threads
|
// resume all threads
|
||||||
var threadID string
|
var threadID string
|
||||||
@ -565,7 +567,7 @@ continueLoop:
|
|||||||
// the ctrlC flag to know that we are the originators.
|
// the ctrlC flag to know that we are the originators.
|
||||||
switch sig {
|
switch sig {
|
||||||
case interruptSignal: // interrupt
|
case interruptSignal: // interrupt
|
||||||
if p.ctrlC {
|
if p.getCtrlC() {
|
||||||
break continueLoop
|
break continueLoop
|
||||||
}
|
}
|
||||||
case breakpointSignal: // breakpoint
|
case breakpointSignal: // breakpoint
|
||||||
@ -655,18 +657,42 @@ func (p *Process) SwitchGoroutine(gid int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) RequestManualStop() error {
|
func (p *Process) RequestManualStop() error {
|
||||||
|
p.conn.manualStopMutex.Lock()
|
||||||
|
p.manualStopRequested = true
|
||||||
if !p.conn.running {
|
if !p.conn.running {
|
||||||
|
p.conn.manualStopMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p.ctrlC = true
|
p.ctrlC = true
|
||||||
|
p.conn.manualStopMutex.Unlock()
|
||||||
return p.conn.sendCtrlC()
|
return p.conn.sendCtrlC()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Process) ManualStopRequested() bool {
|
||||||
|
p.conn.manualStopMutex.Lock()
|
||||||
|
msr := p.manualStopRequested
|
||||||
|
p.manualStopRequested = false
|
||||||
|
p.conn.manualStopMutex.Unlock()
|
||||||
|
return msr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) setCtrlC(v bool) {
|
||||||
|
p.conn.manualStopMutex.Lock()
|
||||||
|
p.ctrlC = v
|
||||||
|
p.conn.manualStopMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) getCtrlC() bool {
|
||||||
|
p.conn.manualStopMutex.Lock()
|
||||||
|
defer p.conn.manualStopMutex.Unlock()
|
||||||
|
return p.ctrlC
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Process) Halt() error {
|
func (p *Process) Halt() error {
|
||||||
if p.exited {
|
if p.exited {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p.ctrlC = true
|
p.setCtrlC(true)
|
||||||
return p.conn.sendCtrlC()
|
return p.conn.sendCtrlC()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -715,7 +741,7 @@ func (p *Process) Restart(pos string) error {
|
|||||||
th.clearBreakpointState()
|
th.clearBreakpointState()
|
||||||
}
|
}
|
||||||
|
|
||||||
p.ctrlC = false
|
p.setCtrlC(false)
|
||||||
|
|
||||||
err := p.conn.restart(pos)
|
err := p.conn.restart(pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1129,8 +1155,11 @@ func (t *Thread) Blocked() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
pc := regs.PC()
|
pc := regs.PC()
|
||||||
fn := t.BinInfo().PCToFunc(pc)
|
f, ln, fn := t.BinInfo().PCToLine(pc)
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
|
if f == "" && ln == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch fn.Name {
|
switch fn.Name {
|
||||||
@ -1306,6 +1335,12 @@ func (t *Thread) reloadGAtPC() error {
|
|||||||
|
|
||||||
_, _, err = t.p.conn.step(t.strID, nil)
|
_, _, err = t.p.conn.step(t.strID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == threadBlockedError {
|
||||||
|
t.regs.tls = 0
|
||||||
|
t.regs.gaddr = 0
|
||||||
|
t.regs.hasgaddr = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1353,6 +1388,12 @@ func (t *Thread) reloadGAlloc() error {
|
|||||||
|
|
||||||
_, _, err = t.p.conn.step(t.strID, nil)
|
_, _, err = t.p.conn.step(t.strID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == threadBlockedError {
|
||||||
|
t.regs.tls = 0
|
||||||
|
t.regs.gaddr = 0
|
||||||
|
t.regs.hasgaddr = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derekparker/delve/pkg/proc"
|
"github.com/derekparker/delve/pkg/proc"
|
||||||
@ -23,8 +24,9 @@ type gdbConn struct {
|
|||||||
inbuf []byte
|
inbuf []byte
|
||||||
outbuf bytes.Buffer
|
outbuf bytes.Buffer
|
||||||
|
|
||||||
running bool
|
manualStopMutex sync.Mutex
|
||||||
resumeChan chan<- struct{}
|
running bool
|
||||||
|
resumeChan chan<- struct{}
|
||||||
|
|
||||||
direction proc.Direction // direction of execution
|
direction proc.Direction // direction of execution
|
||||||
|
|
||||||
@ -536,12 +538,17 @@ func (conn *gdbConn) resume(sig uint8, tu *threadUpdater) (string, uint8, error)
|
|||||||
conn.outbuf.Reset()
|
conn.outbuf.Reset()
|
||||||
fmt.Fprint(&conn.outbuf, "$bc")
|
fmt.Fprint(&conn.outbuf, "$bc")
|
||||||
}
|
}
|
||||||
|
conn.manualStopMutex.Lock()
|
||||||
if err := conn.send(conn.outbuf.Bytes()); err != nil {
|
if err := conn.send(conn.outbuf.Bytes()); err != nil {
|
||||||
|
conn.manualStopMutex.Unlock()
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
conn.running = true
|
conn.running = true
|
||||||
|
conn.manualStopMutex.Unlock()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
conn.manualStopMutex.Lock()
|
||||||
conn.running = false
|
conn.running = false
|
||||||
|
conn.manualStopMutex.Unlock()
|
||||||
}()
|
}()
|
||||||
if conn.resumeChan != nil {
|
if conn.resumeChan != nil {
|
||||||
close(conn.resumeChan)
|
close(conn.resumeChan)
|
||||||
@ -568,7 +575,11 @@ func (conn *gdbConn) step(threadID string, tu *threadUpdater) (string, uint8, er
|
|||||||
return conn.waitForvContStop("singlestep", threadID, tu)
|
return conn.waitForvContStop("singlestep", threadID, tu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var threadBlockedError = errors.New("thread blocked")
|
||||||
|
|
||||||
func (conn *gdbConn) waitForvContStop(context string, threadID string, tu *threadUpdater) (string, uint8, error) {
|
func (conn *gdbConn) waitForvContStop(context string, threadID string, tu *threadUpdater) (string, uint8, error) {
|
||||||
|
count := 0
|
||||||
|
failed := false
|
||||||
for {
|
for {
|
||||||
conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval))
|
conn.conn.SetReadDeadline(time.Now().Add(heartbeatInterval))
|
||||||
resp, err := conn.recv(nil, context)
|
resp, err := conn.recv(nil, context)
|
||||||
@ -581,6 +592,13 @@ func (conn *gdbConn) waitForvContStop(context string, threadID string, tu *threa
|
|||||||
if conn.isDebugserver {
|
if conn.isDebugserver {
|
||||||
conn.send([]byte("$?"))
|
conn.send([]byte("$?"))
|
||||||
}
|
}
|
||||||
|
if count > 1 && context == "singlestep" {
|
||||||
|
failed = true
|
||||||
|
conn.sendCtrlC()
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
} else if failed {
|
||||||
|
return "", 0, threadBlockedError
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -87,6 +87,9 @@ type ProcessManipulation interface {
|
|||||||
SwitchThread(int) error
|
SwitchThread(int) error
|
||||||
SwitchGoroutine(int) error
|
SwitchGoroutine(int) error
|
||||||
RequestManualStop() error
|
RequestManualStop() error
|
||||||
|
// ManualStopRequested returns true the first time it's called after a call
|
||||||
|
// to RequestManualStop.
|
||||||
|
ManualStopRequested() bool
|
||||||
Halt() error
|
Halt() error
|
||||||
Kill() error
|
Kill() error
|
||||||
Detach(bool) error
|
Detach(bool) error
|
||||||
|
|||||||
@ -44,6 +44,7 @@ type Process struct {
|
|||||||
ptraceChan chan func()
|
ptraceChan chan func()
|
||||||
ptraceDoneChan chan interface{}
|
ptraceDoneChan chan interface{}
|
||||||
childProcess bool // this process was launched, not attached to
|
childProcess bool // this process was launched, not attached to
|
||||||
|
manualStopRequested bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns an initialized Process struct. Before returning,
|
// New returns an initialized Process struct. Before returning,
|
||||||
@ -179,10 +180,19 @@ func (dbp *Process) RequestManualStop() error {
|
|||||||
}
|
}
|
||||||
dbp.haltMu.Lock()
|
dbp.haltMu.Lock()
|
||||||
defer dbp.haltMu.Unlock()
|
defer dbp.haltMu.Unlock()
|
||||||
|
dbp.manualStopRequested = true
|
||||||
dbp.halt = true
|
dbp.halt = true
|
||||||
return dbp.requestManualStop()
|
return dbp.requestManualStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dbp *Process) ManualStopRequested() bool {
|
||||||
|
dbp.haltMu.Lock()
|
||||||
|
msr := dbp.manualStopRequested
|
||||||
|
dbp.manualStopRequested = false
|
||||||
|
dbp.haltMu.Unlock()
|
||||||
|
return msr
|
||||||
|
}
|
||||||
|
|
||||||
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
|
// SetBreakpoint sets a breakpoint at addr, and stores it in the process wide
|
||||||
// break point table. Setting a break point must be thread specific due to
|
// break point table. Setting a break point must be thread specific due to
|
||||||
// ptrace actions needing the thread to be in a signal-delivery-stop.
|
// ptrace actions needing the thread to be in a signal-delivery-stop.
|
||||||
|
|||||||
@ -95,7 +95,11 @@ func Next(dbp Process) (err error) {
|
|||||||
// process. It will continue until it hits a breakpoint
|
// process. It will continue until it hits a breakpoint
|
||||||
// or is otherwise stopped.
|
// or is otherwise stopped.
|
||||||
func Continue(dbp Process) error {
|
func Continue(dbp Process) error {
|
||||||
|
dbp.ManualStopRequested()
|
||||||
for {
|
for {
|
||||||
|
if dbp.ManualStopRequested() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
trapthread, err := dbp.ContinueOnce()
|
trapthread, err := dbp.ContinueOnce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user