delve/pkg/proc/native/threads_windows.go
Alessandro Arzilli f3e76238e3
proc: move breakpoint condition evaluation out of backends (#2628)
* 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.
2021-08-09 10:16:24 -07:00

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
}