proc: changed windows backend to deal with simultaneous breakpoints (#598)

* proc: changed windows backend to deal with simultaneous breakpoints

* bugfix: forgot to add windowsPrologue3 to the prologues list in e4c7df1

* Tolerate errors returned by Stacktrace in TestStacktraceGoroutine.

* bugfix: proc: propagate debug events we don't cause back to the target process

Fixes: #594

* proc: fixed TestStepConcurrentPtr

Implementation of nextInProgress was wrong.
This commit is contained in:
Alessandro Arzilli 2016-10-22 06:51:34 +02:00 committed by Derek Parker
parent 46803551b8
commit f6e8fb37a4
6 changed files with 214 additions and 66 deletions

22
_fixtures/issue594.go Normal file

@ -0,0 +1,22 @@
package main
import (
"fmt"
"runtime"
)
func dontsegfault() {
var p *int
func() int {
defer func() {
recover()
}()
return *p
}()
}
func main() {
dontsegfault()
runtime.Breakpoint()
fmt.Println("should stop here")
}

@ -123,7 +123,7 @@ var windowsPrologue3 = instrseq{x86asm.MOV, x86asm.MOV, x86asm.MOV, x86asm.CMP,
var unixPrologue = instrseq{x86asm.MOV, x86asm.LEA, x86asm.CMP, x86asm.JBE}
var unixPrologue2 = instrseq{x86asm.MOV, x86asm.CMP, x86asm.JBE}
var unixPrologue3 = instrseq{x86asm.MOV, x86asm.MOV, x86asm.CMP, x86asm.JE}
var prologues = []instrseq{windowsPrologue, windowsPrologue2, unixPrologue, unixPrologue2, unixPrologue3}
var prologues = []instrseq{windowsPrologue, windowsPrologue2, windowsPrologue3, unixPrologue, unixPrologue2, unixPrologue3}
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry

@ -801,7 +801,10 @@ func TestStacktraceGoroutine(t *testing.T) {
for i, g := range gs {
locations, err := g.Stacktrace(40)
assertNoError(err, t, "GoroutineStacktrace()")
if err != nil {
// On windows we do not have frame information for goroutines doing system calls.
continue
}
if stackMatch(mainStack, locations, false) {
mainCount++
@ -2084,8 +2087,8 @@ func TestStepConcurrentDirect(t *testing.T) {
}
func nextInProgress(p *Process) bool {
for _, th := range p.Threads {
if th.CurrentBreakpoint != nil && th.CurrentBreakpoint.Internal() {
for _, bp := range p.Breakpoints {
if bp.Internal() {
return true
}
}
@ -2135,13 +2138,13 @@ func TestStepConcurrentPtr(t *testing.T) {
assertNoError(p.Continue(), t, "Continue()")
}
f, ln = currentLineNumber(p, t)
if ln != 13 {
t.Fatalf("Step did not step into function call (13): %s:%d (gid: %d)", f, ln, p.SelectedGoroutine.ID)
if p.SelectedGoroutine.ID != gid {
t.Fatalf("Step switched goroutines (wanted: %d got: %d)", gid, p.SelectedGoroutine.ID)
}
if p.SelectedGoroutine.ID != gid {
t.Fatalf("Step switched goroutines (%d %d)", gid, p.SelectedGoroutine.ID)
f, ln = currentLineNumber(p, t)
if ln != 13 {
t.Fatalf("Step did not step into function call (13): %s:%d", f, ln)
}
count++
@ -2197,3 +2200,17 @@ func TestStepOnCallPtrInstr(t *testing.T) {
}
})
}
func TestIssue594(t *testing.T) {
// Exceptions that aren't caused by breakpoints should be propagated
// back to the target.
// In particular the target should be able to cause a nil pointer
// dereference panic and recover from it.
withTestProcess("issue594", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()")
f, ln := currentLineNumber(p, t)
if ln != 21 {
t.Fatalf("Program stopped at %s:%d, expected :21", f, ln)
}
})
}

@ -91,19 +91,24 @@ 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, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi)
dbp := New(0)
dbp.execPtraceFunc(func() {
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi)
})
if err != nil {
return nil, err
}
sys.CloseHandle(sys.Handle(pi.Process))
sys.CloseHandle(sys.Handle(pi.Thread))
return newDebugProcess(int(pi.ProcessId), argv0Go)
dbp.Pid = int(pi.ProcessId)
return newDebugProcess(dbp, argv0Go)
}
// newDebugProcess prepares process pid for debugging.
func newDebugProcess(pid int, exepath string) (*Process, error) {
dbp := New(pid)
func newDebugProcess(dbp *Process, exepath string) (*Process, error) {
// It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event
@ -112,7 +117,7 @@ func newDebugProcess(pid int, exepath string) (*Process, error) {
var err error
var tid, exitCode int
dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent()
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking)
})
if err != nil {
return nil, err
@ -121,6 +126,22 @@ func newDebugProcess(pid int, exepath string) (*Process, error) {
dbp.postExit()
return nil, ProcessExitedError{Pid: dbp.Pid, Status: exitCode}
}
// Suspend all threads so that the call to _ContinueDebugEvent will
// not resume the target.
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, exepath, false)
}
@ -169,7 +190,7 @@ func Attach(pid int) (*Process, error) {
if err != nil {
return nil, err
}
return newDebugProcess(pid, exepath)
return newDebugProcess(New(pid), exepath)
}
// Kill kills the process.
@ -198,7 +219,7 @@ func (dbp *Process) updateThreadList() error {
return nil
}
func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach bool) (*Thread, error) {
func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach, suspendNewThreads bool) (*Thread, error) {
if thread, ok := dbp.Threads[threadID]; ok {
return thread, nil
}
@ -212,6 +233,12 @@ func (dbp *Process) addThread(hThread syscall.Handle, threadID int, attach bool)
if dbp.CurrentThread == nil {
dbp.SwitchThread(thread.ID)
}
if suspendNewThreads {
_, err := _SuspendThread(thread.os.hThread)
if err != nil {
return nil, err
}
}
return thread, nil
}
@ -402,12 +429,24 @@ 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) {
type waitForDebugEventFlags int
const (
waitBlocking waitForDebugEventFlags = 1 << iota
waitSuspendNewThreads
)
func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) {
var debugEvent _DEBUG_EVENT
shouldExit := false
for {
continueStatus := uint32(_DBG_CONTINUE)
var milliseconds uint32 = 0
if flags&waitBlocking != 0 {
milliseconds = syscall.INFINITE
}
// Wait for a debug event...
err := _WaitForDebugEvent(&debugEvent, syscall.INFINITE)
err := _WaitForDebugEvent(&debugEvent, milliseconds)
if err != nil {
return 0, 0, err
}
@ -425,14 +464,14 @@ func (dbp *Process) waitForDebugEvent() (threadID, exitCode int, err error) {
}
}
dbp.os.hProcess = debugInfo.Process
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false)
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
break
case _CREATE_THREAD_DEBUG_EVENT:
debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr)
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false)
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0)
if err != nil {
return 0, 0, err
}
@ -458,9 +497,14 @@ func (dbp *Process) waitForDebugEvent() (threadID, exitCode int, err error) {
case _RIP_EVENT:
break
case _EXCEPTION_DEBUG_EVENT:
tid := int(debugEvent.ThreadId)
dbp.os.breakThread = tid
return tid, 0, nil
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
if code := exception.ExceptionRecord.ExceptionCode; code == _EXCEPTION_BREAKPOINT || code == _EXCEPTION_SINGLE_STEP {
tid := int(debugEvent.ThreadId)
dbp.os.breakThread = tid
return tid, 0, nil
} else {
continueStatus = _DBG_EXCEPTION_NOT_HANDLED
}
case _EXIT_PROCESS_DEBUG_EVENT:
debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr)
exitCode = int(debugInfo.ExitCode)
@ -470,7 +514,7 @@ func (dbp *Process) waitForDebugEvent() (threadID, exitCode int, err error) {
}
// .. and then continue unless we received an event that indicated we should break into debugger.
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, _DBG_CONTINUE)
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
if err != nil {
return 0, 0, err
}
@ -485,7 +529,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(waitBlocking)
})
if err != nil {
return nil, err
@ -507,17 +551,51 @@ 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.
// While the debug event that stopped the target was being propagated
// other target threads could generate other debug events.
// After this function we need to know about all the threads
// stopped on a breakpoint. To do that we first suspend all target
// threads and then repeatedly call _ContinueDebugEvent followed by
// waitForDebugEvent in non-blocking mode.
// We need to explicitly call SuspendThread because otherwise the
// call to _ContinueDebugEvent will resume execution of some of the
// target threads.
// 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.
err := trapthread.SetCurrentBreakpoint()
if err != nil {
return err
}
return trapthread.SetCurrentBreakpoint()
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(waitSuspendNewThreads)
}
})
if err != nil {
return err
}
if tid == 0 {
break
}
err = dbp.Threads[tid].SetCurrentBreakpoint()
if err != nil {
return err
}
}
return nil
}
func (dbp *Process) exitGuard(err error) error {
@ -525,21 +603,23 @@ func (dbp *Process) exitGuard(err error) error {
}
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 {
for _, thread := range dbp.Threads {
if thread.CurrentBreakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint = nil
}
}
for _, thread := range dbp.Threads {
thread.running = true
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
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
}
return nil
}

