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