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

     1  package native
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"runtime"
     7  	"time"
     8  
     9  	"gitlab.com/Raven-IO/raven-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 ok, _ := dbp.Valid(); !ok {
   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  func (procgrp *processGroup) Close() error {
   134  	return nil
   135  }
   136  
   137  // Valid returns whether the process is still attached to and
   138  // has not exited.
   139  func (dbp *nativeProcess) Valid() (bool, error) {
   140  	if dbp.detached {
   141  		return false, proc.ErrProcessDetached
   142  	}
   143  	if dbp.exited {
   144  		return false, proc.ErrProcessExited{Pid: dbp.pid}
   145  	}
   146  	return true, nil
   147  }
   148  
   149  // ThreadList returns a list of threads in the process.
   150  func (dbp *nativeProcess) ThreadList() []proc.Thread {
   151  	r := make([]proc.Thread, 0, len(dbp.threads))
   152  	for _, v := range dbp.threads {
   153  		r = append(r, v)
   154  	}
   155  	return r
   156  }
   157  
   158  // FindThread attempts to find the thread with the specified ID.
   159  func (dbp *nativeProcess) FindThread(threadID int) (proc.Thread, bool) {
   160  	th, ok := dbp.threads[threadID]
   161  	return th, ok
   162  }
   163  
   164  // Memory returns the process memory.
   165  func (dbp *nativeProcess) Memory() proc.MemoryReadWriter {
   166  	return dbp.memthread
   167  }
   168  
   169  // Breakpoints returns a list of breakpoints currently set.
   170  func (dbp *nativeProcess) Breakpoints() *proc.BreakpointMap {
   171  	return &dbp.breakpoints
   172  }
   173  
   174  // RequestManualStop sets the `manualStopRequested` flag and
   175  // sends SIGSTOP to all threads.
   176  func (dbp *nativeProcess) RequestManualStop(cctx *proc.ContinueOnceContext) error {
   177  	if ok, err := dbp.Valid(); !ok {
   178  		return err
   179  	}
   180  	return dbp.requestManualStop()
   181  }
   182  
   183  func (dbp *nativeProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
   184  	if bp.WatchType != 0 {
   185  		for _, thread := range dbp.threads {
   186  			err := thread.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   187  			if err != nil {
   188  				return err
   189  			}
   190  		}
   191  		return nil
   192  	}
   193  
   194  	bp.OriginalData = make([]byte, dbp.bi.Arch.BreakpointSize())
   195  	_, err := dbp.memthread.ReadMemory(bp.OriginalData, bp.Addr)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	return dbp.writeSoftwareBreakpoint(dbp.memthread, bp.Addr)
   200  }
   201  
   202  func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
   203  	if bp.WatchType != 0 {
   204  		for _, thread := range dbp.threads {
   205  			err := thread.clearHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   206  			if err != nil {
   207  				return err
   208  			}
   209  		}
   210  		return nil
   211  	}
   212  
   213  	return dbp.memthread.clearSoftwareBreakpoint(bp)
   214  }
   215  
   216  type processGroup struct {
   217  	procs     []*nativeProcess
   218  	addTarget proc.AddTargetFunc
   219  }
   220  
   221  func (procgrp *processGroup) numValid() int {
   222  	n := 0
   223  	for _, p := range procgrp.procs {
   224  		if ok, _ := p.Valid(); ok {
   225  			n++
   226  		}
   227  	}
   228  	return n
   229  }
   230  
   231  func (procgrp *processGroup) procForThread(tid int) *nativeProcess {
   232  	for _, p := range procgrp.procs {
   233  		if p.threads[tid] != nil {
   234  			return p
   235  		}
   236  	}
   237  	return nil
   238  }
   239  
   240  func (procgrp *processGroup) procForPid(pid int) *nativeProcess {
   241  	for _, p := range procgrp.procs {
   242  		if p.pid == pid {
   243  			return p
   244  		}
   245  	}
   246  	return nil
   247  }
   248  
   249  func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason, cmdline string) (*proc.Target, error) {
   250  	tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason, cmdline)
   251  	if tgt == nil {
   252  		i := len(procgrp.procs)
   253  		procgrp.procs = append(procgrp.procs, p)
   254  		procgrp.detachChild(p)
   255  		if i == len(procgrp.procs)-1 {
   256  			procgrp.procs = procgrp.procs[:i]
   257  		}
   258  	}
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	if tgt != nil {
   263  		procgrp.procs = append(procgrp.procs, p)
   264  	}
   265  	return tgt, nil
   266  }
   267  
   268  func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
   269  	if len(procgrp.procs) != 1 && runtime.GOOS != "linux" && runtime.GOOS != "windows" {
   270  		panic("not implemented")
   271  	}
   272  	if procgrp.numValid() == 0 {
   273  		return nil, proc.StopExited, proc.ErrProcessExited{Pid: procgrp.procs[0].pid}
   274  	}
   275  
   276  	for {
   277  		err := procgrp.resume()
   278  		if err != nil {
   279  			return nil, proc.StopUnknown, err
   280  		}
   281  		for _, dbp := range procgrp.procs {
   282  			if valid, _ := dbp.Valid(); valid {
   283  				for _, th := range dbp.threads {
   284  					th.CurrentBreakpoint.Clear()
   285  				}
   286  			}
   287  		}
   288  
   289  		if cctx.ResumeChan != nil {
   290  			close(cctx.ResumeChan)
   291  			cctx.ResumeChan = nil
   292  		}
   293  
   294  		trapthread, err := trapWait(procgrp, -1)
   295  		if err != nil {
   296  			return nil, proc.StopUnknown, err
   297  		}
   298  		trapthread, err = procgrp.stop(cctx, trapthread)
   299  		if err != nil {
   300  			return nil, proc.StopUnknown, err
   301  		}
   302  		if trapthread != nil {
   303  			dbp := procgrp.procForThread(trapthread.ID)
   304  			dbp.memthread = trapthread
   305  			// refresh memthread for every other process
   306  			for _, p2 := range procgrp.procs {
   307  				if p2.exited || p2.detached || p2 == dbp {
   308  					continue
   309  				}
   310  				for _, th := range p2.threads {
   311  					p2.memthread = th
   312  					if th.SoftExc() {
   313  						break
   314  					}
   315  				}
   316  			}
   317  			return trapthread, proc.StopUnknown, nil
   318  		}
   319  	}
   320  }
   321  
   322  // FindBreakpoint finds the breakpoint for the given pc.
   323  func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakpoint, bool) {
   324  	if adjustPC {
   325  		// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
   326  		if bp, ok := dbp.breakpoints.M[pc-uint64(dbp.bi.Arch.BreakpointSize())]; ok {
   327  			return bp, true
   328  		}
   329  	}
   330  	// Directly use addr to lookup breakpoint.
   331  	if bp, ok := dbp.breakpoints.M[pc]; ok {
   332  		return bp, true
   333  	}
   334  	return nil, false
   335  }
   336  
   337  func (dbp *nativeProcess) initializeBasic() (string, error) {
   338  	cmdline, err := initialize(dbp)
   339  	if err != nil {
   340  		return "", err
   341  	}
   342  	if err := dbp.updateThreadList(); err != nil {
   343  		return "", err
   344  	}
   345  	return cmdline, nil
   346  }
   347  
   348  // initialize will ensure that all relevant information is loaded
   349  // so the process is ready to be debugged.
   350  func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.TargetGroup, error) {
   351  	cmdline, err := dbp.initializeBasic()
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  	stopReason := proc.StopLaunched
   356  	if !dbp.childProcess {
   357  		stopReason = proc.StopAttached
   358  	}
   359  	procgrp := &processGroup{}
   360  	grp, addTarget := proc.NewGroup(procgrp, proc.NewTargetGroupConfig{
   361  		DebugInfoDirs: debugInfoDirs,
   362  
   363  		// We disable asyncpreempt for the following reasons:
   364  		//  - on Windows asyncpreempt is incompatible with debuggers, see:
   365  		//    https://github.com/golang/go/issues/36494
   366  		//  - on linux/arm64 asyncpreempt can sometimes restart a sequence of
   367  		//    instructions, if the sequence happens to contain a breakpoint it will
   368  		//    look like the breakpoint was hit twice when it was "logically" only
   369  		//    executed once.
   370  		//    See: https://go-review.googlesource.com/c/go/+/208126
   371  		//	- on linux/ppc64le according to @laboger, they had issues in the past
   372  		//	  with gdb once AsyncPreempt was enabled. While implementing the port,
   373  		//	  few tests failed while it was enabled, but cannot be warrantied that
   374  		//	  disabling it fixed the issues.
   375  		DisableAsyncPreempt: runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64") || (runtime.GOOS == "linux" && runtime.GOARCH == "ppc64le"),
   376  
   377  		StopReason: stopReason,
   378  		CanDump:    runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"),
   379  	})
   380  	procgrp.addTarget = addTarget
   381  	tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason, cmdline)
   382  	if err != nil {
   383  		return nil, err
   384  	}
   385  	if dbp.bi.Arch.Name == "arm64" || dbp.bi.Arch.Name == "ppc64le" {
   386  		dbp.iscgo = tgt.IsCgo()
   387  	}
   388  	return grp, nil
   389  }
   390  
   391  func (pt *ptraceThread) handlePtraceFuncs() {
   392  	// We must ensure here that we are running on the same thread during
   393  	// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
   394  	// all commands after PTRACE_ATTACH to come from the same thread.
   395  	runtime.LockOSThread()
   396  
   397  	// Leaving the OS thread locked currently leads to segfaults in the
   398  	// Go runtime while running on FreeBSD and OpenBSD:
   399  	//   https://github.com/golang/go/issues/52394
   400  	if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" {
   401  		defer runtime.UnlockOSThread()
   402  	}
   403  
   404  	for fn := range pt.ptraceChan {
   405  		fn()
   406  		pt.ptraceDoneChan <- nil
   407  	}
   408  	close(pt.ptraceDoneChan)
   409  }
   410  
   411  func (dbp *nativeProcess) execPtraceFunc(fn func()) {
   412  	dbp.ptraceThread.ptraceChan <- fn
   413  	<-dbp.ptraceThread.ptraceDoneChan
   414  }
   415  
   416  func (dbp *nativeProcess) postExit() {
   417  	dbp.exited = true
   418  	dbp.ptraceThread.release()
   419  	dbp.bi.Close()
   420  	if dbp.ctty != nil {
   421  		dbp.ctty.Close()
   422  	}
   423  	dbp.os.Close()
   424  }
   425  
   426  func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uint64) error {
   427  	_, err := thread.WriteMemory(addr, dbp.bi.Arch.BreakpointInstruction())
   428  	return err
   429  }
   430  
   431  func openRedirects(stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
   432  	toclose := []*os.File{}
   433  
   434  	if stdinPath != "" {
   435  		stdin, err = os.Open(stdinPath)
   436  		if err != nil {
   437  			return nil, nil, nil, nil, err
   438  		}
   439  		toclose = append(toclose, stdin)
   440  	} else if foreground {
   441  		stdin = os.Stdin
   442  	}
   443  
   444  	create := func(redirect proc.OutputRedirect, dflt *os.File) (f *os.File) {
   445  		if redirect.Path != "" {
   446  			f, err = os.Create(redirect.Path)
   447  			if f != nil {
   448  				toclose = append(toclose, f)
   449  			}
   450  
   451  			return f
   452  		} else if redirect.File != nil {
   453  			toclose = append(toclose, redirect.File)
   454  
   455  			return redirect.File
   456  		}
   457  
   458  		return dflt
   459  	}
   460  
   461  	stdout = create(stdoutOR, os.Stdout)
   462  	if err != nil {
   463  		return nil, nil, nil, nil, err
   464  	}
   465  
   466  	stderr = create(stderrOR, os.Stderr)
   467  	if err != nil {
   468  		return nil, nil, nil, nil, err
   469  	}
   470  
   471  	closefn = func() {
   472  		for _, f := range toclose {
   473  			_ = f.Close()
   474  		}
   475  	}
   476  
   477  	return stdin, stdout, stderr, closefn, nil
   478  }
   479  
   480  type ptraceThread struct {
   481  	ptraceRefCnt   int
   482  	ptraceChan     chan func()
   483  	ptraceDoneChan chan interface{}
   484  }
   485  
   486  func newPtraceThread() *ptraceThread {
   487  	pt := &ptraceThread{
   488  		ptraceChan:     make(chan func()),
   489  		ptraceDoneChan: make(chan interface{}),
   490  		ptraceRefCnt:   1,
   491  	}
   492  	go pt.handlePtraceFuncs()
   493  	return pt
   494  }
   495  
   496  func (pt *ptraceThread) acquire() *ptraceThread {
   497  	pt.ptraceRefCnt++
   498  	return pt
   499  }
   500  
   501  func (pt *ptraceThread) release() {
   502  	pt.ptraceRefCnt--
   503  	if pt.ptraceRefCnt == 0 {
   504  		close(pt.ptraceChan)
   505  	}
   506  }