github.com/undoio/delve@v1.9.0/pkg/proc/native/proc.go (about)

     1  package native
     2  
     3  import (
     4  	"os"
     5  	"runtime"
     6  
     7  	"github.com/undoio/delve/pkg/proc"
     8  )
     9  
    10  // Process represents all of the information the debugger
    11  // is holding onto regarding the process we are debugging.
    12  type nativeProcess struct {
    13  	bi *proc.BinaryInfo
    14  
    15  	pid int // Process Pid
    16  
    17  	// Breakpoint table, holds information on breakpoints.
    18  	// Maps instruction address to Breakpoint struct.
    19  	breakpoints proc.BreakpointMap
    20  
    21  	// List of threads mapped as such: pid -> *Thread
    22  	threads map[int]*nativeThread
    23  
    24  	// Thread used to read and write memory
    25  	memthread *nativeThread
    26  
    27  	os             *osProcessDetails
    28  	firstStart     bool
    29  	ptraceChan     chan func()
    30  	ptraceDoneChan chan interface{}
    31  	childProcess   bool // this process was launched, not attached to
    32  
    33  	// Controlling terminal file descriptor for
    34  	// this process.
    35  	ctty *os.File
    36  
    37  	iscgo bool
    38  
    39  	exited, detached bool
    40  }
    41  
    42  // newProcess returns an initialized Process struct. Before returning,
    43  // it will also launch a goroutine in order to handle ptrace(2)
    44  // functions. For more information, see the documentation on
    45  // `handlePtraceFuncs`.
    46  func newProcess(pid int) *nativeProcess {
    47  	dbp := &nativeProcess{
    48  		pid:            pid,
    49  		threads:        make(map[int]*nativeThread),
    50  		breakpoints:    proc.NewBreakpointMap(),
    51  		firstStart:     true,
    52  		os:             new(osProcessDetails),
    53  		ptraceChan:     make(chan func()),
    54  		ptraceDoneChan: make(chan interface{}),
    55  		bi:             proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH),
    56  	}
    57  	go dbp.handlePtraceFuncs()
    58  	return dbp
    59  }
    60  
    61  // BinInfo will return the binary info struct associated with this process.
    62  func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo {
    63  	return dbp.bi
    64  }
    65  
    66  // StartCallInjection notifies the backend that we are about to inject a function call.
    67  func (dbp *nativeProcess) StartCallInjection() (func(), error) { return func() {}, nil }
    68  
    69  // Detach from the process being debugged, optionally killing it.
    70  func (dbp *nativeProcess) Detach(kill bool) (err error) {
    71  	if dbp.exited {
    72  		return nil
    73  	}
    74  	if kill && dbp.childProcess {
    75  		err := dbp.kill()
    76  		if err != nil {
    77  			return err
    78  		}
    79  		dbp.bi.Close()
    80  		return nil
    81  	}
    82  	dbp.execPtraceFunc(func() {
    83  		err = dbp.detach(kill)
    84  		if err != nil {
    85  			return
    86  		}
    87  		if kill {
    88  			err = killProcess(dbp.pid)
    89  		}
    90  	})
    91  	dbp.detached = true
    92  	dbp.postExit()
    93  	return
    94  }
    95  
    96  // Valid returns whether the process is still attached to and
    97  // has not exited.
    98  func (dbp *nativeProcess) Valid() (bool, error) {
    99  	if dbp.detached {
   100  		return false, proc.ErrProcessDetached
   101  	}
   102  	if dbp.exited {
   103  		return false, proc.ErrProcessExited{Pid: dbp.pid}
   104  	}
   105  	return true, nil
   106  }
   107  
   108  // ThreadList returns a list of threads in the process.
   109  func (dbp *nativeProcess) ThreadList() []proc.Thread {
   110  	r := make([]proc.Thread, 0, len(dbp.threads))
   111  	for _, v := range dbp.threads {
   112  		r = append(r, v)
   113  	}
   114  	return r
   115  }
   116  
   117  // FindThread attempts to find the thread with the specified ID.
   118  func (dbp *nativeProcess) FindThread(threadID int) (proc.Thread, bool) {
   119  	th, ok := dbp.threads[threadID]
   120  	return th, ok
   121  }
   122  
   123  // Memory returns the process memory.
   124  func (dbp *nativeProcess) Memory() proc.MemoryReadWriter {
   125  	return dbp.memthread
   126  }
   127  
   128  // Breakpoints returns a list of breakpoints currently set.
   129  func (dbp *nativeProcess) Breakpoints() *proc.BreakpointMap {
   130  	return &dbp.breakpoints
   131  }
   132  
   133  // RequestManualStop sets the `manualStopRequested` flag and
   134  // sends SIGSTOP to all threads.
   135  func (dbp *nativeProcess) RequestManualStop(cctx *proc.ContinueOnceContext) error {
   136  	if dbp.exited {
   137  		return proc.ErrProcessExited{Pid: dbp.pid}
   138  	}
   139  	return dbp.requestManualStop()
   140  }
   141  
   142  func (dbp *nativeProcess) WriteBreakpoint(bp *proc.Breakpoint) error {
   143  	if bp.WatchType != 0 {
   144  		for _, thread := range dbp.threads {
   145  			err := thread.writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   146  			if err != nil {
   147  				return err
   148  			}
   149  		}
   150  		return nil
   151  	}
   152  
   153  	bp.OriginalData = make([]byte, dbp.bi.Arch.BreakpointSize())
   154  	_, err := dbp.memthread.ReadMemory(bp.OriginalData, bp.Addr)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	return dbp.writeSoftwareBreakpoint(dbp.memthread, bp.Addr)
   159  }
   160  
   161  func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
   162  	if bp.WatchType != 0 {
   163  		for _, thread := range dbp.threads {
   164  			err := thread.clearHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex)
   165  			if err != nil {
   166  				return err
   167  			}
   168  		}
   169  		return nil
   170  	}
   171  
   172  	return dbp.memthread.clearSoftwareBreakpoint(bp)
   173  }
   174  
   175  // ContinueOnce will continue the target until it stops.
   176  // This could be the result of a breakpoint or signal.
   177  func (dbp *nativeProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
   178  	if dbp.exited {
   179  		return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid}
   180  	}
   181  
   182  	for {
   183  
   184  		if err := dbp.resume(); err != nil {
   185  			return nil, proc.StopUnknown, err
   186  		}
   187  
   188  		for _, th := range dbp.threads {
   189  			th.CurrentBreakpoint.Clear()
   190  		}
   191  
   192  		if cctx.ResumeChan != nil {
   193  			close(cctx.ResumeChan)
   194  			cctx.ResumeChan = nil
   195  		}
   196  
   197  		trapthread, err := dbp.trapWait(-1)
   198  		if err != nil {
   199  			return nil, proc.StopUnknown, err
   200  		}
   201  		trapthread, err = dbp.stop(cctx, trapthread)
   202  		if err != nil {
   203  			return nil, proc.StopUnknown, err
   204  		}
   205  		if trapthread != nil {
   206  			dbp.memthread = trapthread
   207  			return trapthread, proc.StopUnknown, nil
   208  		}
   209  	}
   210  }
   211  
   212  // FindBreakpoint finds the breakpoint for the given pc.
   213  func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakpoint, bool) {
   214  	if adjustPC {
   215  		// Check to see if address is past the breakpoint, (i.e. breakpoint was hit).
   216  		if bp, ok := dbp.breakpoints.M[pc-uint64(dbp.bi.Arch.BreakpointSize())]; ok {
   217  			return bp, true
   218  		}
   219  	}
   220  	// Directly use addr to lookup breakpoint.
   221  	if bp, ok := dbp.breakpoints.M[pc]; ok {
   222  		return bp, true
   223  	}
   224  	return nil, false
   225  }
   226  
   227  // initialize will ensure that all relevant information is loaded
   228  // so the process is ready to be debugged.
   229  func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.Target, error) {
   230  	if err := initialize(dbp); err != nil {
   231  		return nil, err
   232  	}
   233  	if err := dbp.updateThreadList(); err != nil {
   234  		return nil, err
   235  	}
   236  	stopReason := proc.StopLaunched
   237  	if !dbp.childProcess {
   238  		stopReason = proc.StopAttached
   239  	}
   240  	tgt, err := proc.NewTarget(dbp, dbp.pid, dbp.memthread, proc.NewTargetConfig{
   241  		Path:          path,
   242  		DebugInfoDirs: debugInfoDirs,
   243  
   244  		// We disable asyncpreempt for the following reasons:
   245  		//  - on Windows asyncpreempt is incompatible with debuggers, see:
   246  		//    https://github.com/golang/go/issues/36494
   247  		//  - freebsd's backend is generally broken and asyncpreempt makes it even more so, see:
   248  		//    https://github.com/go-delve/delve/issues/1754
   249  		//  - on linux/arm64 asyncpreempt can sometimes restart a sequence of
   250  		//    instructions, if the sequence happens to contain a breakpoint it will
   251  		//    look like the breakpoint was hit twice when it was "logically" only
   252  		//    executed once.
   253  		//    See: https://go-review.googlesource.com/c/go/+/208126
   254  		DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"),
   255  
   256  		StopReason: stopReason,
   257  		CanDump:    runtime.GOOS == "linux" || runtime.GOOS == "windows",
   258  	})
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	if dbp.bi.Arch.Name == "arm64" {
   263  		dbp.iscgo = tgt.IsCgo()
   264  	}
   265  	return tgt, nil
   266  }
   267  
   268  func (dbp *nativeProcess) handlePtraceFuncs() {
   269  	// We must ensure here that we are running on the same thread during
   270  	// while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects
   271  	// all commands after PTRACE_ATTACH to come from the same thread.
   272  	runtime.LockOSThread()
   273  
   274  	// Leaving the OS thread locked currently leads to segfaults in the
   275  	// Go runtime while running on FreeBSD and OpenBSD:
   276  	//   https://github.com/golang/go/issues/52394
   277  	if runtime.GOOS == "freebsd" || runtime.GOOS == "openbsd" {
   278  		defer runtime.UnlockOSThread()
   279  	}
   280  
   281  	for fn := range dbp.ptraceChan {
   282  		fn()
   283  		dbp.ptraceDoneChan <- nil
   284  	}
   285  }
   286  
   287  func (dbp *nativeProcess) execPtraceFunc(fn func()) {
   288  	dbp.ptraceChan <- fn
   289  	<-dbp.ptraceDoneChan
   290  }
   291  
   292  func (dbp *nativeProcess) postExit() {
   293  	dbp.exited = true
   294  	close(dbp.ptraceChan)
   295  	close(dbp.ptraceDoneChan)
   296  	dbp.bi.Close()
   297  	if dbp.ctty != nil {
   298  		dbp.ctty.Close()
   299  	}
   300  	dbp.os.Close()
   301  }
   302  
   303  func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uint64) error {
   304  	_, err := thread.WriteMemory(addr, dbp.bi.Arch.BreakpointInstruction())
   305  	return err
   306  }
   307  
   308  func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
   309  	toclose := []*os.File{}
   310  
   311  	if redirects[0] != "" {
   312  		stdin, err = os.Open(redirects[0])
   313  		if err != nil {
   314  			return nil, nil, nil, nil, err
   315  		}
   316  		toclose = append(toclose, stdin)
   317  	} else if foreground {
   318  		stdin = os.Stdin
   319  	}
   320  
   321  	create := func(path string, dflt *os.File) *os.File {
   322  		if path == "" {
   323  			return dflt
   324  		}
   325  		var f *os.File
   326  		f, err = os.Create(path)
   327  		if f != nil {
   328  			toclose = append(toclose, f)
   329  		}
   330  		return f
   331  	}
   332  
   333  	stdout = create(redirects[1], os.Stdout)
   334  	if err != nil {
   335  		return nil, nil, nil, nil, err
   336  	}
   337  
   338  	stderr = create(redirects[2], os.Stderr)
   339  	if err != nil {
   340  		return nil, nil, nil, nil, err
   341  	}
   342  
   343  	closefn = func() {
   344  		for _, f := range toclose {
   345  			_ = f.Close()
   346  		}
   347  	}
   348  
   349  	return stdin, stdout, stderr, closefn, nil
   350  }