proc_darwin.go 11.0 KB
Newer Older
1 2
//+build darwin,macnative

3
package native
D
Derek Parker 已提交
4

D
Derek Parker 已提交
5
// #include "proc_darwin.h"
6
// #include "threads_darwin.h"
7
// #include "exec_darwin.h"
8
// #include <stdlib.h>
D
Derek Parker 已提交
9 10
import "C"
import (
11
	"errors"
D
Derek Parker 已提交
12 13
	"fmt"
	"os"
14
	"os/exec"
15
	"path/filepath"
D
Derek Parker 已提交
16 17 18 19
	"sync"
	"unsafe"

	sys "golang.org/x/sys/unix"
20 21

	"github.com/derekparker/delve/pkg/proc"
D
Derek Parker 已提交
22 23
)

D
Derek Parker 已提交
24
// OSProcessDetails holds Darwin specific information.
D
Derek Parker 已提交
25
type OSProcessDetails struct {
26 27 28
	task             C.task_t      // mach task for the debugged process.
	exceptionPort    C.mach_port_t // mach port for receiving mach exceptions.
	notificationPort C.mach_port_t // mach port for dead name notification (process exit).
29
	initialized      bool
30
	halt             bool
31 32 33 34

	// the main port we use, will return messages from both the
	// exception and notification ports.
	portSet C.mach_port_t
D
Derek Parker 已提交
35 36
}

D
Derek Parker 已提交
37
// Launch creates and begins debugging a new process. Uses a
38
// custom fork/exec process in order to take advantage of
D
Derek Parker 已提交
39 40
// PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions.
41
func Launch(cmd []string, wd string, foreground bool, _ []string) (*Process, error) {
42 43
	// check that the argument to Launch is an executable file
	if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 {
44
		return nil, proc.ErrNotExecutable
45
	}
46
	argv0Go, err := filepath.Abs(cmd[0])
47 48 49
	if err != nil {
		return nil, err
	}
50
	// Make sure the binary exists.
51 52 53 54 55 56 57 58 59
	if filepath.Base(cmd[0]) == cmd[0] {
		if _, err := exec.LookPath(cmd[0]); err != nil {
			return nil, err
		}
	}
	if _, err := os.Stat(argv0Go); err != nil {
		return nil, err
	}

60
	argv0 := C.CString(argv0Go)
61
	argvSlice := make([]*C.char, 0, len(cmd)+1)
62 63 64
	for _, arg := range cmd {
		argvSlice = append(argvSlice, C.CString(arg))
	}
65 66
	// argv array must be null terminated.
	argvSlice = append(argvSlice, nil)
67

68 69 70
	dbp := New(0)
	var pid int
	dbp.execPtraceFunc(func() {
71
		ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)),
E
Evgeny L 已提交
72
			C.CString(wd),
73 74
			&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
			&dbp.os.notificationPort)
75 76
		pid = int(ret)
	})
D
Derek Parker 已提交
77
	if pid <= 0 {
D
Derek Parker 已提交
78
		return nil, fmt.Errorf("could not fork/exec")
79
	}
D
Derek Parker 已提交
80
	dbp.pid = pid
81
	dbp.childProcess = true
82 83 84
	for i := range argvSlice {
		C.free(unsafe.Pointer(argvSlice[i]))
	}
D
Derek Parker 已提交
85

86 87 88 89
	// Initialize enough of the Process state so that we can use resume and
	// trapWait to wait until the child process calls execve.

	for {
90 91 92 93 94 95 96 97 98 99 100
		task := C.get_task_for_pid(C.int(dbp.pid))
		// The task_for_pid call races with the fork call. This can
		// result in the parent task being returned instead of the child.
		if task != dbp.os.task {
			err = dbp.updateThreadListForTask(task)
			if err == nil {
				break
			}
			if err != couldNotGetThreadCount && err != couldNotGetThreadList {
				return nil, err
			}
101 102 103 104 105 106 107
		}
	}

	if err := dbp.resume(); err != nil {
		return nil, err
	}

108
	dbp.common.ClearAllGCache()
