delve/pkg/proc/native/threads_windows.go

177 lines
3.7 KiB
Go
Raw Normal View History

package native
2016-01-15 05:26:54 +00:00
import (
"errors"
"syscall"
2016-01-15 05:26:54 +00:00
sys "golang.org/x/sys/windows"
"github.com/derekparker/delve/pkg/proc"
2016-01-15 05:26:54 +00:00
)
// 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
2016-01-15 05:26:54 +00:00
}
proc/native: fix race condition between Halt and process death (linux) If a breakpoint is hit close to process death on a thread that isn't the group leader the process could die while we are trying to stop it. This can be easily reproduced by having the goroutine that's executing main.main (which will almost always run on the thread group leader) wait for a second goroutine before exiting, then setting a breakpoint on the second goroutine and stepping through it (see TestIssue1101 in proc_test.go). When stepping over the return instruction of main.f the deferred wg.Done() call will be executed which will cause the main goroutine to resume and proceed to exit. Both the temporary breakpoint on wg.Done and the temporary breakpoint on the return address of main.f will be in close proximity to main.main calling os.Exit() and causing the death of the thread group leader. Under these circumstances the call to native.(*Thread).waitFast in native.(*Thread).halt can hang forever due to a bug similar to https://sourceware.org/bugzilla/show_bug.cgi?id=12702 (see comment in native.(*Thread).wait for an explanation). Replacing waitFast with a normal wait work in most circumstances, however, besides the performance hit, it looks like in this circumstances trapWait sometimes receives a spurious SIGTRAP on the dying group leader which would cause the subsequent call to wait in halt to accidentally reap the process without noting that it did exit. Instead this patch removes the call to wait from halt and instead calls trapWait in a loop in setCurrentBreakpoints until all threads are set to running=false. This is also a better fix than the workaround to ESRCH error while setting current breakpoints implemented in 94b50d. Fixes #1101
2018-01-29 10:07:38 +00:00
// Halt stops this thread from executing.
func (thread *Thread) Halt() (err error) {
defer func() {
if err == nil {
thread.running = false
}
}()
if thread.Stopped() {
return
}
err = thread.halt()
return
}
2016-01-15 05:26:54 +00:00
func (t *Thread) halt() (err error) {
// Ignore the request to halt. On Windows, all threads are halted
// on return from WaitForDebugEvent.
return nil
// TODO - This may not be correct in all usages of dbp.Halt. There
// are some callers who use dbp.Halt() to stop the process when it is not
// already broken on a debug event.
}
func (t *Thread) singleStep() error {
context := newCONTEXT()
context.ContextFlags = _CONTEXT_ALL
2016-01-15 05:26:54 +00:00
// Set the processor TRAP flag
err := _GetThreadContext(t.os.hThread, context)
if err != nil {
return err
2016-01-15 05:26:54 +00:00
}
context.EFlags |= 0x100
err = _SetThreadContext(t.os.hThread, context)
if err != nil {
return err
2016-01-15 05:26:54 +00:00
}
_, 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 proc.ProcessExitedError{Pid: t.dbp.pid, Status: exitCode}
2016-01-15 05:26:54 +00:00
}
if t.dbp.os.breakThread == t.ID {
break
}
t.dbp.execPtraceFunc(func() {
2017-02-08 00:23:47 +00:00
err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.dbp.os.breakThread), _DBG_CONTINUE)
})
2016-01-15 05:26:54 +00:00
}
_, err = _SuspendThread(t.os.hThread)
if err != nil {
return err
2016-01-15 05:26:54 +00:00
}
t.dbp.execPtraceFunc(func() {
2017-02-08 00:23:47 +00:00
err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE)
})
2016-01-15 05:26:54 +00:00
if err != nil {
return err
}
// Unset the processor TRAP flag
err = _GetThreadContext(t.os.hThread, context)
if err != nil {
return err
2016-01-15 05:26:54 +00:00
}
context.EFlags &= ^uint32(0x100)
2016-01-15 05:26:54 +00:00
return _SetThreadContext(t.os.hThread, context)
2016-01-15 05:26:54 +00:00
}
func (t *Thread) resume() error {
t.running = true
var err error
2016-01-15 05:26:54 +00:00
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.
2017-02-08 00:23:47 +00:00
err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE)
2016-01-15 05:26:54 +00:00
})
return err
2016-01-15 05:26:54 +00:00
}
func (t *Thread) Blocked() bool {
2016-01-15 05:26:54 +00:00
// TODO: Probably incorrect - what are the runtime functions that
// indicate blocking on Windows?
regs, err := t.Registers(false)
2016-01-15 05:26:54 +00:00
if err != nil {
return false
}
pc := regs.PC()
fn := t.BinInfo().PCToFunc(pc)
2016-01-15 05:26:54 +00:00
if fn == nil {
return false
}
switch fn.Name {
case "runtime.kevent", "runtime.usleep":
return true
default:
return false
}
}
func (t *Thread) stopped() bool {
// TODO: We are assuming that threads are always stopped
2016-05-29 19:44:10 +00:00
// during command execution.
2016-01-15 05:26:54 +00:00
return true
}
func (t *Thread) WriteMemory(addr uintptr, data []byte) (int, error) {
if t.dbp.exited {
return 0, proc.ProcessExitedError{Pid: t.dbp.pid}
}
var count uintptr
err := _WriteProcessMemory(t.dbp.os.hProcess, addr, &data[0], uintptr(len(data)), &count)
if err != nil {
return 0, err
2016-01-15 05:26:54 +00:00
}
return int(count), nil
}
var ErrShortRead = errors.New("short read")
func (t *Thread) ReadMemory(buf []byte, addr uintptr) (int, error) {
if t.dbp.exited {
return 0, proc.ProcessExitedError{Pid: t.dbp.pid}
}
if len(buf) == 0 {
return 0, nil
2016-01-15 05:26:54 +00:00
}
var count uintptr
err := _ReadProcessMemory(t.dbp.os.hProcess, addr, &buf[0], uintptr(len(buf)), &count)
if err == nil && count != uintptr(len(buf)) {
err = ErrShortRead
2016-01-15 05:26:54 +00:00
}
return int(count), err
2016-01-15 05:26:54 +00:00
}