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