gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/native/proc_darwin.go (about)

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