@ -54,6 +54,20 @@ type _LOAD_DLL_DEBUG_INFO struct {
Unicode uint16
}
type _EXCEPTION_DEBUG_INFO struct {
ExceptionRecord _EXCEPTION_RECORD
FirstChance uint32
}
type _EXCEPTION_RECORD struct {
ExceptionCode uint32
ExceptionFlags uint32
ExceptionRecord *_EXCEPTION_RECORD
ExceptionAddress uintptr
NumberParameters uint32
ExceptionInformation [_EXCEPTION_MAXIMUM_PARAMETERS]uintptr
}
const (
_ThreadBasicInformation = 0
@ -72,6 +86,11 @@ const (
// DEBUG_ONLY_THIS_PROCESS tracks https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
_DEBUG_ONLY_THIS_PROCESS = 0x00000002
_EXCEPTION_BREAKPOINT = 0x80000003
_EXCEPTION_SINGLE_STEP = 0x80000004
_EXCEPTION_MAXIMUM_PARAMETERS = 15
)
func _NT_SUCCESS(x _NTSTATUS) bool {

@ -42,34 +42,44 @@ 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
}
_, _ = _SuspendThread(thread.os.hThread)
_, err = _ResumeThread(t.os.hThread)
if err != nil {
return err
}
for {
var tid, exitCode int
t.dbp.execPtraceFunc(func() {
tid, exitCode, err = t.dbp.waitForDebugEvent(waitBlocking | waitSuspendNewThreads)
})
if err != nil {
return err
}
if tid == 0 {
t.dbp.postExit()
return ProcessExitedError{Pid: t.dbp.Pid, Status: exitCode}
}
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)
})
}
_, err = _SuspendThread(t.os.hThread)
if err != nil {
return err
}
// 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)
})
if err != nil {
return err
}
_, err = t.dbp.trapWait(0)
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)