github.com/undoio/delve@v1.9.0/pkg/proc/native/proc_darwin.go (about)

     1  //go:build darwin && macnative
     2  // +build darwin,macnative
     3  
     4  package native
     5  
     6  // #include "proc_darwin.h"
     7  // #include "threads_darwin.h"
     8  // #include "exec_darwin.h"
     9  // #include <stdlib.h>
    10  import "C"
    11  import (
    12  	"errors"
    13  	"fmt"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"unsafe"
    18  
    19  	sys "golang.org/x/sys/unix"
    20  
    21  	"github.com/undoio/delve/pkg/proc"
    22  	"github.com/undoio/delve/pkg/proc/internal/ebpf"
    23  	"github.com/undoio/delve/pkg/proc/macutil"
    24  )
    25  
    26  // osProcessDetails holds Darwin specific information.
    27  type osProcessDetails struct {
    28  	task             C.task_t      // mach task for the debugged process.
    29  	exceptionPort    C.mach_port_t // mach port for receiving mach exceptions.
    30  	notificationPort C.mach_port_t // mach port for dead name notification (process exit).
    31  	initialized      bool
    32  	halt             bool
    33  
    34  	// the main port we use, will return messages from both the
    35  	// exception and notification ports.
    36  	portSet C.mach_port_t
    37  }
    38  
    39  func (os *osProcessDetails) Close() {}
    40  
    41  // Launch creates and begins debugging a new process. Uses a
    42  // custom fork/exec process in order to take advantage of
    43  // PT_SIGEXC on Darwin which will turn Unix signals into
    44  // Mach exceptions.
    45  func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) {
    46  	argv0Go, err := filepath.Abs(cmd[0])
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	// Make sure the binary exists.
    51  	if filepath.Base(cmd[0]) == cmd[0] {
    52  		if _, err := exec.LookPath(cmd[0]); err != nil {
    53  			return nil, err
    54  		}
    55  	}
    56  	if _, err := os.Stat(argv0Go); err != nil {
    57  		return nil, err
    58  	}
    59  	if err := macutil.CheckRosetta(); err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	argv0 := C.CString(argv0Go)
    64  	argvSlice := make([]*C.char, 0, len(cmd)+1)
    65  	for _, arg := range cmd {
    66  		argvSlice = append(argvSlice, C.CString(arg))
    67  	}
    68  	// argv array must be null terminated.
    69  	argvSlice = append(argvSlice, nil)
    70  
    71  	dbp := newProcess(0)
    72  	defer func() {
    73  		if err != nil && dbp.pid != 0 {
    74  			_ = dbp.Detach(true)
    75  		}
    76  	}()
    77  	var pid int
    78  	dbp.execPtraceFunc(func() {
    79  		ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)),
    80  			C.CString(wd),
    81  			&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
    82  			&dbp.os.notificationPort)
    83  		pid = int(ret)
    84  	})
    85  	if pid <= 0 {
    86  		return nil, fmt.Errorf("could not fork/exec")
    87  	}
    88  	dbp.pid = pid
    89  	dbp.childProcess = true
    90  	for i := range argvSlice {
    91  		C.free(unsafe.Pointer(argvSlice[i]))
    92  	}
    93  
    94  	// Initialize enough of the Process state so that we can use resume and
    95  	// trapWait to wait until the child process calls execve.
    96  
    97  	for {
    98  		task := C.get_task_for_pid(C.int(dbp.pid))
    99  		// The task_for_pid call races with the fork call. This can
   100  		// result in the parent task being returned instead of the child.
   101  		if task != dbp.os.task {
   102  			err = dbp.updateThreadListForTask(task)
   103  			if err == nil {
   104  				break
   105  			}
   106  			if err != couldNotGetThreadCount && err != couldNotGetThreadList {
   107  				return nil, err
   108  			}
   109  		}
   110  	}
   111  
   112  	if err := dbp.resume(); err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	for _, th := range dbp.threads {
   117  		th.CurrentBreakpoint.Clear()
   118  	}
   119  
   120  	trapthread, err := dbp.trapWait(-1)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	if _, err := dbp.stop(nil, nil); err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	dbp.os.initialized = true
   129  	dbp.memthread = trapthread
   130  
   131  	tgt, err := dbp.initialize(argv0Go, []string{})
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	return tgt, err
   137  }
   138  
   139  // Attach to an existing process with the given PID.
   140  func Attach(pid int, _ []string) (*proc.Target, error) {
   141  	if err := macutil.CheckRosetta(); err != nil {
   142  		return nil, err
   143  	}
   144  	dbp := newProcess(pid)
   145  
   146  	kret := C.acquire_mach_task(C.int(pid),
   147  		&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
   148  		&dbp.os.notificationPort)
   149  
   150  	if kret != C.KERN_SUCCESS {
   151  		return nil, fmt.Errorf("could not attach to %d", pid)
   152  	}
   153  
   154  	dbp.os.initialized = true
   155  
   156  	var err error
   157  	dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) })
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	_, _, err = dbp.wait(dbp.pid, 0)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	tgt, err := dbp.initialize("", []string{})
   167  	if err != nil {
   168  		dbp.Detach(false)
   169  		return nil, err
   170  	}
   171  	return tgt, nil
   172  }
   173  
   174  // Kill kills the process.
   175  func (dbp *nativeProcess) kill() (err error) {
   176  	if dbp.exited {
   177  		return nil
   178  	}
   179  	err = sys.Kill(-dbp.pid, sys.SIGKILL)
   180  	if err != nil {
   181  		return errors.New("could not deliver signal: " + err.Error())
   182  	}
   183  	for port := range dbp.threads {
   184  		if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
   185  			return errors.New("could not resume task")
   186  		}
   187  	}
   188  	for {
   189  		var task C.task_t
   190  		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
   191  		if port == dbp.os.notificationPort {
   192  			break
   193  		}
   194  	}
   195  	dbp.postExit()
   196  	return
   197  }
   198  
   199  func (dbp *nativeProcess) requestManualStop() (err error) {
   200  	var (
   201  		task          = C.mach_port_t(dbp.os.task)
   202  		thread        = C.mach_port_t(dbp.memthread.os.threadAct)
   203  		exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
   204  	)
   205  	dbp.os.halt = true
   206  	kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
   207  	if kret != C.KERN_SUCCESS {
   208  		return fmt.Errorf("could not raise mach exception")
   209  	}
   210  	return nil
   211  }
   212  
   213  var couldNotGetThreadCount = errors.New("could not get thread count")
   214  var couldNotGetThreadList = errors.New("could not get thread list")
   215  
   216  func (dbp *nativeProcess) updateThreadList() error {
   217  	return dbp.updateThreadListForTask(dbp.os.task)
   218  }
   219  
   220  func (dbp *nativeProcess) updateThreadListForTask(task C.task_t) error {
   221  	var (
   222  		err   error
   223  		kret  C.kern_return_t
   224  		count C.int
   225  		list  []uint32
   226  	)
   227  
   228  	for {
   229  		count = C.thread_count(task)
   230  		if count == -1 {
   231  			return couldNotGetThreadCount
   232  		}
   233  		list = make([]uint32, count)
   234  
   235  		// TODO(dp) might be better to malloc mem in C and then free it here
   236  		// instead of getting count above and passing in a slice
   237  		kret = C.get_threads(task, unsafe.Pointer(&list[0]), count)
   238  		if kret != -2 {
   239  			break
   240  		}
   241  	}
   242  	if kret != C.KERN_SUCCESS {
   243  		return couldNotGetThreadList
   244  	}
   245  
   246  	for _, thread := range dbp.threads {
   247  		thread.os.exists = false
   248  	}
   249  
   250  	for _, port := range list {
   251  		thread, ok := dbp.threads[int(port)]
   252  		if !ok {
   253  			thread, err = dbp.addThread(int(port), false)
   254  			if err != nil {
   255  				return err
   256  			}
   257  		}
   258  		thread.os.exists = true
   259  	}
   260  
   261  	for threadID, thread := range dbp.threads {
   262  		if !thread.os.exists {
   263  			delete(dbp.threads, threadID)
   264  		}
   265  	}
   266  
   267  	return nil
   268  }
   269  
   270  func (dbp *nativeProcess) addThread(port int, attach bool) (*nativeThread, error) {
   271  	if thread, ok := dbp.threads[port]; ok {
   272  		return thread, nil
   273  	}
   274  	thread := &nativeThread{
   275  		ID:  port,
   276  		dbp: dbp,
   277  		os:  new(osSpecificDetails),
   278  	}
   279  	dbp.threads[port] = thread
   280  	thread.os.threadAct = C.thread_act_t(port)
   281  	if dbp.memthread == nil {
   282  		dbp.memthread = thread
   283  	}
   284  	return thread, nil
   285  }
   286  
   287  func findExecutable(path string, pid int) string {
   288  	if path == "" {
   289  		path = C.GoString(C.find_executable(C.int(pid)))
   290  	}
   291  	return path
   292  }
   293  
   294  func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
   295  	for {
   296  		task := dbp.os.task
   297  		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
   298  
   299  		switch port {
   300  		case dbp.os.notificationPort:
   301  			// on macOS >= 10.12.1 the task_t changes after an execve, we could
   302  			// receive the notification for the death of the pre-execve task_t,
   303  			// this could also happen *before* we are notified that our task_t has
   304  			// changed.
   305  			if dbp.os.task != task {
   306  				continue
   307  			}
   308  			if !dbp.os.initialized {
   309  				if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask {
   310  					continue
   311  				}
   312  			}
   313  			_, status, err := dbp.wait(dbp.pid, 0)
   314  			if err != nil {
   315  				return nil, err
   316  			}
   317  			dbp.postExit()
   318  			return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
   319  
   320  		case C.MACH_RCV_INTERRUPTED:
   321  			halt := dbp.os.halt
   322  			if !halt {
   323  				// Call trapWait again, it seems
   324  				// MACH_RCV_INTERRUPTED is emitted before
   325  				// process natural death _sometimes_.
   326  				continue
   327  			}
   328  			return nil, nil
   329  
   330  		case 0:
   331  			return nil, fmt.Errorf("error while waiting for task")
   332  		}
   333  
   334  		// In macOS 10.12.1 if we received a notification for a task other than
   335  		// the inferior's task and the inferior's task is no longer valid, this
   336  		// means inferior called execve and its task_t changed.
   337  		if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 {
   338  			dbp.os.task = task
   339  			kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort)
   340  			if kret != C.KERN_SUCCESS {
   341  				return nil, fmt.Errorf("could not follow task across exec: %d\n", kret)
   342  			}
   343  		}
   344  
   345  		// Since we cannot be notified of new threads on OS X
   346  		// this is as good a time as any to check for them.
   347  		dbp.updateThreadList()
   348  		th, ok := dbp.threads[int(port)]
   349  		if !ok {
   350  			halt := dbp.os.halt
   351  			if halt {
   352  				dbp.os.halt = false
   353  				return th, nil
   354  			}
   355  			if dbp.firstStart || th.singleStepping {
   356  				dbp.firstStart = false
   357  				return th, nil
   358  			}
   359  			if err := th.Continue(); err != nil {
   360  				return nil, err
   361  			}
   362  			continue
   363  		}
   364  		return th, nil
   365  	}
   366  }
   367  
   368  func (dbp *nativeProcess) waitForStop() ([]int, error) {
   369  	ports := make([]int, 0, len(dbp.threads))
   370  	count := 0
   371  	for {
   372  		var task C.task_t
   373  		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1))
   374  		if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED {
   375  			count = 0
   376  			ports = append(ports, int(port))
   377  		} else {
   378  			n := C.num_running_threads(dbp.os.task)
   379  			if n == 0 {
   380  				return ports, nil
   381  			} else if n < 0 {
   382  				return nil, fmt.Errorf("error waiting for thread stop %d", n)
   383  			} else if count > 16 {
   384  				return nil, fmt.Errorf("could not stop process %d", n)
   385  			}
   386  		}
   387  	}
   388  }
   389  
   390  func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
   391  	var status sys.WaitStatus
   392  	wpid, err := sys.Wait4(pid, &status, options, nil)
   393  	return wpid, &status, err
   394  }
   395  
   396  func killProcess(pid int) error {
   397  	return sys.Kill(pid, sys.SIGINT)
   398  }
   399  
   400  func (dbp *nativeProcess) exitGuard(err error) error {
   401  	if err != ErrContinueThread {
   402  		return err
   403  	}
   404  	_, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
   405  	if werr == nil && status.Exited() {
   406  		dbp.postExit()
   407  		return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
   408  	}
   409  	return err
   410  }
   411  
   412  func (dbp *nativeProcess) resume() error {
   413  	// all threads stopped over a breakpoint are made to step over it
   414  	for _, thread := range dbp.threads {
   415  		if thread.CurrentBreakpoint.Breakpoint != nil {
   416  			if err := thread.StepInstruction(); err != nil {
   417  				return err
   418  			}
   419  			thread.CurrentBreakpoint.Clear()
   420  		}
   421  	}
   422  	// everything is resumed
   423  	for _, thread := range dbp.threads {
   424  		if err := thread.resume(); err != nil {
   425  			return dbp.exitGuard(err)
   426  		}
   427  	}
   428  	return nil
   429  }
   430  
   431  // stop stops all running threads and sets breakpoints
   432  func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
   433  	if dbp.exited {
   434  		return nil, proc.ErrProcessExited{Pid: dbp.pid}
   435  	}
   436  	for _, th := range dbp.threads {
   437  		if !th.Stopped() {
   438  			if err := th.stop(); err != nil {
   439  				return nil, dbp.exitGuard(err)
   440  			}
   441  		}
   442  	}
   443  
   444  	ports, err := dbp.waitForStop()
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	if !dbp.os.initialized {
   449  		return nil, nil
   450  	}
   451  	trapthread.SetCurrentBreakpoint(true)
   452  	for _, port := range ports {
   453  		if th, ok := dbp.threads[port]; ok {
   454  			err := th.SetCurrentBreakpoint(true)
   455  			if err != nil {
   456  				return nil, err
   457  			}
   458  		}
   459  	}
   460  	return trapthread, nil
   461  }
   462  
   463  func (dbp *nativeProcess) detach(kill bool) error {
   464  	return ptraceDetach(dbp.pid, 0)
   465  }
   466  
   467  func (dbp *nativeProcess) EntryPoint() (uint64, error) {
   468  	//TODO(aarzilli): implement this
   469  	return 0, nil
   470  }
   471  
   472  func (dbp *nativeProcess) SupportsBPF() bool {
   473  	return false
   474  }
   475  
   476  func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error {
   477  	panic("not implemented")
   478  }
   479  
   480  func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
   481  	panic("not implemented")
   482  }
   483  
   484  func initialize(dbp *nativeProcess) error { return nil }