github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/native/proc.go (about)

     1  package native
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"runtime"
     7  	"time"
     8  
     9  	"github.com/go-delve/delve/pkg/proc"
    10  )
    11  
    12  // Process represents all of the information the debugger
    13  // is holding onto regarding the process we are debugging.
    14  type nativeProcess struct {
    15  	bi *proc.BinaryInfo
    16  
    17  	pid int // Process Pid
    18  
    19  	// Breakpoint table, holds information on breakpoints.
    20  	// Maps instruction address to Breakpoint struct.
    21  	breakpoints proc.BreakpointMap
    22  
    23  	// List of threads mapped as such: pid -> *Thread
    24  	threads map[int]*nativeThread
    25  
    26  	// Thread used to read and write memory
    27  	memthread *nativeThread
    28  
    29  	os           *osProcessDetails
    30  	firstStart   bool
    31  	ptraceThread *ptraceThread
    32  	childProcess bool // this process was launched, not attached to
    33  	followExec   bool // automatically attach to new processes
    34  
    35  	// Controlling terminal file descriptor for
    36  	// this process.
    37  	ctty *os.File
    38  
    39  	iscgo bool
    40  
    41  	exited, detached bool
    42  }
    43  
    44  // newProcess returns an initialized Process struct. Before returning,
    45  // it will also launch a goroutine in order to handle ptrace(2)
    46  // functions. For more information, see the documentation on
    47  // `handlePtraceFuncs`.
    48  func newProcess(pid int) *nativeProcess {
    49  	dbp := &nativeProcess{
    50  		pid:          pid,
    51  		threads:      make(map[int]*nativeThread),
    52  		breakpoints:  proc.NewBreakpointMap(),
    53  		firstStart:   true,
    54  		os:           new(osProcessDetails),
    55  		ptraceThread: newPtraceThread(),
    56  		bi:           proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
    57  	}
    58  	return dbp
    59  }
    60  
    61  // newChildProcess is like newProcess but uses the same ptrace thread as dbp.
    62  func newChildProcess(dbp *nativeProcess, pid int) *nativeProcess {
    63  	return &nativeProcess{
    64  		pid:          pid,
    65  		threads:      make(map[int]*nativeThread),
    66  		breakpoints:  proc.NewBreakpointMap(),
    67  		firstStart:   true,
    68  		os:           new(osProcessDetails),
    69  		ptraceThread: dbp.ptraceThread.acquire(),
    70  		bi:           proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
    71  	}
    72  }
    73  
    74  // WaitFor waits for a process as specified by waitFor.
    75  func WaitFor(waitFor *proc.WaitFor) (int, error) {
    76  	t0 := time.Now()
    77  	seen := make(map[int]struct{})
    78  	for (waitFor.Duration == 0) || (time.Since(t0) < waitFor.Duration) {
    79  		pid, err := waitForSearchProcess(waitFor.Name, seen)
    80  		if err != nil {
    81  			return 0, err
    82  		}
    83  		if pid != 0 {
    84  			return pid, nil
    85  		}
    86  		time.Sleep(waitFor.Interval)
    87  	}
    88  	return 0, errors.New("waitfor duration expired")
    89  }
    90  
    91  // BinInfo will return the binary info struct associated with this process.
    92  func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo {
    93  	return dbp.bi
    94  }
    95  
    96  // StartCallInjection notifies the backend that we are about to inject a function call.
    97  func (dbp *nativeProcess) StartCallInjection() (func(), error) { return func() {}, nil }
    98  
    99  // detachWithoutGroup is a helper function to detach from a process which we
   100  // haven't added to a process group yet.
   101  func detachWithoutGroup(dbp *nativeProcess, kill bool) error {
   102  	grp := &processGroup{procs: []*nativeProcess{dbp}}
   103  	return grp.Detach(dbp.pid, kill)
   104  }
   105  
   106  // Detach from the process being debugged, optionally killing it.
   107  func (procgrp *processGroup) Detach(pid int, kill bool) (err error) {
   108  	dbp := procgrp.procForPid(pid)
   109  	if dbp.exited {
   110  		return nil
   111  	}
   112  	if kill && dbp.childProcess {
   113  		err := procgrp.kill(dbp)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		return nil
   118  	}
   119  	dbp.execPtraceFunc(func() {
   120  		err = dbp.detach(kill)
   121  		if err != nil {
   122  			return
   123  		}
   124  		if kill {
   125  			err = killProcess(dbp.pid)
   126  		}
   127  	})
   128  	dbp.detached = true
   129  	dbp.postExit()
   130  	return
   131  }
   132  
   133  // Valid returns whether the process is still attached to and
   134  // has not exited.
   135  func (dbp *nativeProcess) Valid() (bool, error) {
   136  	if dbp.detached {
   137  		return false, proc.ErrProcessDetached
   138  	}
   139  	if dbp.exited {
   140  		return false, proc.ErrProcessExited{Pid: dbp.pid}
   141  	}
   142  	return true, nil
   143  }
   144  
   145  // ThreadList returns a list of threads in the process.
   146  func (dbp *nativeProcess) ThreadList() []proc.Thread {
   147  	r := make([]proc.Thread, 0, len(dbp.threads))
   148  	for _, v := range dbp.threads {
   149  		r = append(r, v)
   150  	}
   151  	return r
   152  }
   153  
   154  // FindThread attempts to find the thread with the specified ID.
   155  func (dbp *nativeProcess) FindThread(threadID int) (proc.Thread, bool) {
   156  	th, ok := dbp.threads[threadID]
   157  	return th, ok
   158  }
   159  
   160  // Memory returns the process memory.
   161  func (dbp *nativeProcess) Memory() proc.MemoryReadWriter {
   162  	return dbp.memthread
   163  }
   164  
   165  // Breakpoints returns a list of breakpoints currently set.
   166  func (dbp *nativeProcess) Breakpoints() *proc.BreakpointMap {
   167  	return &dbp.breakpoints
   168  }
   169  
   170  // RequestManualStop sets the `manualStopRequested` flag and
   171  // sends SIGSTOP to all threads.
   172  func (dbp *nativeProcess) RequestManualStop(cctx *proc.ContinueOnceContext) error {
   173  	if dbp.exited {
   174  		return proc.ErrProcessExited{Pid: dbp.pid}
   175  	}
   176  	return dbp.requestManualStop()
   177  }
   178  
   179  func (dbp *nativeProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
   180  	if bp.WatchType != 0 {
   181  		for _, thread := range dbp.threads {
   182  			err := thread.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   183  			if err != nil {
   184  				return err
   185  			}
   186  		}
   187  		return nil
   188  	}
   189  
   190  	bp.OriginalData = make([]byte, dbp.bi.Arch.BreakpointSize())
   191  	_, err := dbp.memthread.ReadMemory(bp.OriginalData, bp.Addr)
   192  	if err != nil {
   193  		return err
   194  	}
   195  	return dbp.writeSoftwareBreakpoint(dbp.memthread, bp.Addr)
   196  }
   197  
   198  func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
   199  	if bp.WatchType != 0 {
   200  		for _, thread := range dbp.threads {
   201  			err := thread.clearHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   202  			if err != nil {
   203  				return err
   204  			}
   205  		}
   206  		return nil
   207  	}
   208  
   209  	return dbp.memthread.clearSoftwareBreakpoint(bp)
   210  }
   211  
   212  type processGroup struct {
   213  	procs     []*nativeProcess
   214  	addTarget proc.AddTargetFunc
   215  }
   216  
   217  func (procgrp *processGroup) numValid() int {
   218  	n := 0
   219  	for _, p := range procgrp.procs {
   220  		if ok, _ := p.Valid(); ok {
   221  			n++
   222  		}
   223  	}
   224  	return n
   225  }
   226  
   227  func (procgrp *processGroup) procForThread(tid int) *nativeProcess {
   228  	for _, p := range procgrp.procs {
   229  		if p.threads[tid] != nil {
   230  			return p
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  func (procgrp *processGroup) procForPid(pid int) *nativeProcess {
   237  	for _, p := range procgrp.procs {
   238  		if p.pid == pid {
   239  			return p
   240  		}
   241  	}
   242  	return nil
   243  }
   244  
   245  func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason, cmdline string) (*proc.Target, error) {
   246  	tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason, cmdline)
   247  	if tgt == nil {
   248  		i := len(procgrp.procs)
   249  		procgrp.procs = append(procgrp.procs, p)
   250  		procgrp.Detach(p.pid, false)
   251  		if i == len(procgrp.procs)-1 {
   252  			procgrp.procs = procgrp.procs[:i]
   253  		}
   254  	}
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	if tgt != nil {
   259  		procgrp.procs = append(procgrp.procs, p)
   260  	}
   261  	return tgt, nil
   262  }
   263  
   264  func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
   265  	if len(procgrp.procs) != 1 && runtime.GOOS != "linux" {
   266  		panic("not implemented")
   267  	}
   268  	if procgrp.numValid() == 0 {
   269  		return nil, proc.StopExited, proc.ErrProcessExited{Pid: procgrp.procs[0].pid}
   270  	}
   271  
   272  	for {
   273  		err := procgrp.resume()
   274  		if err != nil {
   275  			return nil, proc.StopUnknown, err
   276  		}
   277  		for _, dbp := range procgrp.procs {
   278  			if valid, _ := dbp.Valid(); valid {
   279  				for _, th := range dbp.threads {
   280  					th.CurrentBreakpoint.Clear()
   281  				}
   282  			}
   283  		}
   284  
   285  		if cctx.ResumeChan != nil {
   286  			close(cctx.ResumeChan)
   287  			cctx.ResumeChan = nil
   288  		}
   289  
   290  		trapthread, err := trapWait(procgrp, -1)
   291  		if err != nil {
   292  			return nil, proc.StopUnknown, err
   293  		}
   294  		trapthread, err = procgrp.stop(cctx, trapthread)
   295  		if err != nil {
   296  			return nil, proc.StopUnknown, err
   297  		}
   298  		if trapthread != nil {
   299  			dbp := procgrp.procForThread(trapthread.ID)
   300  			dbp.memthread = trapthread
   301  			// refresh memthread for every other process
   302  			for _, p2 := range procgrp.procs {
   303  				if p2.exited || p2 == dbp {
   304  					continue
   305  				}
   306  				for _, th := range p2.threads {
   307  					p2.memthread = th
   308  					if th.SoftExc() {
   309  						break
   310  					}
   311  				}
   312  			}
   313  			return trapthread, proc.StopUnknown, nil
   314  		}
   315  	}
   316  }
   317  
   318  // FindBreakpoint finds the breakpoint for the given pc.
   319  func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakpoint, bool) {
   320  	if adjustPC {
   321  		// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
   322  		if bp, ok := dbp.breakpoints.M[pc-uint64(dbp.bi.Arch.BreakpointSize())]; ok {
   323  			return bp, true
   324  		}
   325  	}
   326  	// Directly use addr to lookup breakpoint.
   327  	if bp, ok := dbp.breakpoints.M[pc]; ok {
   328  		return bp, true
   329  	}
   330  	return nil, false
   331  }
   332  
   333  func (dbp *nativeProcess) initializeBasic() (string, error) {
   334  	cmdline, err := initialize(dbp)
   335  	if err != nil {
   336  		return "", err
   337  	}
   338  	if err := dbp.updateThreadList(); err != nil {
   339  		return "", err
   340  	}
   341  	return cmdline, nil
   342  }
   343  
   344  // initialize will ensure that all relevant information is loaded
   345  // so the process is ready to be debugged.
   346  func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.TargetGroup, error) {
   347  	cmdline, err := dbp.initializeBasic()
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  	stopReason := proc.StopLaunched
   352  	if !dbp.childProcess {
   353  		stopReason = proc.StopAttached
   354  	}
   355  	procgrp := &processGroup{}
   356  	grp, addTarget := proc.NewGroup(procgrp, proc.NewTargetGroupConfig{
   357  		DebugInfoDirs: debugInfoDirs,
   358  
   359  		// We disable asyncpreempt for the following reasons:
   360  		//  - on Windows asyncpreempt is incompatible with debuggers, see:
   361  		//    https://github.com/golang/go/issues/36494
   362  		//  - on linux/arm64 asyncpreempt can sometimes restart a sequence of
   363  		//    instructions, if the sequence happens to contain a breakpoint it will
   364  		//    look like the breakpoint was hit twice when it was "logically" only
   365  		//    executed once.
   366  		//    See: https://go-review.googlesource.com/c/go/+/208126
   367  		//	- on linux/ppc64le according to @laboger, they had issues in the past
   368  		//	  with gdb once AsyncPreempt was enabled. While implementing the port,
   369  		//	  few tests failed while it was enabled, but cannot be warrantied that
   370  		//	  disabling it fixed the issues.
   371  		DisableAsyncPreempt: runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64") || (runtime.GOOS == "linux" && runtime.GOARCH == "ppc64le"),
   372  
   373  		StopReason: stopReason,
   374  		CanDump:    runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"),
   375  	})
   376  	procgrp.addTarget = addTarget
   377  	tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason, cmdline)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  	if dbp.bi.Arch.Name == "arm64" || dbp.bi.Arch.Name == "ppc64le" {
   382  		dbp.iscgo = tgt.IsCgo()
   383  	}
   384  	return grp, nil
   385  }
   386  
   387  func (pt *ptraceThread) handlePtraceFuncs() {
   388  	// We must ensure here that we are running on the same thread during
   389  	// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
   390  	// all commands after PTRACE_ATTACH to come from the same thread.
   391  	runtime.LockOSThread()
   392  
   393  	// Leaving the OS thread locked currently leads to segfaults in the
   394  	// Go runtime while running on FreeBSD and OpenBSD:
   395  	//   https://github.com/golang/go/issues/52394
   396  	if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" {
   397  		defer runtime.UnlockOSThread()
   398  	}
   399  
   400  	for fn := range pt.ptraceChan {
   401  		fn()
   402  		pt.ptraceDoneChan <- nil
   403  	}
   404  	close(pt.ptraceDoneChan)
   405  }
   406  
   407  func (dbp *nativeProcess) execPtraceFunc(fn func()) {
   408  	dbp.ptraceThread.ptraceChan <- fn
   409  	<-dbp.ptraceThread.ptraceDoneChan
   410  }
   411  
   412  func (dbp *nativeProcess) postExit() {
   413  	dbp.exited = true
   414  	dbp.ptraceThread.release()
   415  	dbp.bi.Close()
   416  	if dbp.ctty != nil {
   417  		dbp.ctty.Close()
   418  	}
   419  	dbp.os.Close()
   420  }
   421  
   422  func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uint64) error {
   423  	_, err := thread.WriteMemory(addr, dbp.bi.Arch.BreakpointInstruction())
   424  	return err
   425  }
   426  
   427  func openRedirects(stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
   428  	toclose := []*os.File{}
   429  
   430  	if stdinPath != "" {
   431  		stdin, err = os.Open(stdinPath)
   432  		if err != nil {
   433  			return nil, nil, nil, nil, err
   434  		}
   435  		toclose = append(toclose, stdin)
   436  	} else if foreground {
   437  		stdin = os.Stdin
   438  	}
   439  
   440  	create := func(redirect proc.OutputRedirect, dflt *os.File) (f *os.File) {
   441  		if redirect.Path != "" {
   442  			f, err = os.Create(redirect.Path)
   443  			if f != nil {
   444  				toclose = append(toclose, f)
   445  			}
   446  
   447  			return f
   448  		} else if redirect.File != nil {
   449  			toclose = append(toclose, redirect.File)
   450  
   451  			return redirect.File
   452  		}
   453  
   454  		return dflt
   455  	}
   456  
   457  	stdout = create(stdoutOR, os.Stdout)
   458  	if err != nil {
   459  		return nil, nil, nil, nil, err
   460  	}
   461  
   462  	stderr = create(stderrOR, os.Stderr)
   463  	if err != nil {
   464  		return nil, nil, nil, nil, err
   465  	}
   466  
   467  	closefn = func() {
   468  		for _, f := range toclose {
   469  			_ = f.Close()
   470  		}
   471  	}
   472  
   473  	return stdin, stdout, stderr, closefn, nil
   474  }
   475  
   476  type ptraceThread struct {
   477  	ptraceRefCnt   int
   478  	ptraceChan     chan func()
   479  	ptraceDoneChan chan interface{}
   480  }
   481  
   482  func newPtraceThread() *ptraceThread {
   483  	pt := &ptraceThread{
   484  		ptraceChan:     make(chan func()),
   485  		ptraceDoneChan: make(chan interface{}),
   486  		ptraceRefCnt:   1,
   487  	}
   488  	go pt.handlePtraceFuncs()
   489  	return pt
   490  }
   491  
   492  func (pt *ptraceThread) acquire() *ptraceThread {
   493  	pt.ptraceRefCnt++
   494  	return pt
   495  }
   496  
   497  func (pt *ptraceThread) release() {
   498  	pt.ptraceRefCnt--
   499  	if pt.ptraceRefCnt == 0 {
   500  		close(pt.ptraceChan)
   501  	}
   502  }