github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/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/go-delve/delve/pkg/proc"
    22  	"github.com/go-delve/delve/pkg/proc/internal/ebpf"
    23  	"github.com/go-delve/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, _ string, _ proc.OutputRedirect, _ proc.OutputRedirect) (*proc.TargetGroup, 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  			_ = detachWithoutGroup(dbp, 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); 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  func waitForSearchProcess(string, map[int]struct{}) (int, error) {
   140  	return 0, proc.ErrWaitForNotImplemented
   141  }
   142  
   143  // Attach to an existing process with the given PID.
   144  func Attach(pid int, waitFor *proc.WaitFor, _ []string) (*proc.TargetGroup, error) {
   145  	if waitFor.Valid() {
   146  		return nil, proc.ErrWaitForNotImplemented
   147  	}
   148  	if err := macutil.CheckRosetta(); err != nil {
   149  		return nil, err
   150  	}
   151  	dbp := newProcess(pid)
   152  
   153  	kret := C.acquire_mach_task(C.int(pid),
   154  		&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
   155  		&dbp.os.notificationPort)
   156  
   157  	if kret != C.KERN_SUCCESS {
   158  		return nil, fmt.Errorf("could not attach to %d", pid)
   159  	}
   160  
   161  	dbp.os.initialized = true
   162  
   163  	var err error
   164  	dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) })
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	_, _, err = dbp.wait(dbp.pid, 0)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	tgt, err := dbp.initialize("", []string{})
   174  	if err != nil {
   175  		detachWithoutGroup(dbp, false)
   176  		return nil, err
   177  	}
   178  	return tgt, nil
   179  }
   180  
   181  // Kill kills the process.
   182  func (procgrp *processGroup) kill(dbp *nativeProcess) (err error) {
   183  	if dbp.exited {
   184  		return nil
   185  	}
   186  	err = sys.Kill(-dbp.pid, sys.SIGKILL)
   187  	if err != nil {
   188  		return errors.New("could not deliver signal: " + err.Error())
   189  	}
   190  	for port := range dbp.threads {
   191  		if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
   192  			return errors.New("could not resume task")
   193  		}
   194  	}
   195  	for {
   196  		var task C.task_t
   197  		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
   198  		if port == dbp.os.notificationPort {
   199  			break
   200  		}
   201  	}
   202  	dbp.postExit()
   203  	return
   204  }
   205  
   206  func (dbp *nativeProcess) requestManualStop() (err error) {
   207  	var (
   208  		task          = C.mach_port_t(dbp.os.task)
   209  		thread        = C.mach_port_t(dbp.memthread.os.threadAct)
   210  		exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
   211  	)
   212  	dbp.os.halt = true
   213  	kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
   214  	if kret != C.KERN_SUCCESS {
   215  		return fmt.Errorf("could not raise mach exception")
   216  	}
   217  	return nil
   218  }
   219  
   220  var couldNotGetThreadCount = errors.New("could not get thread count")
   221  var couldNotGetThreadList = errors.New("could not get thread list")
   222  
   223  func (dbp *nativeProcess) updateThreadList() error {
   224  	return dbp.updateThreadListForTask(dbp.os.task)
   225  }
   226  
   227  func (dbp *nativeProcess) updateThreadListForTask(task C.task_t) error {
   228  	var (
   229  		err   error
   230  		kret  C.kern_return_t
   231  		count C.int
   232  		list  []uint32
   233  	)
   234  
   235  	for {
   236  		count = C.thread_count(task)
   237  		if count == -1 {
   238  			return couldNotGetThreadCount
   239  		}
   240  		list = make([]uint32, count)
   241  
   242  		// TODO(dp) might be better to malloc mem in C and then free it here
   243  		// instead of getting count above and passing in a slice
   244  		kret = C.get_threads(task, unsafe.Pointer(&list[0]), count)
   245  		if kret != -2 {
   246  			break
   247  		}
   248  	}
   249  	if kret != C.KERN_SUCCESS {
   250  		return couldNotGetThreadList
   251  	}
   252  
   253  	for _, thread := range dbp.threads {
   254  		thread.os.exists = false
   255  	}
   256  
   257  	for _, port := range list {
   258  		thread, ok := dbp.threads[int(port)]
   259  		if !ok {
   260  			thread, err = dbp.addThread(int(port), false)
   261  			if err != nil {
   262  				return err
   263  			}
   264  		}
   265  		thread.os.exists = true
   266  	}
   267  
   268  	for threadID, thread := range dbp.threads {
   269  		if !thread.os.exists {
   270  			delete(dbp.threads, threadID)
   271  		}
   272  	}
   273  
   274  	return nil
   275  }
   276  
   277  func (dbp *nativeProcess) addThread(port int, attach bool) (*nativeThread, error) {
   278  	if thread, ok := dbp.threads[port]; ok {
   279  		return thread, nil
   280  	}
   281  	thread := &nativeThread{
   282  		ID:  port,
   283  		dbp: dbp,
   284  		os:  new(osSpecificDetails),
   285  	}
   286  	dbp.threads[port] = thread
   287  	thread.os.threadAct = C.thread_act_t(port)
   288  	if dbp.memthread == nil {
   289  		dbp.memthread = thread
   290  	}
   291  	return thread, nil
   292  }
   293  
   294  func findExecutable(path string, pid int) string {
   295  	if path == "" {
   296  		path = C.GoString(C.find_executable(C.int(pid)))
   297  	}
   298  	return path
   299  }
   300  
   301  func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
   302  	return procgrp.procs[0].trapWait(pid)
   303  }
   304  
   305  func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
   306  	for {
   307  		task := dbp.os.task
   308  		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
   309  
   310  		switch port {
   311  		case dbp.os.notificationPort:
   312  			// on macOS >= 10.12.1 the task_t changes after an execve, we could
   313  			// receive the notification for the death of the pre-execve task_t,
   314  			// this could also happen *before* we are notified that our task_t has
   315  			// changed.
   316  			if dbp.os.task != task {
   317  				continue
   318  			}
   319  			if !dbp.os.initialized {
   320  				if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask {
   321  					continue
   322  				}
   323  			}
   324  			_, status, err := dbp.wait(dbp.pid, 0)
   325  			if err != nil {
   326  				return nil, err
   327  			}
   328  			dbp.postExit()
   329  			return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
   330  
   331  		case C.MACH_RCV_INTERRUPTED:
   332  			halt := dbp.os.halt
   333  			if !halt {
   334  				// Call trapWait again, it seems
   335  				// MACH_RCV_INTERRUPTED is emitted before
   336  				// process natural death _sometimes_.
   337  				continue
   338  			}
   339  			return nil, nil
   340  
   341  		case 0:
   342  			return nil, fmt.Errorf("error while waiting for task")
   343  		}
   344  
   345  		// In macOS 10.12.1 if we received a notification for a task other than
   346  		// the inferior's task and the inferior's task is no longer valid, this
   347  		// means inferior called execve and its task_t changed.
   348  		if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 {
   349  			dbp.os.task = task
   350  			kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort)
   351  			if kret != C.KERN_SUCCESS {
   352  				return nil, fmt.Errorf("could not follow task across exec: %d\n", kret)
   353  			}
   354  		}
   355  
   356  		// Since we cannot be notified of new threads on OS X
   357  		// this is as good a time as any to check for them.
   358  		dbp.updateThreadList()
   359  		th, ok := dbp.threads[int(port)]
   360  		if !ok {
   361  			halt := dbp.os.halt
   362  			if halt {
   363  				dbp.os.halt = false
   364  				return th, nil
   365  			}
   366  			if dbp.firstStart || th.singleStepping {
   367  				dbp.firstStart = false
   368  				return th, nil
   369  			}
   370  			if err := th.Continue(); err != nil {
   371  				return nil, err
   372  			}
   373  			continue
   374  		}
   375  		return th, nil
   376  	}
   377  }
   378  
   379  func (dbp *nativeProcess) waitForStop() ([]int, error) {
   380  	ports := make([]int, 0, len(dbp.threads))
   381  	count := 0
   382  	for {
   383  		var task C.task_t
   384  		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1))
   385  		if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED {
   386  			count = 0
   387  			ports = append(ports, int(port))
   388  		} else {
   389  			n := C.num_running_threads(dbp.os.task)
   390  			if n == 0 {
   391  				return ports, nil
   392  			} else if n < 0 {
   393  				return nil, fmt.Errorf("error waiting for thread stop %d", n)
   394  			} else if count > 16 {
   395  				return nil, fmt.Errorf("could not stop process %d", n)
   396  			}
   397  		}
   398  	}
   399  }
   400  
   401  func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
   402  	var status sys.WaitStatus
   403  	wpid, err := sys.Wait4(pid, &status, options, nil)
   404  	return wpid, &status, err
   405  }
   406  
   407  func killProcess(pid int) error {
   408  	return sys.Kill(pid, sys.SIGINT)
   409  }
   410  
   411  func (dbp *nativeProcess) exitGuard(err error) error {
   412  	if err != ErrContinueThread {
   413  		return err
   414  	}
   415  	_, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
   416  	if werr == nil && status.Exited() {
   417  		dbp.postExit()
   418  		return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
   419  	}
   420  	return err
   421  }
   422  
   423  func (procgrp *processGroup) resume() error {
   424  	return procgrp.procs[0].resume()
   425  }
   426  
   427  func (dbp *nativeProcess) resume() error {
   428  	// all threads stopped over a breakpoint are made to step over it
   429  	for _, thread := range dbp.threads {
   430  		if thread.CurrentBreakpoint.Breakpoint != nil {
   431  			if err := thread.StepInstruction(); err != nil {
   432  				return err
   433  			}
   434  			thread.CurrentBreakpoint.Clear()
   435  		}
   436  	}
   437  	// everything is resumed
   438  	for _, thread := range dbp.threads {
   439  		if err := thread.resume(); err != nil {
   440  			return dbp.exitGuard(err)
   441  		}
   442  	}
   443  	return nil
   444  }
   445  
   446  // stop stops all running threads and sets breakpoints
   447  func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
   448  	return procgrp.procs[0].stop(trapthread)
   449  }
   450  
   451  func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
   452  	if dbp.exited {
   453  		return nil, proc.ErrProcessExited{Pid: dbp.pid}
   454  	}
   455  	for _, th := range dbp.threads {
   456  		if !th.Stopped() {
   457  			if err := th.stop(); err != nil {
   458  				return nil, dbp.exitGuard(err)
   459  			}
   460  		}
   461  	}
   462  
   463  	ports, err := dbp.waitForStop()
   464  	if err != nil {
   465  		return nil, err
   466  	}
   467  	if !dbp.os.initialized {
   468  		return nil, nil
   469  	}
   470  	trapthread.SetCurrentBreakpoint(true)
   471  	for _, port := range ports {
   472  		if th, ok := dbp.threads[port]; ok {
   473  			err := th.SetCurrentBreakpoint(true)
   474  			if err != nil {
   475  				return nil, err
   476  			}
   477  		}
   478  	}
   479  	return trapthread, nil
   480  }
   481  
   482  func (dbp *nativeProcess) detach(kill bool) error {
   483  	return ptraceDetach(dbp.pid, 0)
   484  }
   485  
   486  func (dbp *nativeProcess) EntryPoint() (uint64, error) {
   487  	//TODO(aarzilli): implement this
   488  	return 0, nil
   489  }
   490  
   491  func (dbp *nativeProcess) SupportsBPF() bool {
   492  	return false
   493  }
   494  
   495  func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error {
   496  	panic("not implemented")
   497  }
   498  
   499  func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
   500  	panic("not implemented")
   501  }
   502  
   503  func initialize(dbp *nativeProcess) (string, error) { return "", nil }