D
Derek Parker 已提交
109
	for _, th := range dbp.threads {
110
		th.CurrentBreakpoint.Clear()
111 112 113 114 115 116
	}

	trapthread, err := dbp.trapWait(-1)
	if err != nil {
		return nil, err
	}
117
	if err := dbp.stop(nil); err != nil {
118 119 120 121
		return nil, err
	}

	dbp.os.initialized = true
122
	dbp, err = initializeDebugProcess(dbp, argv0Go, []string{})
123 124 125
	if err != nil {
		return nil, err
	}
126 127 128 129 130

	if err := dbp.SwitchThread(trapthread.ID); err != nil {
		return nil, err
	}

131 132 133
	return dbp, err
}

134
// Attach to an existing process with the given PID.
135
func Attach(pid int, _ []string) (*Process, error) {
136 137 138 139 140 141 142 143 144 145
	dbp := New(pid)

	kret := C.acquire_mach_task(C.int(pid),
		&dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort,
		&dbp.os.notificationPort)

	if kret != C.KERN_SUCCESS {
		return nil, fmt.Errorf("could not attach to %d", pid)
	}

146 147
	dbp.os.initialized = true

148 149 150 151 152 153 154 155 156 157
	var err error
	dbp.execPtraceFunc(func() { err = PtraceAttach(dbp.pid) })
	if err != nil {
		return nil, err
	}
	_, _, err = dbp.wait(dbp.pid, 0)
	if err != nil {
		return nil, err
	}

158
	dbp, err = initializeDebugProcess(dbp, "", []string{})
159 160 161 162 163
	if err != nil {
		dbp.Detach(false)
		return nil, err
	}
	return dbp, nil
164 165
}

D
Derek Parker 已提交
166
// Kill kills the process.
A
aarzilli 已提交
167
func (dbp *Process) kill() (err error) {
168 169 170
	if dbp.exited {
		return nil
	}
D
Derek Parker 已提交
171
	err = sys.Kill(-dbp.pid, sys.SIGKILL)
172 173 174
	if err != nil {
		return errors.New("could not deliver signal: " + err.Error())
	}
D
Derek Parker 已提交
175
	for port := range dbp.threads {
176 177 178 179 180
		if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
			return errors.New("could not resume task")
		}
	}
	for {
181 182
		var task C.task_t
		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
183 184 185 186
		if port == dbp.os.notificationPort {
			break
		}
	}
187
	dbp.postExit()
188 189 190
	return
}

D
Derek Parker 已提交
191
func (dbp *Process) requestManualStop() (err error) {
D
Derek Parker 已提交
192 193
	var (
		task          = C.mach_port_t(dbp.os.task)
194
		thread        = C.mach_port_t(dbp.currentThread.os.threadAct)
D
Derek Parker 已提交
195 196
		exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
	)
197
	dbp.os.halt = true
D
Derek Parker 已提交
198 199
	kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
	if kret != C.KERN_SUCCESS {
D
Derek Parker 已提交
200
		return fmt.Errorf("could not raise mach exception")
D
Derek Parker 已提交
201 202 203 204
	}
	return nil
}

205 206 207
var couldNotGetThreadCount = errors.New("could not get thread count")
var couldNotGetThreadList = errors.New("could not get thread list")

D
Derek Parker 已提交
208
func (dbp *Process) updateThreadList() error {
209 210 211 212
	return dbp.updateThreadListForTask(dbp.os.task)
}

func (dbp *Process) updateThreadListForTask(task C.task_t) error {
D
Derek Parker 已提交
213 214 215
	var (
		err   error
		kret  C.kern_return_t
216 217
		count C.int
		list  []uint32
D
Derek Parker 已提交
218 219
	)

220
	for {
221
		count = C.thread_count(task)
222
		if count == -1 {
223
			return couldNotGetThreadCount
224 225 226 227 228
		}
		list = make([]uint32, count)

		// TODO(dp) might be better to malloc mem in C and then free it here
		// instead of getting count above and passing in a slice
229
		kret = C.get_threads(task, unsafe.Pointer(&list[0]), count)
230 231 232
		if kret != -2 {
			break
		}
D
Derek Parker 已提交
233
	}
234
	if kret != C.KERN_SUCCESS {
235 236 237
		return couldNotGetThreadList
	}

D
Derek Parker 已提交
238
	for _, thread := range dbp.threads {
239
		thread.os.exists = false
D
Derek Parker 已提交
240 241 242
	}

	for _, port := range list {
D
Derek Parker 已提交
243
		thread, ok := dbp.threads[int(port)]
244 245
		if !ok {
			thread, err = dbp.addThread(int(port), false)
D
Derek Parker 已提交
246 247 248
			if err != nil {
				return err
			}
D
Derek Parker 已提交
249
		}
250 251 252
		thread.os.exists = true
	}

D
Derek Parker 已提交
253
	for threadID, thread := range dbp.threads {
254
		if !thread.os.exists {
D
Derek Parker 已提交
255
			delete(dbp.threads, threadID)
256
		}
D
Derek Parker 已提交
257 258 259 260 261
	}

	return nil
}

