diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
commit | 5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch) | |
tree | c0650497e988f47be9c6f2324fa692a52dea82e1 /src/libmach/darwin.c | |
parent | 80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff) | |
download | golang-upstream/60.tar.gz |
Imported Upstream version 60upstream/60
Diffstat (limited to 'src/libmach/darwin.c')
-rw-r--r-- | src/libmach/darwin.c | 887 |
1 files changed, 887 insertions, 0 deletions
diff --git a/src/libmach/darwin.c b/src/libmach/darwin.c new file mode 100644 index 000000000..63abde313 --- /dev/null +++ b/src/libmach/darwin.c @@ -0,0 +1,887 @@ +// Copyright © 2009 The Go Authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#define __DARWIN_UNIX03 0 + +#include <u.h> +#include <sys/ptrace.h> +#include <sys/signal.h> +#include <mach/mach.h> +#include <mach/mach_traps.h> +#include <errno.h> +#include <libc.h> +#include <bio.h> +#include <mach.h> +#define Ureg Ureg32 +#include <ureg_x86.h> +#undef Ureg +#define Ureg Ureg64 +#include <ureg_amd64.h> +#undef Ureg +#undef waitpid /* want Unix waitpid, not Plan 9 */ + +typedef struct Ureg32 Ureg32; +typedef struct Ureg64 Ureg64; + +extern mach_port_t mach_reply_port(void); // should be in system headers, is not + +// Mach-error wrapper. +// Takes a mach return code and converts it into 0 / -1, +// setting errstr when it returns -1. + +static struct { + int code; + char *name; +} macherr[] = { + KERN_INVALID_ADDRESS, "invalid address", + KERN_PROTECTION_FAILURE, "protection failure", + KERN_NO_SPACE, "no space", + KERN_INVALID_ARGUMENT, "invalid argument", + KERN_FAILURE, "failure", + KERN_RESOURCE_SHORTAGE, "resource shortage", + KERN_NOT_RECEIVER, "not receiver", + KERN_NO_ACCESS, "no access", + KERN_MEMORY_FAILURE, "memory failure", + KERN_MEMORY_ERROR, "memory error", + KERN_ALREADY_IN_SET, "already in set", + KERN_NOT_IN_SET, "not in set", + KERN_NAME_EXISTS, "name exists", + KERN_ABORTED, "aborted", + KERN_INVALID_NAME, "invalid name", + KERN_INVALID_TASK, "invalid task", + KERN_INVALID_RIGHT, "invalid right", + KERN_INVALID_VALUE, "invalid value", + KERN_UREFS_OVERFLOW, "urefs overflow", + KERN_INVALID_CAPABILITY, "invalid capability", + KERN_RIGHT_EXISTS, "right exists", + KERN_INVALID_HOST, "invalid host", + KERN_MEMORY_PRESENT, "memory present", + KERN_MEMORY_DATA_MOVED, "memory data moved", + KERN_MEMORY_RESTART_COPY, "memory restart copy", + KERN_INVALID_PROCESSOR_SET, "invalid processor set", + KERN_POLICY_LIMIT, "policy limit", + KERN_INVALID_POLICY, "invalid policy", + KERN_INVALID_OBJECT, "invalid object", + KERN_ALREADY_WAITING, "already waiting", + KERN_DEFAULT_SET, "default set", + KERN_EXCEPTION_PROTECTED, "exception protected", + KERN_INVALID_LEDGER, "invalid ledger", + KERN_INVALID_MEMORY_CONTROL, "invalid memory control", + KERN_INVALID_SECURITY, "invalid security", + KERN_NOT_DEPRESSED, "not depressed", + KERN_TERMINATED, "terminated", + KERN_LOCK_SET_DESTROYED, "lock set destroyed", + KERN_LOCK_UNSTABLE, "lock unstable", + KERN_LOCK_OWNED, "lock owned", + KERN_LOCK_OWNED_SELF, "lock owned self", + KERN_SEMAPHORE_DESTROYED, "semaphore destroyed", + KERN_RPC_SERVER_TERMINATED, "rpc server terminated", + KERN_RPC_TERMINATE_ORPHAN, "rpc terminate orphan", + KERN_RPC_CONTINUE_ORPHAN, "rpc continue orphan", + KERN_NOT_SUPPORTED, "not supported", + KERN_NODE_DOWN, "node down", + KERN_NOT_WAITING, "not waiting", + KERN_OPERATION_TIMED_OUT, "operation timed out", + KERN_RETURN_MAX, "return max", + + MACH_SEND_IN_PROGRESS, "send in progress", + MACH_SEND_INVALID_DATA, "send invalid data", + MACH_SEND_INVALID_DEST, "send invalid dest", + MACH_SEND_TIMED_OUT, "send timed out", + MACH_SEND_INTERRUPTED, "send interrupted", + MACH_SEND_MSG_TOO_SMALL, "send msg too small", + MACH_SEND_INVALID_REPLY, "send invalid reply", + MACH_SEND_INVALID_RIGHT, "send invalid right", + MACH_SEND_INVALID_NOTIFY, "send invalid notify", + MACH_SEND_INVALID_MEMORY, "send invalid memory", + MACH_SEND_NO_BUFFER, "send no buffer", + MACH_SEND_TOO_LARGE, "send too large", + MACH_SEND_INVALID_TYPE, "send invalid type", + MACH_SEND_INVALID_HEADER, "send invalid header", + MACH_SEND_INVALID_TRAILER, "send invalid trailer", + MACH_SEND_INVALID_RT_OOL_SIZE, "send invalid rt ool size", + MACH_RCV_IN_PROGRESS, "rcv in progress", + MACH_RCV_INVALID_NAME, "rcv invalid name", + MACH_RCV_TIMED_OUT, "rcv timed out", + MACH_RCV_TOO_LARGE, "rcv too large", + MACH_RCV_INTERRUPTED, "rcv interrupted", + MACH_RCV_PORT_CHANGED, "rcv port changed", + MACH_RCV_INVALID_NOTIFY, "rcv invalid notify", + MACH_RCV_INVALID_DATA, "rcv invalid data", + MACH_RCV_PORT_DIED, "rcv port died", + MACH_RCV_IN_SET, "rcv in set", + MACH_RCV_HEADER_ERROR, "rcv header error", + MACH_RCV_BODY_ERROR, "rcv body error", + MACH_RCV_INVALID_TYPE, "rcv invalid type", + MACH_RCV_SCATTER_SMALL, "rcv scatter small", + MACH_RCV_INVALID_TRAILER, "rcv invalid trailer", + MACH_RCV_IN_PROGRESS_TIMED, "rcv in progress timed", + + MIG_TYPE_ERROR, "mig type error", + MIG_REPLY_MISMATCH, "mig reply mismatch", + MIG_REMOTE_ERROR, "mig remote error", + MIG_BAD_ID, "mig bad id", + MIG_BAD_ARGUMENTS, "mig bad arguments", + MIG_NO_REPLY, "mig no reply", + MIG_EXCEPTION, "mig exception", + MIG_ARRAY_TOO_LARGE, "mig array too large", + MIG_SERVER_DIED, "server died", + MIG_TRAILER_ERROR, "trailer has an unknown format", +}; + +static int +me(kern_return_t r) +{ + int i; + + if(r == 0) + return 0; + + for(i=0; i<nelem(macherr); i++){ + if(r == macherr[i].code){ + werrstr("mach: %s", macherr[i].name); + return -1; + } + } + werrstr("mach error %#x", r); + return -1; +} + +// Plan 9 and Linux do not distinguish between +// process ids and thread ids, so the interface here doesn't either. +// Unfortunately, Mach has three kinds of identifiers: process ids, +// handles to tasks (processes), and handles to threads within a +// process. All of them are small integers. +// +// To accommodate Mach, we employ a clumsy hack: in this interface, +// if you pass in a positive number, that's a process id. +// If you pass in a negative number, that identifies a thread that +// has been previously returned by procthreadpids (it indexes +// into the Thread table below). + +// Table of threads we have handles for. +typedef struct Thread Thread; +struct Thread +{ + int pid; + mach_port_t task; + mach_port_t thread; + int stopped; + int exc; + int code[10]; + Map *map; +}; +static Thread thr[1000]; +static int nthr; +static pthread_mutex_t mu; +static pthread_cond_t cond; +static void* excthread(void*); +static void* waitthread(void*); +static mach_port_t excport; + +enum { + ExcMask = EXC_MASK_BAD_ACCESS | + EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_ARITHMETIC | + EXC_MASK_BREAKPOINT | + EXC_MASK_SOFTWARE +}; + +// Add process pid to the thread table. +// If it's already there, don't re-add it (unless force != 0). +static Thread* +addpid(int pid, int force) +{ + int i, j; + mach_port_t task; + mach_port_t *thread; + uint nthread; + Thread *ret; + static int first = 1; + + if(first){ + // Allocate a port for exception messages and + // send all thread exceptions to that port. + // The excthread reads that port and signals + // us if we are waiting on that thread. + pthread_t p; + int err; + + excport = mach_reply_port(); + pthread_mutex_init(&mu, nil); + pthread_cond_init(&cond, nil); + err = pthread_create(&p, nil, excthread, nil); + if (err != 0) { + fprint(2, "pthread_create failed: %s\n", strerror(err)); + abort(); + } + err = pthread_create(&p, nil, waitthread, (void*)(uintptr)pid); + if (err != 0) { + fprint(2, "pthread_create failed: %s\n", strerror(err)); + abort(); + } + first = 0; + } + + if(!force){ + for(i=0; i<nthr; i++) + if(thr[i].pid == pid) + return &thr[i]; + } + if(me(task_for_pid(mach_task_self(), pid, &task)) < 0) + return nil; + if(me(task_threads(task, &thread, &nthread)) < 0) + return nil; + mach_port_insert_right(mach_task_self(), excport, excport, MACH_MSG_TYPE_MAKE_SEND); + if(me(task_set_exception_ports(task, ExcMask, + excport, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE)) < 0){ + fprint(2, "warning: cannot set excport: %r\n"); + } + ret = nil; + for(j=0; j<nthread; j++){ + if(force){ + // If we're forcing a refresh, don't re-add existing threads. + for(i=0; i<nthr; i++) + if(thr[i].pid == pid && thr[i].thread == thread[j]){ + if(ret == nil) + ret = &thr[i]; + goto skip; + } + } + if(nthr >= nelem(thr)) + return nil; + // TODO: We probably should save the old thread exception + // ports for each bit and then put them back when we exit. + // Probably the BSD signal handlers have put stuff there. + mach_port_insert_right(mach_task_self(), excport, excport, MACH_MSG_TYPE_MAKE_SEND); + if(me(thread_set_exception_ports(thread[j], ExcMask, + excport, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE)) < 0){ + fprint(2, "warning: cannot set excport: %r\n"); + } + thr[nthr].pid = pid; + thr[nthr].task = task; + thr[nthr].thread = thread[j]; + if(ret == nil) + ret = &thr[nthr]; + nthr++; + skip:; + } + return ret; +} + +static Thread* +idtotable(int id) +{ + if(id >= 0) + return addpid(id, 1); + + id = -(id+1); + if(id >= nthr) + return nil; + return &thr[id]; +} + +/* +static int +idtopid(int id) +{ + Thread *t; + + if((t = idtotable(id)) == nil) + return -1; + return t->pid; +} +*/ + +static mach_port_t +idtotask(int id) +{ + Thread *t; + + if((t = idtotable(id)) == nil) + return -1; + return t->task; +} + +static mach_port_t +idtothread(int id) +{ + Thread *t; + + if((t = idtotable(id)) == nil) + return -1; + return t->thread; +} + +static int machsegrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr); +static int machregrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr); + +Map* +attachproc(int id, Fhdr *fp) +{ + Thread *t; + Map *map; + + if((t = idtotable(id)) == nil) + return nil; + if(t->map) + return t->map; + map = newmap(0, 4); + if(!map) + return nil; + map->pid = -((t-thr) + 1); + if(mach->regsize) + setmap(map, -1, 0, mach->regsize, 0, "regs", machregrw); + setmap(map, -1, fp->txtaddr, fp->txtaddr+fp->txtsz, fp->txtaddr, "*text", machsegrw); + setmap(map, -1, fp->dataddr, mach->utop, fp->dataddr, "*data", machsegrw); + t->map = map; + return map; +} + +// Return list of ids for threads in id. +int +procthreadpids(int id, int *out, int nout) +{ + Thread *t; + int i, n, pid; + + t = idtotable(id); + if(t == nil) + return -1; + pid = t->pid; + addpid(pid, 1); // force refresh of thread list + n = 0; + for(i=0; i<nthr; i++) { + if(thr[i].pid == pid) { + if(n < nout) + out[n] = -(i+1); + n++; + } + } + return n; +} + +// Detach from proc. +// TODO(rsc): Perhaps should unsuspend any threads and clean-up the table. +void +detachproc(Map *m) +{ + free(m); +} + +// Should return array of pending signals (notes) +// but don't know how to do that on OS X. +int +procnotes(int pid, char ***pnotes) +{ + *pnotes = 0; + return 0; +} + +// There must be a way to do this. Gdb can do it. +// But I don't see, in the Apple gdb sources, how. +char* +proctextfile(int pid) +{ + return nil; +} + +// Read/write from a Mach data segment. +static int +machsegrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr) +{ + mach_port_t task; + int r; + + task = idtotask(map->pid); + if(task == -1) + return -1; + + if(isr){ + vm_size_t nn; + nn = n; + if(me(vm_read_overwrite(task, addr, n, (uintptr)v, &nn)) < 0) { + fprint(2, "vm_read_overwrite %#llux %d to %p: %r\n", addr, n, v); + return -1; + } + return nn; + }else{ + r = vm_write(task, addr, (uintptr)v, n); + if(r == KERN_INVALID_ADDRESS){ + // Happens when writing to text segment. + // Change protections. + if(me(vm_protect(task, addr, n, 0, VM_PROT_WRITE|VM_PROT_READ|VM_PROT_EXECUTE)) < 0){ + fprint(2, "vm_protect: %s\n", r); + return -1; + } + r = vm_write(task, addr, (uintptr)v, n); + } + if(r != 0){ + me(r); + return -1; + } + return n; + } +} + +// Convert Ureg offset to x86_thread_state32_t offset. +static int +go2darwin32(uvlong addr) +{ + switch(addr){ + case offsetof(Ureg32, ax): + return offsetof(x86_thread_state32_t, eax); + case offsetof(Ureg32, bx): + return offsetof(x86_thread_state32_t, ebx); + case offsetof(Ureg32, cx): + return offsetof(x86_thread_state32_t, ecx); + case offsetof(Ureg32, dx): + return offsetof(x86_thread_state32_t, edx); + case offsetof(Ureg32, si): + return offsetof(x86_thread_state32_t, esi); + case offsetof(Ureg32, di): + return offsetof(x86_thread_state32_t, edi); + case offsetof(Ureg32, bp): + return offsetof(x86_thread_state32_t, ebp); + case offsetof(Ureg32, fs): + return offsetof(x86_thread_state32_t, fs); + case offsetof(Ureg32, gs): + return offsetof(x86_thread_state32_t, gs); + case offsetof(Ureg32, pc): + return offsetof(x86_thread_state32_t, eip); + case offsetof(Ureg32, cs): + return offsetof(x86_thread_state32_t, cs); + case offsetof(Ureg32, flags): + return offsetof(x86_thread_state32_t, eflags); + case offsetof(Ureg32, sp): + return offsetof(x86_thread_state32_t, esp); + } + return -1; +} + +// Convert Ureg offset to x86_thread_state64_t offset. +static int +go2darwin64(uvlong addr) +{ + switch(addr){ + case offsetof(Ureg64, ax): + return offsetof(x86_thread_state64_t, rax); + case offsetof(Ureg64, bx): + return offsetof(x86_thread_state64_t, rbx); + case offsetof(Ureg64, cx): + return offsetof(x86_thread_state64_t, rcx); + case offsetof(Ureg64, dx): + return offsetof(x86_thread_state64_t, rdx); + case offsetof(Ureg64, si): + return offsetof(x86_thread_state64_t, rsi); + case offsetof(Ureg64, di): + return offsetof(x86_thread_state64_t, rdi); + case offsetof(Ureg64, bp): + return offsetof(x86_thread_state64_t, rbp); + case offsetof(Ureg64, r8): + return offsetof(x86_thread_state64_t, r8); + case offsetof(Ureg64, r9): + return offsetof(x86_thread_state64_t, r9); + case offsetof(Ureg64, r10): + return offsetof(x86_thread_state64_t, r10); + case offsetof(Ureg64, r11): + return offsetof(x86_thread_state64_t, r11); + case offsetof(Ureg64, r12): + return offsetof(x86_thread_state64_t, r12); + case offsetof(Ureg64, r13): + return offsetof(x86_thread_state64_t, r13); + case offsetof(Ureg64, r14): + return offsetof(x86_thread_state64_t, r14); + case offsetof(Ureg64, r15): + return offsetof(x86_thread_state64_t, r15); + case offsetof(Ureg64, fs): + return offsetof(x86_thread_state64_t, fs); + case offsetof(Ureg64, gs): + return offsetof(x86_thread_state64_t, gs); + case offsetof(Ureg64, ip): + return offsetof(x86_thread_state64_t, rip); + case offsetof(Ureg64, cs): + return offsetof(x86_thread_state64_t, cs); + case offsetof(Ureg64, flags): + return offsetof(x86_thread_state64_t, rflags); + case offsetof(Ureg64, sp): + return offsetof(x86_thread_state64_t, rsp); + } + return -1; +} + +extern Mach mi386; + +// Read/write from fake register segment. +static int +machregrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr) +{ + uint nn, count, state; + mach_port_t thread; + int reg; + char buf[100]; + union { + x86_thread_state64_t reg64; + x86_thread_state32_t reg32; + uchar p[1]; + } u; + uchar *p; + + if(n > 8){ + werrstr("asked for %d-byte register", n); + return -1; + } + + thread = idtothread(map->pid); + if(thread == -1){ + werrstr("no such id"); + return -1; + } + + if(mach == &mi386) { + count = x86_THREAD_STATE32_COUNT; + state = x86_THREAD_STATE32; + if((reg = go2darwin32(addr)) < 0 || reg+n > sizeof u){ + if(isr){ + memset(v, 0, n); + return 0; + } + werrstr("register %llud not available", addr); + return -1; + } + } else { + count = x86_THREAD_STATE64_COUNT; + state = x86_THREAD_STATE64; + if((reg = go2darwin64(addr)) < 0 || reg+n > sizeof u){ + if(isr){ + memset(v, 0, n); + return 0; + } + werrstr("register %llud not available", addr); + return -1; + } + } + + if(!isr && me(thread_suspend(thread)) < 0){ + werrstr("thread suspend %#x: %r", thread); + return -1; + } + nn = count; + if(me(thread_get_state(thread, state, (void*)u.p, &nn)) < 0){ + if(!isr) + thread_resume(thread); + rerrstr(buf, sizeof buf); + if(strstr(buf, "send invalid dest") != nil) + werrstr("process exited"); + else + werrstr("thread_get_state: %r"); + return -1; + } + + p = u.p+reg; + if(isr) + memmove(v, p, n); + else{ + memmove(p, v, n); + nn = count; + if(me(thread_set_state(thread, state, (void*)u.p, nn)) < 0){ + thread_resume(thread); + werrstr("thread_set_state: %r"); + return -1; + } + + if(me(thread_resume(thread)) < 0){ + werrstr("thread_resume: %r"); + return -1; + } + } + return 0; +} + +enum +{ + FLAGS_TF = 0x100 // x86 single-step processor flag +}; + +// Is thread t suspended? +static int +threadstopped(Thread *t) +{ + struct thread_basic_info info; + uint size; + + size = sizeof info; + if(me(thread_info(t->thread, THREAD_BASIC_INFO, (thread_info_t)&info, &size)) < 0){ + fprint(2, "threadstopped thread_info %#x: %r\n"); + return 1; + } + return info.suspend_count > 0; +} + +// If thread t is suspended, start it up again. +// If singlestep is set, only let it execute one instruction. +static int +threadstart(Thread *t, int singlestep) +{ + int i; + uint n; + struct thread_basic_info info; + + if(!threadstopped(t)) + return 0; + + // Set or clear the processor single-step flag, as appropriate. + if(mach == &mi386) { + x86_thread_state32_t regs; + n = x86_THREAD_STATE32_COUNT; + if(me(thread_get_state(t->thread, x86_THREAD_STATE32, + (thread_state_t)®s, + &n)) < 0) + return -1; + if(singlestep) + regs.eflags |= FLAGS_TF; + else + regs.eflags &= ~FLAGS_TF; + if(me(thread_set_state(t->thread, x86_THREAD_STATE32, + (thread_state_t)®s, + x86_THREAD_STATE32_COUNT)) < 0) + return -1; + } else { + x86_thread_state64_t regs; + n = x86_THREAD_STATE64_COUNT; + if(me(thread_get_state(t->thread, x86_THREAD_STATE64, + (thread_state_t)®s, + &n)) < 0) + return -1; + if(singlestep) + regs.rflags |= FLAGS_TF; + else + regs.rflags &= ~FLAGS_TF; + if(me(thread_set_state(t->thread, x86_THREAD_STATE64, + (thread_state_t)®s, + x86_THREAD_STATE64_COUNT)) < 0) + return -1; + } + + // Run. + n = sizeof info; + if(me(thread_info(t->thread, THREAD_BASIC_INFO, (thread_info_t)&info, &n)) < 0) + return -1; + for(i=0; i<info.suspend_count; i++) + if(me(thread_resume(t->thread)) < 0) + return -1; + return 0; +} + +// Stop thread t. +static int +threadstop(Thread *t) +{ + if(threadstopped(t)) + return 0; + if(me(thread_suspend(t->thread)) < 0) + return -1; + return 0; +} + +// Callback for exc_server below. Called when a thread we are +// watching has an exception like hitting a breakpoint. +kern_return_t +catch_exception_raise(mach_port_t eport, mach_port_t thread, + mach_port_t task, exception_type_t exception, + exception_data_t code, mach_msg_type_number_t ncode) +{ + Thread *t; + int i; + + t = nil; + for(i=0; i<nthr; i++){ + if(thr[i].thread == thread){ + t = &thr[i]; + goto havet; + } + } + if(nthr > 0) + addpid(thr[0].pid, 1); + for(i=0; i<nthr; i++){ + if(thr[i].thread == thread){ + t = &thr[i]; + goto havet; + } + } + fprint(2, "did not find thread in catch_exception_raise\n"); + return KERN_SUCCESS; // let thread continue + +havet: + t->exc = exception; + if(ncode > nelem(t->code)) + ncode = nelem(t->code); + memmove(t->code, code, ncode*sizeof t->code[0]); + + // Suspend thread, so that we can look at it & restart it later. + if(me(thread_suspend(thread)) < 0) + fprint(2, "catch_exception_raise thread_suspend: %r\n"); + + // Synchronize with waitstop below. + pthread_mutex_lock(&mu); + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mu); + + return KERN_SUCCESS; +} + +// Exception watching thread, started in addpid above. +static void* +excthread(void *v) +{ + extern boolean_t exc_server(); + mach_msg_server(exc_server, 2048, excport, 0); + return 0; +} + +// Wait for pid to exit. +static int exited; +static void* +waitthread(void *v) +{ + int pid, status; + + pid = (int)(uintptr)v; + waitpid(pid, &status, 0); + exited = 1; + // Synchronize with waitstop below. + pthread_mutex_lock(&mu); + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mu); + return nil; +} + +// Wait for thread t to stop. +static int +waitstop(Thread *t) +{ + pthread_mutex_lock(&mu); + while(!exited && !threadstopped(t)) + pthread_cond_wait(&cond, &mu); + pthread_mutex_unlock(&mu); + return 0; +} + +int +ctlproc(int id, char *msg) +{ + Thread *t; + int status; + + // Hang/attached dance is for debugging newly exec'ed programs. + // After fork, the child does ctlproc("hang") before exec, + // and the parent does ctlproc("attached") and then waitstop. + // Using these requires the BSD ptrace interface, unlike everything + // else we do, which uses only the Mach interface. Our goal here + // is to do as little as possible using ptrace and then flip over to Mach. + + if(strcmp(msg, "hang") == 0) + return ptrace(PT_TRACE_ME, 0, 0, 0); + + if(strcmp(msg, "attached") == 0){ + // The pid "id" has done a ctlproc "hang" and then + // exec, so we should find it stoppped just before exec + // of the new program. + #undef waitpid + if(waitpid(id, &status, WUNTRACED) < 0){ + fprint(2, "ctlproc attached waitpid: %r\n"); + return -1; + } + if(WIFEXITED(status) || !WIFSTOPPED(status)){ + fprint(2, "ctlproc attached: bad process state\n"); + return -1; + } + + // Find Mach thread for pid and suspend it. + t = addpid(id, 1); + if(t == nil) { + fprint(2, "ctlproc attached: addpid: %r\n"); + return -1; + } + if(me(thread_suspend(t->thread)) < 0){ + fprint(2, "ctlproc attached: thread_suspend: %r\n"); + return -1; + } + + // Let ptrace tell the process to keep going: + // then ptrace is out of the way and we're back in Mach land. + if(ptrace(PT_CONTINUE, id, (caddr_t)1, 0) < 0) { + fprint(2, "ctlproc attached: ptrace continue: %r\n"); + return -1; + } + + return 0; + } + + // All the other control messages require a Thread structure. + if((t = idtotable(id)) == nil){ + werrstr("no such thread"); + return -1; + } + + if(strcmp(msg, "kill") == 0) + return ptrace(PT_KILL, t->pid, 0, 0); + + if(strcmp(msg, "start") == 0) + return threadstart(t, 0); + + if(strcmp(msg, "stop") == 0) + return threadstop(t); + + if(strcmp(msg, "startstop") == 0){ + if(threadstart(t, 0) < 0) + return -1; + return waitstop(t); + } + + if(strcmp(msg, "step") == 0){ + if(threadstart(t, 1) < 0) + return -1; + return waitstop(t); + } + + if(strcmp(msg, "waitstop") == 0) + return waitstop(t); + + // sysstop not available on OS X + + werrstr("unknown control message"); + return -1; +} + +char* +procstatus(int id) +{ + Thread *t; + + if((t = idtotable(id)) == nil) + return "gone!"; + + if(threadstopped(t)) + return "Stopped"; + + return "Running"; +} + |