From 63b8fa82698bc9bcf8380c0304fe290047d68240 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Thu, 10 Nov 2016 11:20:08 +0100 Subject: [PATCH] proc: support for ver. 10.12.1 the OS formerly known as Mac OS X Fixes #645 --- Makefile | 2 +- proc/exec_darwin.c | 2 + proc/proc_darwin.c | 64 ++++++++++++++++++++---- proc/proc_darwin.go | 110 ++++++++++++++++++++++++++++++++++++----- proc/proc_darwin.h | 11 ++++- proc/proc_windows.go | 2 +- proc/test/support.go | 7 ++- proc/threads_darwin.go | 14 +++++- proc/variables.go | 1 - 9 files changed, 185 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index aa02692d..5a22cd1f 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ else endif test-proc-run: - go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.run="$(RUN)" $(PREFIX)/proc + go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.v -test.run="$(RUN)" $(PREFIX)/proc test-integration-run: go test $(TEST_FLAGS) $(BUILD_FLAGS) -test.run="$(RUN)" $(PREFIX)/service/test diff --git a/proc/exec_darwin.c b/proc/exec_darwin.c index adeaa1bd..67409e3c 100644 --- a/proc/exec_darwin.c +++ b/proc/exec_darwin.c @@ -97,6 +97,8 @@ fork_exec(char *argv0, char **argv, int size, exit(1); } + sleep(1); + // Create the child process. execve(argv0, argv, environ); diff --git a/proc/proc_darwin.c b/proc/proc_darwin.c index c2085691..24a60849 100644 --- a/proc/proc_darwin.c +++ b/proc/proc_darwin.c @@ -68,6 +68,23 @@ acquire_mach_task(int tid, return mach_port_move_member(self, *notification_port, *port_set); } +kern_return_t +reset_exception_ports(task_t task, mach_port_t *exception_port, mach_port_t *notification_port) { + kern_return_t kret; + mach_port_t prev_not; + mach_port_t self = mach_task_self(); + + kret = task_set_exception_ports(task, EXC_MASK_BREAKPOINT|EXC_MASK_SOFTWARE, *exception_port, + EXCEPTION_DEFAULT, THREAD_STATE_NONE); + if (kret != KERN_SUCCESS) return kret; + + kret = mach_port_request_notification(self, task, MACH_NOTIFY_DEAD_NAME, 0, *notification_port, + MACH_MSG_TYPE_MAKE_SEND_ONCE, &prev_not); + if (kret != KERN_SUCCESS) return kret; + + return KERN_SUCCESS; +} + char * find_executable(int pid) { static char pathbuf[PATH_MAX]; @@ -115,7 +132,7 @@ thread_count(task_t task) { } mach_port_t -mach_port_wait(mach_port_t port_set, int nonblocking) { +mach_port_wait(mach_port_t port_set, task_t *task, int nonblocking) { kern_return_t kret; thread_act_t thread; NDR_record_t *ndr; @@ -136,14 +153,20 @@ mach_port_wait(mach_port_t port_set, int nonblocking) { if (kret == MACH_RCV_INTERRUPTED) return kret; if (kret != MACH_MSG_SUCCESS) return 0; - mach_msg_body_t *bod = (mach_msg_body_t*)(&msg.hdr + 1); - mach_msg_port_descriptor_t *desc = (mach_msg_port_descriptor_t *)(bod + 1); - thread = desc[0].name; - ndr = (NDR_record_t *)(desc + 2); - data = (integer_t *)(ndr + 1); switch (msg.hdr.msgh_id) { - case 2401: // Exception + case 2401: { // Exception + // 2401 is the exception_raise event, defined in: + // http://opensource.apple.com/source/xnu/xnu-2422.1.72/osfmk/mach/exc.defs?txt + // compile this file with mig to get the C version of the description + + mach_msg_body_t *bod = (mach_msg_body_t*)(&msg.hdr + 1); + mach_msg_port_descriptor_t *desc = (mach_msg_port_descriptor_t *)(bod + 1); + thread = desc[0].name; + *task = desc[1].name; + ndr = (NDR_record_t *)(desc + 2); + data = (integer_t *)(ndr + 1); + if (thread_suspend(thread) != KERN_SUCCESS) return 0; // Send our reply back so the kernel knows this exception has been handled. kret = mach_send_reply(msg.hdr); @@ -151,13 +174,20 @@ mach_port_wait(mach_port_t port_set, int nonblocking) { if (data[2] == EXC_SOFT_SIGNAL) { if (data[3] != SIGTRAP) { if (thread_resume(thread) != KERN_SUCCESS) return 0; - return mach_port_wait(port_set, nonblocking); + return mach_port_wait(port_set, task, nonblocking); } } return thread; + } - case 72: // Death + case 72: { // Death + // 72 is mach_notify_dead_name, defined in: + // https://opensource.apple.com/source/xnu/xnu-1228.7.58/osfmk/mach/notify.defs?txt + // compile this file with mig to get the C version of the description + ndr = (NDR_record_t *)(&msg.hdr + 1); + *task = *((mach_port_name_t *)(ndr + 1)); return msg.hdr.msgh_local_port; + } } return 0; } @@ -183,3 +213,19 @@ kern_return_t raise_exception(mach_port_t task, mach_port_t thread, mach_port_t exception_port, exception_type_t exception) { return exception_raise(exception_port, thread, task, exception, 0, 0); } + +task_t +get_task_for_pid(int pid) { + task_t task = 0; + mach_port_t self = mach_task_self(); + + task_for_pid(self, pid, &task); + return task; +} + +int +task_is_valid(task_t task) { + struct task_basic_info info; + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + return task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS; +} diff --git a/proc/proc_darwin.go b/proc/proc_darwin.go index 0e1a9ce3..927d7e4b 100644 --- a/proc/proc_darwin.go +++ b/proc/proc_darwin.go @@ -27,6 +27,7 @@ type OSProcessDetails struct { 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). + initialized bool // the main port we use, will return messages from both the // exception and notification ports. @@ -81,11 +82,51 @@ func Launch(cmd []string, wd string) (*Process, error) { C.free(unsafe.Pointer(argvSlice[i])) } + // Initialize enough of the Process state so that we can use resume and + // trapWait to wait until the child process calls execve. + + for { + err = dbp.updateThreadListForTask(C.get_task_for_pid(C.int(dbp.Pid))) + if err == nil { + break + } + if err != couldNotGetThreadCount && err != couldNotGetThreadList { + return nil, err + } + } + + if err := dbp.resume(); err != nil { + return nil, err + } + + dbp.allGCache = nil + for _, th := range dbp.Threads { + 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 dbp, err = initializeDebugProcess(dbp, argv0Go, false) if err != nil { return nil, err } - err = dbp.Continue() + + if err := dbp.SwitchThread(trapthread.ID); err != nil { + return nil, err + } + return dbp, err } @@ -101,6 +142,8 @@ func Attach(pid int) (*Process, error) { return nil, fmt.Errorf("could not attach to %d", pid) } + dbp.os.initialized = true + return initializeDebugProcess(dbp, "", true) } @@ -119,7 +162,8 @@ func (dbp *Process) Kill() (err error) { } } for { - port := C.mach_port_wait(dbp.os.portSet, C.int(0)) + var task C.task_t + port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0)) if port == dbp.os.notificationPort { break } @@ -141,7 +185,14 @@ func (dbp *Process) requestManualStop() (err error) { return nil } +var couldNotGetThreadCount = errors.New("could not get thread count") +var couldNotGetThreadList = errors.New("could not get thread list") + func (dbp *Process) updateThreadList() error { + return dbp.updateThreadListForTask(dbp.os.task) +} + +func (dbp *Process) updateThreadListForTask(task C.task_t) error { var ( err error kret C.kern_return_t @@ -150,30 +201,42 @@ func (dbp *Process) updateThreadList() error { ) for { - count = C.thread_count(dbp.os.task) + count = C.thread_count(task) if count == -1 { - return fmt.Errorf("could not get thread count") + return couldNotGetThreadCount } 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(dbp.os.task, unsafe.Pointer(&list[0]), count) + kret = C.get_threads(task, unsafe.Pointer(&list[0]), count) if kret != -2 { break } } if kret != C.KERN_SUCCESS { - return fmt.Errorf("could not get thread list") + return couldNotGetThreadList + } + + for _, thread := range dbp.Threads { + thread.os.exists = false } for _, port := range list { - if _, ok := dbp.Threads[int(port)]; !ok { - _, err = dbp.addThread(int(port), false) + thread, ok := dbp.Threads[int(port)] + if !ok { + thread, err = dbp.addThread(int(port), false) if err != nil { return err } } + thread.os.exists = true + } + + for threadID, thread := range dbp.Threads { + if !thread.os.exists { + delete(dbp.Threads, threadID) + } } return nil @@ -293,10 +356,23 @@ func (dbp *Process) findExecutable(path string) (*macho.File, error) { func (dbp *Process) trapWait(pid int) (*Thread, error) { for { - port := C.mach_port_wait(dbp.os.portSet, C.int(0)) + task := dbp.os.task + port := C.mach_port_wait(dbp.os.portSet, &task, C.int(0)) switch port { case dbp.os.notificationPort: + // 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 { + if pidtask := C.get_task_for_pid(C.int(dbp.Pid)); pidtask != 0 && dbp.os.task != pidtask { + continue + } + } _, status, err := dbp.wait(dbp.Pid, 0) if err != nil { return nil, err @@ -317,6 +393,17 @@ func (dbp *Process) trapWait(pid int) (*Thread, error) { return nil, fmt.Errorf("error while waiting for task") } + // 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) + } + } + // 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() @@ -343,8 +430,9 @@ func (dbp *Process) waitForStop() ([]int, error) { ports := make([]int, 0, len(dbp.Threads)) count := 0 for { - port := C.mach_port_wait(dbp.os.portSet, C.int(1)) - if port != 0 { + 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 { count = 0 ports = append(ports, int(port)) } else { diff --git a/proc/proc_darwin.h b/proc/proc_darwin.h index 6abb7bfc..022ab141 100644 --- a/proc/proc_darwin.h +++ b/proc/proc_darwin.h @@ -36,10 +36,19 @@ int thread_count(task_t task); mach_port_t -mach_port_wait(mach_port_t, int); +mach_port_wait(mach_port_t, task_t*, int); kern_return_t mach_send_reply(mach_msg_header_t); kern_return_t raise_exception(mach_port_t, mach_port_t, mach_port_t, exception_type_t); + +kern_return_t +reset_exception_ports(task_t task, mach_port_t *exception_port, mach_port_t *notification_port); + +task_t +get_task_for_pid(int pid); + +int +task_is_valid(task_t task); diff --git a/proc/proc_windows.go b/proc/proc_windows.go index f0bad43f..1323fcbf 100644 --- a/proc/proc_windows.go +++ b/proc/proc_windows.go @@ -82,7 +82,7 @@ func Launch(cmd []string, wd string) (*Process, error) { return nil, err } } - + var workingDir *uint16 if wd != "" { if workingDir, err = syscall.UTF16PtrFromString(wd); err != nil { diff --git a/proc/test/support.go b/proc/test/support.go index 6104f112..53bef738 100644 --- a/proc/test/support.go +++ b/proc/test/support.go @@ -54,10 +54,13 @@ func BuildFixture(name string) Fixture { // Work-around for https://github.com/golang/go/issues/13154 buildFlags = append(buildFlags, "-ldflags=-linkmode internal") } - buildFlags = append(buildFlags, "-gcflags=-N -l", "-o", tmpfile, path) + buildFlags = append(buildFlags, "-gcflags=-N -l", "-o", tmpfile, name+".go") + + cmd := exec.Command("go", buildFlags...) + cmd.Dir = fixturesDir // Build the test binary - if err := exec.Command("go", buildFlags...).Run(); err != nil { + if err := cmd.Run(); err != nil { fmt.Printf("Error compiling %s: %s\n", path, err) os.Exit(1) } diff --git a/proc/threads_darwin.go b/proc/threads_darwin.go index 0c79d61e..0c80b17a 100644 --- a/proc/threads_darwin.go +++ b/proc/threads_darwin.go @@ -17,6 +17,7 @@ type WaitStatus sys.WaitStatus type OSSpecificDetails struct { threadAct C.thread_act_t registers C.x86_thread_state64_t + exists bool } // ErrContinueThread is the error returned when a thread could not @@ -27,8 +28,17 @@ func (t *Thread) halt() (err error) { kret := C.thread_suspend(t.os.threadAct) if kret != C.KERN_SUCCESS { errStr := C.GoString(C.mach_error_string(C.mach_error_t(kret))) - err = fmt.Errorf("could not suspend thread %d %s", t.ID, errStr) - return + // check that the thread still exists before complaining + err2 := t.dbp.updateThreadList() + if err2 != nil { + err = fmt.Errorf("could not suspend thread %d %s (additionally could not update thread list: %v)", t.ID, errStr, err2) + return + } + + if _, ok := t.dbp.Threads[t.ID]; ok { + err = fmt.Errorf("could not suspend thread %d %s", t.ID, errStr) + return + } } return } diff --git a/proc/variables.go b/proc/variables.go index 8e359d3a..0734422f 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -834,7 +834,6 @@ func (v *Variable) setValue(y *Variable) error { imag, _ := constant.Float64Val(constant.Imag(y.Value)) err = v.writeComplex(real, imag, v.RealType.Size()) default: - fmt.Printf("default\n") if t, isptr := v.RealType.(*dwarf.PtrType); isptr { err = v.writeUint(uint64(y.Children[0].Addr), int64(t.ByteSize)) } else {