github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/proc" 23 "github.com/undoio/delve/pkg/proc/internal/ebpf" 24 "github.com/undoio/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, redirects [3]string) (*proc.Target, 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(redirects, 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 _ = dbp.Detach(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, debugInfoDirs []string) (*proc.Target, error) { 145 dbp := newProcess(pid) 146 147 var err error 148 dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) }) 149 if err != nil { 150 return nil, err 151 } 152 _, _, err = dbp.wait(dbp.pid, 0) 153 if err != nil { 154 return nil, err 155 } 156 157 tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs) 158 if err != nil { 159 _ = dbp.Detach(false) 160 return nil, err 161 } 162 163 // ElfUpdateSharedObjects can only be done after we initialize because it 164 // needs an initialized BinaryInfo object to work. 165 err = linutil.ElfUpdateSharedObjects(dbp) 166 if err != nil { 167 return nil, err 168 } 169 return tgt, nil 170 } 171 172 func initialize(dbp *nativeProcess) error { 173 comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/comm", dbp.pid)) 174 if err == nil { 175 // removes newline character 176 comm = bytes.TrimSuffix(comm, []byte("\n")) 177 } 178 179 if comm == nil || len(comm) <= 0 { 180 stat, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", dbp.pid)) 181 if err != nil { 182 return fmt.Errorf("could not read proc stat: %v", err) 183 } 184 expr := fmt.Sprintf("%d\\s*\\((.*)\\)", dbp.pid) 185 rexp, err := regexp.Compile(expr) 186 if err != nil { 187 return fmt.Errorf("regexp compile error: %v", err) 188 } 189 match := rexp.FindSubmatch(stat) 190 if match == nil { 191 return fmt.Errorf("no match found using regexp '%s' in /proc/%d/stat", expr, dbp.pid) 192 } 193 comm = match[1] 194 } 195 dbp.os.comm = strings.ReplaceAll(string(comm), "%", "%%") 196 197 return nil 198 } 199 200 func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { 201 if dbp.os.ebpf == nil { 202 return nil 203 } 204 return dbp.os.ebpf.GetBufferedTracepoints() 205 } 206 207 // kill kills the target process. 208 func (dbp *nativeProcess) kill() error { 209 if dbp.exited { 210 return nil 211 } 212 if !dbp.threads[dbp.pid].Stopped() { 213 return errors.New("process must be stopped in order to kill it") 214 } 215 if err := sys.Kill(-dbp.pid, sys.SIGKILL); err != nil { 216 return errors.New("could not deliver signal " + err.Error()) 217 } 218 // wait for other threads first or the thread group leader (dbp.pid) will never exit. 219 for threadID := range dbp.threads { 220 if threadID != dbp.pid { 221 dbp.wait(threadID, 0) 222 } 223 } 224 for { 225 wpid, status, err := dbp.wait(dbp.pid, 0) 226 if err != nil { 227 return err 228 } 229 if wpid == dbp.pid && status != nil && status.Signaled() && status.Signal() == sys.SIGKILL { 230 dbp.postExit() 231 return err 232 } 233 } 234 } 235 236 func (dbp *nativeProcess) requestManualStop() (err error) { 237 return sys.Kill(dbp.pid, sys.SIGTRAP) 238 } 239 240 // Attach to a newly created thread, and store that thread in our list of 241 // known threads. 242 func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) { 243 if thread, ok := dbp.threads[tid]; ok { 244 return thread, nil 245 } 246 247 var err error 248 if attach { 249 dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) }) 250 if err != nil && err != sys.EPERM { 251 // Do not return err if err == EPERM, 252 // we may already be tracing this thread due to 253 // PTRACE_O_TRACECLONE. We will surely blow up later 254 // if we truly don't have permissions. 255 return nil, fmt.Errorf("could not attach to new thread %d %s", tid, err) 256 } 257 pid, status, err := dbp.waitFast(tid) 258 if err != nil { 259 return nil, err 260 } 261 if status.Exited() { 262 return nil, fmt.Errorf("thread already exited %d", pid) 263 } 264 } 265 266 dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) 267 if err == syscall.ESRCH { 268 if _, _, err = dbp.waitFast(tid); err != nil { 269 return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) 270 } 271 dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) 272 if err == syscall.ESRCH { 273 return nil, err 274 } 275 if err != nil { 276 return nil, fmt.Errorf("could not set options for new traced thread %d %s", tid, err) 277 } 278 } 279 280 dbp.threads[tid] = &nativeThread{ 281 ID: tid, 282 dbp: dbp, 283 os: new(osSpecificDetails), 284 } 285 if dbp.memthread == nil { 286 dbp.memthread = dbp.threads[tid] 287 } 288 for _, bp := range dbp.Breakpoints().M { 289 if bp.WatchType != 0 { 290 err := dbp.threads[tid].writeHardwareBreakpoint(bp.Addr, bp.WatchType, bp.HWBreakIndex) 291 if err != nil { 292 return nil, err 293 } 294 } 295 } 296 return dbp.threads[tid], nil 297 } 298 299 func (dbp *nativeProcess) updateThreadList() error { 300 tids, _ := filepath.Glob(fmt.Sprintf("/proc/%d/task/*", dbp.pid)) 301 for _, tidpath := range tids { 302 tidstr := filepath.Base(tidpath) 303 tid, err := strconv.Atoi(tidstr) 304 if err != nil { 305 return err 306 } 307 if _, err := dbp.addThread(tid, tid != dbp.pid); err != nil { 308 return err 309 } 310 } 311 return linutil.ElfUpdateSharedObjects(dbp) 312 } 313 314 func findExecutable(path string, pid int) string { 315 if path == "" { 316 path = fmt.Sprintf("/proc/%d/exe", pid) 317 } 318 return path 319 } 320 321 func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { 322 return dbp.trapWaitInternal(pid, 0) 323 } 324 325 type trapWaitOptions uint8 326 327 const ( 328 trapWaitHalt trapWaitOptions = 1 << iota 329 trapWaitNohang 330 trapWaitDontCallExitGuard 331 ) 332 333 func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*nativeThread, error) { 334 halt := options&trapWaitHalt != 0 335 for { 336 wopt := 0 337 if options&trapWaitNohang != 0 { 338 wopt = sys.WNOHANG 339 } 340 wpid, status, err := dbp.wait(pid, wopt) 341 if err != nil { 342 return nil, fmt.Errorf("wait err %s %d", err, pid) 343 } 344 if wpid == 0 { 345 if options&trapWaitNohang != 0 { 346 return nil, nil 347 } 348 continue 349 } 350 th, ok := dbp.threads[wpid] 351 if ok { 352 th.Status = (*waitStatus)(status) 353 } 354 if status.Exited() { 355 if wpid == dbp.pid { 356 dbp.postExit() 357 return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} 358 } 359 delete(dbp.threads, wpid) 360 continue 361 } 362 if status.Signaled() { 363 // Signaled means the thread was terminated due to a signal. 364 if wpid == dbp.pid { 365 dbp.postExit() 366 return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} 367 } 368 // does this ever happen? 369 delete(dbp.threads, wpid) 370 continue 371 } 372 if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { 373 // A traced thread has cloned a new thread, grab the pid and 374 // add it to our list of traced threads. 375 var cloned uint 376 dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) }) 377 if err != nil { 378 if err == sys.ESRCH { 379 // thread died while we were adding it 380 continue 381 } 382 return nil, fmt.Errorf("could not get event message: %s", err) 383 } 384 th, err = dbp.addThread(int(cloned), false) 385 if err != nil { 386 if err == sys.ESRCH { 387 // thread died while we were adding it 388 delete(dbp.threads, int(cloned)) 389 continue 390 } 391 return nil, err 392 } 393 if halt { 394 th.os.running = false 395 dbp.threads[int(wpid)].os.running = false 396 return nil, nil 397 } 398 if err = th.Continue(); err != nil { 399 if err == sys.ESRCH { 400 // thread died while we were adding it 401 delete(dbp.threads, th.ID) 402 continue 403 } 404 return nil, fmt.Errorf("could not continue new thread %d %s", cloned, err) 405 } 406 if err = dbp.threads[int(wpid)].Continue(); err != nil { 407 if err != sys.ESRCH { 408 return nil, fmt.Errorf("could not continue existing thread %d %s", wpid, err) 409 } 410 } 411 continue 412 } 413 if th == nil { 414 // Sometimes we get an unknown thread, ignore it? 415 continue 416 } 417 if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) { 418 th.os.running = false 419 if status.StopSignal() == sys.SIGTRAP { 420 th.os.setbp = true 421 } 422 return th, nil 423 } 424 425 // TODO(dp) alert user about unexpected signals here. 426 if halt && !th.os.running { 427 // We are trying to stop the process, queue this signal to be delivered 428 // to the thread when we resume. 429 // Do not do this for threads that were running because we sent them a 430 // STOP signal and we need to observe it so we don't mistakenly deliver 431 // it later. 432 th.os.delayedSignal = int(status.StopSignal()) 433 th.os.running = false 434 return th, nil 435 } else if err := th.resumeWithSig(int(status.StopSignal())); err != nil { 436 if err != sys.ESRCH { 437 return nil, err 438 } 439 // do the same thing we do if a thread quit 440 if wpid == dbp.pid { 441 dbp.postExit() 442 return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} 443 } 444 delete(dbp.threads, wpid) 445 } 446 } 447 } 448 449 func status(pid int, comm string) rune { 450 f, err := os.Open(fmt.Sprintf("/proc/%d/stat", pid)) 451 if err != nil { 452 return '\000' 453 } 454 defer f.Close() 455 rd := bufio.NewReader(f) 456 457 var ( 458 p int 459 state rune 460 ) 461 462 // The second field of /proc/pid/stat is the name of the task in parenthesis. 463 // The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters 464 // 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 465 // See: include/linux/sched.c:315 and include/linux/sched.c:1510 466 _, _ = fmt.Fscanf(rd, "%d ("+comm+") %c", &p, &state) 467 return state 468 } 469 470 // waitFast is like wait but does not handle process-exit correctly 471 func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) { 472 var s sys.WaitStatus 473 wpid, err := sys.Wait4(pid, &s, sys.WALL, nil) 474 return wpid, &s, err 475 } 476 477 func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { 478 var s sys.WaitStatus 479 if (pid != dbp.pid) || (options != 0) { 480 wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil) 481 return wpid, &s, err 482 } 483 // If we call wait4/waitpid on a thread that is the leader of its group, 484 // with options == 0, while ptracing and the thread leader has exited leaving 485 // zombies of its own then waitpid hangs forever this is apparently intended 486 // behaviour in the linux kernel because it's just so convenient. 487 // Therefore we call wait4 in a loop with WNOHANG, sleeping a while between 488 // calls and exiting when either wait4 succeeds or we find out that the thread 489 // has become a zombie. 490 // References: 491 // https://sourceware.org/bugzilla/show_bug.cgi?id=12702 492 // https://sourceware.org/bugzilla/show_bug.cgi?id=10095 493 // https://sourceware.org/bugzilla/attachment.cgi?id=5685 494 for { 495 wpid, err := sys.Wait4(pid, &s, sys.WNOHANG|sys.WALL|options, nil) 496 if err != nil { 497 return 0, nil, err 498 } 499 if wpid != 0 { 500 return wpid, &s, err 501 } 502 if status(pid, dbp.os.comm) == statusZombie { 503 return pid, nil, nil 504 } 505 time.Sleep(200 * time.Millisecond) 506 } 507 } 508 509 func (dbp *nativeProcess) exitGuard(err error) error { 510 if err != sys.ESRCH { 511 return err 512 } 513 if status(dbp.pid, dbp.os.comm) == statusZombie { 514 _, err := dbp.trapWaitInternal(-1, trapWaitDontCallExitGuard) 515 return err 516 } 517 518 return err 519 } 520 521 func (dbp *nativeProcess) resume() error { 522 // all threads stopped over a breakpoint are made to step over it 523 for _, thread := range dbp.threads { 524 if thread.CurrentBreakpoint.Breakpoint != nil { 525 if err := thread.StepInstruction(); err != nil { 526 return err 527 } 528 thread.CurrentBreakpoint.Clear() 529 } 530 } 531 // everything is resumed 532 for _, thread := range dbp.threads { 533 if err := thread.resume(); err != nil && err != sys.ESRCH { 534 return err 535 } 536 } 537 return nil 538 } 539 540 // stop stops all running threads and sets breakpoints 541 func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { 542 if dbp.exited { 543 return nil, proc.ErrProcessExited{Pid: dbp.pid} 544 } 545 546 for _, th := range dbp.threads { 547 th.os.setbp = false 548 } 549 trapthread.os.setbp = true 550 551 // check if any other thread simultaneously received a SIGTRAP 552 for { 553 th, err := dbp.trapWaitInternal(-1, trapWaitNohang) 554 if err != nil { 555 return nil, dbp.exitGuard(err) 556 } 557 if th == nil { 558 break 559 } 560 } 561 562 // stop all threads that are still running 563 for _, th := range dbp.threads { 564 if th.os.running { 565 if err := th.stop(); err != nil { 566 return nil, dbp.exitGuard(err) 567 } 568 } 569 } 570 571 // wait for all threads to stop 572 for { 573 allstopped := true 574 for _, th := range dbp.threads { 575 if th.os.running { 576 allstopped = false 577 break 578 } 579 } 580 if allstopped { 581 break 582 } 583 _, err := dbp.trapWaitInternal(-1, trapWaitHalt) 584 if err != nil { 585 return nil, err 586 } 587 } 588 589 if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { 590 return nil, err 591 } 592 593 switchTrapthread := false 594 595 // set breakpoints on SIGTRAP threads 596 var err1 error 597 for _, th := range dbp.threads { 598 pc, _ := th.PC() 599 600 if !th.os.setbp && pc != th.os.phantomBreakpointPC { 601 // check if this could be a breakpoint hit anyway that the OS hasn't notified us about, yet. 602 if _, ok := dbp.FindBreakpoint(pc, dbp.BinInfo().Arch.BreakInstrMovesPC()); ok { 603 th.os.phantomBreakpointPC = pc 604 } 605 } 606 607 if pc != th.os.phantomBreakpointPC { 608 th.os.phantomBreakpointPC = 0 609 } 610 611 if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp { 612 if err := th.SetCurrentBreakpoint(true); err != nil { 613 err1 = err 614 continue 615 } 616 } 617 618 if th.CurrentBreakpoint.Breakpoint == nil && th.os.setbp && (th.Status != nil) && ((*sys.WaitStatus)(th.Status).StopSignal() == sys.SIGTRAP) && dbp.BinInfo().Arch.BreakInstrMovesPC() { 619 manualStop := false 620 if th.ThreadID() == trapthread.ThreadID() { 621 manualStop = cctx.GetManualStopRequested() 622 } 623 if !manualStop && th.os.phantomBreakpointPC == pc { 624 // Thread received a SIGTRAP but we don't have a breakpoint for it and 625 // it wasn't sent by a manual stop request. It's either a hardcoded 626 // breakpoint or a phantom breakpoint hit (a breakpoint that was hit but 627 // we have removed before we could receive its signal). Check if it is a 628 // hardcoded breakpoint, otherwise rewind the thread. 629 isHardcodedBreakpoint := false 630 pc, _ := th.PC() 631 for _, bpinstr := range [][]byte{ 632 dbp.BinInfo().Arch.BreakpointInstruction(), 633 dbp.BinInfo().Arch.AltBreakpointInstruction()} { 634 if bpinstr == nil { 635 continue 636 } 637 buf := make([]byte, len(bpinstr)) 638 _, _ = th.ReadMemory(buf, pc-uint64(len(buf))) 639 if bytes.Equal(buf, bpinstr) { 640 isHardcodedBreakpoint = true 641 break 642 } 643 } 644 if !isHardcodedBreakpoint { 645 // phantom breakpoint hit 646 _ = th.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction()))) 647 th.os.setbp = false 648 if trapthread.ThreadID() == th.ThreadID() { 649 // Will switch to a different thread for trapthread because we don't 650 // want pkg/proc to believe that this thread was stopped by a 651 // hardcoded breakpoint. 652 switchTrapthread = true 653 } 654 } 655 } 656 } 657 } 658 if err1 != nil { 659 return nil, err1 660 } 661 662 if switchTrapthread { 663 trapthreadID := trapthread.ID 664 trapthread = nil 665 for _, th := range dbp.threads { 666 if th.os.setbp && th.ThreadID() != trapthreadID { 667 return th, nil 668 } 669 } 670 } 671 672 return trapthread, nil 673 } 674 675 func (dbp *nativeProcess) detach(kill bool) error { 676 for threadID := range dbp.threads { 677 err := ptraceDetach(threadID, 0) 678 if err != nil { 679 return err 680 } 681 } 682 if kill { 683 return nil 684 } 685 // For some reason the process will sometimes enter stopped state after a 686 // detach, this doesn't happen immediately either. 687 // We have to wait a bit here, then check if the main thread is stopped and 688 // SIGCONT it if it is. 689 time.Sleep(50 * time.Millisecond) 690 if s := status(dbp.pid, dbp.os.comm); s == 'T' { 691 _ = sys.Kill(dbp.pid, sys.SIGCONT) 692 } 693 return nil 694 } 695 696 // EntryPoint will return the process entry point address, useful for 697 // debugging PIEs. 698 func (dbp *nativeProcess) EntryPoint() (uint64, error) { 699 auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid)) 700 if err != nil { 701 return 0, fmt.Errorf("could not read auxiliary vector: %v", err) 702 } 703 704 return linutil.EntryPointFromAuxv(auxvbuf, dbp.bi.Arch.PtrSize()), nil 705 } 706 707 func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { 708 // Lazily load and initialize the BPF program upon request to set a uprobe. 709 if dbp.os.ebpf == nil { 710 var err error 711 dbp.os.ebpf, err = ebpf.LoadEBPFTracingProgram(dbp.bi.Images[0].Path) 712 if err != nil { 713 return err 714 } 715 } 716 717 // We only allow up to 12 args for a BPF probe. 718 // 6 inputs + 6 outputs. 719 // Return early if we have more. 720 if len(args) > 12 { 721 return errors.New("too many arguments in traced function, max is 12 input+return") 722 } 723 724 fn, ok := dbp.bi.LookupFunc[fnName] 725 if !ok { 726 return fmt.Errorf("could not find function: %s", fnName) 727 } 728 729 key := fn.Entry 730 err := dbp.os.ebpf.UpdateArgMap(key, goidOffset, args, dbp.BinInfo().GStructOffset(), false) 731 if err != nil { 732 return err 733 } 734 735 debugname := dbp.bi.Images[0].Path 736 737 // First attach a uprobe at all return addresses. We do this instead of using a uretprobe 738 // for two reasons: 739 // 1. uretprobes do not play well with Go 740 // 2. uretprobes seem to not restore the function return addr on the stack when removed, destroying any 741 // kind of workaround we could come up with. 742 // TODO(derekparker): this whole thing could likely be optimized a bit. 743 img := dbp.BinInfo().PCToImage(fn.Entry) 744 f, err := elf.Open(img.Path) 745 if err != nil { 746 return fmt.Errorf("could not open elf file to resolve symbol offset: %w", err) 747 } 748 749 var regs proc.Registers 750 mem := dbp.Memory() 751 regs, _ = dbp.memthread.Registers() 752 instructions, err := proc.Disassemble(mem, regs, &proc.BreakpointMap{}, dbp.BinInfo(), fn.Entry, fn.End) 753 if err != nil { 754 return err 755 } 756 757 var addrs []uint64 758 for _, instruction := range instructions { 759 if instruction.IsRet() { 760 addrs = append(addrs, instruction.Loc.PC) 761 } 762 } 763 addrs = append(addrs, proc.FindDeferReturnCalls(instructions)...) 764 for _, addr := range addrs { 765 err := dbp.os.ebpf.UpdateArgMap(addr, goidOffset, args, dbp.BinInfo().GStructOffset(), true) 766 if err != nil { 767 return err 768 } 769 off, err := ebpf.AddressToOffset(f, addr) 770 if err != nil { 771 return err 772 } 773 err = dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off) 774 if err != nil { 775 return err 776 } 777 } 778 779 off, err := ebpf.AddressToOffset(f, fn.Entry) 780 if err != nil { 781 return err 782 } 783 784 return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off) 785 } 786 787 func killProcess(pid int) error { 788 return sys.Kill(pid, sys.SIGINT) 789 }