D
Derek Parker 已提交
262
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
D
Derek Parker 已提交
263
	if thread, ok := dbp.threads[port]; ok {
D
Derek Parker 已提交
264 265
		return thread, nil
	}
D
Derek Parker 已提交
266
	thread := &Thread{
D
Derek Parker 已提交
267
		ID:  port,
268 269
		dbp: dbp,
		os:  new(OSSpecificDetails),
D
Derek Parker 已提交
270
	}
D
Derek Parker 已提交
271
	dbp.threads[port] = thread
D
Derek Parker 已提交
272
	thread.os.threadAct = C.thread_act_t(port)
273
	if dbp.currentThread == nil {
D
Derek Parker 已提交
274
		dbp.SwitchThread(thread.ID)
D
Derek Parker 已提交
275
	}
D
Derek Parker 已提交
276 277 278
	return thread, nil
}

279
func findExecutable(path string, pid int) string {
280
	if path == "" {
281
		path = C.GoString(C.find_executable(C.int(pid)))
D
Derek Parker 已提交
282
	}
283
	return path
D
Derek Parker 已提交
284 285
}

D
Derek Parker 已提交
286
func (dbp *Process) trapWait(pid int) (*Thread, error) {
287
	for {
288 289
		task := dbp.os.task
		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
290

291 292
		switch port {
		case dbp.os.notificationPort:
293 294 295 296 297 298 299 300
			// on macOS >= 10.12.1 the task_t changes after an execve, we could
			// receive the notification for the death of the pre-execve task_t,
			// this could also happen *before* we are notified that our task_t has
			// changed.
			if dbp.os.task != task {
				continue
			}
			if !dbp.os.initialized {
D
Derek Parker 已提交
301
				if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask {
302 303 304
					continue
				}
			}
D
Derek Parker 已提交
305
			_, status, err := dbp.wait(dbp.pid, 0)
306 307 308
			if err != nil {
				return nil, err
			}
309
			dbp.postExit()
310
			return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
311

312
		case C.MACH_RCV_INTERRUPTED:
313 314 315
			dbp.stopMu.Lock()
			halt := dbp.os.halt
			dbp.stopMu.Unlock()
316
			if !halt {
317 318 319
				// Call trapWait again, it seems
				// MACH_RCV_INTERRUPTED is emitted before
				// process natural death _sometimes_.
320
				continue
321
			}
D
Derek Parker 已提交
322
			return nil, nil
323

324
		case 0:
D
Derek Parker 已提交
325
			return nil, fmt.Errorf("error while waiting for task")
326 327
		}

328 329 330 331 332 333 334 335 336 337 338
		// In macOS 10.12.1 if we received a notification for a task other than
		// the inferior's task and the inferior's task is no longer valid, this
		// means inferior called execve and its task_t changed.
		if dbp.os.task != task && C.task_is_valid(dbp.os.task) == 0 {
			dbp.os.task = task
			kret := C.reset_exception_ports(dbp.os.task, &dbp.os.exceptionPort, &dbp.os.notificationPort)
			if kret != C.KERN_SUCCESS {
				return nil, fmt.Errorf("could not follow task across exec: %d\n", kret)
			}
		}

339 340 341
		// Since we cannot be notified of new threads on OS X
		// this is as good a time as any to check for them.
		dbp.updateThreadList()
D
Derek Parker 已提交
342
		th, ok := dbp.threads[int(port)]
A
aarzilli 已提交
343
		if !ok {
344 345 346
			dbp.stopMu.Lock()
			halt := dbp.os.halt
			dbp.stopMu.Unlock()
347
			if halt {
348
				dbp.os.halt = false
A
aarzilli 已提交
349
				return th, nil
350
			}
A
aarzilli 已提交
351
			if dbp.firstStart || th.singleStepping {
352
				dbp.firstStart = false
A
aarzilli 已提交
353
				return th, nil
354
			}
A
aarzilli 已提交
355
			if err := th.Continue(); err != nil {
356 357 358
				return nil, err
			}
			continue
359
		}
360
		return th, nil
D
Derek Parker 已提交
361 362 363
	}
}

