proc_darwin.go 10.0 KB
Newer Older
1
package native
D
Derek Parker 已提交
2

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

	sys "golang.org/x/sys/unix"
18 19

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

D
Derek Parker 已提交
22
// OSProcessDetails holds Darwin specific information.
D
Derek Parker 已提交
23
type OSProcessDetails struct {
24 25 26
	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).
27
	initialized      bool
28 29 30 31

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

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

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

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

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

	for {
D
Derek Parker 已提交
87
		err = dbp.updateThreadListForTask(C.get_task_for_pid(C.int(dbp.pid)))
88 89 90 91 92 93 94 95 96 97 98 99 100
		if err == nil {
			break
		}
		if err != couldNotGetThreadCount && err != couldNotGetThreadList {
			return nil, err
		}
	}

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

	dbp.allGCache = nil
D
Derek Parker 已提交
101
	for _, th := range dbp.threads {
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
		th.clearBreakpointState()
	}

	trapthread, err := dbp.trapWait(-1)
	if err != nil {
		return nil, err
	}
	if err := dbp.Halt(); err != nil {
		return nil, dbp.exitGuard(err)
	}

	_, err = dbp.waitForStop()
	if err != nil {
		return nil, err
	}

	dbp.os.initialized = true
119
	dbp, err = initializeDebugProcess(dbp, argv0Go, false)
120 121 122
	if err != nil {
		return nil, err
	}
123 124 125 126 127

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

128 129 130
	return dbp, err
}

131 132 133 134 135 136 137 138 139 140 141 142
// Attach to an existing process with the given PID.
func Attach(pid int) (*Process, error) {
	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)
	}

143 144
	dbp.os.initialized = true

145 146 147
	return initializeDebugProcess(dbp, "", true)
}

D
Derek Parker 已提交
148
// Kill kills the process.
149
func (dbp *Process) Kill() (err error) {
150 151 152
	if dbp.exited {
		return nil
	}
D
Derek Parker 已提交
153
	err = sys.Kill(-dbp.pid, sys.SIGKILL)
154 155 156
	if err != nil {
		return errors.New("could not deliver signal: " + err.Error())
	}
D
Derek Parker 已提交
157
	for port := range dbp.threads {
158 159 160 161 162
		if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
			return errors.New("could not resume task")
		}
	}
	for {
163 164
		var task C.task_t
		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
165 166 167 168
		if port == dbp.os.notificationPort {
			break
		}
	}
169
	dbp.postExit()
170 171 172
	return
}

D
Derek Parker 已提交
173
func (dbp *Process) requestManualStop() (err error) {
D
Derek Parker 已提交
174 175
	var (
		task          = C.mach_port_t(dbp.os.task)
176
		thread        = C.mach_port_t(dbp.currentThread.os.threadAct)
D
Derek Parker 已提交
177 178 179 180
		exceptionPort = C.mach_port_t(dbp.os.exceptionPort)
	)
	kret := C.raise_exception(task, thread, exceptionPort, C.EXC_BREAKPOINT)
	if kret != C.KERN_SUCCESS {
D
Derek Parker 已提交
181
		return fmt.Errorf("could not raise mach exception")
D
Derek Parker 已提交
182 183 184 185
	}
	return nil
}

186 187 188
var couldNotGetThreadCount = errors.New("could not get thread count")
var couldNotGetThreadList = errors.New("could not get thread list")

D
Derek Parker 已提交
189
func (dbp *Process) updateThreadList() error {
190 191 192 193
	return dbp.updateThreadListForTask(dbp.os.task)
}

func (dbp *Process) updateThreadListForTask(task C.task_t) error {
D
Derek Parker 已提交
194 195 196
	var (
		err   error
		kret  C.kern_return_t
197 198
		count C.int
		list  []uint32
D
Derek Parker 已提交
199 200
	)

201
	for {
202
		count = C.thread_count(task)
203
		if count == -1 {
204
			return couldNotGetThreadCount
205 206 207 208 209
		}
		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
210
		kret = C.get_threads(task, unsafe.Pointer(&list[0]), count)
211 212 213
		if kret != -2 {
			break
		}
D
Derek Parker 已提交
214
	}
215
	if kret != C.KERN_SUCCESS {
216 217 218
		return couldNotGetThreadList
	}

D
Derek Parker 已提交
219
	for _, thread := range dbp.threads {
220
		thread.os.exists = false
D
Derek Parker 已提交
221 222 223
	}

	for _, port := range list {
D
Derek Parker 已提交
224
		thread, ok := dbp.threads[int(port)]
225 226
		if !ok {
			thread, err = dbp.addThread(int(port), false)
D
Derek Parker 已提交
227 228 229
			if err != nil {
				return err
			}
D
Derek Parker 已提交
230
		}
231 232 233
		thread.os.exists = true
	}

D
Derek Parker 已提交
234
	for threadID, thread := range dbp.threads {
235
		if !thread.os.exists {
D
Derek Parker 已提交
236
			delete(dbp.threads, threadID)
237
		}
D
Derek Parker 已提交
238 239 240 241 242
	}

	return nil
}

D
Derek Parker 已提交
243
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
D
Derek Parker 已提交
244
	if thread, ok := dbp.threads[port]; ok {
D
Derek Parker 已提交
245 246
		return thread, nil
	}
