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