github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/proc" 22 "github.com/undoio/delve/pkg/proc/internal/ebpf" 23 "github.com/undoio/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, _ [3]string) (*proc.Target, 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 _ = dbp.Detach(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, 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 // Attach to an existing process with the given PID. 140 func Attach(pid int, _ []string) (*proc.Target, error) { 141 if err := macutil.CheckRosetta(); err != nil { 142 return nil, err 143 } 144 dbp := newProcess(pid) 145 146 kret := C.acquire_mach_task(C.int(pid), 147 &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, 148 &dbp.os.notificationPort) 149 150 if kret != C.KERN_SUCCESS { 151 return nil, fmt.Errorf("could not attach to %d", pid) 152 } 153 154 dbp.os.initialized = true 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("", []string{}) 167 if err != nil { 168 dbp.Detach(false) 169 return nil, err 170 } 171 return tgt, nil 172 } 173 174 // Kill kills the process. 175 func (dbp *nativeProcess) kill() (err error) { 176 if dbp.exited { 177 return nil 178 } 179 err = sys.Kill(-dbp.pid, sys.SIGKILL) 180 if err != nil { 181 return errors.New("could not deliver signal: " + err.Error()) 182 } 183 for port := range dbp.threads { 184 if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS { 185 return errors.New("could not resume task") 186 } 187 } 188 for { 189 var task C.task_t 190 port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0)) 191 if port == dbp.os.notificationPort { 192 break 193 } 194 } 195 dbp.postExit() 196 return 197 } 198 199 func (dbp *nativeProcess) requestManualStop() (err error) { 200 var ( 201 task = C.mach_port_t(dbp.os.task) 202 thread = C.mach_port_t(dbp.memthread.os.threadAct) 203 exceptionPort = C.mach_port_t(dbp.os.exceptionPort) 204 ) 205 dbp.os.halt = true 206 kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT) 207 if kret != C.KERN_SUCCESS { 208 return fmt.Errorf("could not raise mach exception") 209 } 210 return nil 211 } 212 213 var couldNotGetThreadCount = errors.New("could not get thread count") 214 var couldNotGetThreadList = errors.New("could not get thread list") 215 216 func (dbp *nativeProcess) updateThreadList() error { 217 return dbp.updateThreadListForTask(dbp.os.task) 218 } 219 220 func (dbp *nativeProcess) updateThreadListForTask(task C.task_t) error { 221 var ( 222 err error 223 kret C.kern_return_t 224 count C.int 225 list []uint32 226 ) 227 228 for { 229 count = C.thread_count(task) 230 if count == -1 { 231 return couldNotGetThreadCount 232 } 233 list = make([]uint32, count) 234 235 // TODO(dp) might be better to malloc mem in C and then free it here 236 // instead of getting count above and passing in a slice 237 kret = C.get_threads(task, unsafe.Pointer(&list[0]), count) 238 if kret != -2 { 239 break 240 } 241 } 242 if kret != C.KERN_SUCCESS { 243 return couldNotGetThreadList 244 } 245 246 for _, thread := range dbp.threads { 247 thread.os.exists = false 248 } 249 250 for _, port := range list { 251 thread, ok := dbp.threads[int(port)] 252 if !ok { 253 thread, err = dbp.addThread(int(port), false) 254 if err != nil { 255 return err 256 } 257 } 258 thread.os.exists = true 259 } 260 261 for threadID, thread := range dbp.threads { 262 if !thread.os.exists { 263 delete(dbp.threads, threadID) 264 } 265 } 266 267 return nil 268 } 269 270 func (dbp *nativeProcess) addThread(port int, attach bool) (*nativeThread, error) { 271 if thread, ok := dbp.threads[port]; ok { 272 return thread, nil 273 } 274 thread := &nativeThread{ 275 ID: port, 276 dbp: dbp, 277 os: new(osSpecificDetails), 278 } 279 dbp.threads[port] = thread 280 thread.os.threadAct = C.thread_act_t(port) 281 if dbp.memthread == nil { 282 dbp.memthread = thread 283 } 284 return thread, nil 285 } 286 287 func findExecutable(path string, pid int) string { 288 if path == "" { 289 path = C.GoString(C.find_executable(C.int(pid))) 290 } 291 return path 292 } 293 294 func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { 295 for { 296 task := dbp.os.task 297 port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0)) 298 299 switch port { 300 case dbp.os.notificationPort: 301 // on macOS >= 10.12.1 the task_t changes after an execve, we could 302 // receive the notification for the death of the pre-execve task_t, 303 // this could also happen *before* we are notified that our task_t has 304 // changed. 305 if dbp.os.task != task { 306 continue 307 } 308 if !dbp.os.initialized { 309 if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask { 310 continue 311 } 312 } 313 _, status, err := dbp.wait(dbp.pid, 0) 314 if err != nil { 315 return nil, err 316 } 317 dbp.postExit() 318 return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()} 319 320 case C.MACH_RCV_INTERRUPTED: 321 halt := dbp.os.halt 322 if !halt { 323 // Call trapWait again, it seems 324 // MACH_RCV_INTERRUPTED is emitted before 325 // process natural death _sometimes_. 326 continue 327 } 328 return nil, nil 329 330 case 0: 331 return nil, fmt.Errorf("error while waiting for task") 332 } 333 334 // In macOS 10.12.1 if we received a notification for a task other than 335 // the inferior's task and the inferior's task is no longer valid, this 336 // means inferior called execve and its task_t changed. 337 if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 { 338 dbp.os.task = task 339 kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort) 340 if kret != C.KERN_SUCCESS { 341 return nil, fmt.Errorf("could not follow task across exec: %d\n", kret) 342 } 343 } 344 345 // Since we cannot be notified of new threads on OS X 346 // this is as good a time as any to check for them. 347 dbp.updateThreadList() 348 th, ok := dbp.threads[int(port)] 349 if !ok { 350 halt := dbp.os.halt 351 if halt { 352 dbp.os.halt = false 353 return th, nil 354 } 355 if dbp.firstStart || th.singleStepping { 356 dbp.firstStart = false 357 return th, nil 358 } 359 if err := th.Continue(); err != nil { 360 return nil, err 361 } 362 continue 363 } 364 return th, nil 365 } 366 } 367 368 func (dbp *nativeProcess) waitForStop() ([]int, error) { 369 ports := make([]int, 0, len(dbp.threads)) 370 count := 0 371 for { 372 var task C.task_t 373 port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1)) 374 if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED { 375 count = 0 376 ports = append(ports, int(port)) 377 } else { 378 n := C.num_running_threads(dbp.os.task) 379 if n == 0 { 380 return ports, nil 381 } else if n < 0 { 382 return nil, fmt.Errorf("error waiting for thread stop %d", n) 383 } else if count > 16 { 384 return nil, fmt.Errorf("could not stop process %d", n) 385 } 386 } 387 } 388 } 389 390 func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { 391 var status sys.WaitStatus 392 wpid, err := sys.Wait4(pid, &status, options, nil) 393 return wpid, &status, err 394 } 395 396 func killProcess(pid int) error { 397 return sys.Kill(pid, sys.SIGINT) 398 } 399 400 func (dbp *nativeProcess) exitGuard(err error) error { 401 if err != ErrContinueThread { 402 return err 403 } 404 _, status, werr := dbp.wait(dbp.pid, sys.WNOHANG) 405 if werr == nil && status.Exited() { 406 dbp.postExit() 407 return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()} 408 } 409 return err 410 } 411 412 func (dbp *nativeProcess) resume() error { 413 // all threads stopped over a breakpoint are made to step over it 414 for _, thread := range dbp.threads { 415 if thread.CurrentBreakpoint.Breakpoint != nil { 416 if err := thread.StepInstruction(); err != nil { 417 return err 418 } 419 thread.CurrentBreakpoint.Clear() 420 } 421 } 422 // everything is resumed 423 for _, thread := range dbp.threads { 424 if err := thread.resume(); err != nil { 425 return dbp.exitGuard(err) 426 } 427 } 428 return nil 429 } 430 431 // stop stops all running threads and sets breakpoints 432 func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { 433 if dbp.exited { 434 return nil, proc.ErrProcessExited{Pid: dbp.pid} 435 } 436 for _, th := range dbp.threads { 437 if !th.Stopped() { 438 if err := th.stop(); err != nil { 439 return nil, dbp.exitGuard(err) 440 } 441 } 442 } 443 444 ports, err := dbp.waitForStop() 445 if err != nil { 446 return nil, err 447 } 448 if !dbp.os.initialized { 449 return nil, nil 450 } 451 trapthread.SetCurrentBreakpoint(true) 452 for _, port := range ports { 453 if th, ok := dbp.threads[port]; ok { 454 err := th.SetCurrentBreakpoint(true) 455 if err != nil { 456 return nil, err 457 } 458 } 459 } 460 return trapthread, nil 461 } 462 463 func (dbp *nativeProcess) detach(kill bool) error { 464 return ptraceDetach(dbp.pid, 0) 465 } 466 467 func (dbp *nativeProcess) EntryPoint() (uint64, error) { 468 //TODO(aarzilli): implement this 469 return 0, nil 470 } 471 472 func (dbp *nativeProcess) SupportsBPF() bool { 473 return false 474 } 475 476 func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf.UProbeArgMap) error { 477 panic("not implemented") 478 } 479 480 func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { 481 panic("not implemented") 482 } 483 484 func initialize(dbp *nativeProcess) error { return nil }