| // 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_amd64.h> | |
| #include <ureg_amd64.h> | |
| typedef struct Ureg Ureg; | |
| #undef waitpid /* want Unix waitpid, not Plan 9 */ | |
| 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("%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 accomodate 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; | |
| excport = mach_reply_port(); | |
| pthread_mutex_init(&mu, nil); | |
| pthread_cond_init(&cond, nil); | |
| pthread_create(&p, nil, excthread, nil); | |
| pthread_create(&p, nil, waitthread, (void*)(uintptr)pid); | |
| 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) | |
| 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_state64_t offset. | |
| static int | |
| go2darwin(uvlong addr) | |
| { | |
| switch(addr){ | |
| case offsetof(Ureg, ax): | |
| return offsetof(x86_thread_state64_t, rax); | |
| case offsetof(Ureg, bx): | |
| return offsetof(x86_thread_state64_t, rbx); | |
| case offsetof(Ureg, cx): | |
| return offsetof(x86_thread_state64_t, rcx); | |
| case offsetof(Ureg, dx): | |
| return offsetof(x86_thread_state64_t, rdx); | |
| case offsetof(Ureg, si): | |
| return offsetof(x86_thread_state64_t, rsi); | |
| case offsetof(Ureg, di): | |
| return offsetof(x86_thread_state64_t, rdi); | |
| case offsetof(Ureg, bp): | |
| return offsetof(x86_thread_state64_t, rbp); | |
| case offsetof(Ureg, r8): | |
| return offsetof(x86_thread_state64_t, r8); | |
| case offsetof(Ureg, r9): | |
| return offsetof(x86_thread_state64_t, r9); | |
| case offsetof(Ureg, r10): | |
| return offsetof(x86_thread_state64_t, r10); | |
| case offsetof(Ureg, r11): | |
| return offsetof(x86_thread_state64_t, r11); | |
| case offsetof(Ureg, r12): | |
| return offsetof(x86_thread_state64_t, r12); | |
| case offsetof(Ureg, r13): | |
| return offsetof(x86_thread_state64_t, r13); | |
| case offsetof(Ureg, r14): | |
| return offsetof(x86_thread_state64_t, r14); | |
| case offsetof(Ureg, r15): | |
| return offsetof(x86_thread_state64_t, r15); | |
| case offsetof(Ureg, fs): | |
| return offsetof(x86_thread_state64_t, fs); | |
| case offsetof(Ureg, gs): | |
| return offsetof(x86_thread_state64_t, gs); | |
| case offsetof(Ureg, ip): | |
| return offsetof(x86_thread_state64_t, rip); | |
| case offsetof(Ureg, cs): | |
| return offsetof(x86_thread_state64_t, cs); | |
| case offsetof(Ureg, flags): | |
| return offsetof(x86_thread_state64_t, rflags); | |
| case offsetof(Ureg, sp): | |
| return offsetof(x86_thread_state64_t, rsp); | |
| } | |
| return -1; | |
| } | |
| // Read/write from fake register segment. | |
| static int | |
| machregrw(Map *map, Seg *seg, uvlong addr, void *v, uint n, int isr) | |
| { | |
| uint nn; | |
| mach_port_t thread; | |
| int reg; | |
| char buf[100]; | |
| union { | |
| x86_thread_state64_t regs; | |
| 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((reg = go2darwin(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 = x86_THREAD_STATE64_COUNT; | |
| if(me(thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&u.regs, &nn)) < 0){ | |
| if(!isr) | |
| thread_resume(thread); | |
| rerrstr(buf, sizeof buf); | |
| if(strcmp(buf, "send invalid dest") == 0) | |
| 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 = x86_THREAD_STATE64_COUNT; | |
| if(me(thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&u.regs, 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 | |
| { | |
| RFLAGS_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; | |
| x86_thread_state64_t regs; | |
| if(!threadstopped(t)) | |
| return 0; | |
| // Set or clear the processor single-step flag, as appropriate. | |
| 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 |= RFLAGS_TF; | |
| else | |
| regs.rflags &= ~RFLAGS_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) | |
| 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. | |
| return ptrace(PT_CONTINUE, id, (caddr_t)1, 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"; | |
| } | |