364
func (dbp *Process) waitForStop() ([]int, error) {
D
Derek Parker 已提交
365
	ports := make([]int, 0, len(dbp.threads))
366
	count := 0
367
	for {
368 369 370
		var task C.task_t
		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(1))
		if port != 0 && port != dbp.os.notificationPort && port != C.MACH_RCV_INTERRUPTED {
371 372 373
			count = 0
			ports = append(ports, int(port))
		} else {
374
			n := C.num_running_threads(dbp.os.task)
375 376 377 378 379
			if n == 0 {
				return ports, nil
			} else if n < 0 {
				return nil, fmt.Errorf("error waiting for thread stop %d", n)
			} else if count > 16 {
D
Derek Parker 已提交
380
				return nil, fmt.Errorf("could not stop process %d", n)
381
			}
382
		}
383 384 385
	}
}

386
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
387
	wg.Done()
388 389 390
}

func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
D
Derek Parker 已提交
391 392 393 394
	var status sys.WaitStatus
	wpid, err := sys.Wait4(pid, &status, options, nil)
	return wpid, &status, err
}
395

L
Luke Hoban 已提交
396 397 398 399
func killProcess(pid int) error {
	return sys.Kill(pid, sys.SIGINT)
}

400
func (dbp *Process) exitGuard(err error) error {
401 402 403
	if err != ErrContinueThread {
		return err
	}
D
Derek Parker 已提交
404
	_, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
405 406
	if werr == nil && status.Exited() {
		dbp.postExit()
407
		return proc.ErrProcessExited{Pid: dbp.pid, Status: status.ExitStatus()}
408
	}
409 410
	return err
}
411 412 413

func (dbp *Process) resume() error {
	// all threads stopped over a breakpoint are made to step over it
D
Derek Parker 已提交
414
	for _, thread := range dbp.threads {
415
		if thread.CurrentBreakpoint.Breakpoint != nil {
416
			if err := thread.StepInstruction(); err != nil {
417 418
				return err
			}
419
			thread.CurrentBreakpoint.Clear()
420 421 422
		}
	}
	// everything is resumed
D
Derek Parker 已提交
423
	for _, thread := range dbp.threads {
424 425 426 427 428 429
		if err := thread.resume(); err != nil {
			return dbp.exitGuard(err)
		}
	}
	return nil
}
430

431
// stop stops all running threads and sets breakpoints
432 433
func (dbp *Process) stop(trapthread *Thread) (err error) {
	if dbp.exited {
434
		return &proc.ErrProcessExited{Pid: dbp.Pid()}
435 436 437
	}
	for _, th := range dbp.threads {
		if !th.Stopped() {
438
			if err := th.stop(); err != nil {
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
				return dbp.exitGuard(err)
			}
		}
	}

	ports, err := dbp.waitForStop()
	if err != nil {
		return err
	}
	if !dbp.os.initialized {
		return nil
	}
	trapthread.SetCurrentBreakpoint()
	for _, port := range ports {
		if th, ok := dbp.threads[port]; ok {
			err := th.SetCurrentBreakpoint()
			if err != nil {
				return err
			}
		}
	}
	return nil
}

463
func (dbp *Process) detach(kill bool) error {
464 465
	return PtraceDetach(dbp.pid, 0)
}
466 467 468 469 470

func (dbp *Process) entryPoint() (uint64, error) {
	//TODO(aarzilli): implement this
	return 0, nil
}