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