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 }