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

     1  package native
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"debug/elf"
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"os/exec"
    11  	"os/signal"
    12  	"path/filepath"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"time"
    18  
    19  	sys "golang.org/x/sys/unix"
    20  
    21  	"gitlab.com/Raven-IO/raven-delve/pkg/logflags"
    22  	"gitlab.com/Raven-IO/raven-delve/pkg/proc"
    23  	"gitlab.com/Raven-IO/raven-delve/pkg/proc/internal/ebpf"
    24  	"gitlab.com/Raven-IO/raven-delve/pkg/proc/linutil"
    25  
    26  	isatty "github.com/mattn/go-isatty"
    27  )
    28  
    29  // Process statuses
    30  const (
    31  	statusSleeping  = 'S'
    32  	statusRunning   = 'R'
    33  	statusTraceStop = 't'
    34  	statusZombie    = 'Z'
    35  
    36  	// Kernel 2.6 has TraceStop as T
    37  	// TODO(derekparker) Since this means something different based on the
    38  	// version of the kernel ('T' is job control stop on modern 3.x+ kernels) we
    39  	// may want to differentiate at some point.
    40  	statusTraceStopT = 'T'
    41  
    42  	personalityGetPersonality = 0xffffffff // argument to pass to personality syscall to get the current personality
    43  	_ADDR_NO_RANDOMIZE        = 0x0040000  // ADDR_NO_RANDOMIZE linux constant
    44  )
    45  
    46  // osProcessDetails contains Linux specific
    47  // process details.
    48  type osProcessDetails struct {
    49  	comm string
    50  
    51  	ebpf *ebpf.EBPFContext
    52  }
    53  
    54  func (os *osProcessDetails) Close() {
    55  	if os.ebpf != nil {
    56  		os.ebpf.Close()
    57  	}
    58  }
    59  
    60  // Launch creates and begins debugging a new process. First entry in
    61  // `cmd` is the program to run, and then rest are the arguments
    62  // to be supplied to that process. `wd` is working directory of the program.
    63  // If the DWARF information cannot be found in the binary, Delve will look
    64  // for external debug files in the directories passed in.
    65  func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) {
    66  	var (
    67  		process *exec.Cmd
    68  		err     error
    69  	)
    70  
    71  	foreground := flags&proc.LaunchForeground != 0
    72  
    73  	stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, foreground)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if stdin == nil || !isatty.IsTerminal(stdin.Fd()) {
    79  		// exec.(*Process).Start will fail if we try to send a process to
    80  		// foreground but we are not attached to a terminal.
    81  		foreground = false
    82  	}
    83  
    84  	dbp := newProcess(0)
    85  	defer func() {
    86  		if err != nil && dbp.pid != 0 {
    87  			_ = detachWithoutGroup(dbp, true)
    88  		}
    89  	}()
    90  	dbp.execPtraceFunc(func() {
    91  		if flags&proc.LaunchDisableASLR != 0 {
    92  			oldPersonality, _, err := syscall.Syscall(sys.SYS_PERSONALITY, personalityGetPersonality, 0, 0)
    93  			if err == syscall.Errno(0) {
    94  				newPersonality := oldPersonality | _ADDR_NO_RANDOMIZE
    95  				syscall.Syscall(sys.SYS_PERSONALITY, newPersonality, 0, 0)
    96  				defer syscall.Syscall(sys.SYS_PERSONALITY, oldPersonality, 0, 0)
    97  			}
    98  		}
    99  
   100  		process = exec.Command(cmd[0])
   101  		process.Args = cmd
   102  		process.Stdin = stdin
   103  		process.Stdout = stdout
   104  		process.Stderr = stderr
   105  		process.SysProcAttr = &syscall.SysProcAttr{
   106  			Ptrace:     true,
   107  			Setpgid:    true,
   108  			Foreground: foreground,
   109  		}
   110  		if foreground {
   111  			signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
   112  		}
   113  		if tty != "" {
   114  			dbp.ctty, err = attachProcessToTTY(process, tty)
   115  			if err != nil {
   116  				return
   117  			}
   118  		}
   119  		if wd != "" {
   120  			process.Dir = wd
   121  		}
   122  		err = process.Start()
   123  	})
   124  	closefn()
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	dbp.pid = process.Process.Pid
   129  	dbp.childProcess = true
   130  	_, _, err = dbp.wait(process.Process.Pid, 0)
   131  	if err != nil {
   132  		return nil, fmt.Errorf("waiting for target execve failed: %s", err)
   133  	}
   134  	tgt, err := dbp.initialize(cmd[0], debugInfoDirs)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	return tgt, nil
   139  }
   140  
   141  // Attach to an existing process with the given PID. Once attached, if
   142  // the DWARF information cannot be found in the binary, Delve will look
   143  // for external debug files in the directories passed in.
   144  func Attach(pid int, waitFor *proc.WaitFor, debugInfoDirs []string) (*proc.TargetGroup, error) {
   145  	if waitFor.Valid() {
   146  		var err error
   147  		pid, err = WaitFor(waitFor)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  	}
   152  
   153  	dbp := newProcess(pid)
   154  
   155  	var err error
   156  	dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) })
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	_, _, err = dbp.wait(dbp.pid, 0)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
   166  	if err != nil {
   167  		_ = detachWithoutGroup(dbp, false)
   168  		return nil, err
   169  	}
   170  
   171  	// ElfUpdateSharedObjects can only be done after we initialize because it
   172  	// needs an initialized BinaryInfo object to work.
   173  	err = linutil.ElfUpdateSharedObjects(dbp)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	return tgt, nil
   178  }
   179  
   180  func isProcDir(name string) bool {
   181  	for _, ch := range name {
   182  		if ch < '0' || ch > '9' {
   183  			return false
   184  		}
   185  	}
   186  	return true
   187  }
   188  
   189  func waitForSearchProcess(pfx string, seen map[int]struct{}) (int, error) {
   190  	log := logflags.DebuggerLogger()
   191  	des, err := os.ReadDir("/proc")
   192  	if err != nil {
   193  		log.Errorf("error reading proc: %v", err)
   194  		return 0, nil
   195  	}
   196  	for _, de := range des {
   197  		if !de.IsDir() {
   198  			continue
   199  		}
   200  		name := de.Name()
   201  		if !isProcDir(name) {
   202  			continue
   203  		}
   204  		pid, _ := strconv.Atoi(name)
   205  		if _, isseen := seen[pid]; isseen {
   206  			continue
   207  		}
   208  		seen[pid] = struct{}{}
   209  		buf, err := os.ReadFile(filepath.Join("/proc", name, "cmdline"))
   210  		if err != nil {
   211  			// probably we just don't have permissions
   212  			continue
   213  		}
   214  		for i := range buf {
   215  			if buf[i] == 0 {
   216  				buf[i] = ' '
   217  			}
   218  		}
   219  		log.Debugf("waitfor: new process %q", string(buf))
   220  		if strings.HasPrefix(string(buf), pfx) {
   221  			return pid, nil
   222  		}
   223  	}
   224  	return 0, nil
   225  }
   226  
   227  func initialize(dbp *nativeProcess) (string, error) {
   228  	comm, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
   229  	if err == nil {
   230  		// removes newline character
   231  		comm = bytes.TrimSuffix(comm, []byte("\n"))
   232  	}
   233  
   234  	if comm == nil || len(comm) <= 0 {
   235  		stat, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
   236  		if err != nil {
   237  			return "", fmt.Errorf("could not read proc stat: %v", err)
   238  		}
   239  		expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
   240  		rexp, err := regexp.Compile(expr)
   241  		if err != nil {
   242  			return "", fmt.Errorf("regexp compile error: %v", err)
   243  		}
   244  		match := rexp.FindSubmatch(stat)
   245  		if match == nil {
   246  			return "", fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
   247  		}
   248  		comm = match[1]
   249  	}
   250  	dbp.os.comm = strings.ReplaceAll(string(comm), "%", "%%")
   251  
   252  	return getCmdLine(dbp.pid), nil
   253  }
   254  
   255  func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
   256  	if dbp.os.ebpf == nil {
   257  		return nil
   258  	}
   259  	return dbp.os.ebpf.GetBufferedTracepoints()
   260  }
   261  
   262  // kill kills the target process.
   263  func (procgrp *processGroup) kill(dbp *nativeProcess) error {
   264  	if ok, _ := dbp.Valid(); !ok {
   265  		return nil
   266  	}
   267  	if !dbp.threads[dbp.pid].Stopped() {
   268  		return errors.New("process must be stopped in order to kill it")
   269  	}
   270  	if err := sys.Kill(-dbp.pid, sys.SIGKILL); err != nil {
   271  		return errors.New("could not deliver signal " + err.Error())
   272  	}
   273  	// wait for other threads first or the thread group leader (dbp.pid) will never exit.
   274  	for threadID := range dbp.threads {
   275  		if threadID != dbp.pid {
   276  			dbp.wait(threadID, 0)
   277  		}
   278  	}
   279  	for {
   280  		wpid, status, err := dbp.wait(dbp.pid, 0)
   281  		if err != nil {
   282  			return err
   283  		}
   284  		if wpid == dbp.pid && status != nil && status.Signaled() && status.Signal() == sys.SIGKILL {
   285  			dbp.postExit()
   286  			return err
   287  		}
   288  	}
   289  }
   290  
   291  func (dbp *nativeProcess) requestManualStop() (err error) {
   292  	return sys.Kill(dbp.pid, sys.SIGTRAP)
   293  }
   294  
   295  const (
   296  	ptraceOptionsNormal     = syscall.PTRACE_O_TRACECLONE
   297  	ptraceOptionsFollowExec = syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEVFORK | syscall.PTRACE_O_TRACEEXEC
   298  )
   299  
   300  // Attach to a newly created thread, and store that thread in our list of
   301  // known threads.
   302  func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
   303  	if thread, ok := dbp.threads[tid]; ok {
   304  		return thread, nil
   305  	}
   306  
   307  	ptraceOptions := ptraceOptionsNormal
   308  	if dbp.followExec {
   309  		ptraceOptions = ptraceOptionsFollowExec
   310  	}
   311  
   312  	var err error
   313  	if attach {
   314  		dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
   315  		if err != nil && err != sys.EPERM {
   316  			// Do not return err if err == EPERM,
   317  			// we may already be tracing this thread due to
   318  			// PTRACE_O_TRACECLONE. We will surely blow up later
   319  			// if we truly don't have permissions.
   320  			return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
   321  		}
   322  		pid, status, err := dbp.waitFast(tid)
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  		if status.Exited() {
   327  			return nil, fmt.Errorf("thread already exited %d", pid)
   328  		}
   329  	}
   330  
   331  	dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })
   332  	if err == syscall.ESRCH {
   333  		if _, _, err = dbp.waitFast(tid); err != nil {
   334  			return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
   335  		}
   336  		dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) })
   337  		if err == syscall.ESRCH {
   338  			return nil, err
   339  		}
   340  		if err != nil {
   341  			return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
   342  		}
   343  	}
   344  
   345  	dbp.threads[tid] = &nativeThread{
   346  		ID:  tid,
   347  		dbp: dbp,
   348  		os:  new(osSpecificDetails),
   349  	}
   350  	if dbp.memthread == nil {
   351  		dbp.memthread = dbp.threads[tid]
   352  	}
   353  	for _, bp := range dbp.Breakpoints().M {
   354  		if bp.WatchType != 0 {
   355  			err := dbp.threads[tid].writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   356  			if err != nil {
   357  				return nil, err
   358  			}
   359  		}
   360  	}
   361  	return dbp.threads[tid], nil
   362  }
   363  
   364  func (dbp *nativeProcess) updateThreadList() error {
   365  	tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.pid))
   366  	for _, tidpath := range tids {
   367  		tidstr := filepath.Base(tidpath)
   368  		tid, err := strconv.Atoi(tidstr)
   369  		if err != nil {
   370  			return err
   371  		}
   372  		if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {
   373  			return err
   374  		}
   375  	}
   376  	return linutil.ElfUpdateSharedObjects(dbp)
   377  }
   378  
   379  func findExecutable(path string, pid int) string {
   380  	if path == "" {
   381  		path = fmt.Sprintf("/proc/%d/exe", pid)
   382  	}
   383  	return path
   384  }
   385  
   386  func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
   387  	return trapWaitInternal(procgrp, pid, 0)
   388  }
   389  
   390  type trapWaitOptions uint8
   391  
   392  const (
   393  	trapWaitHalt trapWaitOptions = 1 << iota
   394  	trapWaitNohang
   395  	trapWaitDontCallExitGuard
   396  )
   397  
   398  func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (*nativeThread, error) {
   399  	var waitdbp *nativeProcess = nil
   400  	if len(procgrp.procs) == 1 {
   401  		// Note that waitdbp is only used to call (*nativeProcess).wait which will
   402  		// behave correctly if waitdbp == nil.
   403  		waitdbp = procgrp.procs[0]
   404  	}
   405  
   406  	halt := options&trapWaitHalt != 0
   407  	for {
   408  		wopt := 0
   409  		if options&trapWaitNohang != 0 {
   410  			wopt = sys.WNOHANG
   411  		}
   412  		wpid, status, err := waitdbp.wait(pid, wopt)
   413  		if err != nil {
   414  			return nil, fmt.Errorf("wait err %s %d", err, pid)
   415  		}
   416  		if wpid == 0 {
   417  			if options&trapWaitNohang != 0 {
   418  				return nil, nil
   419  			}
   420  			continue
   421  		}
   422  		dbp := procgrp.procForThread(wpid)
   423  		var th *nativeThread
   424  		if dbp != nil {
   425  			var ok bool
   426  			th, ok = dbp.threads[wpid]
   427  			if ok {
   428  				th.Status = (*waitStatus)(status)
   429  			}
   430  		} else {
   431  			dbp = procgrp.procs[0]
   432  		}
   433  		if status.Exited() {
   434  			if wpid == dbp.pid {
   435  				dbp.postExit()
   436  				if procgrp.numValid() == 0 {
   437  					return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
   438  				}
   439  				if halt {
   440  					return nil, nil
   441  				}
   442  				continue
   443  			}
   444  			delete(dbp.threads, wpid)
   445  			continue
   446  		}
   447  		if status.Signaled() {
   448  			// Signaled means the thread was terminated due to a signal.
   449  			if wpid == dbp.pid {
   450  				dbp.postExit()
   451  				if procgrp.numValid() == 0 {
   452  					return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
   453  				}
   454  				if halt {
   455  					return nil, nil
   456  				}
   457  				continue
   458  			}
   459  			// does this ever happen?
   460  			delete(dbp.threads, wpid)
   461  			continue
   462  		}
   463  		if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_CLONE || status.TrapCause() == sys.PTRACE_EVENT_VFORK) {
   464  			// A traced thread has cloned a new thread, grab the pid and
   465  			// add it to our list of traced threads.
   466  			// If TrapCause() is sys.PTRACE_EVENT_VFORK this is actually a new
   467  			// process, but treat it as a normal thread until exec happens, so that
   468  			// we can initialize the new process normally.
   469  			var cloned uint
   470  			dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) })
   471  			if err != nil {
   472  				if err == sys.ESRCH {
   473  					// thread died while we were adding it
   474  					continue
   475  				}
   476  				return nil, fmt.Errorf("could not get event message: %s", err)
   477  			}
   478  			th, err = dbp.addThread(int(cloned), false)
   479  			if err != nil {
   480  				if err == sys.ESRCH {
   481  					// thread died while we were adding it
   482  					delete(dbp.threads, int(cloned))
   483  					continue
   484  				}
   485  				return nil, err
   486  			}
   487  			if halt {
   488  				th.os.running = false
   489  				dbp.threads[int(wpid)].os.running = false
   490  				return nil, nil
   491  			}
   492  			if err = th.resume(); err != nil {
   493  				if err == sys.ESRCH {
   494  					// thread died while we were adding it
   495  					delete(dbp.threads, th.ID)
   496  					continue
   497  				}
   498  				return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
   499  			}
   500  			if err = dbp.threads[int(wpid)].resume(); err != nil {
   501  				if err != sys.ESRCH {
   502  					return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err)
   503  				}
   504  			}
   505  			continue
   506  		}
   507  		if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_EXEC) {
   508  			// A thread called exec and we now have a new process. Retrieve the
   509  			// thread ID of the exec'ing thread with PtraceGetEventMsg to remove it
   510  			// and create a new nativeProcess object to track the new process.
   511  			var tid uint
   512  			dbp.execPtraceFunc(func() { tid, err = sys.PtraceGetEventMsg(wpid) })
   513  			if err == nil {
   514  				delete(dbp.threads, int(tid))
   515  			}
   516  			dbp = newChildProcess(procgrp.procs[0], wpid)
   517  			dbp.followExec = true
   518  			cmdline, _ := dbp.initializeBasic()
   519  			tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched, cmdline)
   520  			if err != nil {
   521  				return nil, err
   522  			}
   523  			if halt {
   524  				return nil, nil
   525  			}
   526  			if tgt != nil {
   527  				// If tgt is nil we decided we are not interested in debugging this
   528  				// process, and we have already detached from it.
   529  				err = dbp.threads[dbp.pid].resume()
   530  				if err != nil {
   531  					return nil, err
   532  				}
   533  			}
   534  			//TODO(aarzilli): if we want to give users the ability to stop the target
   535  			//group on exec here is where we should return
   536  			continue
   537  		}
   538  		if th == nil {
   539  			// Sometimes we get an unknown thread, ignore it?
   540  			continue
   541  		}
   542  		if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) {
   543  			th.os.running = false
   544  			if status.StopSignal() == sys.SIGTRAP {
   545  				th.os.setbp = true
   546  			}
   547  			return th, nil
   548  		}
   549  
   550  		// TODO(dp) alert user about unexpected signals here.
   551  		if halt && !th.os.running {
   552  			// We are trying to stop the process, queue this signal to be delivered
   553  			// to the thread when we resume.
   554  			// Do not do this for threads that were running because we sent them a
   555  			// STOP signal and we need to observe it so we don't mistakenly deliver
   556  			// it later.
   557  			th.os.delayedSignal = int(status.StopSignal())
   558  			th.os.running = false
   559  			return th, nil
   560  		} else if err := th.resumeWithSig(int(status.StopSignal())); err != nil {
   561  			if err != sys.ESRCH {
   562  				return nil, err
   563  			}
   564  			// do the same thing we do if a thread quit
   565  			if wpid == dbp.pid {
   566  				exitStatus := 0
   567  				if procgrp.numValid() == 1 {
   568  					// try to recover the real exit status using waitpid
   569  					for {
   570  						wpid2, status2, err := dbp.wait(-1, sys.WNOHANG)
   571  						if wpid2 <= 0 || err != nil {
   572  							break
   573  						}
   574  						if status2.Exited() {
   575  							exitStatus = status2.ExitStatus()
   576  						}
   577  					}
   578  
   579  				}
   580  				dbp.postExit()
   581  				if procgrp.numValid() == 0 {
   582  					return nil, proc.ErrProcessExited{Pid: wpid, Status: exitStatus}
   583  				}
   584  				continue
   585  			}
   586  			delete(dbp.threads, wpid)
   587  		}
   588  	}
   589  }
   590  
   591  func status(pid int, comm string) rune {
   592  	f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
   593  	if err != nil {
   594  		return '\000'
   595  	}
   596  	defer f.Close()
   597  	rd := bufio.NewReader(f)
   598  
   599  	var (
   600  		p     int
   601  		state rune
   602  	)
   603  
   604  	// The second field of /proc/pid/stat is the name of the task in parentheses.
   605  	// The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters
   606  	// Since both parenthesis and spaces can appear inside the name of the task and no escaping happens we need to read the name of the executable first
   607  	// See: include/linux/sched.c:315 and include/linux/sched.c:1510
   608  	_, _ = fmt.Fscanf(rd, "%d ("+comm+")  %c", &p, &state)
   609  	return state
   610  }
   611  
   612  // waitFast is like wait but does not handle process-exit correctly
   613  func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) {
   614  	var s sys.WaitStatus
   615  	wpid, err := sys.Wait4(pid, &s, sys.WALL, nil)
   616  	return wpid, &s, err
   617  }
   618  
   619  func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
   620  	var s sys.WaitStatus
   621  	if (dbp == nil) || (pid != dbp.pid) || (options != 0) {
   622  		wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
   623  		return wpid, &s, err
   624  	}
   625  	// If we call wait4/waitpid on a thread that is the leader of its group,
   626  	// with options == 0, while ptracing and the thread leader has exited leaving
   627  	// zombies of its own then waitpid hangs forever this is apparently intended
   628  	// behaviour in the linux kernel because it's just so convenient.
   629  	// Therefore we call wait4 in a loop with WNOHANG, sleeping a while between
   630  	// calls and exiting when either wait4 succeeds or we find out that the thread
   631  	// has become a zombie.
   632  	// References:
   633  	// https://sourceware.org/bugzilla/show_bug.cgi?id=12702
   634  	// https://sourceware.org/bugzilla/show_bug.cgi?id=10095
   635  	// https://sourceware.org/bugzilla/attachment.cgi?id=5685
   636  	for {
   637  		wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil)
   638  		if err != nil {
   639  			return 0, nil, err
   640  		}
   641  		if wpid != 0 {
   642  			return wpid, &s, err
   643  		}
   644  		if status(pid, dbp.os.comm) == statusZombie {
   645  			return pid, nil, nil
   646  		}
   647  		time.Sleep(200 * time.Millisecond)
   648  	}
   649  }
   650  
   651  func exitGuard(dbp *nativeProcess, procgrp *processGroup, err error) error {
   652  	if err != sys.ESRCH {
   653  		return err
   654  	}
   655  	if status(dbp.pid, dbp.os.comm) == statusZombie {
   656  		_, err := trapWaitInternal(procgrp, -1, trapWaitDontCallExitGuard)
   657  		return err
   658  	}
   659  
   660  	return err
   661  }
   662  
   663  func (procgrp *processGroup) resume() error {
   664  	// all threads stopped over a breakpoint are made to step over it
   665  	for _, dbp := range procgrp.procs {
   666  		if valid, _ := dbp.Valid(); valid {
   667  			for _, thread := range dbp.threads {
   668  				if thread.CurrentBreakpoint.Breakpoint != nil {
   669  					if err := procgrp.stepInstruction(thread); err != nil {
   670  						return err
   671  					}
   672  					thread.CurrentBreakpoint.Clear()
   673  				}
   674  			}
   675  		}
   676  	}
   677  	// everything is resumed
   678  	for _, dbp := range procgrp.procs {
   679  		if valid, _ := dbp.Valid(); valid {
   680  			for _, thread := range dbp.threads {
   681  				if err := thread.resume(); err != nil && err != sys.ESRCH {
   682  					return err
   683  				}
   684  			}
   685  		}
   686  	}
   687  	return nil
   688  }
   689  
   690  // stop stops all running threads and sets breakpoints
   691  func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
   692  	if procgrp.numValid() == 0 {
   693  		return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid}
   694  	}
   695  
   696  	for _, dbp := range procgrp.procs {
   697  		if ok, _ := dbp.Valid(); !ok {
   698  			continue
   699  		}
   700  		for _, th := range dbp.threads {
   701  			th.os.setbp = false
   702  		}
   703  	}
   704  	trapthread.os.setbp = true
   705  
   706  	// check if any other thread simultaneously received a SIGTRAP
   707  	for {
   708  		th, err := trapWaitInternal(procgrp, -1, trapWaitNohang)
   709  		if err != nil {
   710  			return nil, exitGuard(procgrp.procs[0], procgrp, err)
   711  		}
   712  		if th == nil {
   713  			break
   714  		}
   715  	}
   716  
   717  	// stop all threads that are still running
   718  	for _, dbp := range procgrp.procs {
   719  		if ok, _ := dbp.Valid(); !ok {
   720  			continue
   721  		}
   722  		for _, th := range dbp.threads {
   723  			if th.os.running {
   724  				if err := th.stop(); err != nil {
   725  					if err == sys.ESRCH {
   726  						// thread exited
   727  						delete(dbp.threads, th.ID)
   728  					} else {
   729  						return nil, exitGuard(dbp, procgrp, err)
   730  					}
   731  				}
   732  			}
   733  		}
   734  	}
   735  
   736  	// wait for all threads to stop
   737  	for {
   738  		allstopped := true
   739  		for _, dbp := range procgrp.procs {
   740  			if ok, _ := dbp.Valid(); !ok {
   741  				continue
   742  			}
   743  			for _, th := range dbp.threads {
   744  				if th.os.running {
   745  					allstopped = false
   746  					break
   747  				}
   748  			}
   749  		}
   750  		if allstopped {
   751  			break
   752  		}
   753  		_, err := trapWaitInternal(procgrp, -1, trapWaitHalt)
   754  		if err != nil {
   755  			return nil, err
   756  		}
   757  	}
   758  
   759  	switchTrapthread := false
   760  
   761  	for _, dbp := range procgrp.procs {
   762  		if ok, _ := dbp.Valid(); !ok {
   763  			continue
   764  		}
   765  		err := stop1(cctx, dbp, trapthread, &switchTrapthread)
   766  		if err != nil {
   767  			return nil, err
   768  		}
   769  	}
   770  
   771  	if switchTrapthread {
   772  		trapthreadID := trapthread.ID
   773  		trapthread = nil
   774  		for _, dbp := range procgrp.procs {
   775  			if ok, _ := dbp.Valid(); !ok {
   776  				continue
   777  			}
   778  			for _, th := range dbp.threads {
   779  				if th.os.setbp && th.ThreadID() != trapthreadID {
   780  					return th, nil
   781  				}
   782  			}
   783  		}
   784  	}
   785  
   786  	return trapthread, nil
   787  }
   788  
   789  func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativeThread, switchTrapthread *bool) error {
   790  	if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
   791  		return err
   792  	}
   793  
   794  	// set breakpoints on SIGTRAP threads
   795  	var err1 error
   796  	for _, th := range dbp.threads {
   797  		pc, _ := th.PC()
   798  
   799  		if !th.os.setbp && pc != th.os.phantomBreakpointPC {
   800  			// check if this could be a breakpoint hit anyway that the OS hasn't notified us about, yet.
   801  			if _, ok := dbp.FindBreakpoint(pc, dbp.BinInfo().Arch.BreakInstrMovesPC()); ok {
   802  				th.os.phantomBreakpointPC = pc
   803  			}
   804  		}
   805  
   806  		if pc != th.os.phantomBreakpointPC {
   807  			th.os.phantomBreakpointPC = 0
   808  		}
   809  
   810  		if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp {
   811  			if err := th.SetCurrentBreakpoint(true); err != nil {
   812  				err1 = err
   813  				continue
   814  			}
   815  		}
   816  
   817  		if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp && (th.Status != nil) && ((*sys.WaitStatus)(th.Status).StopSignal() == sys.SIGTRAP) && dbp.BinInfo().Arch.BreakInstrMovesPC() {
   818  			manualStop := false
   819  			if th.ThreadID() == trapthread.ThreadID() {
   820  				manualStop = cctx.GetManualStopRequested()
   821  			}
   822  			if !manualStop && th.os.phantomBreakpointPC == pc {
   823  				// Thread received a SIGTRAP but we don't have a breakpoint for it and
   824  				// it wasn't sent by a manual stop request. It's either a hardcoded
   825  				// breakpoint or a phantom breakpoint hit (a breakpoint that was hit but
   826  				// we have removed before we could receive its signal). Check if it is a
   827  				// hardcoded breakpoint, otherwise rewind the thread.
   828  				isHardcodedBreakpoint := false
   829  				pc, _ := th.PC()
   830  				for _, bpinstr := range [][]byte{
   831  					dbp.BinInfo().Arch.BreakpointInstruction(),
   832  					dbp.BinInfo().Arch.AltBreakpointInstruction()} {
   833  					if bpinstr == nil {
   834  						continue
   835  					}
   836  					buf := make([]byte, len(bpinstr))
   837  					_, _ = th.ReadMemory(buf, pc-uint64(len(buf)))
   838  					if bytes.Equal(buf, bpinstr) {
   839  						isHardcodedBreakpoint = true
   840  						break
   841  					}
   842  				}
   843  				if !isHardcodedBreakpoint {
   844  					// phantom breakpoint hit
   845  					_ = th.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
   846  					th.os.setbp = false
   847  					if trapthread.ThreadID() == th.ThreadID() {
   848  						// Will switch to a different thread for trapthread because we don't
   849  						// want pkg/proc to believe that this thread was stopped by a
   850  						// hardcoded breakpoint.
   851  						*switchTrapthread = true
   852  					}
   853  				}
   854  			}
   855  		}
   856  	}
   857  	return err1
   858  }
   859  
   860  func (procgrp *processGroup) detachChild(dbp *nativeProcess) error {
   861  	return procgrp.Detach(dbp.pid, false)
   862  }
   863  
   864  func (dbp *nativeProcess) detach(kill bool) error {
   865  	for threadID := range dbp.threads {
   866  		err := ptraceDetach(threadID, 0)
   867  		if err != nil {
   868  			return err
   869  		}
   870  	}
   871  	if kill {
   872  		return nil
   873  	}
   874  	// For some reason the process will sometimes enter stopped state after a
   875  	// detach, this doesn't happen immediately either.
   876  	// We have to wait a bit here, then check if the main thread is stopped and
   877  	// SIGCONT it if it is.
   878  	time.Sleep(50 * time.Millisecond)
   879  	if s := status(dbp.pid, dbp.os.comm); s == 'T' {
   880  		_ = sys.Kill(dbp.pid, sys.SIGCONT)
   881  	}
   882  	return nil
   883  }
   884  
   885  // EntryPoint will return the process entry point address, useful for
   886  // debugging PIEs.
   887  func (dbp *nativeProcess) EntryPoint() (uint64, error) {
   888  	auxvbuf, err := os.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
   889  	if err != nil {
   890  		return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
   891  	}
   892  
   893  	return linutil.EntryPointFromAuxv(auxvbuf, dbp.bi.Arch.PtrSize()), nil
   894  }
   895  
   896  func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error {
   897  	// Lazily load and initialize the BPF program upon request to set a uprobe.
   898  	if dbp.os.ebpf == nil {
   899  		var err error
   900  		dbp.os.ebpf, err = ebpf.LoadEBPFTracingProgram(dbp.bi.Images[0].Path)
   901  		if err != nil {
   902  			return err
   903  		}
   904  	}
   905  
   906  	// We only allow up to 12 args for a BPF probe.
   907  	// 6 inputs + 6 outputs.
   908  	// Return early if we have more.
   909  	if len(args) > 12 {
   910  		return errors.New("too many arguments in traced function, max is 12 input+return")
   911  	}
   912  
   913  	fns := dbp.bi.LookupFunc()[fnName]
   914  	if len(fns) != 1 {
   915  		return &proc.ErrFunctionNotFound{FuncName: fnName}
   916  	}
   917  	fn := fns[0]
   918  
   919  	offset, err := dbp.BinInfo().GStructOffset(dbp.Memory())
   920  	if err != nil {
   921  		return err
   922  	}
   923  	key := fn.Entry
   924  	err = dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, offset, false)
   925  	if err != nil {
   926  		return err
   927  	}
   928  
   929  	debugname := dbp.bi.Images[0].Path
   930  
   931  	// First attach a uprobe at all return addresses. We do this instead of using a uretprobe
   932  	// for two reasons:
   933  	// 1. uretprobes do not play well with Go
   934  	// 2. uretprobes seem to not restore the function return addr on the stack when removed, destroying any
   935  	//    kind of workaround we could come up with.
   936  	// TODO(derekparker): this whole thing could likely be optimized a bit.
   937  	img := dbp.BinInfo().PCToImage(fn.Entry)
   938  	f, err := elf.Open(img.Path)
   939  	if err != nil {
   940  		return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err)
   941  	}
   942  
   943  	var regs proc.Registers
   944  	mem := dbp.Memory()
   945  	regs, _ = dbp.memthread.Registers()
   946  	instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End)
   947  	if err != nil {
   948  		return err
   949  	}
   950  
   951  	var addrs []uint64
   952  	for _, instruction := range instructions {
   953  		if instruction.IsRet() {
   954  			addrs = append(addrs, instruction.Loc.PC)
   955  		}
   956  	}
   957  	addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
   958  	for _, addr := range addrs {
   959  		err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, offset, true)
   960  		if err != nil {
   961  			return err
   962  		}
   963  		off, err := ebpf.AddressToOffset(f, addr)
   964  		if err != nil {
   965  			return err
   966  		}
   967  		err = dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
   968  		if err != nil {
   969  			return err
   970  		}
   971  	}
   972  
   973  	off, err := ebpf.AddressToOffset(f, fn.Entry)
   974  	if err != nil {
   975  		return err
   976  	}
   977  
   978  	return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
   979  }
   980  
   981  // FollowExec enables (or disables) follow exec mode
   982  func (dbp *nativeProcess) FollowExec(v bool) error {
   983  	dbp.followExec = v
   984  	ptraceOptions := ptraceOptionsNormal
   985  	if dbp.followExec {
   986  		ptraceOptions = ptraceOptionsFollowExec
   987  	}
   988  	var err error
   989  	dbp.execPtraceFunc(func() {
   990  		for tid := range dbp.threads {
   991  			err = syscall.PtraceSetOptions(tid, ptraceOptions)
   992  			if err != nil {
   993  				return
   994  			}
   995  		}
   996  	})
   997  	return err
   998  }
   999  
  1000  func killProcess(pid int) error {
  1001  	return sys.Kill(pid, sys.SIGINT)
  1002  }
  1003  
  1004  func getCmdLine(pid int) string {
  1005  	buf, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid))
  1006  	args := strings.SplitN(string(buf), "\x00", -1)
  1007  	for i := range args {
  1008  		if strings.Contains(args[i], " ") {
  1009  			args[i] = strconv.Quote(args[i])
  1010  		}
  1011  	}
  1012  	if len(args) > 0 && args[len(args)-1] == "" {
  1013  		args = args[:len(args)-1]
  1014  	}
  1015  	return strings.Join(args, " ")
  1016  }