Skip to content

Commit

Permalink
proc: changed windows backend to deal with simultaneous breakpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
aarzilli committed Jul 21, 2016
1 parent 16f16cf commit 4e390b5
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 47 deletions.
104 changes: 76 additions & 28 deletions proc/proc_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import (

sys "golang.org/x/sys/windows"

"golang.org/x/debug/dwarf"
"github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line"
"golang.org/x/debug/dwarf"
)

const (
Expand Down Expand Up @@ -97,14 +97,18 @@ func Launch(cmd []string) (*Process, error) {
si.StdOutput = sys.Handle(fd[1])
si.StdErr = sys.Handle(fd[2])
pi := new(sys.ProcessInformation)
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, DEBUGONLYTHISPROCESS, nil, nil, si, pi)

dbp := New(0)
dbp.execPtraceFunc(func() {
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, DEBUGONLYTHISPROCESS, nil, nil, si, pi)
})
if err != nil {
return nil, err
}
sys.CloseHandle(sys.Handle(pi.Process))
sys.CloseHandle(sys.Handle(pi.Thread))

dbp := New(int(pi.ProcessId))
dbp.Pid = int(pi.ProcessId)

switch runtime.GOARCH {
case "amd64":
Expand All @@ -117,7 +121,7 @@ func Launch(cmd []string) (*Process, error) {
// after launching under DEBUGONLYTHISPROCESS.
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent()
tid, exitCode, err = dbp.waitForDebugEvent(true)
})
if err != nil {
return nil, err
Expand All @@ -127,6 +131,20 @@ func Launch(cmd []string) (*Process, error) {
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
}

for _, thread := range dbp.Threads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}

dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
})
if err != nil {
return nil, err
}

return initializeDebugProcess(dbp, argv0Go, false)
}

Expand Down Expand Up @@ -370,12 +388,16 @@ func dwarfFromPE(f *pe.File) (*dwarf.Data, error) {
return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
}

func (dbp *Process) waitForDebugEvent() (threadID, exitCode int, err error) {
func (dbp *Process) waitForDebugEvent(blocking bool) (threadID, exitCode int, err error) {
var debugEvent _DEBUG_EVENT
shouldExit := false
for {
var milliseconds uint32 = 0
if blocking {
milliseconds = syscall.INFINITE
}
// Wait for a debug event...
err := _WaitForDebugEvent(&debugEvent, syscall.INFINITE)
err := _WaitForDebugEvent(&debugEvent, milliseconds)
if err != nil {
return 0, 0, err
}
Expand Down Expand Up @@ -453,7 +475,7 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) {
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent()
tid, exitCode, err = dbp.waitForDebugEvent(true)
})
if err != nil {
return nil, err
Expand All @@ -475,39 +497,65 @@ func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
}

func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
// TODO: In theory, we should also be setting the breakpoints on other
// threads that happen to have hit this BP. But doing so leads to periodic
// failures in the TestBreakpointsCounts test with hit counts being too high,
// which can be traced back to occurrences of multiple threads hitting a BP
// at the same time.
err := trapthread.SetCurrentBreakpoint()
if err != nil {
return err
}

// My guess is that Windows will correctly trigger multiple DEBUG_EVENT's
// in this case, one for each thread, so we should only handle the BP hit
// on the thread that the debugger was evented on.
for _, thread := range dbp.Threads {
thread.running = false
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return err
}
}

for {
var err error
var tid int
dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.Pid), uint32(dbp.os.breakThread), _DBG_CONTINUE)
if err == nil {
tid, _, _ = dbp.waitForDebugEvent(false)
}
})
if err != nil {
return err
}
if tid == 0 {
break
}
err = dbp.Threads[tid].SetCurrentBreakpoint()
if err != nil {
return err
}
}

return trapthread.SetCurrentBreakpoint()
return nil
}

func (dbp *Process) exitGuard(err error) error {
return err
}

func (dbp *Process) resume() error {
// Only resume the thread that broke into the debugger
thread := dbp.Threads[dbp.os.breakThread]
// This relies on the same assumptions as dbp.setCurrentBreakpoints
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
}
thread.CurrentBreakpoint = nil
}
// In case we are now on a different thread, make sure we resume
// the thread that is broken.
thread = dbp.Threads[dbp.os.breakThread]
if err := thread.resume(); err != nil {
return err

for _, thread := range dbp.Threads {
thread.running = true
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}

return nil
}

Expand Down
41 changes: 22 additions & 19 deletions proc/threads_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,35 +42,38 @@ func (t *Thread) singleStep() error {
return err
}

// Suspend all threads except this one
for _, thread := range t.dbp.Threads {
if thread.ID == t.ID {
continue
_, err = _ResumeThread(t.os.hThread)
if err != nil {
return err
}

for {
_, err = t.dbp.trapWait(0)
if err != nil {
return err
}
_, _ = _SuspendThread(thread.os.hThread)

if t.dbp.os.breakThread == t.ID {
break
}

t.dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(t.dbp.Pid), uint32(t.dbp.os.breakThread), _DBG_CONTINUE)
})
}

// Continue and wait for the step to complete
err = nil
t.dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(t.dbp.Pid), uint32(t.ID), _DBG_CONTINUE)
})
_, err = _SuspendThread(t.os.hThread)
if err != nil {
return err
}
_, err = t.dbp.trapWait(0)

t.dbp.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(t.dbp.Pid), uint32(t.ID), _DBG_CONTINUE)
})
if err != nil {
return err
}

// Resume all threads except this one
for _, thread := range t.dbp.Threads {
if thread.ID == t.ID {
continue
}
_, _ = _ResumeThread(thread.os.hThread)
}

// Unset the processor TRAP flag
err = _GetThreadContext(t.os.hThread, context)
if err != nil {
Expand Down

0 comments on commit 4e390b5

Please sign in to comment.