proc_darwin.go 9.2 KB
Newer Older
D
Derek Parker 已提交
1
package proc
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 9 10
import "C"
import (
	"debug/gosym"
	"debug/macho"
11
	"errors"
D
Derek Parker 已提交
12 13
	"fmt"
	"os"
14
	"os/exec"
15
	"path/filepath"
D
Derek Parker 已提交
16 17 18 19
	"sync"
	"unsafe"

	"github.com/derekparker/delve/dwarf/frame"
D
Derek Parker 已提交
20
	"github.com/derekparker/delve/dwarf/line"
D
Derek Parker 已提交
21 22 23
	sys "golang.org/x/sys/unix"
)

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

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

D
Derek Parker 已提交
35
// Launch creates and begins debugging a new process. Uses a
36
// custom fork/exec process in order to take advantage of
D
Derek Parker 已提交
37 38
// PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions.
D
Derek Parker 已提交
39
func Launch(cmd []string) (*Process, error) {
40
	argv0Go, err := filepath.Abs(cmd[0])
41 42 43
	if err != nil {
		return nil, err
	}
44
	// Make sure the binary exists.
45 46 47 48 49 50 51 52 53
	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
	}

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

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

78
	dbp, err = initializeDebugProcess(dbp, argv0Go, false)
79 80 81
	if err != nil {
		return nil, err
	}
A
aarzilli 已提交
82
	err = dbp.Continue()
83 84 85
	return dbp, err
}

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
// 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)
	}

	return initializeDebugProcess(dbp, "", true)
}

D
Derek Parker 已提交
101
// Kill kills the process.
102
func (dbp *Process) Kill() (err error) {
103 104 105
	if dbp.exited {
		return nil
	}
106
	err = sys.Kill(-dbp.Pid, sys.SIGKILL)
107 108 109 110 111 112 113 114 115
	if err != nil {
		return errors.New("could not deliver signal: " + err.Error())
	}
	for port := range dbp.Threads {
		if C.thread_resume(C.thread_act_t(port)) != C.KERN_SUCCESS {
			return errors.New("could not resume task")
		}
	}
	for {
116
		port := C.mach_port_wait(dbp.os.portSet, C.int(0))
117 118 119 120
		if port == dbp.os.notificationPort {
			break
		}
	}
121
	dbp.postExit()
122 123 124
	return
}

D
Derek Parker 已提交
125
func (dbp *Process) requestManualStop() (err error) {
D
Derek Parker 已提交
126 127
	var (
		task          = C.mach_port_t(dbp.os.task)
D
Derek Parker 已提交
128
		thread        = C.mach_port_t(dbp.CurrentThread.os.threadAct)
D
Derek Parker 已提交
129 130 131 132
		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 已提交
133
		return fmt.Errorf("could not raise mach exception")
D
Derek Parker 已提交
134 135 136 137
	}
	return nil
}

D
Derek Parker 已提交
138
func (dbp *Process) updateThreadList() error {
D
Derek Parker 已提交
139 140 141
	var (
		err   error
		kret  C.kern_return_t
142 143
		count C.int
		list  []uint32
D
Derek Parker 已提交
144 145
	)

146 147 148 149 150 151 152 153 154 155 156 157 158
	for {
		count = C.thread_count(C.task_t(dbp.os.task))
		if count == -1 {
			return fmt.Errorf("could not get thread count")
		}
		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
		kret = C.get_threads(C.task_t(dbp.os.task), unsafe.Pointer(&list[0]), count)
		if kret != -2 {
			break
		}
D
Derek Parker 已提交
159
	}
160
	if kret != C.KERN_SUCCESS {
D
Derek Parker 已提交
161
		return fmt.Errorf("could not get thread list")
D
Derek Parker 已提交
162 163 164
	}

	for _, port := range list {
D
Derek Parker 已提交
165 166 167 168 169
		if _, ok := dbp.Threads[int(port)]; !ok {
			_, err = dbp.addThread(int(port), false)
			if err != nil {
				return err
			}
D
Derek Parker 已提交
170 171 172 173 174 175
		}
	}

	return nil
}

D
Derek Parker 已提交
176
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
D
Derek Parker 已提交
177 178 179
	if thread, ok := dbp.Threads[port]; ok {
		return thread, nil
	}
D
Derek Parker 已提交
180
	thread := &Thread{
D
Derek Parker 已提交
181
		ID:  port,
182 183
		dbp: dbp,
		os:  new(OSSpecificDetails),
D
Derek Parker 已提交
184 185
	}
	dbp.Threads[port] = thread
D
Derek Parker 已提交
186
	thread.os.threadAct = C.thread_act_t(port)
D
Derek Parker 已提交
187
	if dbp.CurrentThread == nil {
D
Derek Parker 已提交
188
		dbp.SwitchThread(thread.ID)
D
Derek Parker 已提交
189
	}
D
Derek Parker 已提交
190 191 192
	return thread, nil
}

D
Derek Parker 已提交
193
func (dbp *Process) parseDebugFrame(exe *macho.File, wg *sync.WaitGroup) {
D
Derek Parker 已提交
194 195 196 197 198 199 200 201
	defer wg.Done()

	if sec := exe.Section("__debug_frame"); sec != nil {
		debugFrame, err := exe.Section("__debug_frame").Data()
		if err != nil {
			fmt.Println("could not get __debug_frame section", err)
			os.Exit(1)
		}
202
		dbp.frameEntries = frame.Parse(debugFrame)
D
Derek Parker 已提交
203 204 205 206 207 208
	} else {
		fmt.Println("could not find __debug_frame section in binary")
		os.Exit(1)
	}
}

