提交 63b8fa82 编写于 作者: A aarzilli

proc: support for ver. 10.12.1 the OS formerly known as Mac OS X

Fixes #645
上级 ee6d2734
......@@ -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
......@@ -97,6 +97,8 @@ fork_exec(char *argv0, char **argv, int size,
exit(1);
}
sleep(1);
// Create the child process.
execve(argv0, argv, environ);
......
......@@ -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;
}
......@@ -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 {
......
......@@ -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);
......@@ -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 {
......
......@@ -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)
}
......
......@@ -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
}
......
......@@ -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 {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册