github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/native/proc_darwin.go (about) 1 //go:build darwin && macnative 2 // +build darwin,macnative 3 4 package native 5 6 // #include "proc_darwin.h" 7 // #include "threads_darwin.h" 8 // #include "exec_darwin.h" 9 // #include <stdlib.h> 10 import "C" 11 import ( 12 "errors" 13 "fmt" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "unsafe" 18 19 sys "golang.org/x/sys/unix" 20 21 "github.com/go-delve/delve/pkg/proc" 22 "github.com/go-delve/delve/pkg/proc/internal/ebpf" 23 "github.com/go-delve/delve/pkg/proc/macutil" 24 ) 25 26 // osProcessDetails holds Darwin specific information. 27 type osProcessDetails struct { 28 task C.task_t // mach task for the debugged process. 29 exceptionPort C.mach_port_t // mach port for receiving mach exceptions. 30 notificationPort C.mach_port_t // mach port for dead name notification (process exit). 31 initialized bool 32 halt bool 33 34 // the main port we use, will return messages from both the 35 // exception and notification ports. 36 portSet C.mach_port_t 37 } 38 39 func (os *osProcessDetails) Close() {} 40 41 // Launch creates and begins debugging a new process. Uses a 42 // custom fork/exec process in order to take advantage of 43 // PT_SIGEXC on Darwin which will turn Unix signals into 44 // Mach exceptions. 45 func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ string, _ proc.OutputRedirect, _ proc.OutputRedirect) (*proc.TargetGroup, error) { 46 argv0Go, err := filepath.Abs(cmd[0]) 47 if err != nil { 48 return nil, err 49 } 50 // Make sure the binary exists. 51 if filepath.Base(cmd[0]) == cmd[0] { 52 if _, err := exec.LookPath(cmd[0]); err != nil { 53 return nil, err 54 } 55 } 56 if _, err := os.Stat(argv0Go); err != nil { 57 return nil, err 58 } 59 if err := macutil.CheckRosetta(); err != nil { 60 return nil, err 61 } 62 63 argv0 := C.CString(argv0Go) 64 argvSlice := make([]*C.char, 0, len(cmd)+1) 65 for _, arg := range cmd { 66 argvSlice = append(argvSlice, C.CString(arg)) 67 } 68 // argv array must be null terminated. 69 argvSlice = append(argvSlice, nil) 70 71 dbp := newProcess(0) 72 defer func() { 73 if err != nil && dbp.pid != 0 { 74 _ = detachWithoutGroup(dbp, true) 75 } 76 }() 77 var pid int 78 dbp.execPtraceFunc(func() { 79 ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)), 80 C.CString(wd), 81 &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, 82 &dbp.os.notificationPort) 83 pid = int(ret) 84 }) 85 if pid <= 0 { 86 return nil, fmt.Errorf("could not fork/exec") 87 } 88 dbp.pid = pid 89 dbp.childProcess = true 90 for i := range argvSlice { 91 C.free(unsafe.Pointer(argvSlice[i])) 92 } 93 94 // Initialize enough of the Process state so that we can use resume and 95 // trapWait to wait until the child process calls execve. 96 97 for { 98 task := C.get_task_for_pid(C.int(dbp.pid)) 99 // The task_for_pid call races with the fork call. This can 100 // result in the parent task being returned instead of the child. 101 if task != dbp.os.task { 102 err = dbp.updateThreadListForTask(task) 103 if err == nil { 104 break 105 } 106 if err != couldNotGetThreadCount && err != couldNotGetThreadList { 107 return nil, err 108 } 109 } 110 } 111 112 if err := dbp.resume(); err != nil { 113 return nil, err 114 } 115 116 for _, th := range dbp.threads { 117 th.CurrentBreakpoint.Clear() 118 } 119 120 trapthread, err := dbp.trapWait(-1) 121 if err != nil { 122 return nil, err 123 } 124 if _, err := dbp.stop(nil); err != nil { 125 return nil, err 126 } 127 128 dbp.os.initialized = true 129 dbp.memthread = trapthread 130 131 tgt, err := dbp.initialize(argv0Go, []string{}) 132 if err != nil { 133 return nil, err 134 } 135 136 return tgt, err 137 } 138 139 func waitForSearchProcess(string, map[int]struct{}) (int, error) { 140 return 0, proc.ErrWaitForNotImplemented 141 } 142 143 // Attach to an existing process with the given PID. 144 func Attach(pid int, waitFor *proc.WaitFor, _ []string) (*proc.TargetGroup, error) { 145 if waitFor.Valid() { 146 return nil, proc.ErrWaitForNotImplemented 147 } 148 if err := macutil.CheckRosetta(); err != nil { 149 return nil, err 150 } 151 dbp := newProcess(pid) 152 153 kret := C.acquire_mach_task(C.int(pid), 154 &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, 155 &dbp.os.notificationPort) 156 157 if kret != C.KERN_SUCCESS { 158 return nil, fmt.Errorf("could not attach to %d", pid) 159 } 160 161 dbp.os.initialized = true 162 163 var err error 164 dbp.execPtraceFunc(func() { err = ptraceAttach(dbp.pid) }) 165 if err != nil { 166 return nil, err 167 } 168 _, _, err = dbp.wait(dbp.pid, 0) 169 if err != nil { 170 return nil, err 171 } 172 173 tgt, err := dbp.initialize("", []string{}) 174 if err != nil { 175 detachWithoutGroup(dbp, false) 176 return nil, err 177 } 178 return tgt, nil 179 } 180 181 // Kill kills the process. 182 func (procgrp *processGroup) kill(dbp *nativeProcess) (err error) { 183 if dbp.exited { 184 return nil 185 } 186 err = sys.Kill(-dbp.pid, sys.SIGKILL) 187 if err != nil { 188 return errors.New("could not deliver signal: " + err.Error()) 189 } 190 for port := range dbp.threads { 191 if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS { 192 return errors.New("could not resume task") 193 } 194 } 195 for { 196 var task C.task_t 197 port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0)) 198 if port == dbp.os.notificationPort { 199 break 200 } 201 } 202 dbp.postExit() 203 return 204 } 205 206 func (dbp *nativeProcess) requestManualStop() (err error) { 207 var ( 208 task = C.mach_port_t(dbp.os.task) 209 thread = C.mach_port_t(dbp.memthread.os.threadAct) 210 exceptionPort = C.mach_port_t(dbp.os.exceptionPort) 211 ) 212 dbp.os.halt = true 213 kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT) 214 if kret != C.KERN_SUCCESS { 215 return fmt.Errorf("could not raise mach exception") 216 } 217 return nil 218 } 219 220 var couldNotGetThreadCount = errors.New("could not get thread count") 221 var couldNotGetThreadList = errors.New("could not get thread list") 222 223 func (dbp *nativeProcess) updateThreadList() error { 224 return dbp.updateThreadListForTask(dbp.os.task) 225 } 226 227 func (dbp *nativeProcess) updateThreadListForTask(task C.task_t) error { 228 var ( 229 err error 230 kret C.kern_return_t 231 count C.int 232 list []uint32 233 ) 234 235 for { 236 count = C.thread_count(task) 237 if count == -1 { 238 return couldNotGetThreadCount 239 } 240 list = make([]uint32, count) 241 242 // TODO(dp) might be better to malloc mem in C and then free it here 243 // instead of getting count above and passing in a slice 244 kret = C.get_threads(task, unsafe.Pointer(&list[0]), count) 245 if kret != -2 { 246 break 247 } 248 } 249 if kret != C.KERN_SUCCESS { 250 return couldNotGetThreadList 251 } 252 253 for _, thread := range dbp.threads { 254 thread.os.exists = false 255 } 256 257 for _, port := range list { 258 thread, ok := dbp.threads[int(port)] 259 if !ok { 260 thread, err = dbp.addThread(int(port), false) 261 if err != nil { 262 return err 263 } 264 } 265 thread.os.exists = true 266 } 267 268 for threadID, thread := range dbp.threads { 269 if !thread.os.exists { 270 delete(dbp.threads, threadID) 271 } 272 } 273 274 return nil 275 } 276 277 func (dbp *nativeProcess) addThread(port int, attach bool) (*nativeThread, error) { 278 if thread, ok := dbp.threads[port]; ok { 279 return thread, nil 280 } 281 thread := &nativeThread{ 282 ID: port, 283 dbp: dbp, 284 os: new(osSpecificDetails), 285 } 286 dbp.threads[port] = thread 287 thread.os.threadAct = C.thread_act_t(port) 288 if dbp.memthread == nil { 289 dbp.memthread = thread 290 } 291 return thread, nil 292 } 293 294 func findExecutable(path string, pid int) string { 295 if path == "" { 296 path = C.GoString(C.find_executable(C.int(pid))) 297 } 298 return path 299 } 300 301 func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { 302 return procgrp.procs[0].trapWait(pid) 303 } 304 305 func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { 306 for { 307 task := dbp.os.task 308 port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0)) 309 310 switch port { 311 case dbp.os.notificationPort: 312 // on macOS >= 10.12.1 the task_t changes after an execve, we could 313 // receive the notification for the death of the pre-execve task_t, 314 // this could also happen *before* we are notified that our task_t has 315 // changed. 316 if dbp.os.task != task { 317 continue 318 } 319 if !dbp.os.initialized { 320 if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask { 321 continue 322 } 323 } 324 _, status, err := dbp.wait(dbp.pid, 0) 325 if err != nil { 326 return nil, err 327 } 328 dbp.postExit() 329 return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()} 330 331 case C.MACH_RCV_INTERRUPTED: 332 halt := dbp.os.halt 333 if !halt { 334 // Call trapWait again, it seems 335 // MACH_RCV_INTERRUPTED is emitted before 336 // process natural death _sometimes_. 337 continue 338 } 339 return nil, nil 340 341 case 0: 342 return nil, fmt.Errorf("error while waiting for task") 343 } 344 345 // In macOS 10.12.1 if we received a notification for a task other than 346 // the inferior's task and the inferior's task is no longer valid, this 347 // means inferior called execve and its task_t changed. 348 if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 { 349 dbp.os.task = task 350 kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort) 351 if kret != C.KERN_SUCCESS { 352 return nil, fmt.Errorf("could not follow task across exec: %d\n", kret) 353 } 354 } 355 356 // Since we cannot be notified of new threads on OS X 357 // this is as good a time as any to check for them. 358 dbp.updateThreadList() 359 th, ok := dbp.threads[int(port)] 360 if !ok { 361 halt := dbp.os.halt 362 if halt { 363 dbp.os.halt = false 364 return th, nil 365 } 366 if dbp.firstStart || th.singleStepping { 367 dbp.firstStart = false 368 return th, nil 369 } 370 if err := th.Continue(); err != nil { 371 return nil, err 372 } 373 continue 374 } 375 return th, nil 376 } 377 } 378 379 func (dbp *nativeProcess) waitForStop() ([]int, error) { 380 ports := make([]int, 0, len(dbp.threads)) 381 count := 0 382 for { 383 var task C.task_t 384 port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1)) 385 if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED { 386 count = 0 387 ports = append(ports, int(port)) 388 } else { 389 n := C.num_running_threads(dbp.os.task) 390 if n == 0 { 391 return ports, nil 392 } else if n < 0 { 393 return nil, fmt.Errorf("error waiting for thread stop %d", n) 394 } else if count > 16 { 395 return nil, fmt.Errorf("could not stop process %d", n) 396 } 397 } 398 } 399 } 400 401 func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { 402 var status sys.WaitStatus 403 wpid, err := sys.Wait4(pid, &status, options, nil) 404 return wpid, &status, err 405 } 406 407 func killProcess(pid int) error { 408 return sys.Kill(pid, sys.SIGINT) 409 } 410 411 func (dbp *nativeProcess) exitGuard(err error) error { 412 if err != ErrContinueThread { 413 return err 414 } 415 _, status, werr := dbp.wait(dbp.pid, sys.WNOHANG) 416 if werr == nil && status.Exited() { 417 dbp.postExit() 418 return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()} 419 } 420 return err 421 } 422 423 func (procgrp *processGroup) resume() error { 424 return procgrp.procs[0].resume() 425 } 426 427 func (dbp *nativeProcess) resume() error { 428 // all threads stopped over a breakpoint are made to step over it 429 for _, thread := range dbp.threads { 430 if thread.CurrentBreakpoint.Breakpoint != nil { 431 if err := thread.StepInstruction(); err != nil { 432 return err 433 } 434 thread.CurrentBreakpoint.Clear() 435 } 436 } 437 // everything is resumed 438 for _, thread := range dbp.threads { 439 if err := thread.resume(); err != nil { 440 return dbp.exitGuard(err) 441 } 442 } 443 return nil 444 } 445 446 // stop stops all running threads and sets breakpoints 447 func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { 448 return procgrp.procs[0].stop(trapthread) 449 } 450 451 func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { 452 if dbp.exited { 453 return nil, proc.ErrProcessExited{Pid: dbp.pid} 454 } 455 for _, th := range dbp.threads { 456 if !th.Stopped() { 457 if err := th.stop(); err != nil { 458 return nil, dbp.exitGuard(err) 459 } 460 } 461 } 462 463 ports, err := dbp.waitForStop() 464 if err != nil { 465 return nil, err 466 } 467 if !dbp.os.initialized { 468 return nil, nil 469 } 470 trapthread.SetCurrentBreakpoint(true) 471 for _, port := range ports { 472 if th, ok := dbp.threads[port]; ok { 473 err := th.SetCurrentBreakpoint(true) 474 if err != nil { 475 return nil, err 476 } 477 } 478 } 479 return trapthread, nil 480 } 481 482 func (dbp *nativeProcess) detach(kill bool) error { 483 return ptraceDetach(dbp.pid, 0) 484 } 485 486 func (dbp *nativeProcess) EntryPoint() (uint64, error) { 487 //TODO(aarzilli): implement this 488 return 0, nil 489 } 490 491 func (dbp *nativeProcess) SupportsBPF() bool { 492 return false 493 } 494 495 func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { 496 panic("not implemented") 497 } 498 499 func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { 500 panic("not implemented") 501 } 502 503 func initialize(dbp *nativeProcess) (string, error) { return "", nil }