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

     1  package native
     2  
     3  // #cgo LDFLAGS: -lprocstat
     4  // #include <stdlib.h>
     5  // #include "proc_freebsd.h"
     6  import "C"
     7  import (
     8  	"fmt"
     9  	"os/exec"
    10  	"os/signal"
    11  	"strings"
    12  	"syscall"
    13  	"unsafe"
    14  
    15  	sys "golang.org/x/sys/unix"
    16  
    17  	"github.com/undoio/delve/pkg/proc"
    18  	"github.com/undoio/delve/pkg/proc/internal/ebpf"
    19  
    20  	isatty "github.com/mattn/go-isatty"
    21  )
    22  
    23  // Process statuses
    24  const (
    25  	statusIdle     = 1
    26  	statusRunning  = 2
    27  	statusSleeping = 3
    28  	statusStopped  = 4
    29  	statusZombie   = 5
    30  	statusWaiting  = 6
    31  	statusLocked   = 7
    32  )
    33  
    34  // osProcessDetails contains FreeBSD specific
    35  // process details.
    36  type osProcessDetails struct {
    37  	comm string
    38  	tid  int
    39  }
    40  
    41  func (os *osProcessDetails) Close() {}
    42  
    43  // Launch creates and begins debugging a new process. First entry in
    44  // `cmd` is the program to run, and then rest are the arguments
    45  // to be supplied to that process. `wd` is working directory of the program.
    46  // If the DWARF information cannot be found in the binary, Delve will look
    47  // for external debug files in the directories passed in.
    48  func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
    49  	var (
    50  		process *exec.Cmd
    51  		err     error
    52  	)
    53  
    54  	foreground := flags&proc.LaunchForeground != 0
    55  
    56  	stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	if stdin == nil || !isatty.IsTerminal(stdin.Fd()) {
    62  		// exec.(*Process).Start will fail if we try to send a process to
    63  		// foreground but we are not attached to a terminal.
    64  		foreground = false
    65  	}
    66  
    67  	dbp := newProcess(0)
    68  	defer func() {
    69  		if err != nil && dbp.pid != 0 {
    70  			_ = dbp.Detach(true)
    71  		}
    72  	}()
    73  	dbp.execPtraceFunc(func() {
    74  		process = exec.Command(cmd[0])
    75  		process.Args = cmd
    76  		process.Stdin = stdin
    77  		process.Stdout = stdout
    78  		process.Stderr = stderr
    79  		process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground}
    80  		process.Env = proc.DisableAsyncPreemptEnv()
    81  		if foreground {
    82  			signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
    83  		}
    84  		if tty != "" {
    85  			dbp.ctty, err = attachProcessToTTY(process, tty)
    86  			if err != nil {
    87  				return
    88  			}
    89  		}
    90  		if wd != "" {
    91  			process.Dir = wd
    92  		}
    93  		err = process.Start()
    94  	})
    95  	closefn()
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	dbp.pid = process.Process.Pid
   100  	dbp.childProcess = true
   101  	_, _, err = dbp.wait(process.Process.Pid, 0)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("waiting for target execve failed: %s", err)
   104  	}
   105  	tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	return tgt, nil
   110  }
   111  
   112  // Attach to an existing process with the given PID. Once attached, if
   113  // the DWARF information cannot be found in the binary, Delve will look
   114  // for external debug files in the directories passed in.
   115  func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) {
   116  	dbp := newProcess(pid)
   117  
   118  	var err error
   119  	dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) })
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	_, _, err = dbp.wait(dbp.pid, 0)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
   129  	if err != nil {
   130  		dbp.Detach(false)
   131  		return nil, err
   132  	}
   133  	return tgt, nil
   134  }
   135  
   136  func initialize(dbp *nativeProcess) error {
   137  	comm, _ := C.find_command_name(C.int(dbp.pid))
   138  	defer C.free(unsafe.Pointer(comm))
   139  	comm_str := C.GoString(comm)
   140  	dbp.os.comm = strings.Replace(string(comm_str), "%", "%%", -1)
   141  	return nil
   142  }
   143  
   144  // kill kills the target process.
   145  func (dbp *nativeProcess) kill() (err error) {
   146  	if dbp.exited {
   147  		return nil
   148  	}
   149  	dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, int(sys.SIGKILL)) })
   150  	if err != nil {
   151  		return err
   152  	}
   153  	if _, _, err = dbp.wait(dbp.pid, 0); err != nil {
   154  		return err
   155  	}
   156  	dbp.postExit()
   157  	return nil
   158  }
   159  
   160  // Used by RequestManualStop
   161  func (dbp *nativeProcess) requestManualStop() (err error) {
   162  	return sys.Kill(dbp.pid, sys.SIGTRAP)
   163  }
   164  
   165  // Attach to a newly created thread, and store that thread in our list of
   166  // known threads.
   167  func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
   168  	if thread, ok := dbp.threads[tid]; ok {
   169  		return thread, nil
   170  	}
   171  
   172  	var err error
   173  	dbp.execPtraceFunc(func() { err = sys.PtraceLwpEvents(dbp.pid, 1) })
   174  	if err == syscall.ESRCH {
   175  		if _, _, err = dbp.waitFast(dbp.pid); err != nil {
   176  			return nil, fmt.Errorf("error while waiting after adding process: %d %s", dbp.pid, err)
   177  		}
   178  	}
   179  
   180  	dbp.threads[tid] = &nativeThread{
   181  		ID:  tid,
   182  		dbp: dbp,
   183  		os:  new(osSpecificDetails),
   184  	}
   185  
   186  	if dbp.memthread == nil {
   187  		dbp.memthread = dbp.threads[tid]
   188  	}
   189  
   190  	return dbp.threads[tid], nil
   191  }
   192  
   193  // Used by initialize
   194  func (dbp *nativeProcess) updateThreadList() error {
   195  	var tids []int32
   196  	dbp.execPtraceFunc(func() { tids = ptraceGetLwpList(dbp.pid) })
   197  	for _, tid := range tids {
   198  		if _, err := dbp.addThread(int(tid), false); err != nil {
   199  			return err
   200  		}
   201  	}
   202  	dbp.os.tid = int(tids[0])
   203  	return nil
   204  }
   205  
   206  // Used by Attach
   207  func findExecutable(path string, pid int) string {
   208  	if path == "" {
   209  		cstr := C.find_executable(C.int(pid))
   210  		defer C.free(unsafe.Pointer(cstr))
   211  		path = C.GoString(cstr)
   212  	}
   213  	return path
   214  }
   215  
   216  func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
   217  	return dbp.trapWaitInternal(pid, false)
   218  }
   219  
   220  // Used by stop and trapWait
   221  func (dbp *nativeProcess) trapWaitInternal(pid int, halt bool) (*nativeThread, error) {
   222  	for {
   223  		wpid, status, err := dbp.wait(pid, 0)
   224  		if err != nil {
   225  			return nil, fmt.Errorf("wait err %s %d", err, pid)
   226  		}
   227  		if status.Killed() {
   228  			// "Killed" status may arrive as a result of a Process.Kill() of some other process in
   229  			// the system performed by the same tracer (e.g. in the previous test)
   230  			continue
   231  		}
   232  		if status.Exited() {
   233  			dbp.postExit()
   234  			return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
   235  		}
   236  
   237  		var info sys.PtraceLwpInfoStruct
   238  		dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) })
   239  		if err != nil {
   240  			return nil, fmt.Errorf("ptraceGetLwpInfo err %s %d", err, pid)
   241  		}
   242  		tid := int(info.Lwpid)
   243  		pl_flags := int(info.Flags)
   244  		th, ok := dbp.threads[tid]
   245  		if ok {
   246  			th.Status = (*waitStatus)(status)
   247  		}
   248  
   249  		if status.StopSignal() == sys.SIGTRAP {
   250  			if pl_flags&sys.PL_FLAG_EXITED != 0 {
   251  				delete(dbp.threads, tid)
   252  				dbp.execPtraceFunc(func() { err = ptraceCont(tid, 0) })
   253  				if err != nil {
   254  					return nil, err
   255  				}
   256  				continue
   257  			} else if pl_flags&sys.PL_FLAG_BORN != 0 {
   258  				th, err = dbp.addThread(int(tid), false)
   259  				if err != nil {
   260  					if err == sys.ESRCH {
   261  						// process died while we were adding it
   262  						continue
   263  					}
   264  					return nil, err
   265  				}
   266  				if halt {
   267  					return nil, nil
   268  				}
   269  				if err = th.Continue(); err != nil {
   270  					if err == sys.ESRCH {
   271  						// thread died while we were adding it
   272  						delete(dbp.threads, int(tid))
   273  						continue
   274  					}
   275  					return nil, fmt.Errorf("could not continue new thread %d %s", tid, err)
   276  				}
   277  				continue
   278  			}
   279  		}
   280  
   281  		if th == nil {
   282  			continue
   283  		}
   284  
   285  		if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) {
   286  			return th, nil
   287  		}
   288  
   289  		// TODO(dp) alert user about unexpected signals here.
   290  		if err := th.resumeWithSig(int(status.StopSignal())); err != nil {
   291  			if err == sys.ESRCH {
   292  				return nil, proc.ErrProcessExited{Pid: dbp.pid}
   293  			}
   294  			return nil, err
   295  		}
   296  	}
   297  }
   298  
   299  // Helper function used here and in threads_freebsd.go
   300  // Return the status code
   301  func status(pid int) rune {
   302  	status := rune(C.find_status(C.int(pid)))
   303  	return status
   304  }
   305  
   306  // Used by stop and singleStep
   307  // waitFast is like wait but does not handle process-exit correctly
   308  func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) {
   309  	var s sys.WaitStatus
   310  	wpid, err := sys.Wait4(pid, &s, 0, nil)
   311  	return wpid, &s, err
   312  }
   313  
   314  // Only used in this file
   315  func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
   316  	var s sys.WaitStatus
   317  	wpid, err := sys.Wait4(pid, &s, options, nil)
   318  	return wpid, &s, err
   319  }
   320  
   321  // Only used in this file
   322  func (dbp *nativeProcess) exitGuard(err error) error {
   323  	if err != sys.ESRCH {
   324  		return err
   325  	}
   326  	if status(dbp.pid) == statusZombie {
   327  		_, err := dbp.trapWaitInternal(-1, false)
   328  		return err
   329  	}
   330  
   331  	return err
   332  }
   333  
   334  // Used by ContinueOnce
   335  func (dbp *nativeProcess) resume() error {
   336  	// all threads stopped over a breakpoint are made to step over it
   337  	for _, thread := range dbp.threads {
   338  		if thread.CurrentBreakpoint.Breakpoint != nil {
   339  			if err := thread.StepInstruction(); err != nil {
   340  				return err
   341  			}
   342  			thread.CurrentBreakpoint.Clear()
   343  		}
   344  	}
   345  	// all threads are resumed
   346  	var err error
   347  	dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, 0) })
   348  	return err
   349  }
   350  
   351  // Used by ContinueOnce
   352  // stop stops all running threads and sets breakpoints
   353  func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
   354  	if dbp.exited {
   355  		return nil, proc.ErrProcessExited{Pid: dbp.pid}
   356  	}
   357  	// set breakpoints on all threads
   358  	for _, th := range dbp.threads {
   359  		if th.CurrentBreakpoint.Breakpoint == nil {
   360  			if err := th.SetCurrentBreakpoint(true); err != nil {
   361  				return nil, err
   362  			}
   363  		}
   364  	}
   365  	return trapthread, nil
   366  }
   367  
   368  // Used by Detach
   369  func (dbp *nativeProcess) detach(kill bool) error {
   370  	return ptraceDetach(dbp.pid)
   371  }
   372  
   373  // Used by PostInitializationSetup
   374  // EntryPoint will return the process entry point address, useful for debugging PIEs.
   375  func (dbp *nativeProcess) EntryPoint() (uint64, error) {
   376  	ep, err := C.get_entry_point(C.int(dbp.pid))
   377  	return uint64(ep), err
   378  }
   379  
   380  func (dbp *nativeProcess) SupportsBPF() bool {
   381  	return false
   382  }
   383  
   384  func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error {
   385  	panic("not implemented")
   386  }
   387  
   388  func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
   389  	panic("not implemented")
   390  }
   391  
   392  // Usedy by Detach
   393  func killProcess(pid int) error {
   394  	return sys.Kill(pid, sys.SIGINT)
   395  }