gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/native/proc_freebsd.go (about) 1 package native 2 3 /* 4 #cgo LDFLAGS: -lprocstat 5 6 #include <sys/types.h> 7 #include <sys/sysctl.h> 8 #include <sys/user.h> 9 10 #include <limits.h> 11 #include <stdlib.h> 12 13 #include <libprocstat.h> 14 #include <libutil.h> 15 16 uintptr_t elf_aux_info_ptr(Elf_Auxinfo *aux_info) { 17 return (uintptr_t)aux_info->a_un.a_ptr; 18 } 19 */ 20 import "C" 21 import ( 22 "errors" 23 "fmt" 24 "os/exec" 25 "os/signal" 26 "strings" 27 "syscall" 28 "unsafe" 29 30 sys "golang.org/x/sys/unix" 31 32 "gitlab.com/Raven-IO/raven-delve/pkg/logflags" 33 "gitlab.com/Raven-IO/raven-delve/pkg/proc" 34 "gitlab.com/Raven-IO/raven-delve/pkg/proc/internal/ebpf" 35 36 isatty "github.com/mattn/go-isatty" 37 ) 38 39 const ( 40 _PL_FLAG_BORN = 0x100 41 _PL_FLAG_EXITED = 0x200 42 _PL_FLAG_SI = 0x20 43 ) 44 45 // Process statuses 46 const ( 47 statusIdle = 1 48 statusRunning = 2 49 statusSleeping = 3 50 statusStopped = 4 51 statusZombie = 5 52 statusWaiting = 6 53 statusLocked = 7 54 ) 55 56 // osProcessDetails contains FreeBSD specific 57 // process details. 58 type osProcessDetails struct { 59 comm string 60 tid int 61 62 delayedSignal syscall.Signal 63 trapThreads []int 64 selectedThread *nativeThread 65 } 66 67 func (os *osProcessDetails) Close() {} 68 69 // Launch creates and begins debugging a new process. First entry in 70 // `cmd` is the program to run, and then rest are the arguments 71 // to be supplied to that process. `wd` is working directory of the program. 72 // If the DWARF information cannot be found in the binary, Delve will look 73 // for external debug files in the directories passed in. 74 func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) { 75 var ( 76 process *exec.Cmd 77 err error 78 ) 79 80 foreground := flags&proc.LaunchForeground != 0 81 82 stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, foreground) 83 if err != nil { 84 return nil, err 85 } 86 87 if stdin == nil || !isatty.IsTerminal(stdin.Fd()) { 88 // exec.(*Process).Start will fail if we try to send a process to 89 // foreground but we are not attached to a terminal. 90 foreground = false 91 } 92 93 dbp := newProcess(0) 94 defer func() { 95 if err != nil && dbp.pid != 0 { 96 _ = detachWithoutGroup(dbp, true) 97 } 98 }() 99 dbp.execPtraceFunc(func() { 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{Ptrace: true, Setpgid: true, Foreground: foreground} 106 if foreground { 107 signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) 108 } 109 if tty != "" { 110 dbp.ctty, err = attachProcessToTTY(process, tty) 111 if err != nil { 112 return 113 } 114 } 115 if wd != "" { 116 process.Dir = wd 117 } 118 err = process.Start() 119 }) 120 closefn() 121 if err != nil { 122 return nil, err 123 } 124 dbp.pid = process.Process.Pid 125 dbp.childProcess = true 126 _, _, err = dbp.wait(process.Process.Pid, 0) 127 if err != nil { 128 return nil, fmt.Errorf("waiting for target execve failed: %s", err) 129 } 130 tgt, err := dbp.initialize(cmd[0], debugInfoDirs) 131 if err != nil { 132 return nil, err 133 } 134 return tgt, nil 135 } 136 137 // Attach to an existing process with the given PID. Once attached, if 138 // the DWARF information cannot be found in the binary, Delve will look 139 // for external debug files in the directories passed in. 140 func Attach(pid int, waitFor *proc.WaitFor, debugInfoDirs []string) (*proc.TargetGroup, error) { 141 if waitFor.Valid() { 142 var err error 143 pid, err = WaitFor(waitFor) 144 if err != nil { 145 return nil, err 146 } 147 } 148 149 dbp := newProcess(pid) 150 151 var err error 152 dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) }) 153 if err != nil { 154 return nil, err 155 } 156 _, _, err = dbp.wait(dbp.pid, 0) 157 if err != nil { 158 return nil, err 159 } 160 161 tgt, err := dbp.initialize(findExecutable("", dbp.pid), debugInfoDirs) 162 if err != nil { 163 detachWithoutGroup(dbp, false) 164 return nil, err 165 } 166 return tgt, nil 167 } 168 169 func waitForSearchProcess(pfx string, seen map[int]struct{}) (int, error) { 170 log := logflags.DebuggerLogger() 171 ps := C.procstat_open_sysctl() 172 defer C.procstat_close(ps) 173 var cnt C.uint 174 procs := C.procstat_getprocs(ps, C.KERN_PROC_PROC, 0, &cnt) 175 defer C.procstat_freeprocs(ps, procs) 176 proc := procs 177 for i := 0; i < int(cnt); i++ { 178 if _, isseen := seen[int(proc.ki_pid)]; isseen { 179 continue 180 } 181 seen[int(proc.ki_pid)] = struct{}{} 182 183 argv := strings.Join(getCmdLineInternal(ps, proc), " ") 184 log.Debugf("waitfor: new process %q", argv) 185 if strings.HasPrefix(argv, pfx) { 186 return int(proc.ki_pid), nil 187 } 188 189 proc = (*C.struct_kinfo_proc)(unsafe.Pointer(uintptr(unsafe.Pointer(proc)) + unsafe.Sizeof(*proc))) 190 } 191 return 0, nil 192 } 193 194 func initialize(dbp *nativeProcess) (string, error) { 195 kp, err := C.kinfo_getproc(C.int(dbp.pid)) 196 if err != nil { 197 return "", fmt.Errorf("kinfo_getproc failed: %v", err) 198 } 199 defer C.free(unsafe.Pointer(kp)) 200 201 comm := C.GoString(&kp.ki_comm[0]) 202 dbp.os.comm = strings.ReplaceAll(string(comm), "%", "%%") 203 204 return getCmdLine(dbp.pid), nil 205 } 206 207 // kill kills the target process. 208 func (procgrp *processGroup) kill(dbp *nativeProcess) (err error) { 209 if ok, _ := dbp.Valid(); !ok { 210 return nil 211 } 212 dbp.execPtraceFunc(func() { 213 for _, th := range dbp.threads { 214 ptraceResume(th.ID) 215 } 216 err = ptraceCont(dbp.pid, int(sys.SIGKILL)) 217 }) 218 if err != nil { 219 return err 220 } 221 if _, _, err = dbp.wait(dbp.pid, 0); err != nil { 222 return err 223 } 224 dbp.postExit() 225 return nil 226 } 227 228 // Used by RequestManualStop 229 func (dbp *nativeProcess) requestManualStop() (err error) { 230 return sys.Kill(dbp.pid, sys.SIGSTOP) 231 } 232 233 // Attach to a newly created thread, and store that thread in our list of 234 // known threads. 235 func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) { 236 if thread, ok := dbp.threads[tid]; ok { 237 return thread, nil 238 } 239 240 var err error 241 dbp.execPtraceFunc(func() { err = sys.PtraceLwpEvents(dbp.pid, 1) }) 242 if err == syscall.ESRCH { 243 if _, _, err = dbp.wait(dbp.pid, 0); err != nil { 244 return nil, fmt.Errorf("error while waiting after adding process: %d %s", dbp.pid, err) 245 } 246 } 247 248 dbp.threads[tid] = &nativeThread{ 249 ID: tid, 250 dbp: dbp, 251 os: new(osSpecificDetails), 252 } 253 254 if dbp.memthread == nil { 255 dbp.memthread = dbp.threads[tid] 256 } 257 258 return dbp.threads[tid], nil 259 } 260 261 // Used by initialize 262 func (dbp *nativeProcess) updateThreadList() error { 263 var tids []int32 264 dbp.execPtraceFunc(func() { tids = ptraceGetLwpList(dbp.pid) }) 265 for _, tid := range tids { 266 if _, err := dbp.addThread(int(tid), false); err != nil { 267 return err 268 } 269 } 270 dbp.os.tid = int(tids[0]) 271 return nil 272 } 273 274 func findExecutable(path string, pid int) string { 275 if path != "" { 276 return path 277 } 278 279 ps := C.procstat_open_sysctl() 280 if ps == nil { 281 panic("procstat_open_sysctl failed") 282 } 283 defer C.procstat_close(ps) 284 285 kp := C.kinfo_getproc(C.int(pid)) 286 if kp == nil { 287 panic("kinfo_getproc failed") 288 } 289 defer C.free(unsafe.Pointer(kp)) 290 291 var pathname [C.PATH_MAX]C.char 292 if C.procstat_getpathname(ps, kp, (*C.char)(unsafe.Pointer(&pathname[0])), C.PATH_MAX) != 0 { 293 panic("procstat_getpathname failed") 294 } 295 296 return C.GoString(&pathname[0]) 297 } 298 299 func getCmdLine(pid int) string { 300 ps := C.procstat_open_sysctl() 301 kp := C.kinfo_getproc(C.int(pid)) 302 goargv := getCmdLineInternal(ps, kp) 303 C.free(unsafe.Pointer(kp)) 304 C.procstat_close(ps) 305 return strings.Join(goargv, " ") 306 } 307 308 func getCmdLineInternal(ps *C.struct_procstat, kp *C.struct_kinfo_proc) []string { 309 argv := C.procstat_getargv(ps, kp, 0) 310 if argv == nil { 311 return nil 312 } 313 goargv := []string{} 314 for { 315 arg := *argv 316 if arg == nil { 317 break 318 } 319 argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + unsafe.Sizeof(*argv))) 320 goargv = append(goargv, C.GoString(arg)) 321 } 322 C.procstat_freeargv(ps) 323 return goargv 324 } 325 326 func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { 327 return procgrp.procs[0].trapWaitInternal(pid, trapWaitNormal) 328 } 329 330 type trapWaitMode uint8 331 332 const ( 333 trapWaitNormal trapWaitMode = iota 334 trapWaitStepping 335 ) 336 337 // Used by stop and trapWait 338 func (dbp *nativeProcess) trapWaitInternal(pid int, mode trapWaitMode) (*nativeThread, error) { 339 if dbp.os.selectedThread != nil { 340 th := dbp.os.selectedThread 341 dbp.os.selectedThread = nil 342 return th, nil 343 } 344 for { 345 wpid, status, err := dbp.wait(pid, 0) 346 if wpid != dbp.pid { 347 // possibly a delayed notification from a process we just detached and killed, freebsd bug? 348 continue 349 } 350 if err != nil { 351 return nil, fmt.Errorf("wait err %s %d", err, pid) 352 } 353 if status.Killed() { 354 // "Killed" status may arrive as a result of a Process.Kill() of some other process in 355 // the system performed by the same tracer (e.g. in the previous test) 356 continue 357 } 358 if status.Exited() { 359 dbp.postExit() 360 return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} 361 } 362 if status.Signaled() { 363 // Killed by a signal 364 dbp.postExit() 365 return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} 366 } 367 368 var info sys.PtraceLwpInfoStruct 369 dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) }) 370 if err != nil { 371 return nil, fmt.Errorf("ptraceGetLwpInfo err %s %d", err, pid) 372 } 373 tid := int(info.Lwpid) 374 pl_flags := int(info.Flags) 375 th, ok := dbp.threads[tid] 376 if ok { 377 th.Status = (*waitStatus)(status) 378 } 379 380 if status.StopSignal() == sys.SIGTRAP { 381 if pl_flags&_PL_FLAG_EXITED != 0 { 382 delete(dbp.threads, tid) 383 dbp.execPtraceFunc(func() { err = ptraceCont(tid, 0) }) 384 if err != nil { 385 return nil, err 386 } 387 continue 388 } else if pl_flags&_PL_FLAG_BORN != 0 { 389 th, err = dbp.addThread(int(tid), false) 390 if err != nil { 391 if err == sys.ESRCH { 392 // process died while we were adding it 393 continue 394 } 395 return nil, err 396 } 397 if mode == trapWaitStepping { 398 dbp.execPtraceFunc(func() { ptraceSuspend(tid) }) 399 } 400 if err = dbp.ptraceCont(0); err != nil { 401 if err == sys.ESRCH { 402 // thread died while we were adding it 403 delete(dbp.threads, int(tid)) 404 continue 405 } 406 return nil, fmt.Errorf("could not continue new thread %d %s", tid, err) 407 } 408 continue 409 } 410 } 411 412 if th == nil { 413 continue 414 } 415 416 if mode == trapWaitStepping { 417 return th, nil 418 } 419 if status.StopSignal() == sys.SIGTRAP || status.Continued() { 420 // Continued in this case means we received the SIGSTOP signal 421 return th, nil 422 } 423 424 // TODO(dp) alert user about unexpected signals here. 425 if err := dbp.ptraceCont(int(status.StopSignal())); err != nil { 426 if err == sys.ESRCH { 427 return nil, proc.ErrProcessExited{Pid: dbp.pid} 428 } 429 return nil, err 430 } 431 } 432 } 433 434 // status returns the status code for the given process. 435 func status(pid int) int { 436 kp, err := C.kinfo_getproc(C.int(pid)) 437 if err != nil { 438 return -1 439 } 440 defer C.free(unsafe.Pointer(kp)) 441 return int(kp.ki_stat) 442 } 443 444 // Only used in this file 445 func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { 446 var s sys.WaitStatus 447 wpid, err := sys.Wait4(pid, &s, options, nil) 448 return wpid, &s, err 449 } 450 451 // Only used in this file 452 func (dbp *nativeProcess) exitGuard(err error) error { 453 if err != sys.ESRCH { 454 return err 455 } 456 if status(dbp.pid) == statusZombie { 457 _, err := dbp.trapWaitInternal(-1, trapWaitNormal) 458 return err 459 } 460 461 return err 462 } 463 464 // Used by ContinueOnce 465 func (procgrp *processGroup) resume() error { 466 dbp := procgrp.procs[0] 467 // all threads stopped over a breakpoint are made to step over it 468 for _, thread := range dbp.threads { 469 if thread.CurrentBreakpoint.Breakpoint != nil { 470 if err := procgrp.stepInstruction(thread); err != nil { 471 return err 472 } 473 thread.CurrentBreakpoint.Clear() 474 } 475 } 476 if len(dbp.os.trapThreads) > 0 { 477 // On these threads we have already received a SIGTRAP while stepping on a different thread, 478 // do not resume the process, instead record the breakpoint hits on them. 479 tt := dbp.os.trapThreads 480 dbp.os.trapThreads = dbp.os.trapThreads[:0] 481 var err error 482 dbp.os.selectedThread, err = dbp.postStop(tt...) 483 return err 484 } 485 // all threads are resumed 486 var err error 487 dbp.execPtraceFunc(func() { 488 for _, th := range dbp.threads { 489 err = ptraceResume(th.ID) 490 if err != nil { 491 return 492 } 493 } 494 sig := int(dbp.os.delayedSignal) 495 dbp.os.delayedSignal = 0 496 for _, thread := range dbp.threads { 497 thread.Status = nil 498 } 499 err = ptraceCont(dbp.pid, sig) 500 }) 501 return err 502 } 503 504 // Used by ContinueOnce 505 // stop stops all running threads and sets breakpoints 506 func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { 507 return procgrp.procs[0].stop(trapthread) 508 } 509 510 func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { 511 if ok, err := dbp.Valid(); !ok { 512 return nil, err 513 } 514 515 var err error 516 dbp.execPtraceFunc(func() { 517 for _, th := range dbp.threads { 518 err = ptraceSuspend(th.ID) 519 if err != nil { 520 return 521 } 522 } 523 }) 524 if err != nil { 525 return nil, err 526 } 527 528 if trapthread.Status == nil || (*sys.WaitStatus)(trapthread.Status).StopSignal() != sys.SIGTRAP { 529 return trapthread, nil 530 } 531 532 return dbp.postStop(trapthread.ID) 533 } 534 535 func (dbp *nativeProcess) postStop(tids ...int) (*nativeThread, error) { 536 var pickedTrapThread *nativeThread 537 for _, tid := range tids { 538 trapthread := dbp.threads[tid] 539 if trapthread == nil { 540 continue 541 } 542 543 // Must either be a hardcoded breakpoint, a currently set breakpoint or a 544 // phantom breakpoint hit caused by a SIGTRAP that was delayed. 545 // If someone sent a SIGTRAP directly to the process this will fail, but we 546 // can't do better. 547 548 err := trapthread.SetCurrentBreakpoint(true) 549 if err != nil { 550 return nil, err 551 } 552 553 if trapthread.CurrentBreakpoint.Breakpoint == nil { 554 // hardcoded breakpoint or phantom breakpoint hit 555 if dbp.BinInfo().Arch.BreakInstrMovesPC() { 556 pc, _ := trapthread.PC() 557 if !trapthread.atHardcodedBreakpoint(pc) { 558 // phantom breakpoint hit 559 _ = trapthread.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction()))) 560 trapthread = nil 561 } 562 } 563 } 564 565 if pickedTrapThread == nil && trapthread != nil { 566 pickedTrapThread = trapthread 567 } 568 } 569 570 return pickedTrapThread, nil 571 } 572 573 // Used by Detach 574 func (dbp *nativeProcess) detach(kill bool) error { 575 if !kill { 576 for _, th := range dbp.threads { 577 ptraceResume(th.ID) 578 } 579 } 580 return ptraceDetach(dbp.pid) 581 } 582 583 // EntryPoint returns the entry point address of the process. 584 func (dbp *nativeProcess) EntryPoint() (uint64, error) { 585 ps, err := C.procstat_open_sysctl() 586 if err != nil { 587 return 0, fmt.Errorf("procstat_open_sysctl failed: %v", err) 588 } 589 defer C.procstat_close(ps) 590 591 var count C.uint 592 kipp, err := C.procstat_getprocs(ps, C.KERN_PROC_PID, C.int(dbp.pid), &count) 593 if err != nil { 594 return 0, fmt.Errorf("procstat_getprocs failed: %v", err) 595 } 596 defer C.procstat_freeprocs(ps, kipp) 597 if count == 0 { 598 return 0, errors.New("procstat_getprocs returned no processes") 599 } 600 601 auxv, err := C.procstat_getauxv(ps, kipp, &count) 602 if err != nil { 603 return 0, fmt.Errorf("procstat_getauxv failed: %v", err) 604 } 605 defer C.procstat_freeauxv(ps, auxv) 606 607 for i := 0; i < int(count); i++ { 608 if auxv.a_type == C.AT_ENTRY { 609 return uint64(C.elf_aux_info_ptr(auxv)), nil 610 } 611 auxv = (*C.Elf_Auxinfo)(unsafe.Pointer(uintptr(unsafe.Pointer(auxv)) + unsafe.Sizeof(*auxv))) 612 } 613 return 0, errors.New("entry point not found") 614 } 615 616 func (dbp *nativeProcess) SupportsBPF() bool { 617 return false 618 } 619 620 func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { 621 panic("not implemented") 622 } 623 624 func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { 625 panic("not implemented") 626 } 627 628 func (dbp *nativeProcess) ptraceCont(sig int) error { 629 var err error 630 dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, sig) }) 631 return err 632 } 633 634 func killProcess(pid int) error { 635 return sys.Kill(pid, sys.SIGINT) 636 }