
* proc: move breakpoint condition evaluation out of backends Moves breakpoint condition evaluation from the point where breakpoints are set, inside ContinueOnce, to (*Target).Continue. This accomplishes three things: 1. the breakpoint evaluation method needs not be exported anymore 2. breakpoint condition evaluation can be done with a full scope, containing a Target object, something that wasn't possible before because ContinueOnce doesn't have access to the Target object. 3. moves breakpoint condition evaluation out of the critical section where some of the threads of the target process might be still running. * proc/native: handle process death during stop() on Windows It is possible that the thread dies while we are inside the stop() function. This results in an Access is denied error being returned by SuspendThread being called on threads that no longer exist. Delay the reporting the error from SuspendThread until the end of stop() and only report it if the thread still exists at that point. Fixes flakyness with TestIssue1101 that was exacerbated by moving breakpoint condition evaluation outside of the backends.
171 lines
4.1 KiB
Go
171 lines
4.1 KiB
Go
package native
|
|
|
|
import (
|
|
"errors"
|
|
"syscall"
|
|
|
|
sys "golang.org/x/sys/windows"
|
|
|
|
"github.com/go-delve/delve/pkg/proc"
|
|
"github.com/go-delve/delve/pkg/proc/winutil"
|
|
)
|
|
|
|
// waitStatus is a synonym for the platform-specific WaitStatus
|
|
type waitStatus sys.WaitStatus
|
|
|
|
// osSpecificDetails holds information specific to the Windows
|
|
// operating system / kernel.
|
|
type osSpecificDetails struct {
|
|
hThread syscall.Handle
|
|
dbgUiRemoteBreakIn bool // whether thread is an auxiliary DbgUiRemoteBreakIn thread created by Windows
|
|
delayErr error
|
|
}
|
|
|
|
func (t *nativeThread) singleStep() error {
|
|
context := winutil.NewCONTEXT()
|
|
context.ContextFlags = _CONTEXT_ALL
|
|
|
|
// Set the processor TRAP flag
|
|
err := _GetThreadContext(t.os.hThread, context)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
context.EFlags |= 0x100
|
|
|
|
err = _SetThreadContext(t.os.hThread, context)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
suspendcnt := 0
|
|
|
|
// If a thread simultaneously hits a breakpoint and is suspended by the Go
|
|
// runtime it will have a suspend count greater than 1 and to actually take
|
|
// a single step we have to resume it multiple times here.
|
|
// We keep a counter of how many times it was suspended so that after
|
|
// single-stepping we can re-suspend it the corrent number of times.
|
|
for {
|
|
n, err := _ResumeThread(t.os.hThread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
suspendcnt++
|
|
if n == 1 {
|
|
break
|
|
}
|
|
}
|
|
|
|
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 proc.ErrProcessExited{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)
|
|
})
|
|
}
|
|
|
|
for i := 0; i < suspendcnt; i++ {
|
|
if !t.os.dbgUiRemoteBreakIn {
|
|
_, err = _SuspendThread(t.os.hThread)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
t.dbp.execPtraceFunc(func() {
|
|
err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Unset the processor TRAP flag
|
|
err = _GetThreadContext(t.os.hThread, context)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
context.EFlags &= ^uint32(0x100)
|
|
|
|
return _SetThreadContext(t.os.hThread, context)
|
|
}
|
|
|
|
func (t *nativeThread) resume() error {
|
|
var err error
|
|
t.dbp.execPtraceFunc(func() {
|
|
//TODO: Note that we are ignoring the thread we were asked to continue and are continuing the
|
|
//thread that we last broke on.
|
|
err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE)
|
|
})
|
|
return err
|
|
}
|
|
|
|
// Stopped returns whether the thread is stopped at the operating system
|
|
// level. On windows this always returns true.
|
|
func (t *nativeThread) Stopped() bool {
|
|
return true
|
|
}
|
|
|
|
func (t *nativeThread) WriteMemory(addr uint64, data []byte) (int, error) {
|
|
if t.dbp.exited {
|
|
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
|
|
}
|
|
if len(data) == 0 {
|
|
return 0, nil
|
|
}
|
|
var count uintptr
|
|
err := _WriteProcessMemory(t.dbp.os.hProcess, uintptr(addr), &data[0], uintptr(len(data)), &count)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(count), nil
|
|
}
|
|
|
|
var ErrShortRead = errors.New("short read")
|
|
|
|
func (t *nativeThread) ReadMemory(buf []byte, addr uint64) (int, error) {
|
|
if t.dbp.exited {
|
|
return 0, proc.ErrProcessExited{Pid: t.dbp.pid}
|
|
}
|
|
if len(buf) == 0 {
|
|
return 0, nil
|
|
}
|
|
var count uintptr
|
|
err := _ReadProcessMemory(t.dbp.os.hProcess, uintptr(addr), &buf[0], uintptr(len(buf)), &count)
|
|
if err == nil && count != uintptr(len(buf)) {
|
|
err = ErrShortRead
|
|
}
|
|
return int(count), err
|
|
}
|
|
|
|
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
|
return _SetThreadContext(t.os.hThread, savedRegs.(*winutil.AMD64Registers).Context)
|
|
}
|
|
|
|
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
|
return proc.ErrHWBreakUnsupported
|
|
}
|
|
|
|
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
|
|
return proc.ErrHWBreakUnsupported
|
|
}
|
|
|
|
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
|
|
return nil, nil
|
|
}
|