D
Derek Parker 已提交
247
	thread := &Thread{
D
Derek Parker 已提交
248
		ID:  port,
249 250
		dbp: dbp,
		os:  new(OSSpecificDetails),
D
Derek Parker 已提交
251
	}
D
Derek Parker 已提交
252
	dbp.threads[port] = thread
D
Derek Parker 已提交
253
	thread.os.threadAct = C.thread_act_t(port)
254
	if dbp.currentThread == nil {
D
Derek Parker 已提交
255
		dbp.SwitchThread(thread.ID)
D
Derek Parker 已提交
256
	}
D
Derek Parker 已提交
257 258 259
	return thread, nil
}

260
func findExecutable(path string, pid int) string {
261
	if path == "" {
262
		path = C.GoString(C.find_executable(C.int(pid)))
D
Derek Parker 已提交
263
	}
264
	return path
D
Derek Parker 已提交
265 266
}

D
Derek Parker 已提交
267
func (dbp *Process) trapWait(pid int) (*Thread, error) {
268
	for {
269 270
		task := dbp.os.task
		port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0))
271

272 273
		switch port {
		case dbp.os.notificationPort:
274 275 276 277 278 279 280 281
			// 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 已提交
282
				if pidtask := C.get_task_for_pid(C.int(dbp.pid)); pidtask != 0 && dbp.os.task != pidtask {
283 284 285
					continue
				}
			}
D
Derek Parker 已提交
286
			_, status, err := dbp.wait(dbp.pid, 0)
287 288 289
			if err != nil {
				return nil, err
			}
290
			dbp.postExit()
291
			return nil, proc.ProcessExitedError{Pid: dbp.pid, Status: status.ExitStatus()}
292

293 294 295 296 297
		case C.MACH_RCV_INTERRUPTED:
			if !dbp.halt {
				// Call trapWait again, it seems
				// MACH_RCV_INTERRUPTED is emitted before
				// process natural death _sometimes_.
298
				continue
299
			}
D
Derek Parker 已提交
300
			return nil, nil
301

302
		case 0:
D
Derek Parker 已提交
303
			return nil, fmt.Errorf("error while waiting for task")
304 305
		}

306 307 308 309 310 311 312 313 314 315 316
		// 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)
			}
		}

317 318 319
		// 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 已提交
320
		th, ok := dbp.threads[int(port)]
A
aarzilli 已提交
321
		if !ok {
322 323
			if dbp.halt {
				dbp.halt = false
A
aarzilli 已提交
324
				return th, nil
325
			}
A
aarzilli 已提交
326
			if dbp.firstStart || th.singleStepping {
327
				dbp.firstStart = false
A
aarzilli 已提交
328
				return th, nil
329
			}
A
aarzilli 已提交
330
			if err := th.Continue(); err != nil {
331 332 333
				return nil, err
			}
			continue
334
		}
335
		return th, nil
D
Derek Parker 已提交
336 337 338
	}
}

339
func (dbp *Process) waitForStop() ([]int, error) {
D
Derek Parker 已提交
340
	ports := make([]int, 0, len(dbp.threads))
341
	count := 0
342
	for {
343 344 345
		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 {
346 347 348
			count = 0
			ports = append(ports, int(port))
		} else {
349
			n := C.num_running_threads(dbp.os.task)
350 351 352 353 354
			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 已提交
355
				return nil, fmt.Errorf("could not stop process %d", n)
356
			}
357
		}
358 359 360 361 362 363 364 365 366 367
	}
}

func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
	ports, err := dbp.waitForStop()
	if err != nil {
		return err
	}
	trapthread.SetCurrentBreakpoint()
	for _, port := range ports {
D
Derek Parker 已提交
368
		if th, ok := dbp.threads[port]; ok {
369 370 371 372 373 374
			err := th.SetCurrentBreakpoint()
			if err != nil {
				return err
			}
		}
	}
375
	return nil
376 377
}

378
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
379
	wg.Done()
380 381 382
}

func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
D
Derek Parker 已提交
383 384 385 386
	var status sys.WaitStatus
	wpid, err := sys.Wait4(pid, &status, options, nil)
	return wpid, &status, err
}
387

L
Luke Hoban 已提交
388 389 390 391
func killProcess(pid int) error {
	return sys.Kill(pid, sys.SIGINT)
}

392
func (dbp *Process) exitGuard(err error) error {
393 394 395
	if err != ErrContinueThread {
		return err
	}
D
Derek Parker 已提交
396
	_, status, werr := dbp.wait(dbp.pid, sys.WNOHANG)
397 398
	if werr == nil && status.Exited() {
		dbp.postExit()
399
		return proc.ProcessExitedError{Pid: dbp.pid, Status: status.ExitStatus()}
400
	}
401 402
	return err
}
403 404 405

func (dbp *Process) resume() error {
	// all threads stopped over a breakpoint are made to step over it
D
Derek Parker 已提交
406
	for _, thread := range dbp.threads {
407
		if thread.CurrentBreakpoint != nil {
408
			if err := thread.StepInstruction(); err != nil {
409 410 411 412 413 414
				return err
			}
			thread.CurrentBreakpoint = nil
		}
	}
	// everything is resumed
D
Derek Parker 已提交
415
	for _, thread := range dbp.threads {
416 417 418 419 420 421
		if err := thread.resume(); err != nil {
			return dbp.exitGuard(err)
		}
	}
	return nil
}
422

423
func (dbp *Process) detach(kill bool) error {
424 425
	return PtraceDetach(dbp.pid, 0)
}