
On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
135 lines
3.1 KiB
Go
135 lines
3.1 KiB
Go
//+build darwin,macnative
|
|
|
|
package native
|
|
|
|
// #include "threads_darwin.h"
|
|
// #include "proc_darwin.h"
|
|
import "C"
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
sys "golang.org/x/sys/unix"
|
|
|
|
"github.com/go-delve/delve/pkg/proc"
|
|
)
|
|
|
|
// waitStatus is a synonym for the platform-specific WaitStatus
|
|
type waitStatus sys.WaitStatus
|
|
|
|
// osSpecificDetails holds information specific to the OSX/Darwin
|
|
// operating system / kernel.
|
|
type osSpecificDetails struct {
|
|
threadAct C.thread_act_t
|
|
registers C.x86_thread_state64_t
|
|
exists bool
|
|
}
|
|
|
|
// ErrContinueThread is the error returned when a thread could not
|
|
// be continued.
|
|
var ErrContinueThread = fmt.Errorf("could not continue thread")
|
|
|
|
func (t *nativeThread) stop() (err error) {
|
|
kret := C.thread_suspend(t.os.threadAct)
|
|
if kret != C.KERN_SUCCESS {
|
|
errStr := C.GoString(C.mach_error_string(C.mach_error_t(kret)))
|
|
// check that the thread still exists before complaining
|
|
err2 := t.dbp.updateThreadList()
|
|
if err2 != nil {
|
|
err = fmt.Errorf("could not suspend thread %d %s (additionally could not update thread list: %v)", t.ID, errStr, err2)
|
|
return
|
|
}
|
|
|
|
if _, ok := t.dbp.threads[t.ID]; ok {
|
|
err = fmt.Errorf("could not suspend thread %d %s", t.ID, errStr)
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (t *nativeThread) singleStep() error {
|
|
kret := C.single_step(t.os.threadAct)
|
|
if kret != C.KERN_SUCCESS {
|
|
return fmt.Errorf("could not single step")
|
|
}
|
|
for {
|
|
twthread, err := t.dbp.trapWait(t.dbp.pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if twthread.ID == t.ID {
|
|
break
|
|
}
|
|
}
|
|
|
|
kret = C.clear_trap_flag(t.os.threadAct)
|
|
if kret != C.KERN_SUCCESS {
|
|
return fmt.Errorf("could not clear CPU trap flag")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *nativeThread) resume() error {
|
|
// TODO(dp) set flag for ptrace stops
|
|
var err error
|
|
t.dbp.execPtraceFunc(func() { err = ptraceCont(t.dbp.pid, 0) })
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
kret := C.resume_thread(t.os.threadAct)
|
|
if kret != C.KERN_SUCCESS {
|
|
return ErrContinueThread
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Stopped returns whether the thread is stopped at
|
|
// the operating system level.
|
|
func (t *nativeThread) Stopped() bool {
|
|
return C.thread_blocked(t.os.threadAct) > C.int(0)
|
|
}
|
|
|
|
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 (
|
|
vmData = unsafe.Pointer(&data[0])
|
|
vmAddr = C.mach_vm_address_t(addr)
|
|
length = C.mach_msg_type_number_t(len(data))
|
|
)
|
|
if ret := C.write_memory(t.dbp.os.task, vmAddr, vmData, length); ret < 0 {
|
|
return 0, fmt.Errorf("could not write memory")
|
|
}
|
|
return len(data), nil
|
|
}
|
|
|
|
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 (
|
|
vmData = unsafe.Pointer(&buf[0])
|
|
vmAddr = C.mach_vm_address_t(addr)
|
|
length = C.mach_msg_type_number_t(len(buf))
|
|
)
|
|
|
|
ret := C.read_memory(t.dbp.os.task, vmAddr, vmData, length)
|
|
if ret < 0 {
|
|
return 0, fmt.Errorf("could not read memory")
|
|
}
|
|
return len(buf), nil
|
|
}
|
|
|
|
func (t *nativeThread) restoreRegisters(sr proc.Registers) error {
|
|
return errors.New("not implemented")
|
|
}
|