| // 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 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) { |
| 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(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 = 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) |
| 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"; |
| } |
| |