github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/proc"
    23  	"github.com/undoio/delve/pkg/proc/internal/ebpf"
    24  	"github.com/undoio/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, redirects [3]string) (*proc.Target, 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(redirects, 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  			_ = dbp.Detach(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, debugInfoDirs []string) (*proc.Target, error) {
   145  	dbp := newProcess(pid)
   146  
   147  	var err error
   148  	dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) })
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	_, _, err = dbp.wait(dbp.pid, 0)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs)
   158  	if err != nil {
   159  		_ = dbp.Detach(false)
   160  		return nil, err
   161  	}
   162  
   163  	// ElfUpdateSharedObjects can only be done after we initialize because it
   164  	// needs an initialized BinaryInfo object to work.
   165  	err = linutil.ElfUpdateSharedObjects(dbp)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	return tgt, nil
   170  }
   171  
   172  func initialize(dbp *nativeProcess) error {
   173  	comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid))
   174  	if err == nil {
   175  		// removes newline character
   176  		comm = bytes.TrimSuffix(comm, []byte("\n"))
   177  	}
   178  
   179  	if comm == nil || len(comm) <= 0 {
   180  		stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid))
   181  		if err != nil {
   182  			return fmt.Errorf("could not read proc stat: %v", err)
   183  		}
   184  		expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid)
   185  		rexp, err := regexp.Compile(expr)
   186  		if err != nil {
   187  			return fmt.Errorf("regexp compile error: %v", err)
   188  		}
   189  		match := rexp.FindSubmatch(stat)
   190  		if match == nil {
   191  			return fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid)
   192  		}
   193  		comm = match[1]
   194  	}
   195  	dbp.os.comm = strings.ReplaceAll(string(comm), "%", "%%")
   196  
   197  	return nil
   198  }
   199  
   200  func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams {
   201  	if dbp.os.ebpf == nil {
   202  		return nil
   203  	}
   204  	return dbp.os.ebpf.GetBufferedTracepoints()
   205  }
   206  
   207  // kill kills the target process.
   208  func (dbp *nativeProcess) kill() error {
   209  	if dbp.exited {
   210  		return nil
   211  	}
   212  	if !dbp.threads[dbp.pid].Stopped() {
   213  		return errors.New("process must be stopped in order to kill it")
   214  	}
   215  	if err := sys.Kill(-dbp.pid, sys.SIGKILL); err != nil {
   216  		return errors.New("could not deliver signal " + err.Error())
   217  	}
   218  	// wait for other threads first or the thread group leader (dbp.pid) will never exit.
   219  	for threadID := range dbp.threads {
   220  		if threadID != dbp.pid {
   221  			dbp.wait(threadID, 0)
   222  		}
   223  	}
   224  	for {
   225  		wpid, status, err := dbp.wait(dbp.pid, 0)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		if wpid == dbp.pid && status != nil && status.Signaled() && status.Signal() == sys.SIGKILL {
   230  			dbp.postExit()
   231  			return err
   232  		}
   233  	}
   234  }
   235  
   236  func (dbp *nativeProcess) requestManualStop() (err error) {
   237  	return sys.Kill(dbp.pid, sys.SIGTRAP)
   238  }
   239  
   240  // Attach to a newly created thread, and store that thread in our list of
   241  // known threads.
   242  func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) {
   243  	if thread, ok := dbp.threads[tid]; ok {
   244  		return thread, nil
   245  	}
   246  
   247  	var err error
   248  	if attach {
   249  		dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) })
   250  		if err != nil && err != sys.EPERM {
   251  			// Do not return err if err == EPERM,
   252  			// we may already be tracing this thread due to
   253  			// PTRACE_O_TRACECLONE. We will surely blow up later
   254  			// if we truly don't have permissions.
   255  			return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err)
   256  		}
   257  		pid, status, err := dbp.waitFast(tid)
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  		if status.Exited() {
   262  			return nil, fmt.Errorf("thread already exited %d", pid)
   263  		}
   264  	}
   265  
   266  	dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
   267  	if err == syscall.ESRCH {
   268  		if _, _, err = dbp.waitFast(tid); err != nil {
   269  			return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err)
   270  		}
   271  		dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) })
   272  		if err == syscall.ESRCH {
   273  			return nil, err
   274  		}
   275  		if err != nil {
   276  			return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err)
   277  		}
   278  	}
   279  
   280  	dbp.threads[tid] = &nativeThread{
   281  		ID:  tid,
   282  		dbp: dbp,
   283  		os:  new(osSpecificDetails),
   284  	}
   285  	if dbp.memthread == nil {
   286  		dbp.memthread = dbp.threads[tid]
   287  	}
   288  	for _, bp := range dbp.Breakpoints().M {
   289  		if bp.WatchType != 0 {
   290  			err := dbp.threads[tid].writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   291  			if err != nil {
   292  				return nil, err
   293  			}
   294  		}
   295  	}
   296  	return dbp.threads[tid], nil
   297  }
   298  
   299  func (dbp *nativeProcess) updateThreadList() error {
   300  	tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.pid))
   301  	for _, tidpath := range tids {
   302  		tidstr := filepath.Base(tidpath)
   303  		tid, err := strconv.Atoi(tidstr)
   304  		if err != nil {
   305  			return err
   306  		}
   307  		if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil {
   308  			return err
   309  		}
   310  	}
   311  	return linutil.ElfUpdateSharedObjects(dbp)
   312  }
   313  
   314  func findExecutable(path string, pid int) string {
   315  	if path == "" {
   316  		path = fmt.Sprintf("/proc/%d/exe", pid)
   317  	}
   318  	return path
   319  }
   320  
   321  func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) {
   322  	return dbp.trapWaitInternal(pid, 0)
   323  }
   324  
   325  type trapWaitOptions uint8
   326  
   327  const (
   328  	trapWaitHalt trapWaitOptions = 1 << iota
   329  	trapWaitNohang
   330  	trapWaitDontCallExitGuard
   331  )
   332  
   333  func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*nativeThread, error) {
   334  	halt := options&trapWaitHalt != 0
   335  	for {
   336  		wopt := 0
   337  		if options&trapWaitNohang != 0 {
   338  			wopt = sys.WNOHANG
   339  		}
   340  		wpid, status, err := dbp.wait(pid, wopt)
   341  		if err != nil {
   342  			return nil, fmt.Errorf("wait err %s %d", err, pid)
   343  		}
   344  		if wpid == 0 {
   345  			if options&trapWaitNohang != 0 {
   346  				return nil, nil
   347  			}
   348  			continue
   349  		}
   350  		th, ok := dbp.threads[wpid]
   351  		if ok {
   352  			th.Status = (*waitStatus)(status)
   353  		}
   354  		if status.Exited() {
   355  			if wpid == dbp.pid {
   356  				dbp.postExit()
   357  				return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
   358  			}
   359  			delete(dbp.threads, wpid)
   360  			continue
   361  		}
   362  		if status.Signaled() {
   363  			// Signaled means the thread was terminated due to a signal.
   364  			if wpid == dbp.pid {
   365  				dbp.postExit()
   366  				return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())}
   367  			}
   368  			// does this ever happen?
   369  			delete(dbp.threads, wpid)
   370  			continue
   371  		}
   372  		if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
   373  			// A traced thread has cloned a new thread, grab the pid and
   374  			// add it to our list of traced threads.
   375  			var cloned uint
   376  			dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) })
   377  			if err != nil {
   378  				if err == sys.ESRCH {
   379  					// thread died while we were adding it
   380  					continue
   381  				}
   382  				return nil, fmt.Errorf("could not get event message: %s", err)
   383  			}
   384  			th, err = dbp.addThread(int(cloned), false)
   385  			if err != nil {
   386  				if err == sys.ESRCH {
   387  					// thread died while we were adding it
   388  					delete(dbp.threads, int(cloned))
   389  					continue
   390  				}
   391  				return nil, err
   392  			}
   393  			if halt {
   394  				th.os.running = false
   395  				dbp.threads[int(wpid)].os.running = false
   396  				return nil, nil
   397  			}
   398  			if err = th.Continue(); err != nil {
   399  				if err == sys.ESRCH {
   400  					// thread died while we were adding it
   401  					delete(dbp.threads, th.ID)
   402  					continue
   403  				}
   404  				return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err)
   405  			}
   406  			if err = dbp.threads[int(wpid)].Continue(); err != nil {
   407  				if err != sys.ESRCH {
   408  					return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err)
   409  				}
   410  			}
   411  			continue
   412  		}
   413  		if th == nil {
   414  			// Sometimes we get an unknown thread, ignore it?
   415  			continue
   416  		}
   417  		if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) {
   418  			th.os.running = false
   419  			if status.StopSignal() == sys.SIGTRAP {
   420  				th.os.setbp = true
   421  			}
   422  			return th, nil
   423  		}
   424  
   425  		// TODO(dp) alert user about unexpected signals here.
   426  		if halt && !th.os.running {
   427  			// We are trying to stop the process, queue this signal to be delivered
   428  			// to the thread when we resume.
   429  			// Do not do this for threads that were running because we sent them a
   430  			// STOP signal and we need to observe it so we don't mistakenly deliver
   431  			// it later.
   432  			th.os.delayedSignal = int(status.StopSignal())
   433  			th.os.running = false
   434  			return th, nil
   435  		} else if err := th.resumeWithSig(int(status.StopSignal())); err != nil {
   436  			if err != sys.ESRCH {
   437  				return nil, err
   438  			}
   439  			// do the same thing we do if a thread quit
   440  			if wpid == dbp.pid {
   441  				dbp.postExit()
   442  				return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()}
   443  			}
   444  			delete(dbp.threads, wpid)
   445  		}
   446  	}
   447  }
   448  
   449  func status(pid int, comm string) rune {
   450  	f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid))
   451  	if err != nil {
   452  		return '\000'
   453  	}
   454  	defer f.Close()
   455  	rd := bufio.NewReader(f)
   456  
   457  	var (
   458  		p     int
   459  		state rune
   460  	)
   461  
   462  	// The second field of /proc/pid/stat is the name of the task in parenthesis.
   463  	// The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters
   464  	// 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
   465  	// See: include/linux/sched.c:315 and include/linux/sched.c:1510
   466  	_, _ = fmt.Fscanf(rd, "%d ("+comm+")  %c", &p, &state)
   467  	return state
   468  }
   469  
   470  // waitFast is like wait but does not handle process-exit correctly
   471  func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) {
   472  	var s sys.WaitStatus
   473  	wpid, err := sys.Wait4(pid, &s, sys.WALL, nil)
   474  	return wpid, &s, err
   475  }
   476  
   477  func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) {
   478  	var s sys.WaitStatus
   479  	if (pid != dbp.pid) || (options != 0) {
   480  		wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil)
   481  		return wpid, &s, err
   482  	}
   483  	// If we call wait4/waitpid on a thread that is the leader of its group,
   484  	// with options == 0, while ptracing and the thread leader has exited leaving
   485  	// zombies of its own then waitpid hangs forever this is apparently intended
   486  	// behaviour in the linux kernel because it's just so convenient.
   487  	// Therefore we call wait4 in a loop with WNOHANG, sleeping a while between
   488  	// calls and exiting when either wait4 succeeds or we find out that the thread
   489  	// has become a zombie.
   490  	// References:
   491  	// https://sourceware.org/bugzilla/show_bug.cgi?id=12702
   492  	// https://sourceware.org/bugzilla/show_bug.cgi?id=10095
   493  	// https://sourceware.org/bugzilla/attachment.cgi?id=5685
   494  	for {
   495  		wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil)
   496  		if err != nil {
   497  			return 0, nil, err
   498  		}
   499  		if wpid != 0 {
   500  			return wpid, &s, err
   501  		}
   502  		if status(pid, dbp.os.comm) == statusZombie {
   503  			return pid, nil, nil
   504  		}
   505  		time.Sleep(200 * time.Millisecond)
   506  	}
   507  }
   508  
   509  func (dbp *nativeProcess) exitGuard(err error) error {
   510  	if err != sys.ESRCH {
   511  		return err
   512  	}
   513  	if status(dbp.pid, dbp.os.comm) == statusZombie {
   514  		_, err := dbp.trapWaitInternal(-1, trapWaitDontCallExitGuard)
   515  		return err
   516  	}
   517  
   518  	return err
   519  }
   520  
   521  func (dbp *nativeProcess) resume() error {
   522  	// all threads stopped over a breakpoint are made to step over it
   523  	for _, thread := range dbp.threads {
   524  		if thread.CurrentBreakpoint.Breakpoint != nil {
   525  			if err := thread.StepInstruction(); err != nil {
   526  				return err
   527  			}
   528  			thread.CurrentBreakpoint.Clear()
   529  		}
   530  	}
   531  	// everything is resumed
   532  	for _, thread := range dbp.threads {
   533  		if err := thread.resume(); err != nil && err != sys.ESRCH {
   534  			return err
   535  		}
   536  	}
   537  	return nil
   538  }
   539  
   540  // stop stops all running threads and sets breakpoints
   541  func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
   542  	if dbp.exited {
   543  		return nil, proc.ErrProcessExited{Pid: dbp.pid}
   544  	}
   545  
   546  	for _, th := range dbp.threads {
   547  		th.os.setbp = false
   548  	}
   549  	trapthread.os.setbp = true
   550  
   551  	// check if any other thread simultaneously received a SIGTRAP
   552  	for {
   553  		th, err := dbp.trapWaitInternal(-1, trapWaitNohang)
   554  		if err != nil {
   555  			return nil, dbp.exitGuard(err)
   556  		}
   557  		if th == nil {
   558  			break
   559  		}
   560  	}
   561  
   562  	// stop all threads that are still running
   563  	for _, th := range dbp.threads {
   564  		if th.os.running {
   565  			if err := th.stop(); err != nil {
   566  				return nil, dbp.exitGuard(err)
   567  			}
   568  		}
   569  	}
   570  
   571  	// wait for all threads to stop
   572  	for {
   573  		allstopped := true
   574  		for _, th := range dbp.threads {
   575  			if th.os.running {
   576  				allstopped = false
   577  				break
   578  			}
   579  		}
   580  		if allstopped {
   581  			break
   582  		}
   583  		_, err := dbp.trapWaitInternal(-1, trapWaitHalt)
   584  		if err != nil {
   585  			return nil, err
   586  		}
   587  	}
   588  
   589  	if err := linutil.ElfUpdateSharedObjects(dbp); err != nil {
   590  		return nil, err
   591  	}
   592  
   593  	switchTrapthread := false
   594  
   595  	// set breakpoints on SIGTRAP threads
   596  	var err1 error
   597  	for _, th := range dbp.threads {
   598  		pc, _ := th.PC()
   599  
   600  		if !th.os.setbp && pc != th.os.phantomBreakpointPC {
   601  			// check if this could be a breakpoint hit anyway that the OS hasn't notified us about, yet.
   602  			if _, ok := dbp.FindBreakpoint(pc, dbp.BinInfo().Arch.BreakInstrMovesPC()); ok {
   603  				th.os.phantomBreakpointPC = pc
   604  			}
   605  		}
   606  
   607  		if pc != th.os.phantomBreakpointPC {
   608  			th.os.phantomBreakpointPC = 0
   609  		}
   610  
   611  		if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp {
   612  			if err := th.SetCurrentBreakpoint(true); err != nil {
   613  				err1 = err
   614  				continue
   615  			}
   616  		}
   617  
   618  		if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp && (th.Status != nil) && ((*sys.WaitStatus)(th.Status).StopSignal() == sys.SIGTRAP) && dbp.BinInfo().Arch.BreakInstrMovesPC() {
   619  			manualStop := false
   620  			if th.ThreadID() == trapthread.ThreadID() {
   621  				manualStop = cctx.GetManualStopRequested()
   622  			}
   623  			if !manualStop && th.os.phantomBreakpointPC == pc {
   624  				// Thread received a SIGTRAP but we don't have a breakpoint for it and
   625  				// it wasn't sent by a manual stop request. It's either a hardcoded
   626  				// breakpoint or a phantom breakpoint hit (a breakpoint that was hit but
   627  				// we have removed before we could receive its signal). Check if it is a
   628  				// hardcoded breakpoint, otherwise rewind the thread.
   629  				isHardcodedBreakpoint := false
   630  				pc, _ := th.PC()
   631  				for _, bpinstr := range [][]byte{
   632  					dbp.BinInfo().Arch.BreakpointInstruction(),
   633  					dbp.BinInfo().Arch.AltBreakpointInstruction()} {
   634  					if bpinstr == nil {
   635  						continue
   636  					}
   637  					buf := make([]byte, len(bpinstr))
   638  					_, _ = th.ReadMemory(buf, pc-uint64(len(buf)))
   639  					if bytes.Equal(buf, bpinstr) {
   640  						isHardcodedBreakpoint = true
   641  						break
   642  					}
   643  				}
   644  				if !isHardcodedBreakpoint {
   645  					// phantom breakpoint hit
   646  					_ = th.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction())))
   647  					th.os.setbp = false
   648  					if trapthread.ThreadID() == th.ThreadID() {
   649  						// Will switch to a different thread for trapthread because we don't
   650  						// want pkg/proc to believe that this thread was stopped by a
   651  						// hardcoded breakpoint.
   652  						switchTrapthread = true
   653  					}
   654  				}
   655  			}
   656  		}
   657  	}
   658  	if err1 != nil {
   659  		return nil, err1
   660  	}
   661  
   662  	if switchTrapthread {
   663  		trapthreadID := trapthread.ID
   664  		trapthread = nil
   665  		for _, th := range dbp.threads {
   666  			if th.os.setbp && th.ThreadID() != trapthreadID {
   667  				return th, nil
   668  			}
   669  		}
   670  	}
   671  
   672  	return trapthread, nil
   673  }
   674  
   675  func (dbp *nativeProcess) detach(kill bool) error {
   676  	for threadID := range dbp.threads {
   677  		err := ptraceDetach(threadID, 0)
   678  		if err != nil {
   679  			return err
   680  		}
   681  	}
   682  	if kill {
   683  		return nil
   684  	}
   685  	// For some reason the process will sometimes enter stopped state after a
   686  	// detach, this doesn't happen immediately either.
   687  	// We have to wait a bit here, then check if the main thread is stopped and
   688  	// SIGCONT it if it is.
   689  	time.Sleep(50 * time.Millisecond)
   690  	if s := status(dbp.pid, dbp.os.comm); s == 'T' {
   691  		_ = sys.Kill(dbp.pid, sys.SIGCONT)
   692  	}
   693  	return nil
   694  }
   695  
   696  // EntryPoint will return the process entry point address, useful for
   697  // debugging PIEs.
   698  func (dbp *nativeProcess) EntryPoint() (uint64, error) {
   699  	auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid))
   700  	if err != nil {
   701  		return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
   702  	}
   703  
   704  	return linutil.EntryPointFromAuxv(auxvbuf, dbp.bi.Arch.PtrSize()), nil
   705  }
   706  
   707  func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error {
   708  	// Lazily load and initialize the BPF program upon request to set a uprobe.
   709  	if dbp.os.ebpf == nil {
   710  		var err error
   711  		dbp.os.ebpf, err = ebpf.LoadEBPFTracingProgram(dbp.bi.Images[0].Path)
   712  		if err != nil {
   713  			return err
   714  		}
   715  	}
   716  
   717  	// We only allow up to 12 args for a BPF probe.
   718  	// 6 inputs + 6 outputs.
   719  	// Return early if we have more.
   720  	if len(args) > 12 {
   721  		return errors.New("too many arguments in traced function, max is 12 input+return")
   722  	}
   723  
   724  	fn, ok := dbp.bi.LookupFunc[fnName]
   725  	if !ok {
   726  		return fmt.Errorf("could not find function: %s", fnName)
   727  	}
   728  
   729  	key := fn.Entry
   730  	err := dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, dbp.BinInfo().GStructOffset(), false)
   731  	if err != nil {
   732  		return err
   733  	}
   734  
   735  	debugname := dbp.bi.Images[0].Path
   736  
   737  	// First attach a uprobe at all return addresses. We do this instead of using a uretprobe
   738  	// for two reasons:
   739  	// 1. uretprobes do not play well with Go
   740  	// 2. uretprobes seem to not restore the function return addr on the stack when removed, destroying any
   741  	//    kind of workaround we could come up with.
   742  	// TODO(derekparker): this whole thing could likely be optimized a bit.
   743  	img := dbp.BinInfo().PCToImage(fn.Entry)
   744  	f, err := elf.Open(img.Path)
   745  	if err != nil {
   746  		return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err)
   747  	}
   748  
   749  	var regs proc.Registers
   750  	mem := dbp.Memory()
   751  	regs, _ = dbp.memthread.Registers()
   752  	instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End)
   753  	if err != nil {
   754  		return err
   755  	}
   756  
   757  	var addrs []uint64
   758  	for _, instruction := range instructions {
   759  		if instruction.IsRet() {
   760  			addrs = append(addrs, instruction.Loc.PC)
   761  		}
   762  	}
   763  	addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...)
   764  	for _, addr := range addrs {
   765  		err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true)
   766  		if err != nil {
   767  			return err
   768  		}
   769  		off, err := ebpf.AddressToOffset(f, addr)
   770  		if err != nil {
   771  			return err
   772  		}
   773  		err = dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
   774  		if err != nil {
   775  			return err
   776  		}
   777  	}
   778  
   779  	off, err := ebpf.AddressToOffset(f, fn.Entry)
   780  	if err != nil {
   781  		return err
   782  	}
   783  
   784  	return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off)
   785  }
   786  
   787  func killProcess(pid int) error {
   788  	return sys.Kill(pid, sys.SIGINT)
   789  }