delve/pkg/proc/native/threads_darwin.go
Alessandro Arzilli 1418cfd385
proc: better handling of hardcoded breakpoints (#2852)
This commit improves the handling of hardcoded breakpoints in Delve.
A hardcoded breakpoint is a breakpoint instruction hardcoded in the
text of the program, for example through runtime.Breakpoint.

1. hardcoded breakpoints are now indicated by setting the breakpoint
   field on any thread stopped by a hardcoded breakpoint
2. if multiple hardcoded breakpoints are hit during a single stop all
   will be notified to the user.
3. a debugger breakpoint with an unmet condition can't hide a hardcoded
   breakpoint anymore.
2022-02-22 09:57:37 -08:00

146 lines
3.5 KiB
Go

//go:build darwin && macnative
// +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"
"github.com/go-delve/delve/pkg/proc/amd64util"
)
// 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")
}
func (t *nativeThread) withDebugRegisters(f func(*amd64util.DebugRegisters) error) error {
return proc.ErrHWBreakUnsupported
}
// SoftExc returns true if this thread received a software exception during the last resume.
func (t *nativeThread) SoftExc() bool {
return false
}