gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/native/proc_linux.go (about) 1 package native 2 3 import ( 4 "bufio" 5 "bytes" 6 "debug/elf" 7 "errors" 8 "fmt" 9 "os" 10 "os/exec" 11 "os/signal" 12 "path/filepath" 13 "regexp" 14 "strconv" 15 "strings" 16 "syscall" 17 "time" 18 19 sys "golang.org/x/sys/unix" 20 21 "gitlab.com/Raven-IO/raven-delve/pkg/logflags" 22 "gitlab.com/Raven-IO/raven-delve/pkg/proc" 23 "gitlab.com/Raven-IO/raven-delve/pkg/proc/internal/ebpf" 24 "gitlab.com/Raven-IO/raven-delve/pkg/proc/linutil" 25 26 isatty "github.com/mattn/go-isatty" 27 ) 28 29 // Process statuses 30 const ( 31 statusSleeping = 'S' 32 statusRunning = 'R' 33 statusTraceStop = 't' 34 statusZombie = 'Z' 35 36 // Kernel 2.6 has TraceStop as T 37 // TODO(derekparker) Since this means something different based on the 38 // version of the kernel ('T' is job control stop on modern 3.x+ kernels) we 39 // may want to differentiate at some point. 40 statusTraceStopT = 'T' 41 42 personalityGetPersonality = 0xffffffff // argument to pass to personality syscall to get the current personality 43 _ADDR_NO_RANDOMIZE = 0x0040000 // ADDR_NO_RANDOMIZE linux constant 44 ) 45 46 // osProcessDetails contains Linux specific 47 // process details. 48 type osProcessDetails struct { 49 comm string 50 51 ebpf *ebpf.EBPFContext 52 } 53 54 func (os *osProcessDetails) Close() { 55 if os.ebpf != nil { 56 os.ebpf.Close() 57 } 58 } 59 60 // Launch creates and begins debugging a new process. First entry in 61 // `cmd` is the program to run, and then rest are the arguments 62 // to be supplied to that process. `wd` is working directory of the program. 63 // If the DWARF information cannot be found in the binary, Delve will look 64 // for external debug files in the directories passed in. 65 func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) { 66 var ( 67 process *exec.Cmd 68 err error 69 ) 70 71 foreground := flags&proc.LaunchForeground != 0 72 73 stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, foreground) 74 if err != nil { 75 return nil, err 76 } 77 78 if stdin == nil || !isatty.IsTerminal(stdin.Fd()) { 79 // exec.(*Process).Start will fail if we try to send a process to 80 // foreground but we are not attached to a terminal. 81 foreground = false 82 } 83 84 dbp := newProcess(0) 85 defer func() { 86 if err != nil && dbp.pid != 0 { 87 _ = detachWithoutGroup(dbp, true) 88 } 89 }() 90 dbp.execPtraceFunc(func() { 91 if flags&proc.LaunchDisableASLR != 0 { 92 oldPersonality, _, err := syscall.Syscall(sys.SYS_PERSONALITY, personalityGetPersonality, 0, 0) 93 if err == syscall.Errno(0) { 94 newPersonality := oldPersonality | _ADDR_NO_RANDOMIZE 95 syscall.Syscall(sys.SYS_PERSONALITY, newPersonality, 0, 0) 96 defer syscall.Syscall(sys.SYS_PERSONALITY, oldPersonality, 0, 0) 97 } 98 } 99 100 process = exec.Command(cmd[0]) 101 process.Args = cmd 102 process.Stdin = stdin 103 process.Stdout = stdout 104 process.Stderr = stderr 105 process.SysProcAttr = &syscall.SysProcAttr{ 106 Ptrace: true, 107 Setpgid: true, 108 Foreground: foreground, 109 } 110 if foreground { 111 signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) 112 } 113 if tty != "" { 114 dbp.ctty, err = attachProcessToTTY(process, tty) 115 if err != nil { 116 return 117 } 118 } 119 if wd != "" { 120 process.Dir = wd 121 } 122 err = process.Start() 123 }) 124 closefn() 125 if err != nil { 126 return nil, err 127 } 128 dbp.pid = process.Process.Pid 129 dbp.childProcess = true 130 _, _, err = dbp.wait(process.Process.Pid, 0) 131 if err != nil { 132 return nil, fmt.Errorf("waiting for target execve failed: %s", err) 133 } 134 tgt, err := dbp.initialize(cmd[0], debugInfoDirs) 135 if err != nil { 136 return nil, err 137 } 138 return tgt, nil 139 } 140 141 // Attach to an existing process with the given PID. Once attached, if 142 // the DWARF information cannot be found in the binary, Delve will look 143 // for external debug files in the directories passed in. 144 func Attach(pid int, waitFor *proc.WaitFor, debugInfoDirs []string) (*proc.TargetGroup, error) { 145 if waitFor.Valid() { 146 var err error 147 pid, err = WaitFor(waitFor) 148 if err != nil { 149 return nil, err 150 } 151 } 152 153 dbp := newProcess(pid) 154 155 var err error 156 dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) }) 157 if err != nil { 158 return nil, err 159 } 160 _, _, err = dbp.wait(dbp.pid, 0) 161 if err != nil { 162 return nil, err 163 } 164 165 tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs) 166 if err != nil { 167 _ = detachWithoutGroup(dbp, false) 168 return nil, err 169 } 170 171 // ElfUpdateSharedObjects can only be done after we initialize because it 172 // needs an initialized BinaryInfo object to work. 173 err = linutil.ElfUpdateSharedObjects(dbp) 174 if err != nil { 175 return nil, err 176 } 177 return tgt, nil 178 } 179 180 func isProcDir(name string) bool { 181 for _, ch := range name { 182 if ch < '0' || ch > '9' { 183 return false 184 } 185 } 186 return true 187 } 188 189 func waitForSearchProcess(pfx string, seen map[int]struct{}) (int, error) { 190 log := logflags.DebuggerLogger() 191 des, err := os.ReadDir("/proc") 192 if err != nil { 193 log.Errorf("error reading proc: %v", err) 194 return 0, nil 195 } 196 for _, de := range des { 197 if !de.IsDir() { 198 continue 199 } 200 name := de.Name() 201 if !isProcDir(name) { 202 continue 203 } 204 pid, _ := strconv.Atoi(name) 205 if _, isseen := seen[pid]; isseen { 206 continue 207 } 208 seen[pid] = struct{}{} 209 buf, err := os.ReadFile(filepath.Join("/proc", name, "cmdline")) 210 if err != nil { 211 // probably we just don't have permissions 212 continue 213 } 214 for i := range buf { 215 if buf[i] == 0 { 216 buf[i] = ' ' 217 } 218 } 219 log.Debugf("waitfor: new process %q", string(buf)) 220 if strings.HasPrefix(string(buf), pfx) { 221 return pid, nil 222 } 223 } 224 return 0, nil 225 } 226 227 func initialize(dbp *nativeProcess) (string, error) { 228 comm, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid)) 229 if err == nil { 230 // removes newline character 231 comm = bytes.TrimSuffix(comm, []byte("\n")) 232 } 233 234 if comm == nil || len(comm) <= 0 { 235 stat, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid)) 236 if err != nil { 237 return "", fmt.Errorf("could not read proc stat: %v", err) 238 } 239 expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid) 240 rexp, err := regexp.Compile(expr) 241 if err != nil { 242 return "", fmt.Errorf("regexp compile error: %v", err) 243 } 244 match := rexp.FindSubmatch(stat) 245 if match == nil { 246 return "", fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid) 247 } 248 comm = match[1] 249 } 250 dbp.os.comm = strings.ReplaceAll(string(comm), "%", "%%") 251 252 return getCmdLine(dbp.pid), nil 253 } 254 255 func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { 256 if dbp.os.ebpf == nil { 257 return nil 258 } 259 return dbp.os.ebpf.GetBufferedTracepoints() 260 } 261 262 // kill kills the target process. 263 func (procgrp *processGroup) kill(dbp *nativeProcess) error { 264 if ok, _ := dbp.Valid(); !ok { 265 return nil 266 } 267 if !dbp.threads[dbp.pid].Stopped() { 268 return errors.New("process must be stopped in order to kill it") 269 } 270 if err := sys.Kill(-dbp.pid, sys.SIGKILL); err != nil { 271 return errors.New("could not deliver signal " + err.Error()) 272 } 273 // wait for other threads first or the thread group leader (dbp.pid) will never exit. 274 for threadID := range dbp.threads { 275 if threadID != dbp.pid { 276 dbp.wait(threadID, 0) 277 } 278 } 279 for { 280 wpid, status, err := dbp.wait(dbp.pid, 0) 281 if err != nil { 282 return err 283 } 284 if wpid == dbp.pid && status != nil && status.Signaled() && status.Signal() == sys.SIGKILL { 285 dbp.postExit() 286 return err 287 } 288 } 289 } 290 291 func (dbp *nativeProcess) requestManualStop() (err error) { 292 return sys.Kill(dbp.pid, sys.SIGTRAP) 293 } 294 295 const ( 296 ptraceOptionsNormal = syscall.PTRACE_O_TRACECLONE 297 ptraceOptionsFollowExec = syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEVFORK | syscall.PTRACE_O_TRACEEXEC 298 ) 299 300 // Attach to a newly created thread, and store that thread in our list of 301 // known threads. 302 func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) { 303 if thread, ok := dbp.threads[tid]; ok { 304 return thread, nil 305 } 306 307 ptraceOptions := ptraceOptionsNormal 308 if dbp.followExec { 309 ptraceOptions = ptraceOptionsFollowExec 310 } 311 312 var err error 313 if attach { 314 dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) }) 315 if err != nil && err != sys.EPERM { 316 // Do not return err if err == EPERM, 317 // we may already be tracing this thread due to 318 // PTRACE_O_TRACECLONE. We will surely blow up later 319 // if we truly don't have permissions. 320 return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err) 321 } 322 pid, status, err := dbp.waitFast(tid) 323 if err != nil { 324 return nil, err 325 } 326 if status.Exited() { 327 return nil, fmt.Errorf("thread already exited %d", pid) 328 } 329 } 330 331 dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) }) 332 if err == syscall.ESRCH { 333 if _, _, err = dbp.waitFast(tid); err != nil { 334 return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) 335 } 336 dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) }) 337 if err == syscall.ESRCH { 338 return nil, err 339 } 340 if err != nil { 341 return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err) 342 } 343 } 344 345 dbp.threads[tid] = &nativeThread{ 346 ID: tid, 347 dbp: dbp, 348 os: new(osSpecificDetails), 349 } 350 if dbp.memthread == nil { 351 dbp.memthread = dbp.threads[tid] 352 } 353 for _, bp := range dbp.Breakpoints().M { 354 if bp.WatchType != 0 { 355 err := dbp.threads[tid].writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex) 356 if err != nil { 357 return nil, err 358 } 359 } 360 } 361 return dbp.threads[tid], nil 362 } 363 364 func (dbp *nativeProcess) updateThreadList() error { 365 tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.pid)) 366 for _, tidpath := range tids { 367 tidstr := filepath.Base(tidpath) 368 tid, err := strconv.Atoi(tidstr) 369 if err != nil { 370 return err 371 } 372 if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil { 373 return err 374 } 375 } 376 return linutil.ElfUpdateSharedObjects(dbp) 377 } 378 379 func findExecutable(path string, pid int) string { 380 if path == "" { 381 path = fmt.Sprintf("/proc/%d/exe", pid) 382 } 383 return path 384 } 385 386 func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { 387 return trapWaitInternal(procgrp, pid, 0) 388 } 389 390 type trapWaitOptions uint8 391 392 const ( 393 trapWaitHalt trapWaitOptions = 1 << iota 394 trapWaitNohang 395 trapWaitDontCallExitGuard 396 ) 397 398 func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (*nativeThread, error) { 399 var waitdbp *nativeProcess = nil 400 if len(procgrp.procs) == 1 { 401 // Note that waitdbp is only used to call (*nativeProcess).wait which will 402 // behave correctly if waitdbp == nil. 403 waitdbp = procgrp.procs[0] 404 } 405 406 halt := options&trapWaitHalt != 0 407 for { 408 wopt := 0 409 if options&trapWaitNohang != 0 { 410 wopt = sys.WNOHANG 411 } 412 wpid, status, err := waitdbp.wait(pid, wopt) 413 if err != nil { 414 return nil, fmt.Errorf("wait err %s %d", err, pid) 415 } 416 if wpid == 0 { 417 if options&trapWaitNohang != 0 { 418 return nil, nil 419 } 420 continue 421 } 422 dbp := procgrp.procForThread(wpid) 423 var th *nativeThread 424 if dbp != nil { 425 var ok bool 426 th, ok = dbp.threads[wpid] 427 if ok { 428 th.Status = (*waitStatus)(status) 429 } 430 } else { 431 dbp = procgrp.procs[0] 432 } 433 if status.Exited() { 434 if wpid == dbp.pid { 435 dbp.postExit() 436 if procgrp.numValid() == 0 { 437 return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} 438 } 439 if halt { 440 return nil, nil 441 } 442 continue 443 } 444 delete(dbp.threads, wpid) 445 continue 446 } 447 if status.Signaled() { 448 // Signaled means the thread was terminated due to a signal. 449 if wpid == dbp.pid { 450 dbp.postExit() 451 if procgrp.numValid() == 0 { 452 return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} 453 } 454 if halt { 455 return nil, nil 456 } 457 continue 458 } 459 // does this ever happen? 460 delete(dbp.threads, wpid) 461 continue 462 } 463 if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_CLONE || status.TrapCause() == sys.PTRACE_EVENT_VFORK) { 464 // A traced thread has cloned a new thread, grab the pid and 465 // add it to our list of traced threads. 466 // If TrapCause() is sys.PTRACE_EVENT_VFORK this is actually a new 467 // process, but treat it as a normal thread until exec happens, so that 468 // we can initialize the new process normally. 469 var cloned uint 470 dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) }) 471 if err != nil { 472 if err == sys.ESRCH { 473 // thread died while we were adding it 474 continue 475 } 476 return nil, fmt.Errorf("could not get event message: %s", err) 477 } 478 th, err = dbp.addThread(int(cloned), false) 479 if err != nil { 480 if err == sys.ESRCH { 481 // thread died while we were adding it 482 delete(dbp.threads, int(cloned)) 483 continue 484 } 485 return nil, err 486 } 487 if halt { 488 th.os.running = false 489 dbp.threads[int(wpid)].os.running = false 490 return nil, nil 491 } 492 if err = th.resume(); err != nil { 493 if err == sys.ESRCH { 494 // thread died while we were adding it 495 delete(dbp.threads, th.ID) 496 continue 497 } 498 return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err) 499 } 500 if err = dbp.threads[int(wpid)].resume(); err != nil { 501 if err != sys.ESRCH { 502 return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err) 503 } 504 } 505 continue 506 } 507 if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_EXEC) { 508 // A thread called exec and we now have a new process. Retrieve the 509 // thread ID of the exec'ing thread with PtraceGetEventMsg to remove it 510 // and create a new nativeProcess object to track the new process. 511 var tid uint 512 dbp.execPtraceFunc(func() { tid, err = sys.PtraceGetEventMsg(wpid) }) 513 if err == nil { 514 delete(dbp.threads, int(tid)) 515 } 516 dbp = newChildProcess(procgrp.procs[0], wpid) 517 dbp.followExec = true 518 cmdline, _ := dbp.initializeBasic() 519 tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched, cmdline) 520 if err != nil { 521 return nil, err 522 } 523 if halt { 524 return nil, nil 525 } 526 if tgt != nil { 527 // If tgt is nil we decided we are not interested in debugging this 528 // process, and we have already detached from it. 529 err = dbp.threads[dbp.pid].resume() 530 if err != nil { 531 return nil, err 532 } 533 } 534 //TODO(aarzilli): if we want to give users the ability to stop the target 535 //group on exec here is where we should return 536 continue 537 } 538 if th == nil { 539 // Sometimes we get an unknown thread, ignore it? 540 continue 541 } 542 if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) { 543 th.os.running = false 544 if status.StopSignal() == sys.SIGTRAP { 545 th.os.setbp = true 546 } 547 return th, nil 548 } 549 550 // TODO(dp) alert user about unexpected signals here. 551 if halt && !th.os.running { 552 // We are trying to stop the process, queue this signal to be delivered 553 // to the thread when we resume. 554 // Do not do this for threads that were running because we sent them a 555 // STOP signal and we need to observe it so we don't mistakenly deliver 556 // it later. 557 th.os.delayedSignal = int(status.StopSignal()) 558 th.os.running = false 559 return th, nil 560 } else if err := th.resumeWithSig(int(status.StopSignal())); err != nil { 561 if err != sys.ESRCH { 562 return nil, err 563 } 564 // do the same thing we do if a thread quit 565 if wpid == dbp.pid { 566 exitStatus := 0 567 if procgrp.numValid() == 1 { 568 // try to recover the real exit status using waitpid 569 for { 570 wpid2, status2, err := dbp.wait(-1, sys.WNOHANG) 571 if wpid2 <= 0 || err != nil { 572 break 573 } 574 if status2.Exited() { 575 exitStatus = status2.ExitStatus() 576 } 577 } 578 579 } 580 dbp.postExit() 581 if procgrp.numValid() == 0 { 582 return nil, proc.ErrProcessExited{Pid: wpid, Status: exitStatus} 583 } 584 continue 585 } 586 delete(dbp.threads, wpid) 587 } 588 } 589 } 590 591 func status(pid int, comm string) rune { 592 f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid)) 593 if err != nil { 594 return '\000' 595 } 596 defer f.Close() 597 rd := bufio.NewReader(f) 598 599 var ( 600 p int 601 state rune 602 ) 603 604 // The second field of /proc/pid/stat is the name of the task in parentheses. 605 // The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters 606 // Since both parenthesis and spaces can appear inside the name of the task and no escaping happens we need to read the name of the executable first 607 // See: include/linux/sched.c:315 and include/linux/sched.c:1510 608 _, _ = fmt.Fscanf(rd, "%d ("+comm+") %c", &p, &state) 609 return state 610 } 611 612 // waitFast is like wait but does not handle process-exit correctly 613 func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) { 614 var s sys.WaitStatus 615 wpid, err := sys.Wait4(pid, &s, sys.WALL, nil) 616 return wpid, &s, err 617 } 618 619 func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { 620 var s sys.WaitStatus 621 if (dbp == nil) || (pid != dbp.pid) || (options != 0) { 622 wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil) 623 return wpid, &s, err 624 } 625 // If we call wait4/waitpid on a thread that is the leader of its group, 626 // with options == 0, while ptracing and the thread leader has exited leaving 627 // zombies of its own then waitpid hangs forever this is apparently intended 628 // behaviour in the linux kernel because it's just so convenient. 629 // Therefore we call wait4 in a loop with WNOHANG, sleeping a while between 630 // calls and exiting when either wait4 succeeds or we find out that the thread 631 // has become a zombie. 632 // References: 633 // https://sourceware.org/bugzilla/show_bug.cgi?id=12702 634 // https://sourceware.org/bugzilla/show_bug.cgi?id=10095 635 // https://sourceware.org/bugzilla/attachment.cgi?id=5685 636 for { 637 wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil) 638 if err != nil { 639 return 0, nil, err 640 } 641 if wpid != 0 { 642 return wpid, &s, err 643 } 644 if status(pid, dbp.os.comm) == statusZombie { 645 return pid, nil, nil 646 } 647 time.Sleep(200 * time.Millisecond) 648 } 649 } 650 651 func exitGuard(dbp *nativeProcess, procgrp *processGroup, err error) error { 652 if err != sys.ESRCH { 653 return err 654 } 655 if status(dbp.pid, dbp.os.comm) == statusZombie { 656 _, err := trapWaitInternal(procgrp, -1, trapWaitDontCallExitGuard) 657 return err 658 } 659 660 return err 661 } 662 663 func (procgrp *processGroup) resume() error { 664 // all threads stopped over a breakpoint are made to step over it 665 for _, dbp := range procgrp.procs { 666 if valid, _ := dbp.Valid(); valid { 667 for _, thread := range dbp.threads { 668 if thread.CurrentBreakpoint.Breakpoint != nil { 669 if err := procgrp.stepInstruction(thread); err != nil { 670 return err 671 } 672 thread.CurrentBreakpoint.Clear() 673 } 674 } 675 } 676 } 677 // everything is resumed 678 for _, dbp := range procgrp.procs { 679 if valid, _ := dbp.Valid(); valid { 680 for _, thread := range dbp.threads { 681 if err := thread.resume(); err != nil && err != sys.ESRCH { 682 return err 683 } 684 } 685 } 686 } 687 return nil 688 } 689 690 // stop stops all running threads and sets breakpoints 691 func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { 692 if procgrp.numValid() == 0 { 693 return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid} 694 } 695 696 for _, dbp := range procgrp.procs { 697 if ok, _ := dbp.Valid(); !ok { 698 continue 699 } 700 for _, th := range dbp.threads { 701 th.os.setbp = false 702 } 703 } 704 trapthread.os.setbp = true 705 706 // check if any other thread simultaneously received a SIGTRAP 707 for { 708 th, err := trapWaitInternal(procgrp, -1, trapWaitNohang) 709 if err != nil { 710 return nil, exitGuard(procgrp.procs[0], procgrp, err) 711 } 712 if th == nil { 713 break 714 } 715 } 716 717 // stop all threads that are still running 718 for _, dbp := range procgrp.procs { 719 if ok, _ := dbp.Valid(); !ok { 720 continue 721 } 722 for _, th := range dbp.threads { 723 if th.os.running { 724 if err := th.stop(); err != nil { 725 if err == sys.ESRCH { 726 // thread exited 727 delete(dbp.threads, th.ID) 728 } else { 729 return nil, exitGuard(dbp, procgrp, err) 730 } 731 } 732 } 733 } 734 } 735 736 // wait for all threads to stop 737 for { 738 allstopped := true 739 for _, dbp := range procgrp.procs { 740 if ok, _ := dbp.Valid(); !ok { 741 continue 742 } 743 for _, th := range dbp.threads { 744 if th.os.running { 745 allstopped = false 746 break 747 } 748 } 749 } 750 if allstopped { 751 break 752 } 753 _, err := trapWaitInternal(procgrp, -1, trapWaitHalt) 754 if err != nil { 755 return nil, err 756 } 757 } 758 759 switchTrapthread := false 760 761 for _, dbp := range procgrp.procs { 762 if ok, _ := dbp.Valid(); !ok { 763 continue 764 } 765 err := stop1(cctx, dbp, trapthread, &switchTrapthread) 766 if err != nil { 767 return nil, err 768 } 769 } 770 771 if switchTrapthread { 772 trapthreadID := trapthread.ID 773 trapthread = nil 774 for _, dbp := range procgrp.procs { 775 if ok, _ := dbp.Valid(); !ok { 776 continue 777 } 778 for _, th := range dbp.threads { 779 if th.os.setbp && th.ThreadID() != trapthreadID { 780 return th, nil 781 } 782 } 783 } 784 } 785 786 return trapthread, nil 787 } 788 789 func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativeThread, switchTrapthread *bool) error { 790 if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { 791 return err 792 } 793 794 // set breakpoints on SIGTRAP threads 795 var err1 error 796 for _, th := range dbp.threads { 797 pc, _ := th.PC() 798 799 if !th.os.setbp && pc != th.os.phantomBreakpointPC { 800 // check if this could be a breakpoint hit anyway that the OS hasn't notified us about, yet. 801 if _, ok := dbp.FindBreakpoint(pc, dbp.BinInfo().Arch.BreakInstrMovesPC()); ok { 802 th.os.phantomBreakpointPC = pc 803 } 804 } 805 806 if pc != th.os.phantomBreakpointPC { 807 th.os.phantomBreakpointPC = 0 808 } 809 810 if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp { 811 if err := th.SetCurrentBreakpoint(true); err != nil { 812 err1 = err 813 continue 814 } 815 } 816 817 if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp && (th.Status != nil) && ((*sys.WaitStatus)(th.Status).StopSignal() == sys.SIGTRAP) && dbp.BinInfo().Arch.BreakInstrMovesPC() { 818 manualStop := false 819 if th.ThreadID() == trapthread.ThreadID() { 820 manualStop = cctx.GetManualStopRequested() 821 } 822 if !manualStop && th.os.phantomBreakpointPC == pc { 823 // Thread received a SIGTRAP but we don't have a breakpoint for it and 824 // it wasn't sent by a manual stop request. It's either a hardcoded 825 // breakpoint or a phantom breakpoint hit (a breakpoint that was hit but 826 // we have removed before we could receive its signal). Check if it is a 827 // hardcoded breakpoint, otherwise rewind the thread. 828 isHardcodedBreakpoint := false 829 pc, _ := th.PC() 830 for _, bpinstr := range [][]byte{ 831 dbp.BinInfo().Arch.BreakpointInstruction(), 832 dbp.BinInfo().Arch.AltBreakpointInstruction()} { 833 if bpinstr == nil { 834 continue 835 } 836 buf := make([]byte, len(bpinstr)) 837 _, _ = th.ReadMemory(buf, pc-uint64(len(buf))) 838 if bytes.Equal(buf, bpinstr) { 839 isHardcodedBreakpoint = true 840 break 841 } 842 } 843 if !isHardcodedBreakpoint { 844 // phantom breakpoint hit 845 _ = th.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction()))) 846 th.os.setbp = false 847 if trapthread.ThreadID() == th.ThreadID() { 848 // Will switch to a different thread for trapthread because we don't 849 // want pkg/proc to believe that this thread was stopped by a 850 // hardcoded breakpoint. 851 *switchTrapthread = true 852 } 853 } 854 } 855 } 856 } 857 return err1 858 } 859 860 func (procgrp *processGroup) detachChild(dbp *nativeProcess) error { 861 return procgrp.Detach(dbp.pid, false) 862 } 863 864 func (dbp *nativeProcess) detach(kill bool) error { 865 for threadID := range dbp.threads { 866 err := ptraceDetach(threadID, 0) 867 if err != nil { 868 return err 869 } 870 } 871 if kill { 872 return nil 873 } 874 // For some reason the process will sometimes enter stopped state after a 875 // detach, this doesn't happen immediately either. 876 // We have to wait a bit here, then check if the main thread is stopped and 877 // SIGCONT it if it is. 878 time.Sleep(50 * time.Millisecond) 879 if s := status(dbp.pid, dbp.os.comm); s == 'T' { 880 _ = sys.Kill(dbp.pid, sys.SIGCONT) 881 } 882 return nil 883 } 884 885 // EntryPoint will return the process entry point address, useful for 886 // debugging PIEs. 887 func (dbp *nativeProcess) EntryPoint() (uint64, error) { 888 auxvbuf, err := os.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid)) 889 if err != nil { 890 return 0, fmt.Errorf("could not read auxiliary vector: %v", err) 891 } 892 893 return linutil.EntryPointFromAuxv(auxvbuf, dbp.bi.Arch.PtrSize()), nil 894 } 895 896 func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { 897 // Lazily load and initialize the BPF program upon request to set a uprobe. 898 if dbp.os.ebpf == nil { 899 var err error 900 dbp.os.ebpf, err = ebpf.LoadEBPFTracingProgram(dbp.bi.Images[0].Path) 901 if err != nil { 902 return err 903 } 904 } 905 906 // We only allow up to 12 args for a BPF probe. 907 // 6 inputs + 6 outputs. 908 // Return early if we have more. 909 if len(args) > 12 { 910 return errors.New("too many arguments in traced function, max is 12 input+return") 911 } 912 913 fns := dbp.bi.LookupFunc()[fnName] 914 if len(fns) != 1 { 915 return &proc.ErrFunctionNotFound{FuncName: fnName} 916 } 917 fn := fns[0] 918 919 offset, err := dbp.BinInfo().GStructOffset(dbp.Memory()) 920 if err != nil { 921 return err 922 } 923 key := fn.Entry 924 err = dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, offset, false) 925 if err != nil { 926 return err 927 } 928 929 debugname := dbp.bi.Images[0].Path 930 931 // First attach a uprobe at all return addresses. We do this instead of using a uretprobe 932 // for two reasons: 933 // 1. uretprobes do not play well with Go 934 // 2. uretprobes seem to not restore the function return addr on the stack when removed, destroying any 935 // kind of workaround we could come up with. 936 // TODO(derekparker): this whole thing could likely be optimized a bit. 937 img := dbp.BinInfo().PCToImage(fn.Entry) 938 f, err := elf.Open(img.Path) 939 if err != nil { 940 return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) 941 } 942 943 var regs proc.Registers 944 mem := dbp.Memory() 945 regs, _ = dbp.memthread.Registers() 946 instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End) 947 if err != nil { 948 return err 949 } 950 951 var addrs []uint64 952 for _, instruction := range instructions { 953 if instruction.IsRet() { 954 addrs = append(addrs, instruction.Loc.PC) 955 } 956 } 957 addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...) 958 for _, addr := range addrs { 959 err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, offset, true) 960 if err != nil { 961 return err 962 } 963 off, err := ebpf.AddressToOffset(f, addr) 964 if err != nil { 965 return err 966 } 967 err = dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off) 968 if err != nil { 969 return err 970 } 971 } 972 973 off, err := ebpf.AddressToOffset(f, fn.Entry) 974 if err != nil { 975 return err 976 } 977 978 return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off) 979 } 980 981 // FollowExec enables (or disables) follow exec mode 982 func (dbp *nativeProcess) FollowExec(v bool) error { 983 dbp.followExec = v 984 ptraceOptions := ptraceOptionsNormal 985 if dbp.followExec { 986 ptraceOptions = ptraceOptionsFollowExec 987 } 988 var err error 989 dbp.execPtraceFunc(func() { 990 for tid := range dbp.threads { 991 err = syscall.PtraceSetOptions(tid, ptraceOptions) 992 if err != nil { 993 return 994 } 995 } 996 }) 997 return err 998 } 999 1000 func killProcess(pid int) error { 1001 return sys.Kill(pid, sys.SIGINT) 1002 } 1003 1004 func getCmdLine(pid int) string { 1005 buf, _ := os.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pid)) 1006 args := strings.SplitN(string(buf), "\x00", -1) 1007 for i := range args { 1008 if strings.Contains(args[i], " ") { 1009 args[i] = strconv.Quote(args[i]) 1010 } 1011 } 1012 if len(args) > 0 && args[len(args)-1] == "" { 1013 args = args[:len(args)-1] 1014 } 1015 return strings.Join(args, " ") 1016 }