proc_darwin.go 7.6 KB
Newer Older
D
Derek Parker 已提交
1
package proc
D
Derek Parker 已提交
2

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

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

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

34 35
// Create and begin debugging a new process. Uses a
// 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.
D
Derek Parker 已提交
38
func Launch(cmd []string) (*Process, error) {
39
	argv0Go, err := filepath.Abs(cmd[0])
40 41 42
	if err != nil {
		return nil, err
	}
43
	// Make sure the binary exists.
44 45 46 47 48 49 50 51 52
	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
	}

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

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

77
	dbp, err = initializeDebugProcess(dbp, argv0Go, false)
78 79 80 81 82 83 84
	if err != nil {
		return nil, err
	}
	err = dbp.Continue()
	return dbp, err
}

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

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
func (dbp *Process) Kill() (err error) {
	err = sys.Kill(dbp.Pid, sys.SIGKILL)
	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 {
		port := C.mach_port_wait(dbp.os.portSet)
		if port == dbp.os.notificationPort {
			break
		}
	}
	dbp.exited = true
	return
}

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

D
Derek Parker 已提交
133
func (dbp *Process) updateThreadList() error {
D
Derek Parker 已提交
134 135 136 137 138
	var (
		err   error
		kret  C.kern_return_t
		count = C.thread_count(C.task_t(dbp.os.task))
	)
D
Derek Parker 已提交
139
	if count == -1 {
D
Derek Parker 已提交
140
		return fmt.Errorf("could not get thread count")
D
Derek Parker 已提交
141 142
	}
	list := make([]uint32, count)
D
Derek Parker 已提交
143

D
Derek Parker 已提交
144
	// TODO(dp) might be better to malloc mem in C and then free it here
D
Derek Parker 已提交
145 146 147
	// instead of getting count above and passing in a slice
	kret = C.get_threads(C.task_t(dbp.os.task), unsafe.Pointer(&list[0]))
	if kret != C.KERN_SUCCESS {
D
Derek Parker 已提交
148
		return fmt.Errorf("could not get thread list")
D
Derek Parker 已提交
149 150
	}
	if count < 0 {
D
Derek Parker 已提交
151
		return fmt.Errorf("could not get thread list")
D
Derek Parker 已提交
152 153 154
	}

	for _, port := range list {
D
Derek Parker 已提交
155 156 157 158 159
		if _, ok := dbp.Threads[int(port)]; !ok {
			_, err = dbp.addThread(int(port), false)
			if err != nil {
				return err
			}
D
Derek Parker 已提交
160 161 162 163 164 165
		}
	}

	return nil
}

D
Derek Parker 已提交
166
func (dbp *Process) addThread(port int, attach bool) (*Thread, error) {
D
Derek Parker 已提交
167 168 169
	if thread, ok := dbp.Threads[port]; ok {
		return thread, nil
	}
D
Derek Parker 已提交
170
	thread := &Thread{
171 172 173
		Id:  port,
		dbp: dbp,
		os:  new(OSSpecificDetails),
D
Derek Parker 已提交
174 175 176
	}
	dbp.Threads[port] = thread
	thread.os.thread_act = C.thread_act_t(port)
D
Derek Parker 已提交
177 178 179
	if dbp.CurrentThread == nil {
		dbp.CurrentThread = thread
	}
D
Derek Parker 已提交
180 181 182
	return thread, nil
}

D
Derek Parker 已提交
183
func (dbp *Process) parseDebugFrame(exe *macho.File, wg *sync.WaitGroup) {
D
Derek Parker 已提交
184 185 186 187 188 189 190 191
	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)
		}
192
		dbp.frameEntries = frame.Parse(debugFrame)
D
Derek Parker 已提交
193 194 195 196 197 198
	} else {
		fmt.Println("could not find __debug_frame section in binary")
		os.Exit(1)
	}
}

D
Derek Parker 已提交
199
func (dbp *Process) obtainGoSymbols(exe *macho.File, wg *sync.WaitGroup) {
D
Derek Parker 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
	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)
	}

231
	dbp.goSymTable = tab
D
Derek Parker 已提交
232 233
}

D
Derek Parker 已提交
234
func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) {
D
Derek Parker 已提交
235 236 237 238 239 240 241 242
	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)
		}
243
		dbp.lineInfo = line.Parse(debugLine)
D
Derek Parker 已提交
244 245 246 247 248 249
	} else {
		fmt.Println("could not find __debug_line section in binary")
		os.Exit(1)
	}
}

D
Derek Parker 已提交
250
func (dbp *Process) findExecutable(path string) (*macho.File, error) {
251 252
	if path == "" {
		path = C.GoString(C.find_executable(C.int(dbp.Pid)))
D
Derek Parker 已提交
253
	}
254
	exe, err := macho.Open(path)
D
Derek Parker 已提交
255 256 257 258 259 260 261
	if err != nil {
		return nil, err
	}
	data, err := exe.DWARF()
	if err != nil {
		return nil, err
	}
262
	dbp.dwarf = data
D
Derek Parker 已提交
263
	return exe, nil
D
Derek Parker 已提交
264 265
}

D
Derek Parker 已提交
266
func (dbp *Process) trapWait(pid int) (*Thread, error) {
267
	var (
D
Derek Parker 已提交
268
		th  *Thread
269 270 271 272
		err error
	)
	for {
		port := C.mach_port_wait(dbp.os.portSet)
273

274 275
		switch port {
		case dbp.os.notificationPort:
276
			_, status, err := wait(dbp.Pid, dbp.Pid, 0)
277 278 279 280 281 282 283 284 285 286
			if err != nil {
				return nil, err
			}
			dbp.exited = true
			return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
		case C.MACH_RCV_INTERRUPTED:
			if !dbp.halt {
				// Call trapWait again, it seems
				// MACH_RCV_INTERRUPTED is emitted before
				// process natural death _sometimes_.
287
				continue
288
			}
D
Derek Parker 已提交
289
			return nil, nil
290
		case 0:
D
Derek Parker 已提交
291
			return nil, fmt.Errorf("error while waiting for task")
292 293 294 295 296 297
		}

		// 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()
		th, err = dbp.handleBreakpointOnThread(int(port))
298
		if err != nil {
D
Derek Parker 已提交
299
			if _, ok := err.(NoBreakpointError); ok {
D
Derek Parker 已提交
300 301 302 303
				if dbp.halt {
					dbp.halt = false
					return dbp.Threads[int(port)], nil
				}
304 305
				th := dbp.Threads[int(port)]
				if dbp.firstStart || dbp.singleStepping || th.singleStepping {
306 307 308
					dbp.firstStart = false
					return dbp.Threads[int(port)], nil
				}
309 310
				if err := th.Continue(); err != nil {
					return nil, err
311 312 313
				}
				continue
			}
314
			return nil, err
315
		}
316
		return th, nil
D
Derek Parker 已提交
317
	}
318
	return th, nil
D
Derek Parker 已提交
319 320
}

321
func wait(pid, tgid, options int) (int, *sys.WaitStatus, error) {
D
Derek Parker 已提交
322 323 324 325
	var status sys.WaitStatus
	wpid, err := sys.Wait4(pid, &status, options, nil)
	return wpid, &status, err
}