D
Derek Parker 已提交
209
func (dbp *Process) obtainGoSymbols(exe *macho.File, wg *sync.WaitGroup) {
D
Derek Parker 已提交
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
	defer wg.Done()

	var (
		symdat  []byte
		pclndat []byte
		err     error
	)

	if sec := exe.Section("__gosymtab"); sec != nil {
		symdat, err = sec.Data()
		if err != nil {
			fmt.Println("could not get .gosymtab section", err)
			os.Exit(1)
		}
	}

	if sec := exe.Section("__gopclntab"); sec != nil {
		pclndat, err = sec.Data()
		if err != nil {
			fmt.Println("could not get .gopclntab section", err)
			os.Exit(1)
		}
	}

	pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr)
	tab, err := gosym.NewTable(symdat, pcln)
	if err != nil {
		fmt.Println("could not get initialize line table", err)
		os.Exit(1)
	}

241
	dbp.goSymTable = tab
D
Derek Parker 已提交
242 243
}

D
Derek Parker 已提交
244
func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) {
D
Derek Parker 已提交
245 246 247 248 249 250 251 252
	defer wg.Done()

	if sec := exe.Section("__debug_line"); sec != nil {
		debugLine, err := exe.Section("__debug_line").Data()
		if err != nil {
			fmt.Println("could not get __debug_line section", err)
			os.Exit(1)
		}
253
		dbp.lineInfo = line.Parse(debugLine)
D
Derek Parker 已提交
254 255 256 257 258 259
	} else {
		fmt.Println("could not find __debug_line section in binary")
		os.Exit(1)
	}
}

D
Derek Parker 已提交
260
func (dbp *Process) findExecutable(path string) (*macho.File, error) {
261 262
	if path == "" {
		path = C.GoString(C.find_executable(C.int(dbp.Pid)))
D
Derek Parker 已提交
263
	}
264
	exe, err := macho.Open(path)
D
Derek Parker 已提交
265 266 267 268 269 270 271
	if err != nil {
		return nil, err
	}
	data, err := exe.DWARF()
	if err != nil {
		return nil, err
	}
272
	dbp.dwarf = data
D
Derek Parker 已提交
273
	return exe, nil
D
Derek Parker 已提交
274 275
}

D
Derek Parker 已提交
276
func (dbp *Process) trapWait(pid int) (*Thread, error) {
277
	for {
278
		port := C.mach_port_wait(dbp.os.portSet, C.int(0))
279

280 281
		switch port {
		case dbp.os.notificationPort:
282
			_, status, err := dbp.wait(dbp.Pid, 0)
283 284 285
			if err != nil {
				return nil, err
			}
286
			dbp.postExit()
287
			return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
288

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

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

		// 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()
A
aarzilli 已提交
305 306
		th, ok := dbp.Threads[int(port)]
		if !ok {
307 308
			if dbp.halt {
				dbp.halt = false
A
aarzilli 已提交
309
				return th, nil
310
			}
A
aarzilli 已提交
311
			if dbp.firstStart || th.singleStepping {
312
				dbp.firstStart = false
A
aarzilli 已提交
313
				return th, nil
314
			}
A
aarzilli 已提交
315
			if err := th.Continue(); err != nil {
316 317 318
				return nil, err
			}
			continue
319
		}
320
		return th, nil
D
Derek Parker 已提交
321 322 323
	}
}

324 325 326
func (dbp *Process) waitForStop() ([]int, error) {
	ports := make([]int, 0, len(dbp.Threads))
	count := 0
327 328
	for {
		port := C.mach_port_wait(dbp.os.portSet, C.int(1))
329 330 331 332 333 334 335 336 337 338
		if port != 0 {
			count = 0
			ports = append(ports, int(port))
		} else {
			n := C.num_running_threads(C.task_t(dbp.os.task))
			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 已提交
339
				return nil, fmt.Errorf("could not stop process %d", n)
340
			}
341
		}
342 343 344 345 346 347 348 349 350 351 352
	}
}

func (dbp *Process) setCurrentBreakpoints(trapthread *Thread) error {
	ports, err := dbp.waitForStop()
	if err != nil {
		return err
	}
	trapthread.SetCurrentBreakpoint()
	for _, port := range ports {
		if th, ok := dbp.Threads[port]; ok {
353 354 355 356 357 358
			err := th.SetCurrentBreakpoint()
			if err != nil {
				return err
			}
		}
	}
359
	return nil
360 361
}

362
func (dbp *Process) loadProcessInformation(wg *sync.WaitGroup) {
363
	wg.Done()
364 365 366
}

func (dbp *Process) wait(pid, options int) (int, *sys.WaitStatus, error) {
D
Derek Parker 已提交
367 368 369 370
	var status sys.WaitStatus
	wpid, err := sys.Wait4(pid, &status, options, nil)
	return wpid, &status, err
}
371

L
Luke Hoban 已提交
372 373 374 375
func killProcess(pid int) error {
	return sys.Kill(pid, sys.SIGINT)
}

376
func (dbp *Process) exitGuard(err error) error {
377 378 379 380 381 382 383 384
	if err != ErrContinueThread {
		return err
	}
	_, status, werr := dbp.wait(dbp.Pid, sys.WNOHANG)
	if werr == nil && status.Exited() {
		dbp.postExit()
		return ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
	}
385 386
	return err
}
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405

func (dbp *Process) resume() error {
	// all threads stopped over a breakpoint are made to step over it
	for _, thread := range dbp.Threads {
		if thread.CurrentBreakpoint != nil {
			if err := thread.Step(); err != nil {
				return err
			}
			thread.CurrentBreakpoint = nil
		}
	}
	// everything is resumed
	for _, thread := range dbp.Threads {
		if err := thread.resume(); err != nil {
			return dbp.exitGuard(err)
		}
	}
	return nil
}