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 }