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 }