github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/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  // #include <sys/sysctl.h>
     7  import "C"
     8  import (
     9  	"fmt"
    10  	"os/exec"
    11  	"os/signal"
    12  	"strings"
    13  	"syscall"
    14  	"unsafe"
    15  
    16  	sys "golang.org/x/sys/unix"
    17  
    18  	"github.com/go-delve/delve/pkg/logflags"
    19  	"github.com/go-delve/delve/pkg/proc"
    20  	"github.com/go-delve/delve/pkg/proc/internal/ebpf"
    21  
    22  	isatty "github.com/mattn/go-isatty"
    23  )
    24  
    25  const (
    26  	_PL_FLAG_BORN   = 0x100
    27  	_PL_FLAG_EXITED = 0x200
    28  	_PL_FLAG_SI     = 0x20
    29  )
    30  
    31  // Process statuses
    32  const (
    33  	statusIdle     = 1
    34  	statusRunning  = 2
    35  	statusSleeping = 3
    36  	statusStopped  = 4
    37  	statusZombie   = 5
    38  	statusWaiting  = 6
    39  	statusLocked   = 7
    40  )
    41  
    42  // osProcessDetails contains FreeBSD specific
    43  // process details.
    44  type osProcessDetails struct {
    45  	comm string
    46  	tid  int
    47  
    48  	delayedSignal  syscall.Signal
    49  	trapThreads    []int
    50  	selectedThread *nativeThread
    51  }
    52  
    53  func (os *osProcessDetails) Close() {}
    54  
    55  // Launch creates and begins debugging a new process. First entry in
    56  // `cmd` is the program to run, and then rest are the arguments
    57  // to be supplied to that process. `wd` is working directory of the program.
    58  // If the DWARF information cannot be found in the binary, Delve will look
    59  // for external debug files in the directories passed in.
    60  func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) {
    61  	var (
    62  		process *exec.Cmd
    63  		err     error
    64  	)
    65  
    66  	foreground := flags&proc.LaunchForeground != 0
    67  
    68  	stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, foreground)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	if stdin == nil || !isatty.IsTerminal(stdin.Fd()) {
    74  		// exec.(*Process).Start will fail if we try to send a process to
    75  		// foreground but we are not attached to a terminal.
    76  		foreground = false
    77  	}
    78  
    79  	dbp := newProcess(0)
    80  	defer func() {
    81  		if err != nil && dbp.pid != 0 {
    82  			_ = detachWithoutGroup(dbp, true)
    83  		}
    84  	}()
    85  	dbp.execPtraceFunc(func() {
    86  		process = exec.Command(cmd[0])
    87  		process.Args = cmd
    88  		process.Stdin = stdin
    89  		process.Stdout = stdout
    90  		process.Stderr = stderr
    91  		process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground}
    92  		if foreground {
    93  			signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
    94  		}
    95  		if tty != "" {
    96  			dbp.ctty, err = attachProcessToTTY(process, tty)
    97  			if err != nil {
    98  				return
    99  			}
   100  		}
   101  		if wd != "" {
   102  			process.Dir = wd
   103  		}
   104  		err = process.Start()
   105  	})
   106  	closefn()
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	dbp.pid = process.Process.Pid
   111  	dbp.childProcess = true
   112  	_, _, err = dbp.wait(process.Process.Pid, 0)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("waiting for target execve failed: %s", err)
   115  	}
   116  	tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	return tgt, nil
   121  }
   122  
   123  // Attach to an existing process with the given PID. Once attached, if
   124  // the DWARF information cannot be found in the binary, Delve will look
   125  // for external debug files in the directories passed in.
   126  func Attach(pid int, waitFor *proc.WaitFor, debugInfoDirs []string) (*proc.TargetGroup, error) {
   127  	if waitFor.Valid() {
   128  		var err error
   129  		pid, err = WaitFor(waitFor)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  
   135  	dbp := newProcess(pid)
   136  
   137  	var err error
   138  	dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) })
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	_, _, err = dbp.wait(dbp.pid, 0)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
   148  	if err != nil {
   149  		detachWithoutGroup(dbp, false)
   150  		return nil, err
   151  	}
   152  	return tgt, nil
   153  }
   154  
   155  func waitForSearchProcess(pfx string, seen map[int]struct{}) (int, error) {
   156  	log := logflags.DebuggerLogger()
   157  	ps := C.procstat_open_sysctl()
   158  	defer C.procstat_close(ps)
   159  	var cnt C.uint
   160  	procs := C.procstat_getprocs(ps, C.KERN_PROC_PROC, 0, &cnt)
   161  	defer C.procstat_freeprocs(ps, procs)
   162  	proc := procs
   163  	for i := 0; i < int(cnt); i++ {
   164  		if _, isseen := seen[int(proc.ki_pid)]; isseen {
   165  			continue
   166  		}
   167  		seen[int(proc.ki_pid)] = struct{}{}
   168  
   169  		argv := strings.Join(getCmdLineInternal(ps, proc), " ")
   170  		log.Debugf("waitfor: new process %q", argv)
   171  		if strings.HasPrefix(argv, pfx) {
   172  			return int(proc.ki_pid), nil
   173  		}
   174  
   175  		proc = (*C.struct_kinfo_proc)(unsafe.Pointer(uintptr(unsafe.Pointer(proc)) + unsafe.Sizeof(*proc)))
   176  	}
   177  	return 0, nil
   178  }
   179  
   180  func initialize(dbp *nativeProcess) (string, error) {
   181  	comm, _ := C.find_command_name(C.int(dbp.pid))
   182  	defer C.free(unsafe.Pointer(comm))
   183  	comm_str := C.GoString(comm)
   184  	dbp.os.comm = strings.ReplaceAll(string(comm_str), "%", "%%")
   185  	return getCmdLine(dbp.pid), nil
   186  }
   187  
   188  // kill kills the target process.
   189  func (procgrp *processGroup) kill(dbp *nativeProcess) (err error) {
   190  	if dbp.exited {
   191  		return nil
   192  	}
   193  	dbp.execPtraceFunc(func() {
   194  		for _, th := range dbp.threads {
   195  			ptraceResume(th.ID)
   196  		}
   197  		err = ptraceCont(dbp.pid, int(sys.SIGKILL))
   198  	})
   199  	if err != nil {
   200  		return err
   201  	}
   202  	if _, _, err = dbp.wait(dbp.pid, 0); err != nil {
   203  		return err
   204  	}
   205  	dbp.postExit()
   206  	return nil
   207  }
   208  
   209  // Used by RequestManualStop
   210  func (dbp *nativeProcess) requestManualStop() (err error) {
   211  	return sys.Kill(dbp.pid, sys.SIGSTOP)
   212  }
   213  
   214  // Attach to a newly created thread, and store that thread in our list of
   215  // known threads.
   216  func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
   217  	if thread, ok := dbp.threads[tid]; ok {
   218  		return thread, nil
   219  	}
   220  
   221  	var err error
   222  	dbp.execPtraceFunc(func() { err = sys.PtraceLwpEvents(dbp.pid, 1) })
   223  	if err == syscall.ESRCH {
   224  		if _, _, err = dbp.wait(dbp.pid, 0); err != nil {
   225  			return nil, fmt.Errorf("error while waiting after adding process: %d %s", dbp.pid, err)
   226  		}
   227  	}
   228  
   229  	dbp.threads[tid] = &nativeThread{
   230  		ID:  tid,
   231  		dbp: dbp,
   232  		os:  new(osSpecificDetails),
   233  	}
   234  
   235  	if dbp.memthread == nil {
   236  		dbp.memthread = dbp.threads[tid]
   237  	}
   238  
   239  	return dbp.threads[tid], nil
   240  }
   241  
   242  // Used by initialize
   243  func (dbp *nativeProcess) updateThreadList() error {
   244  	var tids []int32
   245  	dbp.execPtraceFunc(func() { tids = ptraceGetLwpList(dbp.pid) })
   246  	for _, tid := range tids {
   247  		if _, err := dbp.addThread(int(tid), false); err != nil {
   248  			return err
   249  		}
   250  	}
   251  	dbp.os.tid = int(tids[0])
   252  	return nil
   253  }
   254  
   255  // Used by Attach
   256  func findExecutable(path string, pid int) string {
   257  	if path == "" {
   258  		cstr := C.find_executable(C.int(pid))
   259  		defer C.free(unsafe.Pointer(cstr))
   260  		path = C.GoString(cstr)
   261  	}
   262  	return path
   263  }
   264  
   265  func getCmdLine(pid int) string {
   266  	ps := C.procstat_open_sysctl()
   267  	kp := C.kinfo_getproc(C.int(pid))
   268  	goargv := getCmdLineInternal(ps, kp)
   269  	C.free(unsafe.Pointer(kp))
   270  	C.procstat_close(ps)
   271  	return strings.Join(goargv, " ")
   272  }
   273  
   274  func getCmdLineInternal(ps *C.struct_procstat, kp *C.struct_kinfo_proc) []string {
   275  	argv := C.procstat_getargv(ps, kp, 0)
   276  	if argv == nil {
   277  		return nil
   278  	}
   279  	goargv := []string{}
   280  	for {
   281  		arg := *argv
   282  		if arg == nil {
   283  			break
   284  		}
   285  		argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(*argv)))
   286  		goargv = append(goargv, C.GoString(arg))
   287  	}
   288  	C.procstat_freeargv(ps)
   289  	return goargv
   290  }
   291  
   292  func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
   293  	return procgrp.procs[0].trapWaitInternal(pid, trapWaitNormal)
   294  }
   295  
   296  type trapWaitMode uint8
   297  
   298  const (
   299  	trapWaitNormal trapWaitMode = iota
   300  	trapWaitStepping
   301  )
   302  
   303  // Used by stop and trapWait
   304  func (dbp *nativeProcess) trapWaitInternal(pid int, mode trapWaitMode) (*nativeThread, error) {
   305  	if dbp.os.selectedThread != nil {
   306  		th := dbp.os.selectedThread
   307  		dbp.os.selectedThread = nil
   308  		return th, nil
   309  	}
   310  	for {
   311  		wpid, status, err := dbp.wait(pid, 0)
   312  		if wpid != dbp.pid {
   313  			// possibly a delayed notification from a process we just detached and killed, freebsd bug?
   314  			continue
   315  		}
   316  		if err != nil {
   317  			return nil, fmt.Errorf("wait err %s %d", err, pid)
   318  		}
   319  		if status.Killed() {
   320  			// "Killed" status may arrive as a result of a Process.Kill() of some other process in
   321  			// the system performed by the same tracer (e.g. in the previous test)
   322  			continue
   323  		}
   324  		if status.Exited() {
   325  			dbp.postExit()
   326  			return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
   327  		}
   328  		if status.Signaled() {
   329  			// Killed by a signal
   330  			dbp.postExit()
   331  			return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
   332  		}
   333  
   334  		var info sys.PtraceLwpInfoStruct
   335  		dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) })
   336  		if err != nil {
   337  			return nil, fmt.Errorf("ptraceGetLwpInfo err %s %d", err, pid)
   338  		}
   339  		tid := int(info.Lwpid)
   340  		pl_flags := int(info.Flags)
   341  		th, ok := dbp.threads[tid]
   342  		if ok {
   343  			th.Status = (*waitStatus)(status)
   344  		}
   345  
   346  		if status.StopSignal() == sys.SIGTRAP {
   347  			if pl_flags&_PL_FLAG_EXITED != 0 {
   348  				delete(dbp.threads, tid)
   349  				dbp.execPtraceFunc(func() { err = ptraceCont(tid, 0) })
   350  				if err != nil {
   351  					return nil, err
   352  				}
   353  				continue
   354  			} else if pl_flags&_PL_FLAG_BORN != 0 {
   355  				th, err = dbp.addThread(int(tid), false)
   356  				if err != nil {
   357  					if err == sys.ESRCH {
   358  						// process died while we were adding it
   359  						continue
   360  					}
   361  					return nil, err
   362  				}
   363  				if mode == trapWaitStepping {
   364  					dbp.execPtraceFunc(func() { ptraceSuspend(tid) })
   365  				}
   366  				if err = dbp.ptraceCont(0); err != nil {
   367  					if err == sys.ESRCH {
   368  						// thread died while we were adding it
   369  						delete(dbp.threads, int(tid))
   370  						continue
   371  					}
   372  					return nil, fmt.Errorf("could not continue new thread %d %s", tid, err)
   373  				}
   374  				continue
   375  			}
   376  		}
   377  
   378  		if th == nil {
   379  			continue
   380  		}
   381  
   382  		if mode == trapWaitStepping {
   383  			return th, nil
   384  		}
   385  		if status.StopSignal() == sys.SIGTRAP || status.Continued() {
   386  			// Continued in this case means we received the SIGSTOP signal
   387  			return th, nil
   388  		}
   389  
   390  		// TODO(dp) alert user about unexpected signals here.
   391  		if err := dbp.ptraceCont(int(status.StopSignal())); err != nil {
   392  			if err == sys.ESRCH {
   393  				return nil, proc.ErrProcessExited{Pid: dbp.pid}
   394  			}
   395  			return nil, err
   396  		}
   397  	}
   398  }
   399  
   400  // Helper function used here and in threads_freebsd.go
   401  // Return the status code
   402  func status(pid int) rune {
   403  	status := rune(C.find_status(C.int(pid)))
   404  	return status
   405  }
   406  
   407  // Only used in this file
   408  func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
   409  	var s sys.WaitStatus
   410  	wpid, err := sys.Wait4(pid, &s, options, nil)
   411  	return wpid, &s, err
   412  }
   413  
   414  // Only used in this file
   415  func (dbp *nativeProcess) exitGuard(err error) error {
   416  	if err != sys.ESRCH {
   417  		return err
   418  	}
   419  	if status(dbp.pid) == statusZombie {
   420  		_, err := dbp.trapWaitInternal(-1, trapWaitNormal)
   421  		return err
   422  	}
   423  
   424  	return err
   425  }
   426  
   427  // Used by ContinueOnce
   428  func (procgrp *processGroup) resume() error {
   429  	dbp := procgrp.procs[0]
   430  	// all threads stopped over a breakpoint are made to step over it
   431  	for _, thread := range dbp.threads {
   432  		if thread.CurrentBreakpoint.Breakpoint != nil {
   433  			if err := thread.StepInstruction(); err != nil {
   434  				return err
   435  			}
   436  			thread.CurrentBreakpoint.Clear()
   437  		}
   438  	}
   439  	if len(dbp.os.trapThreads) > 0 {
   440  		// On these threads we have already received a SIGTRAP while stepping on a different thread,
   441  		// do not resume the process, instead record the breakpoint hits on them.
   442  		tt := dbp.os.trapThreads
   443  		dbp.os.trapThreads = dbp.os.trapThreads[:0]
   444  		var err error
   445  		dbp.os.selectedThread, err = dbp.postStop(tt...)
   446  		return err
   447  	}
   448  	// all threads are resumed
   449  	var err error
   450  	dbp.execPtraceFunc(func() {
   451  		for _, th := range dbp.threads {
   452  			err = ptraceResume(th.ID)
   453  			if err != nil {
   454  				return
   455  			}
   456  		}
   457  		sig := int(dbp.os.delayedSignal)
   458  		dbp.os.delayedSignal = 0
   459  		for _, thread := range dbp.threads {
   460  			thread.Status = nil
   461  		}
   462  		err = ptraceCont(dbp.pid, sig)
   463  	})
   464  	return err
   465  }
   466  
   467  // Used by ContinueOnce
   468  // stop stops all running threads and sets breakpoints
   469  func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
   470  	return procgrp.procs[0].stop(trapthread)
   471  }
   472  
   473  func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) {
   474  	if dbp.exited {
   475  		return nil, proc.ErrProcessExited{Pid: dbp.pid}
   476  	}
   477  
   478  	var err error
   479  	dbp.execPtraceFunc(func() {
   480  		for _, th := range dbp.threads {
   481  			err = ptraceSuspend(th.ID)
   482  			if err != nil {
   483  				return
   484  			}
   485  		}
   486  	})
   487  	if err != nil {
   488  		return nil, err
   489  	}
   490  
   491  	if trapthread.Status == nil || (*sys.WaitStatus)(trapthread.Status).StopSignal() != sys.SIGTRAP {
   492  		return trapthread, nil
   493  	}
   494  
   495  	return dbp.postStop(trapthread.ID)
   496  }
   497  
   498  func (dbp *nativeProcess) postStop(tids ...int) (*nativeThread, error) {
   499  	var pickedTrapThread *nativeThread
   500  	for _, tid := range tids {
   501  		trapthread := dbp.threads[tid]
   502  		if trapthread == nil {
   503  			continue
   504  		}
   505  
   506  		// Must either be a hardcoded breakpoint, a currently set breakpoint or a
   507  		// phantom breakpoint hit caused by a SIGTRAP that was delayed.
   508  		// If someone sent a SIGTRAP directly to the process this will fail, but we
   509  		// can't do better.
   510  
   511  		err := trapthread.SetCurrentBreakpoint(true)
   512  		if err != nil {
   513  			return nil, err
   514  		}
   515  
   516  		if trapthread.CurrentBreakpoint.Breakpoint == nil {
   517  			// hardcoded breakpoint or phantom breakpoint hit
   518  			if dbp.BinInfo().Arch.BreakInstrMovesPC() {
   519  				pc, _ := trapthread.PC()
   520  				if !trapthread.atHardcodedBreakpoint(pc) {
   521  					// phantom breakpoint hit
   522  					_ = trapthread.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
   523  					trapthread = nil
   524  				}
   525  			}
   526  		}
   527  
   528  		if pickedTrapThread == nil && trapthread != nil {
   529  			pickedTrapThread = trapthread
   530  		}
   531  	}
   532  
   533  	return pickedTrapThread, nil
   534  }
   535  
   536  // Used by Detach
   537  func (dbp *nativeProcess) detach(kill bool) error {
   538  	if !kill {
   539  		for _, th := range dbp.threads {
   540  			ptraceResume(th.ID)
   541  		}
   542  	}
   543  	return ptraceDetach(dbp.pid)
   544  }
   545  
   546  // Used by PostInitializationSetup
   547  // EntryPoint will return the process entry point address, useful for debugging PIEs.
   548  func (dbp *nativeProcess) EntryPoint() (uint64, error) {
   549  	ep, err := C.get_entry_point(C.int(dbp.pid))
   550  	return uint64(ep), err
   551  }
   552  
   553  func (dbp *nativeProcess) SupportsBPF() bool {
   554  	return false
   555  }
   556  
   557  func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error {
   558  	panic("not implemented")
   559  }
   560  
   561  func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
   562  	panic("not implemented")
   563  }
   564  
   565  func (dbp *nativeProcess) ptraceCont(sig int) error {
   566  	var err error
   567  	dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, sig) })
   568  	return err
   569  }
   570  
   571  // Usedy by Detach
   572  func killProcess(pid int) error {
   573  	return sys.Kill(pid, sys.SIGINT)
   574  }