delve/pkg/proc/native/proc_darwin.go

467 lines
11 KiB
Go
Raw Normal View History

//+build darwin,macnative
package native
2015-01-14 02:37:10 +00:00
2015-06-12 19:49:23 +00:00
// #include "proc_darwin.h"
// #include "threads_darwin.h"
// #include "exec_darwin.h"
// #include <stdlib.h>
2015-01-14 02:37:10 +00:00
import "C"
import (
"errors"
2015-01-14 02:37:10 +00:00
"fmt"
"os"
"os/exec"
"path/filepath"
2015-01-14 02:37:10 +00:00
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc"
2015-01-14 02:37:10 +00:00
)
2016-01-10 08:57:52 +00:00
// OSProcessDetails holds Darwin specific information.
2015-01-14 02:37:10 +00:00
type OSProcessDetails struct {
2016-04-13 05:53:13 +00:00
task C.task_t // mach task for the debugged process.
exceptionPort C.mach_port_t // mach port for receiving mach exceptions.
notificationPort C.mach_port_t // mach port for dead name notification (process exit).
initialized bool
halt bool
// the main port we use, will return messages from both the
// exception and notification ports.
portSet C.mach_port_t
2015-01-14 02:37:10 +00:00
}
2016-01-10 08:57:52 +00:00
// Launch creates and begins debugging a new process. Uses a
// custom fork/exec process in order to take advantage of
2015-06-19 13:15:54 +00:00
// PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions.
func Launch(cmd []string, wd string, foreground bool, _ []string) (*proc.Target, error) {
// check that the argument to Launch is an executable file
if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
return nil, proc.ErrNotExecutable
}
argv0Go, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
}
2015-06-27 15:50:07 +00:00
// Make sure the binary exists.
if filepath.Base(cmd[0]) == cmd[0] {
if _, err := exec.LookPath(cmd[0]); err != nil {
return nil, err
}
}
if _, err := os.Stat(argv0Go); err != nil {
return nil, err
}
2015-06-27 15:50:07 +00:00
argv0 := C.CString(argv0Go)
argvSlice := make([]*C.char, 0, len(cmd)+1)
for _, arg := range cmd {
argvSlice = append(argvSlice, C.CString(arg))
}
// argv array must be null terminated.
argvSlice = append(argvSlice, nil)
dbp := New(0)
var pid int
dbp.execPtraceFunc(func() {
ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)),
C.CString(wd),
&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
&dbp.os.notificationPort)
pid = int(ret)
})
2015-04-25 20:13:00 +00:00
if pid <= 0 {
2015-05-04 22:31:13 +00:00
return nil, fmt.Errorf("could not fork/exec")
}
2017-02-08 00:23:47 +00:00
dbp.pid = pid
dbp.childProcess = true
for i := range argvSlice {
C.free(unsafe.Pointer(argvSlice[i]))
}
2015-04-25 20:13:00 +00:00
// Initialize enough of the Process state so that we can use resume and
// trapWait to wait until the child process calls execve.
for {
task := C.get_task_for_pid(C.int(dbp.pid))
// The task_for_pid call races with the fork call. This can
// result in the parent task being returned instead of the child.
if task != dbp.os.task {
err = dbp.updateThreadListForTask(task)
if err == nil {
break
}
if err != couldNotGetThreadCount && err != couldNotGetThreadList {
return nil, err
}
}
}
if err := dbp.resume(); err != nil {
return nil, err
}
2017-02-08 00:23:47 +00:00
for _, th := range dbp.threads {
th.CurrentBreakpoint.Clear()
}
trapthread, err := dbp.trapWait(-1)
if err != nil {
return nil, err
}
if err := dbp.stop(nil); err != nil {
return nil, err
}
dbp.os.initialized = true
err = dbp.initialize(argv0Go, []string{})
if err != nil {
return nil, err
}
if err := dbp.SwitchThread(trapthread.ID); err != nil {
return nil, err
}
*: Go 1.14 support branch (#1727) * tests: misc test fixes for go1.14 - math.go is now ambiguous due to changes to the go runtime so specify that we mean our own math.go in _fixtures - go list -m requires vendor-mode to be disabled so pass '-mod=' to it in case user has GOFLAGS=-mod=vendor - update version of go/packages, required to work with go 1.14 (and executed go mod vendor) - Increased goroutine migration in one development version of Go 1.14 revealed a problem with TestCheckpoints in command_test.go and rr_test.go. The tests were always wrong because Restart(checkpoint) doesn't change the current thread but we can't assume that when the checkpoint was taken the current goroutine was running on the same thread. * goversion: update maximum supported version * Makefile: disable testing lldb-server backend on linux with Go 1.14 There seems to be some incompatibility with lldb-server version 6.0.0 on linux and Go 1.14. * proc/gdbserial: better handling of signals - if multiple signals are received simultaneously propagate all of them to the target threads instead of only one. - debugserver will drop an interrupt request if a target thread simultaneously receives a signal, handle this situation. * dwarf/line: normalize backslashes for windows executables Starting with Go 1.14 the compiler sometimes emits backslashes as well as forward slashes in debug_line, normalize everything to / for conformity with the behavior of previous versions. * proc/native: partial support for Windows async preempt mechanism See https://github.com/golang/go/issues/36494 for a description of why full support for 1.14 under windows is problematic. * proc/native: disable Go 1.14 async preemption on Windows See https://github.com/golang/go/issues/36494
2020-02-11 01:31:54 +00:00
return proc.NewTarget(dbp, false), err
}
// Attach to an existing process with the given PID.
func Attach(pid int, _ []string) (*proc.Target, error) {
dbp := New(pid)
kret := C.acquire_mach_task(C.int(pid),
&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
&dbp.os.notificationPort)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not attach to %d", pid)
}
dbp.os.initialized = true
var err error
dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
if err != nil {
return nil, err
}
_, _, err = dbp.wait(dbp.pid, 0)
if err != nil {
return nil, err
}
err = dbp.initialize("", []string{})
if err != nil {
dbp.Detach(false)
return nil, err
}
*: Go 1.14 support branch (#1727) * tests: misc test fixes for go1.14 - math.go is now ambiguous due to changes to the go runtime so specify that we mean our own math.go in _fixtures - go list -m requires vendor-mode to be disabled so pass '-mod=' to it in case user has GOFLAGS=-mod=vendor - update version of go/packages, required to work with go 1.14 (and executed go mod vendor) - Increased goroutine migration in one development version of Go 1.14 revealed a problem with TestCheckpoints in command_test.go and rr_test.go. The tests were always wrong because Restart(checkpoint) doesn't change the current thread but we can't assume that when the checkpoint was taken the current goroutine was running on the same thread. * goversion: update maximum supported version * Makefile: disable testing lldb-server backend on linux with Go 1.14 There seems to be some incompatibility with lldb-server version 6.0.0 on linux and Go 1.14. * proc/gdbserial: better handling of signals - if multiple signals are received simultaneously propagate all of them to the target threads instead of only one. - debugserver will drop an interrupt request if a target thread simultaneously receives a signal, handle this situation. * dwarf/line: normalize backslashes for windows executables Starting with Go 1.14 the compiler sometimes emits backslashes as well as forward slashes in debug_line, normalize everything to / for conformity with the behavior of previous versions. * proc/native: partial support for Windows async preempt mechanism See https://github.com/golang/go/issues/36494 for a description of why full support for 1.14 under windows is problematic. * proc/native: disable Go 1.14 async preemption on Windows See https://github.com/golang/go/issues/36494
2020-02-11 01:31:54 +00:00
return proc.NewTarget(dbp, false), nil
}
2016-01-10 08:57:52 +00:00
// Kill kills the process.
func (dbp *Process) kill() (err error) {
if dbp.exited {
return nil
}
2017-02-08 00:23:47 +00:00
err = sys.Kill(-dbp.pid, sys.SIGKILL)
if err != nil {
return errors.New("could not deliver signal: " + err.Error())
}
2017-02-08 00:23:47 +00:00
for port := range dbp.threads {
if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
return errors.New("could not resume task")
}
}
for {
var task C.task_t
port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
if port == dbp.os.notificationPort {
break
}
}
dbp.postExit()
return
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) requestManualStop() (err error) {
2015-04-13 22:17:06 +00:00
var (
task = C.mach_port_t(dbp.os.task)
thread = C.mach_port_t(dbp.currentThread.os.threadAct)
2015-04-13 22:17:06 +00:00
exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
)
dbp.os.halt = true
2015-04-13 22:17:06 +00:00
kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
if kret != C.KERN_SUCCESS {
2015-05-04 22:31:13 +00:00
return fmt.Errorf("could not raise mach exception")
2015-01-14 02:37:10 +00:00
}
return nil
}
var couldNotGetThreadCount = errors.New("could not get thread count")
var couldNotGetThreadList = errors.New("could not get thread list")
2015-06-20 22:54:52 +00:00
func (dbp *Process) updateThreadList() error {
return dbp.updateThreadListForTask(dbp.os.task)
}
func (dbp *Process) updateThreadListForTask(task C.task_t) error {
2015-01-14 02:37:10 +00:00
var (
err error
kret C.kern_return_t
count C.int
list []uint32
2015-01-14 02:37:10 +00:00
)
for {
count = C.thread_count(task)
if count == -1 {
return couldNotGetThreadCount
}
list = make([]uint32, count)
// TODO(dp) might be better to malloc mem in C and then free it here
// instead of getting count above and passing in a slice
kret = C.get_threads(task, unsafe.Pointer(&list[0]), count)
if kret != -2 {
break
}
2015-01-14 02:37:10 +00:00
}
if kret != C.KERN_SUCCESS {
return couldNotGetThreadList
}
2017-02-08 00:23:47 +00:00
for _, thread := range dbp.threads {
thread.os.exists = false
2015-01-14 02:37:10 +00:00
}
for _, port := range list {
2017-02-08 00:23:47 +00:00
thread, ok := dbp.threads[int(port)]
if !ok {
thread, err = dbp.addThread(int(port), false)
2015-03-01 03:34:55 +00:00
if err != nil {
return err
}
2015-01-14 02:37:10 +00:00
}
thread.os.exists = true
}
2017-02-08 00:23:47 +00:00
for threadID, thread := range dbp.threads {
if !thread.os.exists {
2017-02-08 00:23:47 +00:00
delete(dbp.threads, threadID)
}
2015-01-14 02:37:10 +00:00
}
return nil
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
2017-02-08 00:23:47 +00:00
if thread, ok := dbp.threads[port]; ok {
2015-03-01 04:08:42 +00:00
return thread, nil
}
2015-06-12 19:51:23 +00:00
thread := &Thread{
2016-01-10 08:57:52 +00:00
ID: port,
dbp: dbp,
os: new(OSSpecificDetails),
2015-01-14 02:37:10 +00:00
}
2017-02-08 00:23:47 +00:00
dbp.threads[port] = thread
2016-01-10 08:57:52 +00:00
thread.os.threadAct = C.thread_act_t(port)
if dbp.currentThread == nil {
2016-01-10 08:57:52 +00:00
dbp.SwitchThread(thread.ID)
2015-03-01 03:34:55 +00:00
}
2015-01-14 02:37:10 +00:00
return thread, nil
}
func findExecutable(path string, pid int) string {
if path == "" {
path = C.GoString(C.find_executable(C.int(pid)))
}
return path
2015-01-14 02:37:10 +00:00
}
2015-06-20 22:54:52 +00:00
func (dbp *Process) trapWait(pid int) (*Thread, error) {
for {
task := dbp.os.task
port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
switch port {
case dbp.os.notificationPort:
// on macOS >= 10.12.1 the task_t changes after an execve, we could
// receive the notification for the death of the pre-execve task_t,
// this could also happen *before* we are notified that our task_t has
// changed.
if dbp.os.task != task {
continue
}
if !dbp.os.initialized {
2017-02-08 00:23:47 +00:00
if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask {
continue
}
}
2017-02-08 00:23:47 +00:00
_, status, err := dbp.wait(dbp.pid, 0)
if err != nil {
return nil, err
}
dbp.postExit()
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
case C.MACH_RCV_INTERRUPTED:
dbp.stopMu.Lock()
halt := dbp.os.halt
dbp.stopMu.Unlock()
if !halt {
// Call trapWait again, it seems
// MACH_RCV_INTERRUPTED is emitted before
// process natural death _sometimes_.
2015-04-25 19:53:55 +00:00
continue
}
return nil, nil
case 0:
2015-05-04 22:31:13 +00:00
return nil, fmt.Errorf("error while waiting for task")
}
// In macOS 10.12.1 if we received a notification for a task other than
// the inferior's task and the inferior's task is no longer valid, this
// means inferior called execve and its task_t changed.
if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 {
dbp.os.task = task
kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort)
if kret != C.KERN_SUCCESS {
return nil, fmt.Errorf("could not follow task across exec: %d\n", kret)
}
}
// Since we cannot be notified of new threads on OS X
// this is as good a time as any to check for them.
dbp.updateThreadList()
2017-02-08 00:23:47 +00:00
th, ok := dbp.threads[int(port)]
2015-11-24 21:48:28 +00:00
if !ok {
dbp.stopMu.Lock()
halt := dbp.os.halt
dbp.stopMu.Unlock()
if halt {
dbp.os.halt = false
2015-11-24 21:48:28 +00:00
return th, nil
}
2015-11-24 21:48:28 +00:00
if dbp.firstStart || th.singleStepping {
dbp.firstStart = false
2015-11-24 21:48:28 +00:00
return th, nil
}
2015-11-24 21:48:28 +00:00
if err := th.Continue(); err != nil {
return nil, err
}
continue
2015-03-06 23:28:53 +00:00
}
return th, nil
2015-01-14 02:37:10 +00:00
}
}
func (dbp *Process) waitForStop() ([]int, error) {
2017-02-08 00:23:47 +00:00
ports := make([]int, 0, len(dbp.threads))
count := 0
for {
var task C.task_t
port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1))
if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED {
count = 0
ports = append(ports, int(port))
} else {
2016-04-13 05:53:13 +00:00
n := C.num_running_threads(dbp.os.task)
if n == 0 {
return ports, nil
} else if n < 0 {
return nil, fmt.Errorf("error waiting for thread stop %d", n)
} else if count > 16 {
2016-01-09 21:46:18 +00:00
return nil, fmt.Errorf("could not stop process %d", n)
}
}
}
}
func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
2015-01-14 02:37:10 +00:00
var status sys.WaitStatus
wpid, err := sys.Wait4(pid, &status, options, nil)
return wpid, &status, err
}
2016-01-15 05:26:54 +00:00
func killProcess(pid int) error {
return sys.Kill(pid, sys.SIGINT)
}
func (dbp *Process) exitGuard(err error) error {
if err != ErrContinueThread {
return err
}
2017-02-08 00:23:47 +00:00
_, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
if werr == nil && status.Exited() {
dbp.postExit()
return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
}
return err
}
func (dbp *Process) resume() error {
// all threads stopped over a breakpoint are made to step over it
2017-02-08 00:23:47 +00:00
for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil {
if err := thread.StepInstruction(); err != nil {
return err
}
thread.CurrentBreakpoint.Clear()
}
}
// everything is resumed
2017-02-08 00:23:47 +00:00
for _, thread := range dbp.threads {
if err := thread.resume(); err != nil {
return dbp.exitGuard(err)
}
}
return nil
}
// stop stops all running threads and sets breakpoints
func (dbp *Process) stop(trapthread *Thread) (err error) {
if dbp.exited {
return &proc.ErrProcessExited{Pid: dbp.Pid()}
}
for _, th := range dbp.threads {
if !th.Stopped() {
if err := th.stop(); err != nil {
return dbp.exitGuard(err)
}
}
}
ports, err := dbp.waitForStop()
if err != nil {
return err
}
if !dbp.os.initialized {
return nil
}
trapthread.SetCurrentBreakpoint(true)
for _, port := range ports {
if th, ok := dbp.threads[port]; ok {
err := th.SetCurrentBreakpoint(true)
if err != nil {
return err
}
}
}
return nil
}
func (dbp *Process) detach(kill bool) error {
return PtraceDetach(dbp.pid, 0)
}
func (dbp *Process) EntryPoint() (uint64, error) {
//TODO(aarzilli): implement this
return 0, nil
}
func initialize(dbp *Process) error { return nil }